Skip to content

Commit

Permalink
feat: enhance PostgreSQL provider with json_extract and coalesce meth…
Browse files Browse the repository at this point in the history
…ods; update method call handling for improved SQL generation
  • Loading branch information
skynetigor committed Jan 15, 2025
1 parent 4edfc2d commit ee95931
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 38 deletions.
68 changes: 63 additions & 5 deletions keep/api/core/cel_to_sql/sql_providers/base.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Any, List
from typing import List
from keep.api.core.cel_to_sql.ast_nodes import (
ConstantNode,
MemberAccessNode,
Expand All @@ -23,6 +23,65 @@ def __init__(self, where: str, select_fields: str = None, select_json: str = Non
self.select_json = select_json

class BaseCelToSqlProvider:
"""
Base class for converting CEL (Common Expression Language) expressions to SQL strings.
Methods:
convert_to_sql_str(cel: str) -> BuiltQueryMetadata:
Converts a CEL expression to an SQL string.
json_extract(column: str, path: str) -> str:
Abstract method to extract JSON data from a column. Must be implemented in the child class.
coalesce(args: List[str]) -> str:
Abstract method to perform COALESCE operation. Must be implemented in the child class.
_visit_parentheses(node: str) -> str:
Wraps a given SQL string in parentheses.
_visit_logical_node(logical_node: LogicalNode) -> str:
Visits a logical node and converts it to an SQL string.
_visit_logical_and(left: str, right: str) -> str:
Converts a logical AND operation to an SQL string.
_visit_logical_or(left: str, right: str) -> str:
Converts a logical OR operation to an SQL string.
_visit_comparison_node(comparison_node: ComparisonNode) -> str:
Visits a comparison node and converts it to an SQL string.
_cast_property(exp: str, to_type: type) -> str:
Casts a property to a specified type in SQL.
_visit_equal(first_operand: str, second_operand: str) -> str:
Converts an equality comparison to an SQL string.
_visit_not_equal(first_operand: str, second_operand: str) -> str:
Converts a not-equal comparison to an SQL string.
_visit_greater_than(first_operand: str, second_operand: str) -> str:
Converts a greater-than comparison to an SQL string.
_visit_greater_than_or_equal(first_operand: str, second_operand: str) -> str:
Converts a greater-than-or-equal comparison to an SQL string.
_visit_less_than(first_operand: str, second_operand: str) -> str:
Converts a less-than comparison to an SQL string.
_visit_less_than_or_equal(first_operand: str, second_operand: str) -> str:
Converts a less-than-or-equal comparison to an SQL string.
_visit_in(first_operand: Node, array: list[ConstantNode]) -> str:
Converts an IN operation to an SQL string.
_visit_constant_node(value: str) -> str:
Converts a constant value to an SQL string.
_visit_multiple_fields_node(multiple_fields_node: MultipleFieldsNode) -> str:
Visits a multiple fields node and converts it to an SQL string.
_visit_member_access_node(member_access_node: MemberAccessNode) -> str:
Visits a member access node and converts it to an SQL string.
_visit_property_access_node(property_access_node: PropertyAccessNode) -> str:
Visits a property access node and converts it to an SQL string.
_visit_index_property(property_path: str) -> str:
Abstract method to handle index properties. Must be implemented in the child class.
_visit_method_calling(property_path: str, method_name: str, method_args: List[str]) -> str:
Visits a method calling node and converts it to an SQL string.
_visit_contains_method_calling(property_path: str, method_args: List[str]) -> str:
Abstract method to handle 'contains' method calls. Must be implemented in the child class.
_visit_startwith_method_calling(property_path: str, method_args: List[str]) -> str:
Abstract method to handle 'startsWith' method calls. Must be implemented in the child class.
_visit_endswith_method_calling(property_path: str, method_args: List[str]) -> str:
Abstract method to handle 'endsWith' method calls. Must be implemented in the child class.
_visit_unary_node(unary_node: UnaryNode) -> str:
Visits a unary node and converts it to an SQL string.
_visit_unary_not(operand: str) -> str:
Converts a NOT operation to an SQL string.
"""

__null_replacement = "'__@NULL@__'"

def __init__(self, properties_metadata: PropertiesMetadata):
Expand Down Expand Up @@ -149,7 +208,6 @@ def _cast_property(self, exp: str, to_type: type) -> str:
return exp
if to_type == bool:
return exp
# return "TRUE" if exp == "true" else "FALSE"

raise NotImplementedError(f"{to_type.__name__} type casting is not supported yet")

Expand Down Expand Up @@ -247,17 +305,17 @@ def _visit_method_calling(
def _visit_contains_method_calling(
self, property_path: str, method_args: List[str]
) -> str:
raise NotImplementedError("'contains' method call is not supported")
raise NotImplementedError("'contains' method must be implemented in the child class")

def _visit_startwith_method_calling(
self, property_path: str, method_args: List[str]
) -> str:
raise NotImplementedError("'startswith' method call is not supported")
raise NotImplementedError("'startswith' method call must be implemented in the child class")

def _visit_endswith_method_calling(
self, property_path: str, method_args: List[str]
) -> str:
raise NotImplementedError("'endswith' method call is not supported")
raise NotImplementedError("'endswith' method call must be implemented in the child class")

# endregion

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from keep.api.core.cel_to_sql.sql_providers.base import BaseCelToSqlProvider
from keep.api.core.cel_to_sql.sql_providers.postgresql import CelToPostgreSqlProvider
from keep.api.core.cel_to_sql.sql_providers.sqlite import CelToSqliteProvider
from keep.api.core.cel_to_sql.sql_providers.mysql import CelToMySqlProvider

Expand All @@ -7,6 +8,8 @@ def get_cel_to_sql_provider_for_dialect(dialect: str) -> type[BaseCelToSqlProvid
return CelToSqliteProvider
elif dialect == "mysql":
return CelToMySqlProvider
elif dialect == "postgresql":
return CelToPostgreSqlProvider

else:
raise ValueError(f"Unsupported dialect: {dialect}")
42 changes: 9 additions & 33 deletions keep/api/core/cel_to_sql/sql_providers/postgresql.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,18 @@
from keep.api.core.cel_to_sql.sql_providers.base import BaseCelToSqlProvider

class CelToPostgreSqlProvider(BaseCelToSqlProvider):
def _visit_property(self, property_path: str):
if property_path.startswith('event'):
property_access = property_path.replace('event.', '')
return f"event ->> '{property_access}'"
return super()._visit_property(property_path)
def json_extract(self, column: str, path: str) -> str:
' -> '.join([column] + path.split('.'))
return ' -> '.join([column] + path.split('.')) # example: 'json_column' -> 'key1' -> 'key2'

def coalesce(self, args):
return f"COALESCE({', '.join(args)})"

def _visit_contains_method_calling(self, property_path: str, method_args: List[str]) -> str:
if property_path and property_path.startswith('event'):
prop = property_path.replace('event.', '')
return f"event ->> '{prop}' LIKE '%{method_args[0]}%'"

if property_path and property_path.startswith('enrichments'):
prop = property_path.replace('enrichments.', '')
return f"enrichments ->> '{prop}' LIKE '%{method_args[0]}%'"

return f"{property_path} LIKE '%{method_args[0]}%'"
return f"{property_path} LIKE \"%{method_args[0]}%\""

def _visit_starts_with_method_calling(self, property_path: str, method_args: List[str]) -> str:
if property_path and property_path.startswith('event'):
prop = property_path.replace('event.', '')
return f"event ->> '{prop}' LIKE '{method_args[0]}%'"

if property_path and property_path.startswith('enrichments'):
prop = property_path.replace('enrichments.', '')
return f"enrichments ->> '{prop}' LIKE '{method_args[0]}%'"

return f"{property_path} LIKE '{method_args[0]}%'"
return f"{property_path} LIKE \"{method_args[0]}%\""

def _visit_ends_with_method_calling(self, property_path: str, method_args: List[str]) -> str:
if property_path and property_path.startswith('event'):
prop = property_path.replace('event.', '')
return f"event ->> '{prop}' LIKE '%{method_args[0]}'"

if property_path and property_path.startswith('enrichments'):
prop = property_path.replace('enrichments.', '')
return f"enrichments ->> '{prop}' LIKE '%{method_args[0]}'"

return f"{property_path} LIKE '%{method_args[0]}'"
return f"{property_path} LIKE \"%{method_args[0]}\""

0 comments on commit ee95931

Please sign in to comment.