Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 18 additions & 11 deletions src/memos/graph_dbs/neo4j.py
Original file line number Diff line number Diff line change
Expand Up @@ -1441,17 +1441,24 @@ def build_filter_condition(condition_dict: dict, param_counter: list) -> tuple[s
f"{node_alias}.{key} {cypher_op} ${param_name}"
)
elif op == "contains":
# Handle contains operator (for array fields like tags, sources)
param_name = f"filter_{key}_{op}_{param_counter[0]}"
param_counter[0] += 1
params[param_name] = op_value

# For array fields, check if element is in array
if key in ("tags", "sources"):
condition_parts.append(f"${param_name} IN {node_alias}.{key}")
else:
# For non-array fields, contains might not be applicable, but we'll treat it as IN for consistency
condition_parts.append(f"${param_name} IN {node_alias}.{key}")
# Handle contains operator (for array fields)
# Only supports array format: {"field": {"contains": ["value1", "value2"]}}
# Single string values are not supported, use array format instead: {"field": {"contains": ["value"]}}
if not isinstance(op_value, list):
raise ValueError(
f"contains operator only supports array format. "
f"Use {{'{key}': {{'contains': ['{op_value}']}}}} instead of {{'{key}': {{'contains': '{op_value}'}}}}"
)
# Handle array of values: generate AND conditions for each value (all must be present)
and_conditions = []
for item in op_value:
param_name = f"filter_{key}_{op}_{param_counter[0]}"
param_counter[0] += 1
params[param_name] = item
# For array fields, check if element is in array
and_conditions.append(f"${param_name} IN {node_alias}.{key}")
if and_conditions:
condition_parts.append(f"({' AND '.join(and_conditions)})")
elif op == "like":
# Handle like operator (for fuzzy matching, similar to SQL LIKE '%value%')
# Neo4j uses CONTAINS for string matching
Expand Down
94 changes: 64 additions & 30 deletions src/memos/graph_dbs/polardb.py
Original file line number Diff line number Diff line change
Expand Up @@ -3443,23 +3443,40 @@ def build_cypher_filter_condition(condition_dict: dict) -> str:
condition_parts.append(f"n.{key} = {op_value}")
elif op == "contains":
# Handle contains operator (for array fields)
# Only supports array format: {"field": {"contains": ["value1", "value2"]}}
# Single string values are not supported, use array format instead: {"field": {"contains": ["value"]}}
if not isinstance(op_value, list):
raise ValueError(
f"contains operator only supports array format. "
f"Use {{'{key}': {{'contains': ['{op_value}']}}}} instead of {{'{key}': {{'contains': '{op_value}'}}}}"
)
# Check if key starts with "info." prefix
if key.startswith("info."):
info_field = key[5:] # Remove "info." prefix
if isinstance(op_value, str):
escaped_value = escape_cypher_string(op_value)
condition_parts.append(
f"'{escaped_value}' IN n.info.{info_field}"
)
else:
condition_parts.append(f"{op_value} IN n.info.{info_field}")
# Handle array of values: generate AND conditions for each value (all must be present)
and_conditions = []
for item in op_value:
if isinstance(item, str):
escaped_value = escape_cypher_string(item)
and_conditions.append(
f"'{escaped_value}' IN n.info.{info_field}"
)
else:
and_conditions.append(f"{item} IN n.info.{info_field}")
if and_conditions:
condition_parts.append(f"({' AND '.join(and_conditions)})")
else:
# Direct property access
if isinstance(op_value, str):
escaped_value = escape_cypher_string(op_value)
condition_parts.append(f"'{escaped_value}' IN n.{key}")
else:
condition_parts.append(f"{op_value} IN n.{key}")
# Handle array of values: generate AND conditions for each value (all must be present)
and_conditions = []
for item in op_value:
if isinstance(item, str):
escaped_value = escape_cypher_string(item)
and_conditions.append(f"'{escaped_value}' IN n.{key}")
else:
and_conditions.append(f"{item} IN n.{key}")
if and_conditions:
condition_parts.append(f"({' AND '.join(and_conditions)})")
elif op == "like":
# Handle like operator (for fuzzy matching, similar to SQL LIKE '%value%')
# Check if key starts with "info." prefix
Expand Down Expand Up @@ -3668,29 +3685,46 @@ def build_filter_condition(condition_dict: dict) -> str:
)
elif op == "contains":
# Handle contains operator (for array fields) - use @> operator
# Only supports array format: {"field": {"contains": ["value1", "value2"]}}
# Single string values are not supported, use array format instead: {"field": {"contains": ["value"]}}
if not isinstance(op_value, list):
raise ValueError(
f"contains operator only supports array format. "
f"Use {{'{key}': {{'contains': ['{op_value}']}}}} instead of {{'{key}': {{'contains': '{op_value}'}}}}"
)
# Check if key starts with "info." prefix
if key.startswith("info."):
info_field = key[5:] # Remove "info." prefix
if isinstance(op_value, str):
escaped_value = escape_sql_string(op_value)
condition_parts.append(
f"ag_catalog.agtype_access_operator(VARIADIC ARRAY[properties, '\"info\"'::ag_catalog.agtype, '\"{info_field}\"'::ag_catalog.agtype]) @> '\"{escaped_value}\"'::agtype"
)
else:
condition_parts.append(
f"ag_catalog.agtype_access_operator(VARIADIC ARRAY[properties, '\"info\"'::ag_catalog.agtype, '\"{info_field}\"'::ag_catalog.agtype]) @> {op_value}::agtype"
)
# Handle array of values: generate AND conditions for each value (all must be present)
and_conditions = []
for item in op_value:
if isinstance(item, str):
escaped_value = escape_sql_string(item)
and_conditions.append(
f"ag_catalog.agtype_access_operator(VARIADIC ARRAY[properties, '\"info\"'::ag_catalog.agtype, '\"{info_field}\"'::ag_catalog.agtype]) @> '\"{escaped_value}\"'::agtype"
)
else:
and_conditions.append(
f"ag_catalog.agtype_access_operator(VARIADIC ARRAY[properties, '\"info\"'::ag_catalog.agtype, '\"{info_field}\"'::ag_catalog.agtype]) @> {item}::agtype"
)
if and_conditions:
condition_parts.append(f"({' AND '.join(and_conditions)})")
else:
# Direct property access
if isinstance(op_value, str):
escaped_value = escape_sql_string(op_value)
condition_parts.append(
f"ag_catalog.agtype_access_operator(properties, '\"{key}\"'::agtype) @> '\"{escaped_value}\"'::agtype"
)
else:
condition_parts.append(
f"ag_catalog.agtype_access_operator(properties, '\"{key}\"'::agtype) @> {op_value}::agtype"
)
# Handle array of values: generate AND conditions for each value (all must be present)
and_conditions = []
for item in op_value:
if isinstance(item, str):
escaped_value = escape_sql_string(item)
and_conditions.append(
f"ag_catalog.agtype_access_operator(properties, '\"{key}\"'::agtype) @> '\"{escaped_value}\"'::agtype"
)
else:
and_conditions.append(
f"ag_catalog.agtype_access_operator(properties, '\"{key}\"'::agtype) @> {item}::agtype"
)
if and_conditions:
condition_parts.append(f"({' AND '.join(and_conditions)})")
elif op == "like":
# Handle like operator (for fuzzy matching, similar to SQL LIKE '%value%')
# Check if key starts with "info." prefix
Expand Down
Loading