Skip to content

feat(nodes): migrate neo4j onto the graph base class (graph_neo4j)#1611

Merged
dsapandora merged 1 commit into
developfrom
feat/graph-neo4j-migration
Jul 16, 2026
Merged

feat(nodes): migrate neo4j onto the graph base class (graph_neo4j)#1611
dsapandora merged 1 commit into
developfrom
feat/graph-neo4j-migration

Conversation

@dsapandora

@dsapandora dsapandora commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Rename db_neo4jgraph_neo4j (graph DBs use the graph_ prefix, matching graph_falkordb) and derive it from GraphGlobalBase / GraphInstanceBase instead of its own copy of the lane handling, NL-to-Cypher loop and tool surface.
  • classType database → graph; unified get_data / get_query / execute / dialect tools. get_cypher kept as a deprecated alias for get_query; get_schema overridden to preserve Neo4J's label filter and database key.
  • Reads run through READ access-mode Bolt sessions + the is_cypher_safe gate (Bolt has no server-side read-only mode for a standalone instance, so the gate is the primary defence). _run_query honours the base's limit contract (returns limit+1 for honest truncation); _run_query_raw now counts affected_rows unconditionally, fixing the 0 if rows bug (a write that also returns rows was reported as 0).

Why

Follow-up to #1584 (the graph base class). @asclearuc's main review point was that db_neo4j still shipped its own copy of the extracted logic, so the two graph nodes presented divergent tool surfaces. This unifies them and should land before a third graph node arrives.

Type

feature + refactor (node rename)

Testing

  • Tests added or updated — nodes/test/test_graph_neo4j.py (13 tests: read-only gate, READ access mode, limit/truncation, unconditional affected_rows, get_cypher alias, get_schema label filter, envelope unwrap).
  • Unit suite green locally — test_graph_neo4j.py + test_graph_falkordb.py = 51 passed; ruff clean; services.json parses.
  • ./builder nodes:testnot run locally (the machine OOM-killed the engine build/deps step, SIGKILL 137; unrelated to this change). Relying on CI for the engine-level test_contracts / dynamic loading.

Checklist

  • Commit messages follow conventional commits
  • No secrets or credentials included
  • Wiki updated (if applicable)
  • Breaking changes documented — renaming the provider (db_neo4jgraph_neo4j) is breaking for saved pipes, which update when the new services.json ships. Neo4J-specific config keys keep working (field prefix neo4jdb.graph_neo4j. to match the node). Old doc URL redirected.

Linked Issue

Fixes #1600


Scoped to Neo4J only. Per the #1600 discussion, db_arango (multi-model, classType database) and db_hydradb (memory store, no query language) are not graph-base candidates, so Neo4J is the last graph-query node to migrate.

Summary by CodeRabbit

  • New Features

    • Added a Neo4j graph integration (graph_neo4j) with tools for schema inspection, querying, and safe execution plus dialect reporting.
    • Improved read-query safety and limit handling, including truncation detection and enforced row caps.
  • Documentation

    • Updated node and catalog/service documentation to reflect the graph-based integration naming.
    • Updated legacy Neo4j URLs to redirect to the new graph_neo4j path.
  • Tests

    • Added dedicated test coverage for safety checks, truncation/limit behavior, validation, schema filtering, and deprecated tool aliasing.
  • Chores

    • Updated service versioning and pipeline upgrade mapping for graph providers.

@github-actions github-actions Bot added docs Documentation module:nodes Python pipeline nodes labels Jul 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor
🤖 Internal: Discord sync marker

Auto-managed by the Discord notification workflow. Stores the linked Discord message ID and forum thread ID. Do not edit or delete.

@dsapandora
dsapandora marked this pull request as draft July 16, 2026 14:56
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: ec5d3ab4-1c9b-4dbc-abfd-391803f58c3a

📥 Commits

Reviewing files that changed from the base of the PR and between fe208f5 and 75f6787.

⛔ Files ignored due to path filters (1)
  • nodes/src/nodes/graph_neo4j/neo4j.svg is excluded by !**/*.svg
📒 Files selected for processing (16)
  • docs/README-nodes.md
  • nodes/src/nodes/db_neo4j/IGlobal.py
  • nodes/src/nodes/db_neo4j/IInstance.py
  • nodes/src/nodes/db_neo4j/utils.py
  • nodes/src/nodes/graph_falkordb/README.md
  • nodes/src/nodes/graph_neo4j/IGlobal.py
  • nodes/src/nodes/graph_neo4j/IInstance.py
  • nodes/src/nodes/graph_neo4j/README.md
  • nodes/src/nodes/graph_neo4j/__init__.py
  • nodes/src/nodes/graph_neo4j/requirements.txt
  • nodes/src/nodes/graph_neo4j/services.json
  • nodes/test/test_graph_neo4j.py
  • packages/docs/redirects.ts
  • packages/server/engine-lib/engLib/store/headers/services.hpp
  • packages/server/engine-lib/engLib/store/pipeline/pipeline_config.cpp
  • packages/server/engine-lib/test/store/pipeline/pipeline_config.cpp
💤 Files with no reviewable changes (3)
  • nodes/src/nodes/db_neo4j/utils.py
  • nodes/src/nodes/db_neo4j/IInstance.py
  • nodes/src/nodes/db_neo4j/IGlobal.py

📝 Walkthrough

Walkthrough

Neo4j was migrated from db_neo4j to graph_neo4j, adopting shared graph base classes, updated service configuration, Bolt query handling, schema tools, compatibility aliases, pipeline upgrades, documentation, redirects, and tests.

Changes

Neo4j graph service migration

Layer / File(s) Summary
Graph service contract and packaging
nodes/src/nodes/graph_neo4j/..., packages/docs/redirects.ts, docs/README-nodes.md, nodes/src/nodes/graph_falkordb/README.md
Neo4j now uses the graph_neo4j protocol, package path, configuration keys, dependency declaration, documentation, and redirect target.
Neo4j global connection and query runtime
nodes/src/nodes/graph_neo4j/IGlobal.py
Bolt lifecycle, connection probing, read-only and raw Cypher execution, validation, schema reflection, serialization, row limits, and affected-row reporting are implemented.
Graph instance tool surface
nodes/src/nodes/graph_neo4j/IInstance.py
The instance extends GraphInstanceBase, exposes dialect and filtered schema tools, and retains get_cypher as a deprecated alias for get_query.
Pipeline provider migration
packages/server/engine-lib/engLib/store/...
Service version 2 upgrades legacy graph provider names and synchronizes component configuration types.
Runtime and tool validation
nodes/test/test_graph_neo4j.py, packages/server/engine-lib/test/store/pipeline/pipeline_config.cpp
Tests cover query safety, read access, limits, raw execution, validation, schema filtering, aliases, truncation, and provider upgrades.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested labels: module:ai

Suggested reviewers: jmaionchi, stepmikhaylov, rod-christensen

Sequence Diagram(s)

sequenceDiagram
  participant GraphNode
  participant IGlobal
  participant Neo4jDriver
  participant Neo4j
  GraphNode->>IGlobal: request query or schema
  IGlobal->>Neo4jDriver: open read session
  Neo4jDriver->>Neo4j: execute Cypher or reflection procedure
  Neo4j-->>Neo4jDriver: return records and counters
  Neo4jDriver-->>IGlobal: provide result data
  IGlobal-->>GraphNode: return serialized rows or schema
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the Neo4j migration onto the graph base and matches the main change.
Linked Issues check ✅ Passed The changes implement the Neo4j graph-base migration, unified tools, row caps, READ sessions, schema behavior, and affected_rows fix required by #1600.
Out of Scope Changes check ✅ Passed No clearly unrelated code changes stand out; the docs, redirects, version bump, and upgrade mapping all support the Neo4j graph-node migration.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/graph-neo4j-migration

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Biome (2.5.3)
nodes/src/nodes/graph_neo4j/services.json

File contains syntax errors that prevent linting: Line 2: Expected a property but instead found '//'.; Line 6: End of file expected; Line 6: End of file expected; Line 6: End of file expected; Line 6: End of file expected; Line 11: End of file expected; Line 11: End of file expected; Line 11: End of file expected; Line 11: End of file expected; Line 16: End of file expected; Line 16: End of file expected; Line 16: End of file expected; Line 16: End of file expected; Line 22: End of file expected; Line 22: End of file expected; Line 22: End of file expected; Line 22: End of file expected; Line 28: End of file expected; Line 28: End of file expected; Line 28: End of file expected; Line 28: End of file expected; Line 34: End of file expected; Line 34: End of file expected; Line 34: End of file expected; Line 34: End of file expected; Line 40: End of file expected; Line 40: End of file expected; Line 40: End of file expected; Line 40: End of file expected; Line 45: End of file expected; Li

... [truncated 1795 characters] ...

ine 170: End of file expected; Line 177: End of file expected; Line 178: End of file expected; Line 178: End of file expected; Line 178: End of file expected; Line 183: End of file expected; Line 184: End of file expected; Line 184: End of file expected; Line 191: Expected an array, an object, or a literal but instead found '// "graph_neo4j.label",'.; Line 184: End of file expected; Line 191: End of file expected; Line 192: End of file expected; Line 192: End of file expected; Line 193: End of file expected; Line 194: End of file expected; Line 196: End of file expected; Line 196: End of file expected; Line 196: End of file expected; Line 208: End of file expected; Line 214: End of file expected; Line 214: End of file expected; Line 214: End of file expected; Line 221: End of file expected

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

packages/docs/redirects.ts

Oops! Something went wrong! :(

ESLint: 9.39.4

TypeError: expand is not a function
at Minimatch.braceExpand (/node_modules/.pnpm/minimatch@3.1.5/node_modules/minimatch/minimatch.js:271:10)
at Minimatch.make (/node_modules/.pnpm/minimatch@3.1.5/node_modules/minimatch/minimatch.js:180:33)
at new Minimatch (/node_modules/.pnpm/minimatch@3.1.5/node_modules/minimatch/minimatch.js:156:8)
at doMatch (/node_modules/.pnpm/@eslint+config-array@0.21.2/node_modules/@eslint/config-array/dist/cjs/index.cjs:422:13)
at match (/node_modules/.pnpm/@eslint+config-array@0.21.2/node_modules/@eslint/config-array/dist/cjs/index.cjs:756:11)
at /node_modules/.pnpm/@eslint+config-array@0.21.2/node_modules/@eslint/config-array/dist/cjs/index.cjs:772:10
at Array.some ()
at pathMatches (/node_modules/.pnpm/@eslint+config-array@0.21.2/node_modules/@eslint/config-array/dist/cjs/index.cjs:767:44)
at /node_modules/.pnpm/@eslint+config-array@0.21.2/node_modules/@eslint/config-array/dist/cjs/index.cjs:1368:8
at FlatConfigArray.forEach ()


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@dsapandora
dsapandora marked this pull request as ready for review July 16, 2026 14:59

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@nodes/src/nodes/graph_neo4j/IGlobal.py`:
- Around line 191-200: Update the node_props construction in the session.run
schema loop to use each record’s nodeLabels collection instead of the
nodeType-derived label. Add the property and its type to every individual label
in nodeLabels, preserving the existing fallback type of ANY and skipping records
without usable labels.
- Around line 62-76: The _open_driver method must keep the newly created Neo4j
driver local, close it if connectivity or database verification fails, and
assign it to self.driver only after both checks succeed. In _reflect_nodes, read
the nodeLabels field instead of nodeType so each label is reflected individually
for get_schema.

In `@nodes/test/test_graph_neo4j.py`:
- Around line 161-198: Update _scoped_stubs and _load_node to restore every
dynamically injected canonical module after loading. Add ai.common.graph,
ai.common.config_utils, ai.common.tool_args, and all nodes.graph_neo4j module
names to _STUB_NAMES; capture the graph module before cleanup and return it
alongside the loaded node modules so graph_base does not rely on stale
sys.modules entries.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f0a41f26-8056-4f8d-aa81-10db97523c0d

📥 Commits

Reviewing files that changed from the base of the PR and between 0dd4c52 and 3e13fe4.

⛔ Files ignored due to path filters (1)
  • nodes/src/nodes/graph_neo4j/neo4j.svg is excluded by !**/*.svg
📒 Files selected for processing (13)
  • docs/README-nodes.md
  • nodes/src/nodes/db_neo4j/IGlobal.py
  • nodes/src/nodes/db_neo4j/IInstance.py
  • nodes/src/nodes/db_neo4j/utils.py
  • nodes/src/nodes/graph_falkordb/README.md
  • nodes/src/nodes/graph_neo4j/IGlobal.py
  • nodes/src/nodes/graph_neo4j/IInstance.py
  • nodes/src/nodes/graph_neo4j/README.md
  • nodes/src/nodes/graph_neo4j/__init__.py
  • nodes/src/nodes/graph_neo4j/requirements.txt
  • nodes/src/nodes/graph_neo4j/services.json
  • nodes/test/test_graph_neo4j.py
  • packages/docs/redirects.ts
💤 Files with no reviewable changes (3)
  • nodes/src/nodes/db_neo4j/IInstance.py
  • nodes/src/nodes/db_neo4j/utils.py
  • nodes/src/nodes/db_neo4j/IGlobal.py

Comment thread nodes/src/nodes/graph_neo4j/IGlobal.py
Comment thread nodes/src/nodes/graph_neo4j/IGlobal.py
Comment thread nodes/test/test_graph_neo4j.py
joshuadarron
joshuadarron previously approved these changes Jul 16, 2026

@dylan-savage dylan-savage left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing to fix: Unbounded _run_query — [IGlobal.py:130-131] graph_neo4j/IGlobal.py L128-L133). The limit is None path streams all rows uncapped, violating the GraphGlobalBase contract that the sibling graph_falkordb honors (max_rows cap). The pipeline questions lane always hits this path — memory-blowup risk.

@dsapandora

dsapandora commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator Author

Good catch

_run_query drained the full result set on the limit is None path, and that is the questions lane path, so a broad query could blow up worker memory. GraphGlobalBase documents that no-limit means "fall back to read_row_cap"

Fixed in fe208f5, mirroring the sibling:

cap = self.read_row_cap if limit is None else min(int(limit), self.read_row_cap) + 1

That also clamps an explicit limit to the ceiling, which was the other half of the gap. read_row_cap here is the base default (max_execute_rows, 25k) since this node has no max_rows config of its own. Added two tests for the lane path and the clamp, and a README line since the cap is user-visible now.

dylan-savage
dylan-savage previously approved these changes Jul 16, 2026
@dsapandora
dsapandora force-pushed the feat/graph-neo4j-migration branch from fe208f5 to 75f6787 Compare July 16, 2026 19:26
@github-actions github-actions Bot added the module:server C++ engine and server components label Jul 16, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
nodes/src/nodes/graph_neo4j/README.md (1)

118-130: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Run the documentation generator to update schema keys.

The generated parameters still show the old neo4jdb.* field keys instead of the updated graph_neo4j.* keys. As per path instructions, do not hand-edit this block; please run the nodes:docs-generate script to refresh this section so it reflects the recent changes made to services.json.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nodes/src/nodes/graph_neo4j/README.md` around lines 118 - 130, Run the
nodes:docs-generate script to regenerate the parameter table in the graph_neo4j
README, replacing the outdated neo4jdb.* keys with the current graph_neo4j.*
keys. Do not manually edit the generated documentation block.

Source: Path instructions

♻️ Duplicate comments (2)
nodes/src/nodes/graph_neo4j/IGlobal.py (2)

67-76: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Close the temporary driver on failed init.

If connectivity or the database probe raises an exception, the newly created driver stays assigned and open. Keep the driver in a local variable, close it on failure, and assign self.driver only after both checks pass to prevent leaking resources or leaving the driver in a broken state.

🔧 Proposed fix
-        try:
-            self.driver = neo4j.GraphDatabase.driver(self.uri, auth=self._build_auth(config))
-            self.driver.verify_connectivity()
-            # verify_connectivity() only checks the home database — probe the
-            # configured one so a wrong name or missing permission fails fast.
-            with self.driver.session(database=self.database) as session:
-                session.run('RETURN 1').consume()
-        except (ServiceUnavailable, Neo4jError) as e:
-            error(f'Neo4J connection failed: {e}')
-            raise
+        try:
+            driver = neo4j.GraphDatabase.driver(self.uri, auth=self._build_auth(config))
+            try:
+                driver.verify_connectivity()
+                # verify_connectivity() only checks the home database — probe the
+                # configured one so a wrong name or missing permission fails fast.
+                with driver.session(database=self.database) as session:
+                    session.run('RETURN 1').consume()
+                self.driver = driver
+            except Exception:
+                driver.close()
+                raise
+        except (ServiceUnavailable, Neo4jError) as e:
+            error(f'Neo4J connection failed: {e}')
+            raise
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nodes/src/nodes/graph_neo4j/IGlobal.py` around lines 67 - 76, Update the
initialization flow around self.driver and the connectivity/database probe to
create the Neo4j driver in a local variable, close that local driver in the
exception path, and assign it to self.driver only after both checks succeed.
Preserve the existing error logging and re-raise behavior.

191-200: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use nodeLabels when building node_props.

nodeType collapses multi-label nodes into a combined key like Person:Employee, which can cause get_schema queries on a single label to miss entries. Populate each label individually from nodeLabels instead.

🔧 Proposed fix
-            for record in session.run('CALL db.schema.nodeTypeProperties()'):
-                raw_label = record.get('nodeType', '')
-                label = raw_label.lstrip(':') if raw_label else ''
-                if not label:
-                    continue
-                node_props.setdefault(label, [])
-                prop = record.get('propertyName') or ''
-                if prop:
-                    types = record.get('propertyTypes') or []
-                    node_props[label].append((prop, types[0] if types else 'ANY'))
+            for record in session.run('CALL db.schema.nodeTypeProperties()'):
+                labels = record.get('nodeLabels') or []
+                prop = record.get('propertyName') or ''
+                types = record.get('propertyTypes') or []
+                prop_type = types[0] if types else 'ANY'
+                for label in labels:
+                    if not label:
+                        continue
+                    node_props.setdefault(label, [])
+                    if prop:
+                        node_props[label].append((prop, prop_type))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nodes/src/nodes/graph_neo4j/IGlobal.py` around lines 191 - 200, Update the
node_props construction in the session.run schema loop to read nodeLabels and
add each label independently, rather than deriving a single label from nodeType.
Preserve the existing propertyName and propertyTypes handling, associating each
property with every label returned in nodeLabels so single-label get_schema
queries find the entries.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@nodes/src/nodes/graph_neo4j/__init__.py`:
- Line 31: Update the requirements path construction in the module
initialization code to use os.path.join with the directory derived from __file__
and "requirements.txt", replacing string concatenation while preserving the
resulting file location.

In `@nodes/test/test_graph_neo4j.py`:
- Around line 413-422: Expand test_get_schema_filters_by_label with at least one
relationship unrelated to the Person label, then assert the filtered result
excludes that relationship while retaining the Person-related relationship. Keep
the existing label, node, and database assertions unchanged.

---

Outside diff comments:
In `@nodes/src/nodes/graph_neo4j/README.md`:
- Around line 118-130: Run the nodes:docs-generate script to regenerate the
parameter table in the graph_neo4j README, replacing the outdated neo4jdb.* keys
with the current graph_neo4j.* keys. Do not manually edit the generated
documentation block.

---

Duplicate comments:
In `@nodes/src/nodes/graph_neo4j/IGlobal.py`:
- Around line 67-76: Update the initialization flow around self.driver and the
connectivity/database probe to create the Neo4j driver in a local variable,
close that local driver in the exception path, and assign it to self.driver only
after both checks succeed. Preserve the existing error logging and re-raise
behavior.
- Around line 191-200: Update the node_props construction in the session.run
schema loop to read nodeLabels and add each label independently, rather than
deriving a single label from nodeType. Preserve the existing propertyName and
propertyTypes handling, associating each property with every label returned in
nodeLabels so single-label get_schema queries find the entries.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: ec5d3ab4-1c9b-4dbc-abfd-391803f58c3a

📥 Commits

Reviewing files that changed from the base of the PR and between fe208f5 and 75f6787.

⛔ Files ignored due to path filters (1)
  • nodes/src/nodes/graph_neo4j/neo4j.svg is excluded by !**/*.svg
📒 Files selected for processing (16)
  • docs/README-nodes.md
  • nodes/src/nodes/db_neo4j/IGlobal.py
  • nodes/src/nodes/db_neo4j/IInstance.py
  • nodes/src/nodes/db_neo4j/utils.py
  • nodes/src/nodes/graph_falkordb/README.md
  • nodes/src/nodes/graph_neo4j/IGlobal.py
  • nodes/src/nodes/graph_neo4j/IInstance.py
  • nodes/src/nodes/graph_neo4j/README.md
  • nodes/src/nodes/graph_neo4j/__init__.py
  • nodes/src/nodes/graph_neo4j/requirements.txt
  • nodes/src/nodes/graph_neo4j/services.json
  • nodes/test/test_graph_neo4j.py
  • packages/docs/redirects.ts
  • packages/server/engine-lib/engLib/store/headers/services.hpp
  • packages/server/engine-lib/engLib/store/pipeline/pipeline_config.cpp
  • packages/server/engine-lib/test/store/pipeline/pipeline_config.cpp
💤 Files with no reviewable changes (3)
  • nodes/src/nodes/db_neo4j/utils.py
  • nodes/src/nodes/db_neo4j/IInstance.py
  • nodes/src/nodes/db_neo4j/IGlobal.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
nodes/src/nodes/graph_neo4j/README.md (1)

118-130: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Run the documentation generator to update schema keys.

The generated parameters still show the old neo4jdb.* field keys instead of the updated graph_neo4j.* keys. As per path instructions, do not hand-edit this block; please run the nodes:docs-generate script to refresh this section so it reflects the recent changes made to services.json.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nodes/src/nodes/graph_neo4j/README.md` around lines 118 - 130, Run the
nodes:docs-generate script to regenerate the parameter table in the graph_neo4j
README, replacing the outdated neo4jdb.* keys with the current graph_neo4j.*
keys. Do not manually edit the generated documentation block.

Source: Path instructions

♻️ Duplicate comments (2)
nodes/src/nodes/graph_neo4j/IGlobal.py (2)

67-76: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Close the temporary driver on failed init.

If connectivity or the database probe raises an exception, the newly created driver stays assigned and open. Keep the driver in a local variable, close it on failure, and assign self.driver only after both checks pass to prevent leaking resources or leaving the driver in a broken state.

🔧 Proposed fix
-        try:
-            self.driver = neo4j.GraphDatabase.driver(self.uri, auth=self._build_auth(config))
-            self.driver.verify_connectivity()
-            # verify_connectivity() only checks the home database — probe the
-            # configured one so a wrong name or missing permission fails fast.
-            with self.driver.session(database=self.database) as session:
-                session.run('RETURN 1').consume()
-        except (ServiceUnavailable, Neo4jError) as e:
-            error(f'Neo4J connection failed: {e}')
-            raise
+        try:
+            driver = neo4j.GraphDatabase.driver(self.uri, auth=self._build_auth(config))
+            try:
+                driver.verify_connectivity()
+                # verify_connectivity() only checks the home database — probe the
+                # configured one so a wrong name or missing permission fails fast.
+                with driver.session(database=self.database) as session:
+                    session.run('RETURN 1').consume()
+                self.driver = driver
+            except Exception:
+                driver.close()
+                raise
+        except (ServiceUnavailable, Neo4jError) as e:
+            error(f'Neo4J connection failed: {e}')
+            raise
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nodes/src/nodes/graph_neo4j/IGlobal.py` around lines 67 - 76, Update the
initialization flow around self.driver and the connectivity/database probe to
create the Neo4j driver in a local variable, close that local driver in the
exception path, and assign it to self.driver only after both checks succeed.
Preserve the existing error logging and re-raise behavior.

191-200: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use nodeLabels when building node_props.

nodeType collapses multi-label nodes into a combined key like Person:Employee, which can cause get_schema queries on a single label to miss entries. Populate each label individually from nodeLabels instead.

🔧 Proposed fix
-            for record in session.run('CALL db.schema.nodeTypeProperties()'):
-                raw_label = record.get('nodeType', '')
-                label = raw_label.lstrip(':') if raw_label else ''
-                if not label:
-                    continue
-                node_props.setdefault(label, [])
-                prop = record.get('propertyName') or ''
-                if prop:
-                    types = record.get('propertyTypes') or []
-                    node_props[label].append((prop, types[0] if types else 'ANY'))
+            for record in session.run('CALL db.schema.nodeTypeProperties()'):
+                labels = record.get('nodeLabels') or []
+                prop = record.get('propertyName') or ''
+                types = record.get('propertyTypes') or []
+                prop_type = types[0] if types else 'ANY'
+                for label in labels:
+                    if not label:
+                        continue
+                    node_props.setdefault(label, [])
+                    if prop:
+                        node_props[label].append((prop, prop_type))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nodes/src/nodes/graph_neo4j/IGlobal.py` around lines 191 - 200, Update the
node_props construction in the session.run schema loop to read nodeLabels and
add each label independently, rather than deriving a single label from nodeType.
Preserve the existing propertyName and propertyTypes handling, associating each
property with every label returned in nodeLabels so single-label get_schema
queries find the entries.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@nodes/src/nodes/graph_neo4j/__init__.py`:
- Line 31: Update the requirements path construction in the module
initialization code to use os.path.join with the directory derived from __file__
and "requirements.txt", replacing string concatenation while preserving the
resulting file location.

In `@nodes/test/test_graph_neo4j.py`:
- Around line 413-422: Expand test_get_schema_filters_by_label with at least one
relationship unrelated to the Person label, then assert the filtered result
excludes that relationship while retaining the Person-related relationship. Keep
the existing label, node, and database assertions unchanged.

---

Outside diff comments:
In `@nodes/src/nodes/graph_neo4j/README.md`:
- Around line 118-130: Run the nodes:docs-generate script to regenerate the
parameter table in the graph_neo4j README, replacing the outdated neo4jdb.* keys
with the current graph_neo4j.* keys. Do not manually edit the generated
documentation block.

---

Duplicate comments:
In `@nodes/src/nodes/graph_neo4j/IGlobal.py`:
- Around line 67-76: Update the initialization flow around self.driver and the
connectivity/database probe to create the Neo4j driver in a local variable,
close that local driver in the exception path, and assign it to self.driver only
after both checks succeed. Preserve the existing error logging and re-raise
behavior.
- Around line 191-200: Update the node_props construction in the session.run
schema loop to read nodeLabels and add each label independently, rather than
deriving a single label from nodeType. Preserve the existing propertyName and
propertyTypes handling, associating each property with every label returned in
nodeLabels so single-label get_schema queries find the entries.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: ec5d3ab4-1c9b-4dbc-abfd-391803f58c3a

📥 Commits

Reviewing files that changed from the base of the PR and between fe208f5 and 75f6787.

⛔ Files ignored due to path filters (1)
  • nodes/src/nodes/graph_neo4j/neo4j.svg is excluded by !**/*.svg
📒 Files selected for processing (16)
  • docs/README-nodes.md
  • nodes/src/nodes/db_neo4j/IGlobal.py
  • nodes/src/nodes/db_neo4j/IInstance.py
  • nodes/src/nodes/db_neo4j/utils.py
  • nodes/src/nodes/graph_falkordb/README.md
  • nodes/src/nodes/graph_neo4j/IGlobal.py
  • nodes/src/nodes/graph_neo4j/IInstance.py
  • nodes/src/nodes/graph_neo4j/README.md
  • nodes/src/nodes/graph_neo4j/__init__.py
  • nodes/src/nodes/graph_neo4j/requirements.txt
  • nodes/src/nodes/graph_neo4j/services.json
  • nodes/test/test_graph_neo4j.py
  • packages/docs/redirects.ts
  • packages/server/engine-lib/engLib/store/headers/services.hpp
  • packages/server/engine-lib/engLib/store/pipeline/pipeline_config.cpp
  • packages/server/engine-lib/test/store/pipeline/pipeline_config.cpp
💤 Files with no reviewable changes (3)
  • nodes/src/nodes/db_neo4j/utils.py
  • nodes/src/nodes/db_neo4j/IInstance.py
  • nodes/src/nodes/db_neo4j/IGlobal.py
🛑 Comments failed to post (2)
nodes/src/nodes/graph_neo4j/__init__.py (1)

31-31: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use os.path.join for path construction.

Consider using os.path.join instead of string concatenation to build the file path, as it is more robust and idiomatic in Python.

♻️ Proposed refactor
-requirements = os.path.dirname(os.path.realpath(__file__)) + '/requirements.txt'
+requirements = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'requirements.txt')
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

requirements = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'requirements.txt')
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nodes/src/nodes/graph_neo4j/__init__.py` at line 31, Update the requirements
path construction in the module initialization code to use os.path.join with the
directory derived from __file__ and "requirements.txt", replacing string
concatenation while preserving the resulting file location.
nodes/test/test_graph_neo4j.py (1)

413-422: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Exercise relationship filtering in this test.

The fixture only contains a relationship touching Person, so returning unrelated relationships would still pass.

Proposed test expansion
-        'relationships': [{'type': 'LIVES_IN', 'start': 'Person', 'end': 'City'}],
+        'relationships': [
+            {'type': 'LIVES_IN', 'start': 'Person', 'end': 'City'},
+            {'type': 'CONNECTED_TO', 'start': 'City', 'end': 'City'},
+        ],
...
     assert out['database'] == 'neo4j'
+    assert out['relationships'] == [
+        {'type': 'LIVES_IN', 'start': 'Person', 'end': 'City'}
+    ]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

def test_get_schema_filters_by_label():
    glb = _FakeGlobal(_FakeDriver())
    glb.graph_schema = {
        'nodes': {'Person': [('name', 'STRING')], 'City': [('name', 'STRING')]},
        'relationships': [
            {'type': 'LIVES_IN', 'start': 'Person', 'end': 'City'},
            {'type': 'CONNECTED_TO', 'start': 'City', 'end': 'City'},
        ],
    }
    out = _instance(glb).get_schema({'label': 'Person'})
    assert out['labels'] == ['Person']
    assert 'City' not in out['nodes']
    assert out['database'] == 'neo4j'
    assert out['relationships'] == [
        {'type': 'LIVES_IN', 'start': 'Person', 'end': 'City'}
    ]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nodes/test/test_graph_neo4j.py` around lines 413 - 422, Expand
test_get_schema_filters_by_label with at least one relationship unrelated to the
Person label, then assert the filtered result excludes that relationship while
retaining the Person-related relationship. Keep the existing label, node, and
database assertions unchanged.

joshuadarron
joshuadarron previously approved these changes Jul 16, 2026

@joshuadarron joshuadarron left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving — this is a clean migration and I verified it end-to-end rather than just reading it:

  • Ran the new test suite against the real base class (stubs for rocketlib/neo4j only, real ai.common.graph + ai.common.utils from disk, mirroring test_graph_falkordb.py): 15/15 pass. Ruff on the three new Python files: clean.
  • Base-class contract: _run_query's min(limit, cap) + 1 truncation shape, READ access mode, unconditional affected_rows, and the read_row_cap fallback all match both the GraphGlobalBase docstrings and the FalkorDB implementation line-for-line.
  • Saved-pipe compatibility: the neo4jdb.graph_neo4j. field-id rename is safe because dotted field ids flatten to unprefixed root config keys (uri, user, ...), which are unchanged; the provider rename is handled by the new version < 2 upgrade in PipelineConfig::upgradeComponent, which also retrofits the tool_falkordb rename that #1584 shipped without a migration. The version-range validation (1 <= v <= VERSION) and the Catch2 sections line up.
  • Loose ends checked: repo-wide grep for db_neo4j finds only comments/docstrings (below) and constraints.lock, which CI regenerates when the renamed requirements.txt path lands. Docs redirect covers the old URL.

Two non-blocking items, one of which I'd fix before merge:

  1. get_cypher alias returns a different payload than the old tool (inline comment) — the compat claim only holds for the call, not the response.
  2. Stale docstring: packages/ai/src/ai/common/graph/__init__.py:29 still describes db_neo4j as graph-capable-but-not-migrated — worth updating in this PR since it's the sentence this change makes untrue (same for the passing mention in graph_falkordb/services.json:202).

Comment thread nodes/src/nodes/graph_neo4j/IInstance.py Outdated
asclearuc
asclearuc previously approved these changes Jul 16, 2026

@asclearuc asclearuc left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving,

one non-blocking change

Comment thread nodes/src/nodes/graph_neo4j/README.md
Renames db_neo4j to graph_neo4j and derives it from GraphGlobalBase /
GraphInstanceBase, the base extracted in #1584. The node keeps only what is
specific to Neo4J -- the Bolt driver, READ access-mode sessions, and schema
reflection -- while the lane handling, the natural-language-to-Cypher loop
and the shared tools come from the base. IInstance drops from 431 to 129
lines and the local utils.py copy of the safety helpers is gone.

The rename follows the graph_ convention and moves the node out of the
Databases category into Graph Databases, so classType, protocol, path and
the form field prefix change with it. get_schema is overridden to keep the
label filter, and get_cypher stays as a deprecated alias for get_query: it
mirrors the statement under the old `cypher` key, since get_query returns it
under `query` and the alias would otherwise keep the name while silently
changing the payload.

Fixes carried in the migration:
- _run_query refused unsafe Cypher and opened sessions in READ access mode.
  Bolt has no server-side read-only mode for a standalone instance, so
  is_cypher_safe is the primary defence here, not just defence-in-depth.
- _run_query honours the base row contract: limit+1 rows for truncation
  detection, and read_row_cap as the ceiling when the pipeline lane calls
  with no limit.
- affected_rows is counted unconditionally, since a Cypher write commonly
  also returns rows (CREATE (n) RETURN n).

Pipelines are upgraded to services version 2 so saved configs still resolve.
This covers both graph renames: db_neo4j -> graph_neo4j, and the earlier
tool_falkordb -> graph_falkordb from #1584, which shipped without an upgrade
path. Only the provider (and config type) changes -- the stored field names
are untouched by either rename, and the new falkordb fields have defaults.

Fixes #1600
@dsapandora
dsapandora dismissed stale reviews from asclearuc and joshuadarron via 97b1c03 July 16, 2026 20:27
@dsapandora
dsapandora force-pushed the feat/graph-neo4j-migration branch from 75f6787 to 97b1c03 Compare July 16, 2026 20:27
@github-actions github-actions Bot added the module:ai AI/ML modules label Jul 16, 2026
@dsapandora
dsapandora enabled auto-merge (squash) July 16, 2026 21:16
@dsapandora
dsapandora merged commit b834b37 into develop Jul 16, 2026
30 checks passed
@dsapandora
dsapandora deleted the feat/graph-neo4j-migration branch July 16, 2026 21:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Documentation module:ai AI/ML modules module:nodes Python pipeline nodes module:server C++ engine and server components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate db_neo4j onto the graph base class (ai.common.graph)

4 participants