Skip to content

Commit 564d7b1

Browse files
committed
fix(client,notebooks): use fullmatch for DDL validator; cap outer LIMIT in query_facts
- client.py: _CYPHER_IDENT_RE.fullmatch() instead of .match() — pattern is already anchored but fullmatch is explicit for DDL safety - base.py, graph.py: add comments explaining SET r += {} is intentional (no-op supported since coordinode v0.3.12, removes conditional branch) - 03_langgraph_agent.ipynb: fix LIMIT bypass in query_facts sandbox guard; _LIMIT_RE matched inner clauses (WITH ... LIMIT 1), leaving outer result unbounded; replaced with _LIMIT_AT_END_RE anchored to end of query
1 parent 9044d9b commit 564d7b1

4 files changed

Lines changed: 10 additions & 4 deletions

File tree

coordinode/coordinode/client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636

3737
def _validate_cypher_identifier(value: str, param_name: str) -> None:
3838
"""Raise :exc:`ValueError` if *value* is not a valid Cypher identifier."""
39-
if not isinstance(value, str) or not _CYPHER_IDENT_RE.match(value):
39+
if not isinstance(value, str) or not _CYPHER_IDENT_RE.fullmatch(value):
4040
raise ValueError(
4141
f"{param_name} must be a valid Cypher identifier (letters, digits, underscores, "
4242
f"starting with a letter or underscore); got {value!r}"

demo/notebooks/03_langgraph_agent.ipynb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -254,11 +254,11 @@
254254
" # Enforce a result cap so agents cannot dump the entire graph.\n",
255255
" # Cap explicit LIMIT to 20 (preserves smaller limits like LIMIT 1),\n",
256256
" # or append LIMIT 20 when no LIMIT clause is present.\n",
257-
" _LIMIT_RE = re.compile(r\"\\bLIMIT\\s+(\\d+)\\b\", re.IGNORECASE)\n",
257+
" _LIMIT_AT_END_RE = re.compile(r\"\\bLIMIT\\s+(\\d+)\\s*;?\\s*$\", re.IGNORECASE | re.DOTALL)\n",
258258
" def _cap_limit(m):\n",
259259
" return f\"LIMIT {min(int(m.group(1)), 20)}\"\n",
260-
" if _LIMIT_RE.search(q):\n",
261-
" q = _LIMIT_RE.sub(_cap_limit, q)\n",
260+
" if _LIMIT_AT_END_RE.search(q):\n",
261+
" q = _LIMIT_AT_END_RE.sub(_cap_limit, q)\n",
262262
" else:\n",
263263
" q = q.rstrip().rstrip(\";\") + \" LIMIT 20\"\n",
264264
" rows = client.cypher(q, params={\"sess\": SESSION})\n",

langchain-coordinode/langchain_coordinode/graph.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,9 @@ def _create_edge(self, rel: Any) -> None:
215215
dst_label = _cypher_ident(rel.target.type or "Entity")
216216
rel_type = _cypher_ident(rel.type)
217217
props = dict(rel.properties or {})
218+
# SET r += $props is intentionally unconditional (even for empty dicts).
219+
# CoordiNode ≥ v0.3.12 supports SET r += {} as a no-op, which lets us
220+
# keep a single code path instead of branching on emptiness.
218221
self._client.cypher(
219222
f"MATCH (src:{src_label} {{name: $src}}) "
220223
f"MATCH (dst:{dst_label} {{name: $dst}}) "

llama-index-coordinode/llama_index/graph_stores/coordinode/base.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,9 @@ def upsert_relations(self, relations: list[Relation]) -> None:
240240
for rel in relations:
241241
props = rel.properties or {}
242242
label = _cypher_ident(rel.label)
243+
# SET r += $props is intentionally unconditional (even for empty dicts).
244+
# CoordiNode ≥ v0.3.12 supports SET r += {} as a no-op, which lets us
245+
# keep a single code path instead of branching on emptiness.
243246
cypher = (
244247
f"MATCH (src {{id: $src_id}}) MATCH (dst {{id: $dst_id}}) "
245248
f"MERGE (src)-[r:{label}]->(dst) SET r += $props"

0 commit comments

Comments
 (0)