Skip to content

Commit 5759599

Browse files
authored
update delete_node_by_prams add delete_node_by_prams (#568)
* update delete_node_by_prams add delete_node_by_prams * update delete_node_by_prams add delete_node_by_prams * add log
1 parent 71f8edf commit 5759599

File tree

2 files changed

+148
-82
lines changed

2 files changed

+148
-82
lines changed

src/memos/graph_dbs/neo4j.py

Lines changed: 59 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1530,6 +1530,7 @@ def _parse_node(self, node_data: dict[str, Any]) -> dict[str, Any]:
15301530

15311531
def delete_node_by_prams(
15321532
self,
1533+
writable_cube_ids: list[str],
15331534
memory_ids: list[str] | None = None,
15341535
file_ids: list[str] | None = None,
15351536
filter: dict | None = None,
@@ -1538,56 +1539,90 @@ def delete_node_by_prams(
15381539
Delete nodes by memory_ids, file_ids, or filter.
15391540
15401541
Args:
1542+
writable_cube_ids (list[str]): List of cube IDs (user_name) to filter nodes. Required parameter.
15411543
memory_ids (list[str], optional): List of memory node IDs to delete.
15421544
file_ids (list[str], optional): List of file node IDs to delete.
15431545
filter (dict, optional): Filter dictionary to query matching nodes for deletion.
15441546
15451547
Returns:
15461548
int: Number of nodes deleted.
15471549
"""
1548-
# Collect all node IDs to delete
1549-
ids_to_delete = set()
1550+
logger.info(
1551+
f"[delete_node_by_prams] memory_ids: {memory_ids}, file_ids: {file_ids}, filter: {filter}, writable_cube_ids: {writable_cube_ids}"
1552+
)
1553+
print(
1554+
f"[delete_node_by_prams] memory_ids: {memory_ids}, file_ids: {file_ids}, filter: {filter}, writable_cube_ids: {writable_cube_ids}"
1555+
)
15501556

1551-
# Add memory_ids if provided
1557+
# Validate writable_cube_ids
1558+
if not writable_cube_ids or len(writable_cube_ids) == 0:
1559+
raise ValueError("writable_cube_ids is required and cannot be empty")
1560+
1561+
# Build WHERE conditions separately for memory_ids and file_ids
1562+
where_clauses = []
1563+
params = {}
1564+
1565+
# Build user_name condition from writable_cube_ids (OR relationship - match any cube_id)
1566+
user_name_conditions = []
1567+
for idx, cube_id in enumerate(writable_cube_ids):
1568+
param_name = f"cube_id_{idx}"
1569+
user_name_conditions.append(f"n.user_name = ${param_name}")
1570+
params[param_name] = cube_id
1571+
1572+
# Handle memory_ids: query n.id
15521573
if memory_ids and len(memory_ids) > 0:
1553-
ids_to_delete.update(memory_ids)
1574+
where_clauses.append("n.id IN $memory_ids")
1575+
params["memory_ids"] = memory_ids
15541576

1555-
# Add file_ids if provided (treating them as node IDs)
1577+
# Handle file_ids: query n.file_ids field
1578+
# All file_ids must be present in the array field (AND relationship)
15561579
if file_ids and len(file_ids) > 0:
1557-
ids_to_delete.update(file_ids)
1580+
file_id_and_conditions = []
1581+
for idx, file_id in enumerate(file_ids):
1582+
param_name = f"file_id_{idx}"
1583+
params[param_name] = file_id
1584+
# Check if this file_id is in the file_ids array field
1585+
file_id_and_conditions.append(f"${param_name} IN n.file_ids")
1586+
if file_id_and_conditions:
1587+
# Use AND to require all file_ids to be present
1588+
where_clauses.append(f"({' AND '.join(file_id_and_conditions)})")
15581589

15591590
# Query nodes by filter if provided
1591+
filter_ids = []
15601592
if filter:
15611593
# Use get_by_metadata with empty filters list and filter
15621594
filter_ids = self.get_by_metadata(
15631595
filters=[],
15641596
user_name=None,
15651597
filter=filter,
1566-
knowledgebase_ids=None,
1567-
user_name_flag=False,
1598+
knowledgebase_ids=writable_cube_ids,
15681599
)
1569-
ids_to_delete.update(filter_ids)
15701600

1571-
# If no IDs to delete, return 0
1572-
if not ids_to_delete:
1573-
logger.warning("[delete_node_by_prams] No nodes to delete")
1601+
# If filter returned IDs, add condition for them
1602+
if filter_ids:
1603+
where_clauses.append("n.id IN $filter_ids")
1604+
params["filter_ids"] = filter_ids
1605+
1606+
# If no conditions (except user_name), return 0
1607+
if not where_clauses:
1608+
logger.warning(
1609+
"[delete_node_by_prams] No nodes to delete (no memory_ids, file_ids, or filter provided)"
1610+
)
15741611
return 0
15751612

1576-
# Convert to list for easier handling
1577-
ids_list = list(ids_to_delete)
1578-
logger.info(f"[delete_node_by_prams] Deleting {len(ids_list)} nodes: {ids_list}")
1613+
# Build WHERE clause
1614+
# First, combine memory_ids, file_ids, and filter conditions with OR (any condition can match)
1615+
data_conditions = " OR ".join([f"({clause})" for clause in where_clauses])
15791616

1580-
# Build WHERE condition for collected IDs (query n.id)
1581-
ids_where = "n.id IN $ids_to_delete"
1582-
params = {"ids_to_delete": ids_list}
1617+
# Then, combine with user_name condition using AND (must match user_name AND one of the data conditions)
1618+
user_name_where = " OR ".join(user_name_conditions)
1619+
ids_where = f"({user_name_where}) AND ({data_conditions})"
15831620

1584-
# Calculate total count for logging
1585-
total_count = len(ids_list)
15861621
logger.info(
15871622
f"[delete_node_by_prams] Deleting nodes - memory_ids: {memory_ids}, file_ids: {file_ids}, filter: {filter}"
15881623
)
15891624
print(
1590-
f"[delete_node_by_prams] Deleting {total_count} nodes - memory_ids: {memory_ids}, file_ids: {file_ids}, filter: {filter}"
1625+
f"[delete_node_by_prams] Deleting nodes - memory_ids: {memory_ids}, file_ids: {file_ids}, filter: {filter}"
15911626
)
15921627

15931628
# First count matching nodes to get accurate count
@@ -1599,16 +1634,17 @@ def delete_node_by_prams(
15991634
delete_query = f"MATCH (n:Memory) WHERE {ids_where} DETACH DELETE n"
16001635
logger.info(f"[delete_node_by_prams] delete_query: {delete_query}")
16011636
print(f"[delete_node_by_prams] delete_query: {delete_query}")
1637+
print(f"[delete_node_by_prams] params: {params}")
16021638

16031639
deleted_count = 0
16041640
try:
16051641
with self.driver.session(database=self.db_name) as session:
16061642
# Count nodes before deletion
16071643
count_result = session.run(count_query, **params)
16081644
count_record = count_result.single()
1609-
expected_count = total_count
1645+
expected_count = 0
16101646
if count_record:
1611-
expected_count = count_record["node_count"] or total_count
1647+
expected_count = count_record["node_count"] or 0
16121648

16131649
# Delete nodes
16141650
session.run(delete_query, **params)

src/memos/graph_dbs/polardb.py

Lines changed: 89 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1818,14 +1818,14 @@ def get_by_metadata(
18181818
raise ValueError(f"Unsupported operator: {op}")
18191819

18201820
# Build user_name filter with knowledgebase_ids support (OR relationship) using common method
1821-
user_name_conditions = []
1822-
if user_name_flag:
1823-
user_name_conditions = self._build_user_name_and_kb_ids_conditions_cypher(
1824-
user_name=user_name,
1825-
knowledgebase_ids=knowledgebase_ids,
1826-
default_user_name=self._get_config_value("user_name"),
1827-
)
1828-
print(f"[get_by_metadata] user_name_conditions: {user_name_conditions}")
1821+
# Build user_name filter with knowledgebase_ids support (OR relationship) using common method
1822+
# Build user_name filter with knowledgebase_ids support (OR relationship) using common method
1823+
user_name_conditions = self._build_user_name_and_kb_ids_conditions_cypher(
1824+
user_name=user_name,
1825+
knowledgebase_ids=knowledgebase_ids,
1826+
default_user_name=self._get_config_value("user_name"),
1827+
)
1828+
print(f"[111get_by_metadata] user_name_conditions: {user_name_conditions}")
18291829

18301830
# Add user_name WHERE clause
18311831
if user_name_conditions:
@@ -1837,26 +1837,16 @@ def get_by_metadata(
18371837
# Build filter conditions using common method
18381838
filter_where_clause = self._build_filter_conditions_cypher(filter)
18391839

1840-
# Build WHERE clause: if where_conditions is empty, filter_where_clause should not have " AND " prefix
1841-
if where_conditions:
1842-
where_str = " AND ".join(where_conditions) + filter_where_clause
1843-
else:
1844-
# If no other conditions, remove " AND " prefix from filter_where_clause if present
1845-
if filter_where_clause.startswith(" AND "):
1846-
where_str = filter_where_clause[5:] # Remove " AND " prefix
1847-
else:
1848-
where_str = filter_where_clause
1840+
where_str = " AND ".join(where_conditions) + filter_where_clause
18491841

18501842
# Use cypher query
1851-
# Only include WHERE clause if where_str is not empty
1852-
where_clause = f"WHERE {where_str}" if where_str else ""
18531843
cypher_query = f"""
1854-
SELECT * FROM cypher('{self.db_name}_graph', $$
1855-
MATCH (n:Memory)
1856-
{where_clause}
1857-
RETURN n.id AS id
1858-
$$) AS (id agtype)
1859-
"""
1844+
SELECT * FROM cypher('{self.db_name}_graph', $$
1845+
MATCH (n:Memory)
1846+
WHERE {where_str}
1847+
RETURN n.id AS id
1848+
$$) AS (id agtype)
1849+
"""
18601850

18611851
ids = []
18621852
conn = self._get_connection()
@@ -4008,6 +3998,7 @@ def process_condition(condition):
40083998
@timed
40093999
def delete_node_by_prams(
40104000
self,
4001+
writable_cube_ids: list[str],
40114002
memory_ids: list[str] | None = None,
40124003
file_ids: list[str] | None = None,
40134004
filter: dict | None = None,
@@ -4016,61 +4007,102 @@ def delete_node_by_prams(
40164007
Delete nodes by memory_ids, file_ids, or filter.
40174008
40184009
Args:
4010+
writable_cube_ids (list[str]): List of cube IDs (user_name) to filter nodes. Required parameter.
40194011
memory_ids (list[str], optional): List of memory node IDs to delete.
40204012
file_ids (list[str], optional): List of file node IDs to delete.
40214013
filter (dict, optional): Filter dictionary to query matching nodes for deletion.
40224014
40234015
Returns:
40244016
int: Number of nodes deleted.
40254017
"""
4026-
# Collect all node IDs to delete
4027-
ids_to_delete = set()
4018+
logger.info(
4019+
f"[delete_node_by_prams] memory_ids: {memory_ids}, file_ids: {file_ids}, filter: {filter}, writable_cube_ids: {writable_cube_ids}"
4020+
)
4021+
print(
4022+
f"[delete_node_by_prams] memory_ids: {memory_ids}, file_ids: {file_ids}, filter: {filter}, writable_cube_ids: {writable_cube_ids}"
4023+
)
40284024

4029-
# Add memory_ids if provided
4030-
if memory_ids and len(memory_ids) > 0:
4031-
ids_to_delete.update(memory_ids)
4025+
# Validate writable_cube_ids
4026+
if not writable_cube_ids or len(writable_cube_ids) == 0:
4027+
raise ValueError("writable_cube_ids is required and cannot be empty")
4028+
4029+
# Build user_name condition from writable_cube_ids (OR relationship - match any cube_id)
4030+
user_name_conditions = []
4031+
for cube_id in writable_cube_ids:
4032+
# Escape single quotes in cube IDs
4033+
escaped_cube_id = str(cube_id).replace("'", "\\'")
4034+
user_name_conditions.append(f"n.user_name = '{escaped_cube_id}'")
40324035

4033-
# Add file_ids if provided (treating them as node IDs)
4036+
# Build WHERE conditions separately for memory_ids and file_ids
4037+
where_conditions = []
4038+
4039+
# Handle memory_ids: query n.id
4040+
if memory_ids and len(memory_ids) > 0:
4041+
memory_id_conditions = []
4042+
for node_id in memory_ids:
4043+
# Escape single quotes in node IDs
4044+
escaped_id = str(node_id).replace("'", "\\'")
4045+
memory_id_conditions.append(f"'{escaped_id}'")
4046+
if memory_id_conditions:
4047+
where_conditions.append(f"n.id IN [{', '.join(memory_id_conditions)}]")
4048+
4049+
# Handle file_ids: query n.file_ids field
4050+
# All file_ids must be present in the array field (AND relationship)
40344051
if file_ids and len(file_ids) > 0:
4035-
ids_to_delete.update(file_ids)
4052+
file_id_and_conditions = []
4053+
for file_id in file_ids:
4054+
# Escape single quotes in file IDs
4055+
escaped_id = str(file_id).replace("'", "\\'")
4056+
# Check if this file_id is in the file_ids array field
4057+
file_id_and_conditions.append(f"'{escaped_id}' IN n.file_ids")
4058+
if file_id_and_conditions:
4059+
# Use AND to require all file_ids to be present
4060+
where_conditions.append(f"({' AND '.join(file_id_and_conditions)})")
40364061

40374062
# Query nodes by filter if provided
4063+
filter_ids = set()
40384064
if filter:
40394065
# Parse filter to validate and transform field names (e.g., add "info." prefix if needed)
40404066
parsed_filter = self.parse_filter(filter)
40414067
if parsed_filter:
40424068
# Use get_by_metadata with empty filters list and parsed filter
4043-
filter_ids = self.get_by_metadata(
4044-
filters=[],
4045-
user_name=None,
4046-
filter=parsed_filter,
4047-
knowledgebase_ids=None,
4048-
user_name_flag=False,
4069+
filter_ids = set(
4070+
self.get_by_metadata(
4071+
filters=[],
4072+
user_name=None,
4073+
filter=parsed_filter,
4074+
knowledgebase_ids=writable_cube_ids,
4075+
)
40494076
)
4050-
ids_to_delete.update(filter_ids)
40514077
else:
40524078
logger.warning(
40534079
"[delete_node_by_prams] Filter parsed to None, skipping filter query"
40544080
)
40554081

4056-
# If no IDs to delete, return 0
4057-
if not ids_to_delete:
4058-
logger.warning("[delete_node_by_prams] No nodes to delete")
4082+
# If filter returned IDs, add condition for them
4083+
if filter_ids:
4084+
filter_id_conditions = []
4085+
for node_id in filter_ids:
4086+
# Escape single quotes in node IDs
4087+
escaped_id = str(node_id).replace("'", "\\'")
4088+
filter_id_conditions.append(f"'{escaped_id}'")
4089+
if filter_id_conditions:
4090+
where_conditions.append(f"n.id IN [{', '.join(filter_id_conditions)}]")
4091+
4092+
# If no conditions (except user_name), return 0
4093+
if not where_conditions:
4094+
logger.warning(
4095+
"[delete_node_by_prams] No nodes to delete (no memory_ids, file_ids, or filter provided)"
4096+
)
40594097
return 0
40604098

4061-
# Convert to list for easier handling
4062-
ids_list = list(ids_to_delete)
4063-
logger.info(f"[delete_node_by_prams] Deleting {len(ids_list)} nodes: {ids_list}")
4064-
4065-
# Build WHERE condition for collected IDs (query n.id)
4066-
id_conditions = []
4067-
for node_id in ids_list:
4068-
# Escape single quotes in node IDs
4069-
escaped_id = str(node_id).replace("'", "\\'")
4070-
id_conditions.append(f"'{escaped_id}'")
4099+
# Build WHERE clause
4100+
# First, combine memory_ids, file_ids, and filter conditions with OR (any condition can match)
4101+
data_conditions = " OR ".join([f"({cond})" for cond in where_conditions])
40714102

4072-
# Build WHERE clause for IDs
4073-
ids_where = f"n.id IN [{', '.join(id_conditions)}]"
4103+
# Then, combine with user_name condition using AND (must match user_name AND one of the data conditions)
4104+
user_name_where = " OR ".join(user_name_conditions)
4105+
ids_where = f"({user_name_where}) AND ({data_conditions})"
40744106

40754107
# Use Cypher DELETE query
40764108
# First count matching nodes to get accurate count
@@ -4093,13 +4125,11 @@ def delete_node_by_prams(
40934125
$$) AS (result agtype)
40944126
"""
40954127

4096-
# Calculate total count for logging
4097-
total_count = len(ids_list)
40984128
logger.info(
40994129
f"[delete_node_by_prams] Deleting nodes - memory_ids: {memory_ids}, file_ids: {file_ids}, filter: {filter}"
41004130
)
41014131
print(
4102-
f"[delete_node_by_prams] Deleting {total_count} nodes - memory_ids: {memory_ids}, file_ids: {file_ids}, filter: {filter}"
4132+
f"[delete_node_by_prams] Deleting nodes - memory_ids: {memory_ids}, file_ids: {file_ids}, filter: {filter}"
41034133
)
41044134
logger.info(f"[delete_node_by_prams] delete_query: {delete_query}")
41054135
print(f"[delete_node_by_prams] delete_query: {delete_query}")
@@ -4111,11 +4141,11 @@ def delete_node_by_prams(
41114141
# Count nodes before deletion
41124142
cursor.execute(count_query)
41134143
count_results = cursor.fetchall()
4114-
expected_count = total_count
4144+
expected_count = 0
41154145
if count_results and len(count_results) > 0:
41164146
count_str = str(count_results[0][0])
41174147
count_str = count_str.strip('"').strip("'")
4118-
expected_count = int(count_str) if count_str.isdigit() else total_count
4148+
expected_count = int(count_str) if count_str.isdigit() else 0
41194149

41204150
# Delete nodes
41214151
cursor.execute(delete_query)

0 commit comments

Comments
 (0)