diff --git a/.gitattributes b/.gitattributes
index 5803f6f23..5ca5349c8 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -32,4 +32,9 @@
*.whl binary
-.github/workflows/*.lock.yml linguist-generated=true merge=ours
\ No newline at end of file
+.github/workflows/*.lock.yml linguist-generated=true merge=ours
+# ANTLR-generated openCypher/agtype parsers (committed so devs don't need Java);
+# vendored apache/age agtype decoder. Collapsed in diffs — see the age layer README.
+packages/ai/src/ai/common/graph/age/_cypher/gen/** linguist-generated=true
+packages/ai/src/ai/common/graph/age/_agtype/gen/** linguist-generated=true
+packages/ai/src/ai/common/graph/age/_agtype/** linguist-vendored=true
diff --git a/.github/workflows/_build.yaml b/.github/workflows/_build.yaml
index b82400876..f54a1826e 100644
--- a/.github/workflows/_build.yaml
+++ b/.github/workflows/_build.yaml
@@ -215,6 +215,46 @@ jobs:
echo "ROCKETRIDE_TEST_AZURE_CONTAINER=rocketride-test"
} >> "$GITHUB_ENV"
+ # Postgres for the RocketRide cloud DB node integration suites (Linux
+ # only): pgvector for rocketride_sql/vector, Apache AGE for
+ # rocketride_graph. Same pattern as MinIO/Azurite above — env vars are
+ # exported to $GITHUB_ENV only after both databases are healthy, and
+ # RR_REQUIRE_DB_TESTS additionally turns the suites' unreachable-DB
+ # skip into a hard failure, so a container that fails to start can
+ # never let the safety-control tests go silently green.
+ # NB: close to, not exactly, the cloud pin (PG 16.14 + AGE 1.5.0 +
+ # pgvector 0.8.0 in ONE database); publishing that exact image to GHCR
+ # is a noted follow-up. AGE version here IS the pinned 1.5.0.
+ - name: Start Postgres (pgvector + AGE) for tests / Linux
+ if: matrix.platform == 'ubuntu' && inputs.codeql == false
+ run: |
+ set -euo pipefail
+ docker run -d --name rr-pgvector -p 55432:5432 \
+ -e POSTGRES_USER=rruser -e POSTGRES_PASSWORD=rrpass -e POSTGRES_DB=rrtenant \
+ pgvector/pgvector:pg16
+ docker run -d --name rr-age -p 55433:5432 \
+ -e POSTGRES_USER=rruser -e POSTGRES_PASSWORD=rrpass -e POSTGRES_DB=rrtenant \
+ apache/age:release_PG16_1.5.0 -c shared_preload_libraries=age
+ for name in rr-pgvector rr-age; do
+ ready=
+ for i in $(seq 1 30); do
+ if docker exec "$name" pg_isready -U rruser -d rrtenant >/dev/null 2>&1; then ready=1; break; fi
+ sleep 2
+ done
+ [ "${ready:-}" = "1" ] || { echo "$name did not become ready"; docker logs "$name" || true; exit 1; }
+ done
+ docker exec rr-pgvector psql -U rruser -d rrtenant -c 'CREATE EXTENSION IF NOT EXISTS vector;'
+ docker exec rr-age psql -U rruser -d rrtenant -c 'CREATE EXTENSION IF NOT EXISTS age;'
+ # Smoke both: a vector literal and an AGE graph round-trip.
+ docker exec rr-pgvector psql -U rruser -d rrtenant -c "SELECT '[1,2,3]'::vector;"
+ docker exec rr-age psql -U rruser -d rrtenant -c "SET search_path=ag_catalog,\"\$user\",public; SELECT create_graph('ci_smoke'); SELECT drop_graph('ci_smoke', true);"
+ # Export test vars only once both databases are live.
+ {
+ echo "RR_TEST_PG_DSN=postgresql://rruser:rrpass@localhost:55432/rrtenant"
+ echo "RR_TEST_AGE_DSN=postgresql://rruser:rrpass@localhost:55433/rrtenant"
+ echo "RR_REQUIRE_DB_TESTS=1"
+ } >> "$GITHUB_ENV"
+
- name: Test
if: inputs.codeql == false
# Integration tests boot a local server on :5565 and connect a test
diff --git a/nodes/src/nodes/rocketride_graph/IGlobal.py b/nodes/src/nodes/rocketride_graph/IGlobal.py
new file mode 100644
index 000000000..31577985f
--- /dev/null
+++ b/nodes/src/nodes/rocketride_graph/IGlobal.py
@@ -0,0 +1,358 @@
+# =============================================================================
+# MIT License
+# Copyright (c) 2026 Aparavi Software AG
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# =============================================================================
+
+"""RocketRide cloud graph global state.
+
+Mirrors ``graph_neo4j`` on :class:`GraphGlobalBase`, with two differences:
+
+1. **Connection** — no host/user/password config; the per-tenant DSN comes
+ from the account layer (``ai.common.rocketride_db``), same seam as
+ ``rocketride_sql`` / ``rocketride_vector``. The tenant database is
+ Postgres + Apache AGE.
+2. **Query paths** — Apache AGE cannot run bare Cypher, so ``_run_query``
+ (safe), ``_run_query_raw`` (EXECUTE) and ``_validate_query`` all route
+ through the Cypher->AGE translation layer (``ai.common.graph.age``).
+
+Session discipline: the cloud endpoint is a transaction-mode pooler, so every
+query runs inside one transaction whose settings are ``SET LOCAL`` only (the
+translated plan carries them). The safe path additionally makes its
+transaction ``READ ONLY`` — server-side write protection, with the base's
+``is_cypher_safe`` regex and the layer's semantic firewall as defence-in-depth.
+"""
+
+import re
+from typing import Any, Dict, List, Optional, Tuple
+
+import psycopg2
+from psycopg2 import sql
+
+from rocketlib import warning
+from ai.common.graph import GraphGlobalBase
+from ai.common.graph.age import (
+ AgeTranslationError,
+ FirewallConfig,
+ TranslateMode,
+ TranslatedQuery,
+ decode_agtype,
+ decode_row,
+ translate,
+)
+from ai.common.rocketride_db import resolve_rocketride_dsn
+
+DEFAULT_GRAPH_NAME = 'rocketride'
+DEFAULT_MAX_ROWS = 1000
+DEFAULT_QUERY_TIMEOUT_MS = 30000
+
+# Reflection fan-out bounds (mirrors graph_falkordb's sampling posture).
+SCHEMA_LABEL_LIMIT = 200
+SCHEMA_REL_LIMIT = 100
+
+# Same identifier rule the translation layer applies to graph names — labels
+# interpolated into Cypher text must conform (see _sample_edge_endpoints).
+VALID_LABEL = re.compile(r'^[A-Za-z_][A-Za-z0-9_]{0,62}$')
+
+
+class IGlobal(GraphGlobalBase):
+ """Postgres + Apache AGE global for the RocketRide cloud graph node."""
+
+ client = None
+ graph_name: str = DEFAULT_GRAPH_NAME
+ max_rows: int = DEFAULT_MAX_ROWS
+ query_timeout_ms: int = DEFAULT_QUERY_TIMEOUT_MS
+ age_version: str = ''
+
+ # ------------------------------------------------------------------
+ # Driver lifecycle
+ # ------------------------------------------------------------------
+
+ def _open_driver(self, config: Dict[str, Any]) -> None:
+ """Resolve the tenant DSN, connect, and fail fast on a missing graph."""
+ self.graph_name = str(config.get('graph') or DEFAULT_GRAPH_NAME).strip()
+ self.max_rows = self._config_int(config, 'max_rows', DEFAULT_MAX_ROWS)
+ self.query_timeout_ms = self._config_int(config, 'query_timeout_ms', DEFAULT_QUERY_TIMEOUT_MS)
+
+ # The one RocketRide difference: DSN from the account seam, no config.
+ dsn = resolve_rocketride_dsn()
+ # Bounded connect so an unreachable pooler fails fast (matches the
+ # probe's 3s timeout) instead of waiting out the OS TCP timeout.
+ self.client = psycopg2.connect(dsn, connect_timeout=3)
+
+ # Fail-fast round-trip: AGE present + the tenant graph exists.
+ with self.client.cursor() as cur:
+ cur.execute("SELECT extversion FROM pg_extension WHERE extname = 'age'")
+ row = cur.fetchone()
+ if row is None:
+ raise RuntimeError('The tenant database does not have the Apache AGE extension installed')
+ self.age_version = row[0]
+ cur.execute('SELECT 1 FROM ag_catalog.ag_graph WHERE name = %s', (self.graph_name,))
+ graph_exists = cur.fetchone() is not None
+ self.client.commit()
+
+ if not graph_exists:
+ # OPEN QUESTION (create_graph ownership, see the node README and
+ # the design's open-questions): whether the node or the cloud
+ # provisioner creates the per-tenant graph is undecided. Until it
+ # is, the node refuses to run against a missing graph rather than
+ # silently creating one.
+ raise RuntimeError(
+ f'AGE graph {self.graph_name!r} does not exist in the tenant database. '
+ 'Graph provisioning ownership is pending; create the graph '
+ f"(SELECT create_graph('{self.graph_name}')) or configure an existing one."
+ )
+
+ def _close_driver(self) -> None:
+ if self.client is not None:
+ self.client.close()
+ self.client = None
+
+ def _probe_connection(self, config: Dict[str, Any]) -> None:
+ """Save-time probe: warn-only (the user is still editing the node)."""
+ conn = None
+ try:
+ dsn = resolve_rocketride_dsn()
+ conn = psycopg2.connect(dsn, connect_timeout=3)
+ with conn.cursor() as cur:
+ cur.execute('SELECT 1')
+ cur.execute("SELECT 1 FROM pg_extension WHERE extname = 'age'")
+ if cur.fetchone() is None:
+ warning('The tenant database does not have the Apache AGE extension installed')
+ return
+ graph = str(config.get('graph') or DEFAULT_GRAPH_NAME).strip()
+ cur.execute('SELECT 1 FROM ag_catalog.ag_graph WHERE name = %s', (graph,))
+ if cur.fetchone() is None:
+ warning(f'AGE graph {graph!r} does not exist yet in the tenant database')
+ except NotImplementedError as e:
+ # The OSS account stub: no cloud sign-in available.
+ warning(str(e))
+ except Exception as e:
+ warning(str(e).strip().splitlines()[0] if str(e).strip() else repr(e))
+ finally:
+ try:
+ if conn is not None:
+ conn.close()
+ except Exception:
+ pass
+
+ # ------------------------------------------------------------------
+ # Query paths — everything routes through the translation layer
+ # ------------------------------------------------------------------
+
+ @property
+ def read_row_cap(self) -> int:
+ """Node-owner row ceiling for the safe read path."""
+ return self.max_rows
+
+ def _firewall_config(self) -> FirewallConfig:
+ return FirewallConfig(statement_timeout_ms=self.query_timeout_ms)
+
+ def _translate(
+ self,
+ query: str,
+ params: Optional[Dict[str, Any]] = None,
+ limit: Optional[int] = None,
+ mode: TranslateMode = TranslateMode.SAFE,
+ ) -> TranslatedQuery:
+ return translate(
+ query,
+ params=params,
+ limit=limit,
+ mode=mode,
+ graph_name=self.graph_name,
+ age_version=self.age_version or '1.5.0',
+ firewall=self._firewall_config(),
+ )
+
+ def _execute_plan(self, plan: TranslatedQuery, fetch_cap: Optional[int] = None) -> List[Tuple]:
+ """Run one translated plan in its own transaction; return raw rows.
+
+ The plan's READ ONLY flag is applied with ``SET TRANSACTION READ
+ ONLY`` as the first in-transaction statement (psycopg2 has already
+ opened the transaction), giving server-side write protection on the
+ safe path.
+ """
+ if self.client is None:
+ raise RuntimeError('Database connection is not initialized')
+ # Both server-side controls below (SET TRANSACTION READ ONLY and the
+ # plan's SET LOCAL statement_timeout) are silent no-ops outside a
+ # transaction. They hold because psycopg2 opens one implicitly on the
+ # first execute — which requires autocommit off. Assert the
+ # precondition so a future change fails loudly instead of quietly
+ # dropping the write protection and the timeout cap.
+ if self.client.autocommit:
+ raise RuntimeError(
+ 'rocketride_graph requires autocommit=False: READ ONLY and SET LOCAL '
+ 'statement_timeout only apply inside a transaction'
+ )
+ rows: List[Tuple] = []
+ try:
+ with self.client.cursor() as cur:
+ if plan.read_only:
+ cur.execute('SET TRANSACTION READ ONLY')
+ for i, (stmt, binds) in enumerate(zip(plan.statements, plan.binds)):
+ cur.execute(stmt, binds or None)
+ if i == plan.result_index and cur.description is not None:
+ rows = cur.fetchmany(fetch_cap) if fetch_cap else cur.fetchall()
+ self.client.commit()
+ except Exception:
+ self.client.rollback()
+ raise
+ return rows
+
+ def _run_query(self, query: str, params: Optional[Dict] = None, limit: Optional[int] = None) -> List[Dict]:
+ """Read-only path: translate (safe mode) and run in a READ ONLY txn."""
+ cap = self.read_row_cap if limit is None else min(int(limit), self.read_row_cap) + 1
+ plan = self._translate(query, params=params, limit=cap, mode=TranslateMode.SAFE)
+ raw_rows = self._execute_plan(plan, fetch_cap=cap)
+ return [decode_row(plan, row) for row in raw_rows]
+
+ def _run_query_raw(self, query: str) -> Dict[str, Any]:
+ """EXECUTE path: translation still applies (AGE can't run bare Cypher);
+ only the semantic firewall is skipped, never the resource caps.
+ """
+ plan = self._translate(query, mode=TranslateMode.RAW)
+ max_rows = self.max_execute_rows
+ raw_rows = self._execute_plan(plan, fetch_cap=max_rows + 1)
+ if len(raw_rows) > max_rows:
+ raise ValueError(f'EXECUTE query exceeded max_execute_rows={max_rows}')
+ rows = [decode_row(plan, row) for row in raw_rows]
+ # AGE exposes no write counters (unlike Bolt); affected_rows reports
+ # the result-row count — 0 for writes without a RETURN clause.
+ return {'rows': rows, 'affected_rows': len(rows)}
+
+ def _validate_query(self, query: str) -> Tuple[bool, str]:
+ """EXPLAIN the translated statement without executing it.
+
+ Translation errors (syntax, firewall, capability) surface here too, so
+ the LLM repair loop receives the layer's actionable messages.
+ """
+ try:
+ plan = self._translate(query, mode=TranslateMode.SAFE)
+ except AgeTranslationError as e:
+ return False, str(e)
+ if self.client is None:
+ return False, 'Database connection is not initialized'
+ try:
+ with self.client.cursor() as cur:
+ cur.execute('SET TRANSACTION READ ONLY')
+ for i, (stmt, binds) in enumerate(zip(plan.statements, plan.binds)):
+ if i == plan.result_index:
+ cur.execute('EXPLAIN ' + stmt, binds or None)
+ break
+ cur.execute(stmt, binds or None)
+ self.client.rollback()
+ return True, ''
+ except Exception as e:
+ self.client.rollback()
+ return False, str(e)
+
+ # ------------------------------------------------------------------
+ # Schema reflection — AGE catalog + bounded sampling
+ # ------------------------------------------------------------------
+
+ def _reflect_schema(self) -> Dict[str, Any]:
+ """Labels from ``ag_catalog.ag_label``; property types by sampling.
+
+ Best-effort by contract: any failure warns and degrades to a partial
+ (or empty) schema rather than blocking startup.
+ """
+ schema: Dict[str, Any] = {'nodes': {}, 'relationships': []}
+ if self.client is None:
+ return schema
+
+ try:
+ with self.client.cursor() as cur:
+ cur.execute(
+ 'SELECT l.name, l.kind FROM ag_catalog.ag_label l '
+ 'JOIN ag_catalog.ag_graph g ON l.graph = g.graphid '
+ "WHERE g.name = %s AND l.name NOT LIKE '\\_ag\\_%%'",
+ (self.graph_name,),
+ )
+ labels = cur.fetchall()
+ self.client.commit()
+ except Exception as e:
+ self.client.rollback()
+ warning(f'rocketride_graph: unable to list graph labels: {e}')
+ return schema
+
+ vertex_labels = [name for name, kind in labels if kind == 'v'][:SCHEMA_LABEL_LIMIT]
+ edge_labels = [name for name, kind in labels if kind == 'e'][:SCHEMA_REL_LIMIT]
+
+ for label in vertex_labels:
+ schema['nodes'][label] = self._sample_properties(label)
+
+ for etype in edge_labels:
+ start, end = self._sample_edge_endpoints(etype)
+ schema['relationships'].append({'type': etype, 'start': start, 'end': end})
+
+ return schema
+
+ def _sample_properties(self, label: str) -> List[Tuple[str, str]]:
+ """One-row property sample from the label's backing table."""
+ try:
+ with self.client.cursor() as cur:
+ cur.execute(sql.SQL('SELECT properties FROM {} LIMIT 1').format(sql.Identifier(self.graph_name, label)))
+ row = cur.fetchone()
+ self.client.commit()
+ except Exception as e:
+ self.client.rollback()
+ warning(f'rocketride_graph: unable to sample label {label!r}: {e}')
+ return []
+ if row is None:
+ return []
+ properties = decode_agtype(row[0]) or {}
+ return [(key, type(value).__name__) for key, value in properties.items()]
+
+ def _sample_edge_endpoints(self, etype: str) -> Tuple[str, str]:
+ """Start/end labels for one sampled edge of the given type."""
+ # The one Cypher string the node assembles itself, so hold the label
+ # to the same identifier rule the layer applies to graph names: AGE
+ # allows backtick-quoted labels with arbitrary characters, and those
+ # must not be spliced into query text.
+ if not VALID_LABEL.fullmatch(etype or ''):
+ warning(f'rocketride_graph: skipping relationship sample for non-identifier label {etype!r}')
+ return '', ''
+ try:
+ plan = self._translate(
+ f'MATCH (a)-[r:{etype}]->(b) RETURN label(a) AS s, label(b) AS e',
+ limit=1,
+ mode=TranslateMode.SAFE,
+ )
+ raw = self._execute_plan(plan, fetch_cap=1)
+ except Exception as e:
+ warning(f'rocketride_graph: unable to sample relationship {etype!r}: {e}')
+ return '', ''
+ if not raw:
+ return '', ''
+ row = decode_row(plan, raw[0])
+ return str(row.get('s') or ''), str(row.get('e') or '')
+
+ # ------------------------------------------------------------------
+ # Helpers
+ # ------------------------------------------------------------------
+
+ @staticmethod
+ def _config_int(config: Dict[str, Any], key: str, default: int) -> int:
+ try:
+ return max(1, int(config.get(key, default)))
+ except (TypeError, ValueError):
+ return default
diff --git a/nodes/src/nodes/rocketride_graph/IInstance.py b/nodes/src/nodes/rocketride_graph/IInstance.py
new file mode 100644
index 000000000..b23ec35c4
--- /dev/null
+++ b/nodes/src/nodes/rocketride_graph/IInstance.py
@@ -0,0 +1,25 @@
+# =============================================================================
+# MIT License
+# Copyright (c) 2026 Aparavi Software AG
+# =============================================================================
+
+from ai.common.graph import GraphInstanceBase
+from .IGlobal import IGlobal
+
+
+class IInstance(GraphInstanceBase):
+ """RocketRide cloud graph instance.
+
+ All tool methods (get_data, get_schema, get_query, execute, dialect) and
+ lane handlers are inherited from GraphInstanceBase; the Cypher->AGE
+ translation lives entirely behind IGlobal's query hooks. The tool surface
+ speaks Cypher to callers, so _query_language stays the base default.
+ """
+
+ IGlobal: IGlobal
+
+ def _db_display_name(self) -> str:
+ return 'RocketRide Graph'
+
+ def _db_dialect(self) -> str:
+ return 'age'
diff --git a/nodes/src/nodes/rocketride_graph/README.md b/nodes/src/nodes/rocketride_graph/README.md
new file mode 100644
index 000000000..d8ce1700d
--- /dev/null
+++ b/nodes/src/nodes/rocketride_graph/README.md
@@ -0,0 +1,45 @@
+# rocketride_graph
+
+A RocketRide-managed graph database node backed by PostgreSQL + Apache AGE in your own provisioned RocketRide cloud database — with **zero database setup**.
+
+## What it does
+
+Mirrors the `graph_neo4j` node: as a pipeline node it takes natural-language questions on the `questions` lane, asks a connected LLM to translate them to Cypher, executes, and emits results; as a tool node agents call `get_data`, `get_schema`, `get_query`, `execute`, and `dialect` (dialect: `age`).
+
+Two differences from the generic graph nodes:
+
+1. **No connection fields.** The per-tenant DSN is resolved from the account layer (`Account.resolve_db_dsn(client_id)`), keyed by the authenticated connection identity — the same seam as `rocketride_sql` and `rocketride_vector` (one database per tenant backs all three). Requires signing into RocketRide cloud; the open-source build without a cloud identity fails with `RocketRide cloud DB nodes require signing into RocketRide cloud`.
+2. **Cypher → AGE translation.** Apache AGE cannot run bare Cypher, so every query path routes through the translation layer at `ai.common.graph.age` (openCypher ANTLR parse → firewall → dialect capability gate → `cypher()` envelope with synthesized column list → prepared-statement parameter binding → agtype decode). Even the raw EXECUTE path translates — only the *semantic* firewall is skipped there, never the resource caps.
+
+## Safety model
+
+- **Safe path** (LLM/tool reads): runs in a server-side **READ ONLY transaction** (writes are refused by Postgres itself), plus the layer's semantic firewall (no write clauses, no CALL) and the base's `is_cypher_safe` regex as defence-in-depth.
+- **Resource caps** (both paths): query length limit, variable-length traversal depth cap (unbounded `*` patterns are rejected), and a per-transaction `statement_timeout`.
+- **EXECUTE** is gated by `allow_execute` (default off); isolation for raw writes is the database-per-tenant boundary.
+- All per-query settings are `SET LOCAL` — the cloud endpoint is a transaction-mode pooler, so session-level `SET` would bleed across tenants. AGE is preloaded server-side (no `LOAD`).
+
+## Graph provisioning (open)
+
+Ownership of per-tenant `create_graph` is **pending** (cloud provisioner vs node). Until decided, the node fails fast at pipeline start when the configured graph does not exist rather than creating one silently.
+
+## Configuration
+
+### Fields
+
+| Field | Type | Description |
+|---|---|---|
+| `graph` | string | Default "rocketride". Name of the AGE graph to query |
+| `db_description` | string | Default empty. What the graph contains; improves LLM query quality |
+| `max_attempts` | integer | Default 5. LLM re-ask ceiling when validation rejects generated Cypher |
+| `max_rows` | integer | Default 1000. Row ceiling for the read path |
+| `query_timeout_ms` | integer | Default 30000. Per-transaction statement timeout |
+| `allow_execute` | boolean | Default false. Enables the raw EXECUTE path |
+
+There are intentionally no `host` / `user` / `password` / `database` fields.
+
+### Dialect notes (AGE 1.5.0)
+
+The capability table (see `ai.common.graph.age.capabilities`) rejects constructs the cloud's AGE 1.5.0 cannot run with actionable messages: `datetime()` (store ISO-8601 strings or epoch numbers), `RETURN *` (list columns explicitly), `ORDER BY` on a projection alias (order by the expression), `MERGE ... ON CREATE/MATCH SET` (plain `MERGE` then a separate `SET`), label predicates in `WHERE` (put the label in the `MATCH` pattern), multi-labels like `(n:A:B)` (model the second label as a property or category-node edge), and `shortestPath()` (use a bounded variable-length match). All cells are empirically verified against the exact cloud pin — none pass through unverified.
+
+
+
diff --git a/nodes/src/nodes/rocketride_graph/__init__.py b/nodes/src/nodes/rocketride_graph/__init__.py
new file mode 100644
index 000000000..28e1eed0e
--- /dev/null
+++ b/nodes/src/nodes/rocketride_graph/__init__.py
@@ -0,0 +1,38 @@
+# =============================================================================
+# MIT License
+# Copyright (c) 2026 Aparavi Software AG
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# =============================================================================
+
+# ------------------------------------------------------------------------------
+# Main module
+# ------------------------------------------------------------------------------
+import os
+from depends import depends # type: ignore
+
+# Install psycopg2-binary before importing any driver code (this node drives
+# psycopg2 directly; it does not use SQLAlchemy).
+requirements = os.path.dirname(os.path.realpath(__file__)) + '/requirements.txt'
+depends(requirements)
+
+from .IGlobal import IGlobal # noqa: E402
+from .IInstance import IInstance # noqa: E402
+
+__all__ = ['IGlobal', 'IInstance']
diff --git a/nodes/src/nodes/rocketride_graph/requirements.txt b/nodes/src/nodes/rocketride_graph/requirements.txt
new file mode 100644
index 000000000..27e38fe13
--- /dev/null
+++ b/nodes/src/nodes/rocketride_graph/requirements.txt
@@ -0,0 +1 @@
+psycopg2-binary==2.9.12
diff --git a/nodes/src/nodes/rocketride_graph/rocketride_graph.svg b/nodes/src/nodes/rocketride_graph/rocketride_graph.svg
new file mode 100644
index 000000000..ecfcb48da
--- /dev/null
+++ b/nodes/src/nodes/rocketride_graph/rocketride_graph.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/nodes/src/nodes/rocketride_graph/services.json b/nodes/src/nodes/rocketride_graph/services.json
new file mode 100644
index 000000000..389da5fe8
--- /dev/null
+++ b/nodes/src/nodes/rocketride_graph/services.json
@@ -0,0 +1,173 @@
+{
+ //
+ // Required:
+ // The displayable name of this node
+ //
+ "title": "RocketRide Graph",
+ //
+ // Required:
+ // The protocol is the endpoint protocol
+ //
+ "protocol": "rocketride_graph://",
+ //
+ // Required:
+ // Class type of the node - what it does
+ //
+ "classType": ["graph", "tool"],
+ //
+ // Required:
+ // Capabilities are flags that change the behavior of the underlying
+ // engine
+ //
+ "capabilities": ["noremote", "invoke"],
+ //
+ // Optional:
+ // Register is either filter, endpoint or ignored if not specified. If the
+ // type is specified, a factory is registered of that given type
+ //
+ "register": "filter",
+ //
+ // Optional:
+ // The node is the actual physical node to instantiate - if
+ // not specified, the protocol will be used
+ //
+ "node": "python",
+ //
+ // Optional:
+ // The path is the executable/script code - it is node dependent
+ // and is optional for most node
+ //
+ "path": "nodes.rocketride_graph",
+ //
+ // Required:
+ // The prefix map when added/removed when converting URLs <=> paths
+ //
+ "prefix": "rocketridegraph",
+ //
+ // Optional:
+ // Description of this driver
+ //
+ "description": ["A RocketRide-managed graph database backed by PostgreSQL + Apache AGE. ", "Answers natural-language questions over your own provisioned RocketRide cloud ", "graph with zero setup — no host, user, or password to enter; the connection is ", "resolved automatically from your signed-in RocketRide identity. Cypher queries ", "are translated to AGE with a resource firewall, and safe reads run in read-only ", "transactions server-side."],
+ //
+ // Optional:
+ // The icon is the icon to display in the UI for this node
+ //
+ "icon": "rocketride_graph.svg",
+ "documentation": "https://docs.rocketride.org",
+ //
+ // Optional:
+ // Defines the invoke connections that may/must be connected
+ // which are utilized by this driver
+ //
+ "invoke": {
+ "llm": {
+ "description": "LLM to use to craft Cypher queries from question",
+ "min": 1
+ }
+ },
+ //
+ // Optional:
+ // As a pipe component, define what this pipe component takes
+ // and what it produces
+ //
+ "lanes": {
+ "questions": ["table", "text", "answers"]
+ },
+ //
+ // Optional:
+ // Profile section are configuration options used by the driver
+ // itself
+ //
+ "preconfig": {
+ // Define the values that will be merged into any profile configuration
+ // specified, unless the profile is 'absolute'
+ "default": "default",
+ // Defines profiles used with the "profile": key
+ "profiles": {
+ "default": {
+ "graph": "rocketride"
+ }
+ }
+ },
+ //
+ // Optional:
+ // Local fields definitions - these define fields only for the
+ // current service. You may specify them here, or directly
+ // in the shape
+ //
+ "fields": {
+ "rocketridegraph.graph": {
+ "type": "string",
+ "title": "Graph name",
+ "default": "rocketride",
+ "description": "Name of the AGE graph to query in your RocketRide cloud database"
+ },
+ "rocketridegraph.db_description": {
+ "type": "string",
+ "title": "Graph description",
+ "default": "",
+ "description": "What is this graph used for? Describe its content and purpose, this helps the LLM generate more accurate queries.",
+ "ui": {
+ "ui:widget": "textarea"
+ }
+ },
+ "rocketridegraph.max_attempts": {
+ "type": "integer",
+ "title": "Max validation attempts",
+ "default": 5,
+ "minimum": 1,
+ "maximum": 20,
+ "description": "Maximum number of times to re-ask the LLM if validation rejects the generated Cypher"
+ },
+ "rocketridegraph.max_rows": {
+ "type": "integer",
+ "title": "Max read rows",
+ "default": 1000,
+ "minimum": 1,
+ "maximum": 25000,
+ "description": "Row ceiling for read queries on the questions lane and get_data tool"
+ },
+ "rocketridegraph.query_timeout_ms": {
+ "type": "integer",
+ "title": "Query timeout (ms)",
+ "default": 30000,
+ "minimum": 100,
+ "maximum": 600000,
+ "description": "Per-query statement timeout applied inside each query's transaction"
+ },
+ "rocketridegraph.allow_execute": {
+ "type": "boolean",
+ "title": "Allow direct query execution",
+ "default": false,
+ "description": "Permit QuestionType.EXECUTE callers to run raw Cypher without LLM translation or read-only safety checks. Resource limits still apply. Leave OFF unless a trusted application explicitly needs to issue Cypher directly."
+ },
+ "rocketridegraph.default": {
+ "object": "default",
+ "properties": ["rocketridegraph.db_description", "rocketridegraph.graph", "rocketridegraph.max_attempts", "rocketridegraph.max_rows", "rocketridegraph.query_timeout_ms", "rocketridegraph.allow_execute"]
+ },
+ "rocketridegraph.profile": {
+ "hidden": true,
+ "type": "string",
+ "default": "default",
+ "enum": [["default", "Default"]],
+ "conditional": [
+ {
+ "value": "default",
+ "properties": ["rocketridegraph.default"]
+ }
+ ]
+ }
+ },
+ //
+ // Required:
+ // Defines the fields (shape) of the service. Either source or target
+ // may be specified, or both, but at least one is required
+ //
+ "shape": [
+ {
+ "section": "Pipe",
+ "title": "RocketRide Graph",
+ "properties": ["rocketridegraph.profile"]
+ }
+ ]
+}
diff --git a/nodes/src/nodes/rocketride_sql/IGlobal.py b/nodes/src/nodes/rocketride_sql/IGlobal.py
new file mode 100644
index 000000000..b5b35ac72
--- /dev/null
+++ b/nodes/src/nodes/rocketride_sql/IGlobal.py
@@ -0,0 +1,88 @@
+# =============================================================================
+# MIT License
+# Copyright (c) 2026 Aparavi Software AG
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# =============================================================================
+
+from typing import Any, Dict
+
+from ai.common.database import DatabaseGlobalBase
+from ai.common.rocketride_db import (
+ parse_dsn_fields,
+ resolve_rocketride_dsn,
+ to_sqlalchemy_url,
+)
+
+
+class IGlobal(DatabaseGlobalBase):
+ """RocketRide cloud SQL global state.
+
+ Identical to the generic PostgreSQL node in every respect except one: it
+ takes **no** connection config. Instead of reading host/user/password, it
+ resolves a ready per-tenant DSN from the account layer, keyed by the
+ authenticated ``client_id`` (see ``ai.common.rocketride_db``). Everything
+ else — schema reflection, EXPLAIN validation, the structured query surface,
+ and the raw EXECUTE path — is inherited unchanged from the base.
+ """
+
+ # Cache the resolved DSN between the paired _connection_params /
+ # _build_connection_url calls the base makes in beginGlobal and
+ # validateConfig, so both come from a single resolution.
+ _dsn: str = ''
+
+ def _connection_params(self, config: Dict[str, Any]) -> Dict[str, str]:
+ """Resolve the cloud DSN and expose the pieces the base needs.
+
+ There are no host/user/password fields; the DSN comes from
+ ``Account.resolve_db_dsn(client_id)``. Resolve it once here, cache it
+ for ``_build_connection_url``, and parse out ``database`` so the base's
+ schema-reflection messaging names the real tenant database. ``table``
+ remains the one structured, user-facing target field.
+ """
+ self._dsn = resolve_rocketride_dsn()
+ fields = parse_dsn_fields(self._dsn)
+ return {
+ 'host': fields['host'],
+ 'user': fields['user'],
+ 'password': fields['password'],
+ 'database': fields['database'] or 'rocketride',
+ 'table': config.get('table', 'table').strip(),
+ }
+
+ def _build_connection_url(self, params: Dict[str, str]) -> str:
+ """Return the SQLAlchemy engine URL for the per-tenant cloud DSN.
+
+ The DSN was resolved and cached in ``_connection_params`` (the base
+ always calls that first). Fall back to a fresh resolution if invoked
+ directly, so the method is safe in isolation.
+ """
+ dsn = self._dsn or resolve_rocketride_dsn()
+ return to_sqlalchemy_url(dsn)
+
+ def _max_validation_attempts(self, config: Dict[str, Any]) -> int:
+ """Return the EXPLAIN-validation retry count from config (default 5)."""
+ try:
+ return int(config.get('max_attempts', 5))
+ except (ValueError, TypeError):
+ return 5
+
+ def _db_description(self, config: Dict[str, Any]) -> str:
+ """Return the user-provided database description (empty string if unset)."""
+ return config.get('db_description', '')
diff --git a/nodes/src/nodes/rocketride_sql/IInstance.py b/nodes/src/nodes/rocketride_sql/IInstance.py
new file mode 100644
index 000000000..3190cd28b
--- /dev/null
+++ b/nodes/src/nodes/rocketride_sql/IInstance.py
@@ -0,0 +1,24 @@
+# =============================================================================
+# MIT License
+# Copyright (c) 2026 Aparavi Software AG
+# =============================================================================
+
+from ai.common.database import DatabaseInstanceBase
+from .IGlobal import IGlobal
+
+
+class IInstance(DatabaseInstanceBase):
+ """RocketRide cloud SQL instance.
+
+ All tool methods and lane handlers — including the raw EXECUTE path — are
+ inherited from DatabaseInstanceBase. The only per-driver knowledge is the
+ display name and dialect (the tenant database is PostgreSQL).
+ """
+
+ IGlobal: IGlobal
+
+ def _db_display_name(self) -> str:
+ return 'RocketRide SQL'
+
+ def _db_dialect(self) -> str:
+ return 'postgres'
diff --git a/nodes/src/nodes/rocketride_sql/README.md b/nodes/src/nodes/rocketride_sql/README.md
new file mode 100644
index 000000000..2964c883f
--- /dev/null
+++ b/nodes/src/nodes/rocketride_sql/README.md
@@ -0,0 +1,53 @@
+# rocketride_sql
+
+A RocketRide-managed database node that answers natural-language questions against your own provisioned RocketRide cloud database and inserts structured pipeline data into tables — with **zero database setup**.
+
+## What it does
+
+The same two roles as the generic `db_postgres` node. As a pipeline node, it receives natural-language questions on the `questions` lane, asks a connected LLM to translate them into SQL, executes the query, and emits the results; it also accepts structured data on the `answers` lane and inserts it into the configured table. As a tool node, agents call it directly through `get_data`, `get_schema`, `get_sql`, `execute`, and `dialect`.
+
+The defining difference: **there are no connection fields**. Instead of host/user/password, the node resolves a ready per-tenant DSN from the account layer (`Account.resolve_db_dsn(client_id)`), keyed by the authenticated connection identity. The RocketRide cloud provisions one database per tenant; the same database backs `rocketride_sql`, `rocketride_vector`, and `rocketride_graph`, so raw SQL over the vector tables also goes through this node.
+
+Requires signing into RocketRide cloud. On the open-source build without a cloud identity the node fails at start with `RocketRide cloud DB nodes require signing into RocketRide cloud`.
+
+Safety defaults match `db_postgres`: only `SELECT` statements are permitted for LLM-generated queries, generated SQL is validated with `EXPLAIN` before execution, and raw SQL execution (`QuestionType.EXECUTE`) is disabled by default via `allow_execute`. Isolation for raw execution comes from the database-per-tenant boundary, not query inspection.
+
+---
+
+## Connections
+
+| Connection | Required | Description |
+| ---------- | -------- | ---------------------------------------------- |
+| `llm` | yes | LLM used to generate SQL from natural language |
+
+---
+
+## Configuration
+
+### Lanes
+
+| Lane in | Lane out | Description |
+| ----------- | --------- | -------------------------------------------------------------- |
+| `questions` | `table` | Translate question to SQL, execute, return as a markdown table |
+| `questions` | `text` | Translate question to SQL, execute, return as text |
+| `questions` | `answers` | Translate question to SQL, execute, return as answers |
+| `answers` | (none) | Parse structured rows and insert into the table |
+
+Two special question types are handled on the `questions` lane:
+
+- **`QuestionType.DIALECT`**: emits `{"dialect": "postgres"}` on the `answers` lane so SDK callers can branch on the underlying engine.
+- **`QuestionType.EXECUTE`**: runs the question text as raw SQL (read or write, no LLM, no safety check). Gated by `allow_execute`; when disabled the request is logged and dropped. `SELECT` results are capped at 25,000 rows; write statements report `affected_rows`.
+
+### Fields
+
+| Field | Type | Description |
+|---|---|---|
+| `table` | string | Default "table". Name of the table to read from or write to |
+| `db_description` | string | Default empty. What is this database used for? Helps the LLM generate more accurate queries. |
+| `max_attempts` | integer | Default 5. Maximum number of times to re-ask the LLM if EXPLAIN rejects the generated SQL |
+| `allow_execute` | boolean | Default false. Permit QuestionType.EXECUTE callers to run raw SQL without LLM translation or safety checks. |
+
+There are intentionally no `host` / `user` / `password` / `database` fields — the connection is resolved from your signed-in RocketRide identity.
+
+
+
diff --git a/nodes/src/nodes/rocketride_sql/__init__.py b/nodes/src/nodes/rocketride_sql/__init__.py
new file mode 100644
index 000000000..de809fdfe
--- /dev/null
+++ b/nodes/src/nodes/rocketride_sql/__init__.py
@@ -0,0 +1,39 @@
+# =============================================================================
+# MIT License
+# Copyright (c) 2026 Aparavi Software AG
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# =============================================================================
+
+# ------------------------------------------------------------------------------
+# Main module
+# ------------------------------------------------------------------------------
+import os
+from depends import depends # type: ignore
+
+# Install psycopg2-binary before importing any driver code. SQLAlchemy is
+# provided by ai.common's own depends() (SQLAlchemy>=2.0,<2.1), the same way
+# the other DatabaseGlobalBase nodes get it.
+requirements = os.path.dirname(os.path.realpath(__file__)) + '/requirements.txt'
+depends(requirements)
+
+from .IGlobal import IGlobal # noqa: E402
+from .IInstance import IInstance # noqa: E402
+
+__all__ = ['IGlobal', 'IInstance']
diff --git a/nodes/src/nodes/rocketride_sql/requirements.txt b/nodes/src/nodes/rocketride_sql/requirements.txt
new file mode 100644
index 000000000..27e38fe13
--- /dev/null
+++ b/nodes/src/nodes/rocketride_sql/requirements.txt
@@ -0,0 +1 @@
+psycopg2-binary==2.9.12
diff --git a/nodes/src/nodes/rocketride_sql/rocketride_sql.svg b/nodes/src/nodes/rocketride_sql/rocketride_sql.svg
new file mode 100644
index 000000000..ecfcb48da
--- /dev/null
+++ b/nodes/src/nodes/rocketride_sql/rocketride_sql.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/nodes/src/nodes/rocketride_sql/services.json b/nodes/src/nodes/rocketride_sql/services.json
new file mode 100644
index 000000000..830ff45dc
--- /dev/null
+++ b/nodes/src/nodes/rocketride_sql/services.json
@@ -0,0 +1,156 @@
+{
+ //
+ // Required:
+ // The displayable name of this node
+ //
+ "title": "RocketRide SQL",
+ //
+ // Required:
+ // The protocol is the endpoint protocol
+ //
+ "protocol": "rocketride_sql://",
+ //
+ // Required:
+ // Class type of the node - what it does
+ //
+ "classType": ["database", "tool"],
+ //
+ // Required:
+ // Capabilities are flags that change the behavior of the underlying
+ // engine
+ //
+ "capabilities": ["noremote", "invoke"],
+ //
+ // Optional:
+ // Register is either filter, endpoint or ignored if not specified. If the
+ // type is specified, a factory is registered of that given type
+ //
+ "register": "filter",
+ //
+ // Optional:
+ // The node is the actual physical node to instantiate - if
+ // not specified, the protocol will be used
+ //
+ "node": "python",
+ //
+ // Optional:
+ // The path is the executable/script code - it is node dependent
+ // and is optional for most node
+ //
+ "path": "nodes.rocketride_sql",
+ //
+ // Required:
+ // The prefix map when added/removed when converting URLs <=> paths
+ //
+ "prefix": "rocketridesql",
+ //
+ // Optional:
+ // Description of this driver
+ //
+ "description": ["A RocketRide-managed relational database. Stores structured table data ", "in your own provisioned RocketRide cloud database with zero setup — no host, ", "user, or password to enter. The connection is resolved automatically from ", "your signed-in RocketRide identity. Supports the same natural-language query, ", "schema reflection, and direct-execute surface as the generic PostgreSQL node."],
+ //
+ // Optional:
+ // The icon is the icon to display in the UI for this node
+ //
+ "icon": "rocketride_sql.svg",
+ "documentation": "https://docs.rocketride.org",
+ //
+ // Optional:
+ // Defines the invoke connections that may/must be connected
+ // which are utilized by this driver
+ //
+ "invoke": {
+ "llm": {
+ "description": "LLM to use to craft SQL queries from question",
+ "min": 1
+ }
+ },
+ //
+ // Optional:
+ // As a pipe component, define what this pipe component takes
+ // and what it produces
+ //
+ "lanes": {
+ "answers": [],
+ "questions": ["table", "text", "answers"]
+ },
+ //
+ // Optional:
+ // Profile section are configuration options used by the driver
+ // itself
+ //
+ "preconfig": {
+ // Define the values that will be merged into any profile configuration
+ // specified, unless the profile is 'absolute'
+ "default": "default",
+ // Defines profiles used with the "profile": key
+ "profiles": {
+ "default": {}
+ }
+ },
+ //
+ // Optional:
+ // Local fields definitions - these define fields only for the
+ // current service. You may specify them here, or directly
+ // in the shape
+ //
+ "fields": {
+ "rocketridesql.table": {
+ "type": "string",
+ "title": "Table name",
+ "default": "table",
+ "description": "Name of the table to read from or write to in your RocketRide cloud database"
+ },
+ "rocketridesql.db_description": {
+ "type": "string",
+ "title": "Database description",
+ "default": "",
+ "description": "What is this database used for? Describe its content and purpose, this helps the LLM generate more accurate queries.",
+ "ui": {
+ "ui:widget": "textarea"
+ }
+ },
+ "rocketridesql.max_attempts": {
+ "type": "integer",
+ "title": "Max validation attempts",
+ "default": 5,
+ "minimum": 1,
+ "maximum": 20,
+ "description": "Maximum number of times to re-ask the LLM if EXPLAIN rejects the generated SQL"
+ },
+ "rocketridesql.allow_execute": {
+ "type": "boolean",
+ "title": "Allow direct query execution",
+ "default": false,
+ "description": "Permit QuestionType.EXECUTE callers to run raw SQL without LLM translation or safety checks. Leave OFF unless a trusted application explicitly needs to issue SQL directly."
+ },
+ "rocketridesql.default": {
+ "object": "default",
+ "properties": ["rocketridesql.db_description", "rocketridesql.table", "rocketridesql.max_attempts", "rocketridesql.allow_execute"]
+ },
+ "rocketridesql.profile": {
+ "hidden": true,
+ "type": "string",
+ "default": "default",
+ "enum": [["default", "Default"]],
+ "conditional": [
+ {
+ "value": "default",
+ "properties": ["rocketridesql.default"]
+ }
+ ]
+ }
+ },
+ //
+ // Required:
+ // Defines the fields (shape) of the service. Either source or target
+ // may be specified, or both, but at least one is required
+ //
+ "shape": [
+ {
+ "section": "Pipe",
+ "title": "RocketRide SQL",
+ "properties": ["rocketridesql.profile"]
+ }
+ ]
+}
diff --git a/nodes/src/nodes/rocketride_vector/IEndpoint.py b/nodes/src/nodes/rocketride_vector/IEndpoint.py
new file mode 100644
index 000000000..d12a71f54
--- /dev/null
+++ b/nodes/src/nodes/rocketride_vector/IEndpoint.py
@@ -0,0 +1,28 @@
+# =============================================================================
+# MIT License
+# Copyright (c) 2026 Aparavi Software AG
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# =============================================================================
+
+from ai.common.transform import IEndpointTransform
+
+
+class IEndpoint(IEndpointTransform):
+ pass
diff --git a/nodes/src/nodes/rocketride_vector/IGlobal.py b/nodes/src/nodes/rocketride_vector/IGlobal.py
new file mode 100644
index 000000000..b9bb0db92
--- /dev/null
+++ b/nodes/src/nodes/rocketride_vector/IGlobal.py
@@ -0,0 +1,157 @@
+# =============================================================================
+# MIT License
+# Copyright (c) 2026 Aparavi Software AG
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# =============================================================================
+
+# ------------------------------------------------------------------------------
+# This class controls the data shared between all threads for the task.
+#
+# Mirrors vectordb_postgres/IGlobal, differing only in how it connects: there
+# are no host/user/password fields; the per-tenant DSN is resolved from the
+# account layer (keyed by the authenticated client_id).
+# ------------------------------------------------------------------------------
+import re
+from rocketlib import OPEN_MODE, warning
+from ai.common.transform import IGlobalTransform
+from ai.common.config import Config
+from ai.common.rocketride_db import resolve_rocketride_dsn
+
+# Valid unquoted PostgreSQL identifier: letter/underscore start, alphanumeric/underscore body, max 63 chars
+VALID_TABLE = re.compile(r'^[A-Za-z_][A-Za-z0-9_]{0,62}$')
+
+
+class IGlobal(IGlobalTransform):
+ def beginGlobal(self):
+ # Are we in config mode or some other mode?
+ if self.IEndpoint.endpoint.openMode == OPEN_MODE.CONFIG:
+ # We are going to get a call to configureService but
+ # we don't actually need to load the driver for that
+ pass
+ else:
+ # Import store definition - even though
+ from .rocketride_vector import Store
+
+ # Declare store
+ self.store: Store | None = None
+
+ # Get our bag
+ bag = self.IEndpoint.endpoint.bag
+
+ # Get the passed configuration
+ connConfig = self.getConnConfig()
+
+ # Get the configuration
+ self.store = Store(self.glb.logicalType, connConfig, bag)
+
+ # Get the info about our store
+ collection = self.store.collection
+
+ # Scope the subKey by tenant: the resolved DSN points at the
+ # per-tenant database, so key on that database — the store must
+ # not be shared across tenants even when the pooler host/port are
+ # identical. (Identity rides the task file since #1686; env
+ # identity is untrusted, and the tenant DB is the actual
+ # isolation unit.)
+ tenant_scope = self.store.database or f'{self.store.host}:{self.store.port}'
+ subKey = f'{tenant_scope}/{collection}'
+
+ # Call the base
+ super().beginGlobal(subKey)
+ return
+
+ def validateConfig(self):
+ """
+ Validate the RocketRide cloud vector store config with fast, read-only probes.
+
+ - Enforce a safe unquoted identifier for the table (collection).
+ - Resolve the per-tenant DSN, connect with a short timeout, run SELECT 1.
+ - Verify pgvector availability via ``SELECT NULL::vector``.
+ - Surface raw provider errors without truncation.
+ """
+ try:
+ cfg = Config.getNodeConfig(self.glb.logicalType, self.glb.connConfig)
+ # Support legacy key 'collection' if the UI still sends it
+ table = ((cfg.get('table') or cfg.get('collection')) or '').strip()
+
+ # Table name validation: unquoted identifier rules, total ≤63 chars
+ if not VALID_TABLE.fullmatch(table or ''):
+ warning(
+ 'Table name must be ≤63 chars; start with letter/underscore; only letters, digits, underscore; no spaces or special characters (- . / \\ \' " ` ( ) [ ] { } , ; : * + = | & # @ % ^ ! ? ~ $)'
+ )
+ return
+
+ # No host/user/password fields — resolve the per-tenant DSN.
+ dsn = resolve_rocketride_dsn()
+
+ # Load deps on demand: in CONFIG mode beginGlobal never imports
+ # .rocketride_vector (where the node's other depends() lives), so
+ # this is the only thing that installs the driver at save time —
+ # same pattern as store_postgres and the other DB nodes.
+ import os
+
+ from depends import depends # type: ignore
+
+ depends(os.path.dirname(os.path.realpath(__file__)) + '/requirements.txt')
+
+ import psycopg2 # type: ignore
+
+ conn = None
+ try:
+ # Connect quickly; surface auth/host/db errors
+ conn = psycopg2.connect(dsn, connect_timeout=3)
+ with conn.cursor() as cur:
+ # Minimal probe
+ cur.execute('SELECT 1')
+ # Explicit type check - surfaces provider error if pgvector is not installed
+ cur.execute('SELECT NULL::vector')
+ cur.fetchone()
+ finally:
+ try:
+ if conn is not None:
+ conn.close()
+ except Exception:
+ pass
+ except Exception as e:
+ warning(_format_error(e))
+
+ def endGlobal(self):
+ # Release the index and embeddings
+ self.store = None
+
+
+def _format_error(e: Exception) -> str:
+ """Concise error string for psycopg2 exceptions.
+
+ psycopg2 errors carry SQL fragment + caret context on subsequent lines
+ that are noisy in an LLM transcript, so we keep only the first line of
+ the message. Anything else (including the case where ``psycopg2`` is
+ not installed) falls back to ``str(e).strip()``.
+ """
+ try:
+ from psycopg2 import OperationalError, ProgrammingError # type: ignore
+ import socket
+
+ if isinstance(e, (OperationalError, ProgrammingError, socket.timeout)):
+ msg = str(e).strip()
+ return msg.splitlines()[0] if msg else msg
+ except Exception:
+ pass
+ return str(e).strip()
diff --git a/nodes/src/nodes/rocketride_vector/IInstance.py b/nodes/src/nodes/rocketride_vector/IInstance.py
new file mode 100644
index 000000000..6fb840276
--- /dev/null
+++ b/nodes/src/nodes/rocketride_vector/IInstance.py
@@ -0,0 +1,80 @@
+# =============================================================================
+# MIT License
+# Copyright (c) 2026 Aparavi Software AG
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# =============================================================================
+
+# ------------------------------------------------------------------------------
+# This class controls the data for each thread of the task
+# ------------------------------------------------------------------------------
+from rocketlib import Entry
+from typing import List
+from .IGlobal import IGlobal
+from ai.common.schema import Doc, Question
+from ai.common.transform import IInstanceTransform
+
+
+class IInstance(IInstanceTransform):
+ IGlobal: IGlobal
+
+ def writeQuestions(self, question: Question):
+ """
+ Take a question, performs a search, and writes the results as documents.
+ """
+ # Dispatch to the search handler
+ if self.IGlobal.store is None:
+ raise Exception('No document store')
+ self.IGlobal.store.dispatchSearch(self, question)
+
+ def writeDocuments(self, documents: List[Doc]):
+ """
+ Take a list of documents and adds them to the vector store.
+
+ Any chunks in the database that have the same object id will
+ be removed
+ """
+ # Add the document chunks
+ if self.IGlobal.store is None:
+ raise Exception('No document store')
+ self.IGlobal.store.addChunks(documents)
+
+ def renderObject(self, object: Entry):
+ """
+ Output all the document text to the writeText lane.
+ """
+
+ def callback(text: str) -> None:
+ self.instance.sendText(text)
+
+ # Check it
+ if self.IGlobal.store is None:
+ raise Exception('No document store')
+
+ # If we do not have a vectorize flag, or we have not vectorized
+ # it, allow the next driver to render
+ if not object.hasVectorBatchId or not object.vectorBatchId:
+ return
+
+ # Render the data on this object from the store and
+ # send it to the renderData function
+ self.IGlobal.store.render(objectId=object.objectId, callback=callback)
+
+ # Stop right here
+ self.preventDefault()
diff --git a/nodes/src/nodes/rocketride_vector/README.md b/nodes/src/nodes/rocketride_vector/README.md
new file mode 100644
index 000000000..622821a96
--- /dev/null
+++ b/nodes/src/nodes/rocketride_vector/README.md
@@ -0,0 +1,42 @@
+# rocketride_vector
+
+A RocketRide-managed vector store backed by PostgreSQL + pgvector in your own provisioned RocketRide cloud database — with **zero database setup** and a real vector index from the first write.
+
+## What it does
+
+Mirrors the generic `vectordb_postgres` store node: accepts documents (with embeddings from a bound `vectorizer`) on the `documents` lane, upserts them into a pgvector table, and serves keyword and semantic search on the `questions` lane.
+
+Two differences from the generic node:
+
+1. **No connection fields.** Instead of host/user/password, the node resolves a ready per-tenant DSN from the account layer (`Account.resolve_db_dsn(client_id)`), keyed by the authenticated connection identity. Requires signing into RocketRide cloud; on the open-source build without a cloud identity the node fails at start with `RocketRide cloud DB nodes require signing into RocketRide cloud`.
+2. **Default HNSW index.** The generic node creates no index, so every semantic search is a sequential scan. This node creates an HNSW index over the embedding column when the table is first created. The operator class is derived from the `similarity` config so Postgres actually uses the index (`cosine → vector_cosine_ops`, `l2 → vector_l2_ops`, `inner_product → vector_ip_ops`), with build parameters `m = 16`, `ef_construction = 64` (overridable). pgvector's HNSW supports at most 2000 dimensions; for wider vectors the index is skipped with a warning and search falls back to a sequential scan.
+
+There is **no direct-execute path** — vector stores are structured (search/upsert), and raw SQL over the vector tables is covered by `rocketride_sql` (same tenant database).
+
+Embeddings come from the separate `vectorizer` binding — not in-node.
+
+---
+
+## Configuration
+
+### Lanes
+
+| Lane in | Lane out | Description |
+| ----------- | ----------- | ---------------------------------------------------- |
+| `documents` | (none) | Upsert document chunks into the vector table |
+| `questions` | `documents` | Keyword / semantic search, results emitted as documents |
+
+### Fields
+
+| Field | Type | Description |
+|---|---|---|
+| `collection` | string | Default "rocketride". Name of the table to store vectors |
+| `score` | number | Default 0.5. Minimum similarity score for a document to be returned |
+| `similarity` | string | Default "cosine". One of `cosine`, `l2`, `inner_product`. Also selects the HNSW index operator class |
+| `hnsw_m` | integer | Default 16. HNSW graph degree used when the index is first created |
+| `hnsw_ef_construction` | integer | Default 64. HNSW build-time candidate list size used when the index is first created |
+
+There are intentionally no `host` / `port` / `user` / `password` / `database` fields — the connection is resolved from your signed-in RocketRide identity. The vector dimension is not configured; it is derived from the first document's embedding at write time.
+
+
+
diff --git a/nodes/src/nodes/rocketride_vector/__init__.py b/nodes/src/nodes/rocketride_vector/__init__.py
new file mode 100644
index 000000000..1aa446e9f
--- /dev/null
+++ b/nodes/src/nodes/rocketride_vector/__init__.py
@@ -0,0 +1,48 @@
+# =============================================================================
+# MIT License
+# Copyright (c) 2026 Aparavi Software AG
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# =============================================================================
+
+# ------------------------------------------------------------------------------
+# Main module
+# ------------------------------------------------------------------------------
+# Import the filters
+from .IEndpoint import IEndpoint
+from .IGlobal import IGlobal
+from .IInstance import IInstance
+
+
+# Allow direct access to the underlying store
+def getStore():
+ """
+ Return the RocketRide cloud vector store class.
+ """
+ from .rocketride_vector import Store
+
+ return Store
+
+
+__all__ = [
+ 'IEndpoint',
+ 'IGlobal',
+ 'IInstance',
+ 'getStore',
+]
diff --git a/nodes/src/nodes/rocketride_vector/requirements.txt b/nodes/src/nodes/rocketride_vector/requirements.txt
new file mode 100644
index 000000000..69c8db71c
--- /dev/null
+++ b/nodes/src/nodes/rocketride_vector/requirements.txt
@@ -0,0 +1,2 @@
+psycopg2-binary
+pgvector
diff --git a/nodes/src/nodes/rocketride_vector/rocketride_vector.py b/nodes/src/nodes/rocketride_vector/rocketride_vector.py
new file mode 100644
index 000000000..cabe3fa66
--- /dev/null
+++ b/nodes/src/nodes/rocketride_vector/rocketride_vector.py
@@ -0,0 +1,620 @@
+# =============================================================================
+# MIT License
+# Copyright (c) 2026 Aparavi Software AG
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# =============================================================================
+
+# ------------------------------------------------------------------------------
+# RocketRide cloud vector store.
+#
+# Mirrors the generic ``vectordb_postgres`` driver in every respect except two:
+# 1. Connection — instead of reading host/user/password from config, it
+# resolves a ready per-tenant DSN via ``ai.common.rocketride_db``
+# (injected by the task engine, or via the account broker).
+# 2. Default index — it creates an HNSW index at first write so semantic
+# search uses the index instead of a sequential scan (the generic node
+# creates no index at all). The operator class is derived from the
+# configured ``similarity`` metric, else Postgres ignores the index.
+# Everything else (schema, upsert, keyword/semantic search, render) is identical.
+# ------------------------------------------------------------------------------
+import os
+from depends import depends
+
+requirements = os.path.dirname(os.path.realpath(__file__)) + '/requirements.txt'
+depends(requirements)
+
+from typing import List, Callable, Dict, Any, cast
+import numpy as np
+
+import psycopg2
+from pgvector.psycopg2 import register_vector
+
+from rocketlib import warning
+from ai.common.schema import Doc, DocFilter, DocMetadata, QuestionText
+from ai.common.store import DocumentStoreBase
+from ai.common.config import Config
+from ai.common.rocketride_db import parse_dsn_fields, resolve_rocketride_dsn
+from .IGlobal import VALID_TABLE
+
+# Default PostgreSQL port
+DEFAULT_POSTGRES_PORT = 5432
+
+# Minimum similarity score threshold for document filtering
+MIN_SIMILARITY_SCORE = 0.20
+
+# pgvector HNSW cannot index vectors wider than this many dimensions.
+HNSW_MAX_DIMS = 2000
+
+# SQL Queries
+SQL_QUERIES = {
+ 'check_collection_exists': "SELECT EXISTS (SELECT FROM pg_tables WHERE schemaname = 'public' AND tablename = %s)",
+ # fmt: off
+ 'create_collection': (
+ 'CREATE TABLE IF NOT EXISTS {collection} (id bigserial PRIMARY KEY, content text, objectId text, nodeId text, parent text, permissionId int, isDeleted boolean, chunkId int, isTable boolean, tableId int, vectorSize int, modelName text, embedding vector({vector_size}));'
+ ),
+ # fmt: on
+ 'count_documents': 'SELECT COUNT(*) FROM {collection}',
+ 'search_keyword': 'SELECT * FROM {collection} WHERE content LIKE %s {where_clause} LIMIT %s',
+ 'get_documents': 'SELECT * FROM {collection} {where_clause} LIMIT %s',
+ 'get_paths': 'SELECT parent, objectId FROM {collection} WHERE {where_clause} OFFSET %s LIMIT %s',
+ 'insert_chunk': 'INSERT INTO {collection} ({columns}) VALUES ({placeholders})',
+ 'delete_by_object_ids': 'DELETE FROM {collection} WHERE objectId = ANY(%s)',
+ 'mark_deleted': 'UPDATE {collection} SET isDeleted = TRUE WHERE objectId = ANY(%s)',
+ 'mark_active': 'UPDATE {collection} SET isDeleted = FALSE WHERE objectId = ANY(%s)',
+ 'render_document': 'SELECT content, chunkId FROM {collection} WHERE objectId = %s AND chunkId >= %s AND chunkId < %s ORDER BY chunkId',
+ 'semantic_search': 'SELECT *, embedding {similarity_operator} %s AS distance FROM {collection} {where_clause} ORDER BY distance LIMIT %s',
+ # fmt: off
+ 'create_hnsw_index': (
+ 'CREATE INDEX IF NOT EXISTS {index_name} ON {collection} USING hnsw (embedding {opclass}) WITH (m = {m}, ef_construction = {ef_construction});'
+ ),
+ # fmt: on
+}
+
+
+class Store(DocumentStoreBase):
+ apikey: str | None = None
+ node: str = 'rocketride'
+ collection: str = ''
+ host: str = ''
+ port: int = 0
+ vectorSize: int = 0
+ renderChunkSize: int = 32 * 1024 * 1024
+ similarity: str = 'Cosine'
+ hnsw_m: int = 16
+ hnsw_ef_construction: int = 64
+ client: Any | None = None
+ similarityDict: dict = {'cosine': '<=>', 'l2': '<->', 'inner_product': '<#>'}
+ # Map the query operator (set from ``similarity``) to the HNSW operator
+ # class. The index opclass MUST match the search operator or Postgres will
+ # not use the index.
+ opclassDict: dict = {'<=>': 'vector_cosine_ops', '<->': 'vector_l2_ops', '<#>': 'vector_ip_ops'}
+
+ def __init__(self, provider: str, connConfig: Dict[str, Any], bag: Dict[str, Any]):
+ """
+ Initialize the RocketRide cloud vector store.
+
+ @param provider: The name of the provider.
+ @param connConfig: The connection configuration.
+ @param bag: A dictionary of additional parameters.
+ """
+ # Init the base
+ super().__init__(provider, connConfig, bag)
+
+ # Get our configuration
+ config = Config.getNodeConfig(provider, connConfig)
+
+ # Save our parameters — validate to prevent SQL injection via .format()
+ self.collection = ((config.get('table') or config.get('collection')) or '').strip()
+ if not self.collection or not VALID_TABLE.fullmatch(self.collection):
+ raise ValueError(
+ f'Invalid collection name: {self.collection!r}. Must be a valid PostgreSQL identifier (letters, digits, underscores; max 63 chars).'
+ )
+
+ self.renderChunkSize = config.get('renderChunkSize', self.renderChunkSize)
+ self.threshold_search = config.get('score', 0.5)
+
+ # check if the similarity matches the pgvector configuration options
+ similarity = config.get('similarity', 'cosine')
+ if similarity in ['cosine', 'l2', 'inner_product']:
+ self.similarity = self.similarityDict[similarity]
+ else:
+ raise Exception(
+ 'The metric you provided in the config.json does not match required postgres configurations'
+ )
+
+ # HNSW index build parameters (overridable via config), clamped to
+ # pgvector's supported ranges: m in [2, 100], ef_construction in
+ # [4, 1000] and >= 2 * m — out-of-range values error at CREATE INDEX.
+ try:
+ self.hnsw_m = min(100, max(2, int(config.get('hnsw_m', 16))))
+ except (TypeError, ValueError):
+ self.hnsw_m = 16
+ try:
+ self.hnsw_ef_construction = min(1000, max(4, int(config.get('hnsw_ef_construction', 64))))
+ except (TypeError, ValueError):
+ self.hnsw_ef_construction = 64
+ self.hnsw_ef_construction = max(self.hnsw_ef_construction, 2 * self.hnsw_m)
+
+ # RocketRide cloud: no host/user/password fields. Resolve the
+ # per-tenant DSN (task-engine injected, or via the account broker) and
+ # connect directly. host/port/database are parsed only for bookkeeping
+ # (the connection subKey keys on database); the DSN is the source of
+ # truth.
+ dsn = resolve_rocketride_dsn()
+ fields = parse_dsn_fields(dsn)
+ self.host = fields['host']
+ self.port = fields['port'] or DEFAULT_POSTGRES_PORT
+ self.database = fields['database']
+
+ # Bounded connect so an unreachable pooler fails fast (matches the
+ # 3s timeout IGlobal.validateConfig uses) instead of the OS TCP timeout.
+ self.client = psycopg2.connect(dsn, connect_timeout=3)
+
+ register_vector(self.client)
+
+ return
+
+ def __del__(self):
+ """
+ Deinitializes the postgres client.
+ """
+ # Deinit everything we did
+ self.collection = ''
+ self.renderChunkSize = 0
+ self.similarity = 'cosine'
+
+ # Close the client to omit memory leaks
+ if self.client is not None:
+ self.client.close()
+ self.client = None
+
+ self.vectorSize = 0
+
+ def _doesCollectionExist(self) -> bool:
+ """
+ Check if the collection exists.
+
+ @returns: True if the collection exists, False otherwise.
+ """
+ with self.client.cursor() as cur:
+ cur.execute(SQL_QUERIES['check_collection_exists'], (self.collection,))
+ return cur.fetchone()[0]
+
+ def _createCollection(self, vectorSize: int = 0) -> None:
+ """
+ Create a collection and its default HNSW index.
+
+ Unlike the generic ``vectordb_postgres`` driver (which creates no index,
+ leaving every semantic search as a sequential scan), this creates an
+ HNSW index over the embedding column at first write. The operator class
+ is derived from the configured similarity metric so Postgres actually
+ uses the index. HNSW cannot index vectors wider than ``HNSW_MAX_DIMS``
+ dimensions, so for very wide vectors we skip the index and warn.
+
+ @param vectorSize: The size of the vector.
+ """
+ with self.client.cursor() as cur:
+ cur.execute(SQL_QUERIES['create_collection'].format(collection=self.collection, vector_size=vectorSize))
+
+ if vectorSize > HNSW_MAX_DIMS:
+ warning(
+ f'pgvector HNSW supports at most {HNSW_MAX_DIMS} dimensions; got {vectorSize} for table '
+ f'{self.collection!r}. Skipping index creation — semantic search will use a sequential scan.'
+ )
+ else:
+ # opclass comes from a fixed map; m / ef_construction are
+ # int-coerced in __init__; collection is validated against
+ # VALID_TABLE — all safe to interpolate into the DDL.
+ opclass = self.opclassDict.get(self.similarity, 'vector_cosine_ops')
+ cur.execute(
+ SQL_QUERIES['create_hnsw_index'].format(
+ index_name=f'{self.collection}_embedding_hnsw',
+ collection=self.collection,
+ opclass=opclass,
+ m=self.hnsw_m,
+ ef_construction=self.hnsw_ef_construction,
+ )
+ )
+
+ self.client.commit()
+
+ def _convertFilter(self, docFilter: DocFilter) -> tuple[str, list]:
+ """
+ Build the generic filter based on required permissions, node, parent, etc.
+
+ @param docFilter: The document filter.
+ @returns: A tuple containing the WHERE clause string and a list of parameters.
+ """
+ where_clauses = []
+ params = []
+
+ if docFilter.nodeId is not None:
+ where_clauses.append('nodeId = %s')
+ params.append(docFilter.nodeId)
+
+ if docFilter.isTable is not None:
+ where_clauses.append('isTable = %s')
+ params.append(docFilter.isTable)
+
+ if docFilter.tableIds is not None:
+ where_clauses.append('tableId = ANY(%s)')
+ params.append(list(docFilter.tableIds))
+
+ if docFilter.parent is not None:
+ where_clauses.append('parent = %s')
+ params.append(docFilter.parent)
+
+ if docFilter.permissions is not None:
+ where_clauses.append('permissionId = ANY(%s)')
+ params.append(list(docFilter.permissions))
+
+ if docFilter.objectIds is not None:
+ where_clauses.append('objectId = ANY(%s)')
+ params.append(list(docFilter.objectIds))
+
+ if docFilter.isDeleted is None or not docFilter.isDeleted:
+ where_clauses.append('isDeleted = %s')
+ params.append(False)
+
+ if docFilter.chunkIds is not None:
+ where_clauses.append('chunkId = ANY(%s)')
+ params.append(list(docFilter.chunkIds))
+
+ if docFilter.minChunkId is not None:
+ where_clauses.append('chunkId >= %s')
+ params.append(docFilter.minChunkId)
+
+ if docFilter.maxChunkId is not None:
+ where_clauses.append('chunkId <= %s')
+ params.append(docFilter.maxChunkId)
+
+ if not where_clauses:
+ return '', []
+
+ where_sql = ' AND '.join(where_clauses)
+ return 'WHERE ' + where_sql, params
+
+ def _convertToDocs(self, points: List[Any]) -> List[Doc]:
+ """
+ Convert a list of points or records to a docGroup.
+
+ @param points: A list of points or records.
+ @returns: A list of documents.
+ """
+ docs: List[Doc] = []
+
+ # Now, add the documents to the results
+ for point in points:
+ # Get the payload content and metadata
+ metadata_dict = {k: v for k, v in point.items() if k not in {'content', 'embedding', 'distance'}}
+
+ key_map = {
+ 'objectid': 'objectId',
+ 'nodeid': 'nodeId',
+ 'permissionid': 'permissionId',
+ 'isdeleted': 'isDeleted',
+ 'chunkid': 'chunkId',
+ 'istable': 'isTable',
+ 'tableid': 'tableId',
+ 'modelname': 'modelName',
+ 'vectorsize': 'vectorSize',
+ }
+
+ renamed_metadata = {key_map.get(k, k): v for k, v in metadata_dict.items()}
+
+ # Create a new dictionary with only the expected keys
+ expected_keys = [
+ 'objectId',
+ 'nodeId',
+ 'parent',
+ 'permissionId',
+ 'isDeleted',
+ 'chunkId',
+ 'isTable',
+ 'tableId',
+ 'modelName',
+ 'vectorSize',
+ 'id',
+ ]
+ filtered_metadata = {k: v for k, v in renamed_metadata.items() if k in expected_keys}
+
+ # Ensure modelName and vectorSize have default values if not present or None
+ if 'modelName' not in filtered_metadata or filtered_metadata['modelName'] is None:
+ filtered_metadata['modelName'] = ''
+ if 'vectorSize' not in filtered_metadata or filtered_metadata['vectorSize'] is None:
+ filtered_metadata['vectorSize'] = 0
+
+ # Ensure other fields have default values if not present or None
+ if 'nodeId' not in filtered_metadata or filtered_metadata['nodeId'] is None:
+ filtered_metadata['nodeId'] = ''
+ if 'parent' not in filtered_metadata or filtered_metadata['parent'] is None:
+ filtered_metadata['parent'] = ''
+ if 'permissionId' not in filtered_metadata or filtered_metadata['permissionId'] is None:
+ filtered_metadata['permissionId'] = 0
+ if 'isTable' not in filtered_metadata or filtered_metadata['isTable'] is None:
+ filtered_metadata['isTable'] = False
+ if 'tableId' not in filtered_metadata or filtered_metadata['tableId'] is None:
+ filtered_metadata['tableId'] = 0
+
+ metadata = cast(DocMetadata, filtered_metadata)
+ content = point.get('content')
+
+ score = 0.0
+ if 'distance' in point and point['distance'] is not None:
+ distance = point['distance']
+ if self.similarity == '<=>': # cosine
+ score = 1 - distance
+ elif self.similarity == '<->': # l2
+ score = 1 / (1 + distance)
+ elif self.similarity == '<#>': # inner product
+ score = -distance
+ else:
+ score = 1 - distance
+
+ # Ignore it if it doesn't have a high enough score
+ if score < MIN_SIMILARITY_SCORE:
+ continue
+ else:
+ score = 0.0
+
+ # Create a new document
+ doc = Doc(score=score, page_content=content, metadata=metadata)
+
+ # Append it to this documents chunks
+ docs.append(doc)
+
+ # Return it
+ return docs
+
+ def count_documents(self) -> int:
+ """
+ Return the number of vectors in the document store, not the number of documents themselves.
+
+ @returns: The number of documents in the document store.
+ """
+ if not self._doesCollectionExist():
+ return 0
+
+ with self.client.cursor() as cur:
+ cur.execute(SQL_QUERIES['count_documents'].format(collection=self.collection))
+ return cur.fetchone()[0]
+
+ def searchKeyword(self, query: QuestionText, docFilter: DocFilter) -> List[Doc]:
+ """
+ Perform a keyword search.
+
+ @param query: The query to search for.
+ @param docFilter: The document filter.
+ @returns: A list of documents that match the query.
+ """
+ if not self._doesCollectionExist():
+ return []
+
+ where_sql, params = self._convertFilter(docFilter)
+ query_params = [f'%{query}%'] + params
+
+ with self.client.cursor() as cur:
+ where_clause = ('AND ' + where_sql.split('WHERE ')[1]) if where_sql else ''
+ cur.execute(
+ SQL_QUERIES['search_keyword'].format(collection=self.collection, where_clause=where_clause),
+ query_params + [docFilter.limit],
+ )
+ results = cur.fetchall()
+
+ # Convert results to dictionary
+ points = [dict(zip([desc[0] for desc in cur.description], row)) for row in results]
+ return self._convertToDocs(points)
+
+ def get(self, docFilter: DocFilter, checkCollection: bool = True) -> List[Doc]:
+ """
+ Retrieve document groups matching a given filter.
+
+ @param docFilter: The document filter.
+ @param checkCollection: Whether to check if the collection exists.
+ @returns: A list of documents that match the filter.
+ """
+ if checkCollection and not self._doesCollectionExist():
+ return []
+
+ where_sql, params = self._convertFilter(docFilter)
+
+ with self.client.cursor() as cur:
+ cur.execute(
+ SQL_QUERIES['get_documents'].format(collection=self.collection, where_clause=where_sql),
+ params + [docFilter.limit],
+ )
+ results = cur.fetchall()
+
+ points = [dict(zip([desc[0] for desc in cur.description], row)) for row in results]
+ return self._convertToDocs(points)
+
+ def getPaths(self, parent: str | None = None, offset: int = 0, limit: int = 1000) -> Dict[str, str]:
+ """
+ Retrieve unique parent paths.
+
+ @param parent: The parent path.
+ @param offset: The offset to start from.
+ @param limit: The maximum number of paths to return.
+ @returns: A dictionary of parent paths and their object IDs.
+ """
+ if not self._doesCollectionExist():
+ return {}
+
+ where_clauses = ['chunkId = 0']
+ params = []
+
+ if parent is not None:
+ where_clauses.append('parent = %s')
+ params.append(parent)
+
+ where_sql = ' AND '.join(where_clauses)
+
+ with self.client.cursor() as cur:
+ cur.execute(
+ SQL_QUERIES['get_paths'].format(collection=self.collection, where_clause=where_sql),
+ params + [offset, limit],
+ )
+ results = cur.fetchall()
+
+ paths = {row[0]: row[1] for row in results}
+ return paths
+
+ def addChunks(self, chunks: List[Doc], checkCollection: bool = True) -> None:
+ """
+ Add document chunks to the document store.
+
+ @param chunks: A list of documents to add.
+ @param checkCollection: Whether to check if the collection exists.
+ """
+ if not chunks:
+ return
+
+ if checkCollection and not self.createCollection(chunks):
+ return
+
+ objectIds = {chunk.metadata.objectId for chunk in chunks}
+ self.remove(list(objectIds))
+
+ with self.client.cursor() as cur:
+ for chunk in chunks:
+ data = {'content': chunk.page_content, **chunk.metadata.model_dump(), 'embedding': chunk.embedding}
+ if 'vectorSize' not in data or data['vectorSize'] is None:
+ data['vectorSize'] = len(chunk.embedding) if chunk.embedding else 0
+ if 'modelName' not in data or data['modelName'] is None:
+ data['modelName'] = ''
+ columns = ', '.join(data.keys())
+ placeholders = ', '.join(['%s'] * len(data))
+ cur.execute(
+ SQL_QUERIES['insert_chunk'].format(
+ collection=self.collection, columns=columns, placeholders=placeholders
+ ),
+ list(data.values()),
+ )
+ self.client.commit()
+
+ def remove(self, objectIds: List[str]) -> None:
+ """
+ Delete all documents with matching objectIds from the document store.
+
+ @param objectIds: A list of object IDs to delete.
+ """
+ if not self._doesCollectionExist():
+ return
+
+ with self.client.cursor() as cur:
+ cur.execute(SQL_QUERIES['delete_by_object_ids'].format(collection=self.collection), (objectIds,))
+ self.client.commit()
+
+ def markDeleted(self, objectIds: List[str]) -> None:
+ """
+ Mark the set of documents with the given objectId as deleted.
+
+ @param objectIds: A list of object IDs to mark as deleted.
+ """
+ if not self._doesCollectionExist():
+ return
+
+ with self.client.cursor() as cur:
+ cur.execute(SQL_QUERIES['mark_deleted'].format(collection=self.collection), (objectIds,))
+ self.client.commit()
+
+ def markActive(self, objectIds: List[str]) -> None:
+ """
+ Mark the set of documents with the given objectId as active.
+
+ @param objectIds: A list of object IDs to mark as active.
+ """
+ if not self._doesCollectionExist():
+ return
+
+ with self.client.cursor() as cur:
+ cur.execute(SQL_QUERIES['mark_active'].format(collection=self.collection), (objectIds,))
+ self.client.commit()
+
+ def render(self, objectId: str, callback: Callable[[str], None]) -> None:
+ """
+ Given an object id, renders the complete document.
+
+ @param objectId: The object ID to render.
+ @param callback: The callback function to call with the rendered text.
+ """
+ if not self._doesCollectionExist():
+ return
+
+ offset = 0
+ while True:
+ with self.client.cursor() as cur:
+ cur.execute(
+ SQL_QUERIES['render_document'].format(collection=self.collection),
+ (objectId, offset, offset + self.renderChunkSize),
+ )
+ results = cur.fetchall()
+
+ if not results:
+ break
+
+ # Assemble by chunk index, bounded by the rows actually fetched —
+ # a single row with a high chunkId must not size a buffer.
+ content_by_index = {chunkId - offset: content for content, chunkId in results if chunkId >= offset}
+ if not content_by_index:
+ break
+
+ fullText = ''.join(content_by_index[index] for index in sorted(content_by_index))
+ callback(fullText)
+
+ # NB: len(results) is a row count while renderChunkSize is a
+ # chunkId window width — different units (inherited from
+ # store_postgres). With the default window everything fits in one
+ # pass, so this exit is effectively always taken; the real fix
+ # belongs in the shared store base (dedup follow-up PR).
+ if len(results) < self.renderChunkSize:
+ break
+
+ offset += self.renderChunkSize
+
+ def searchSemantic(self, query: QuestionText, docFilter: DocFilter) -> List[Doc]:
+ """
+ Perform a semantic search.
+
+ @param query: The query to search for.
+ @param docFilter: The document filter.
+ @returns: A list of documents that match the query.
+ """
+ if not self._doesCollectionExist():
+ return []
+
+ if query.embedding is None:
+ raise Exception('To use semantic search, you must bind to an embedding module')
+
+ where_sql, params = self._convertFilter(docFilter)
+ embedding_arr = np.array(query.embedding)
+
+ with self.client.cursor() as cur:
+ cur.execute(
+ SQL_QUERIES['semantic_search'].format(
+ collection=self.collection, similarity_operator=self.similarity, where_clause=where_sql
+ ),
+ (embedding_arr, *params, docFilter.limit),
+ )
+ results = cur.fetchall()
+
+ # Convert results to dictionary
+ points = [dict(zip([desc[0] for desc in cur.description], row)) for row in results]
+ return self._convertToDocs(points)
diff --git a/nodes/src/nodes/rocketride_vector/rocketride_vector.svg b/nodes/src/nodes/rocketride_vector/rocketride_vector.svg
new file mode 100644
index 000000000..ecfcb48da
--- /dev/null
+++ b/nodes/src/nodes/rocketride_vector/rocketride_vector.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/nodes/src/nodes/rocketride_vector/services.json b/nodes/src/nodes/rocketride_vector/services.json
new file mode 100644
index 000000000..8be267f5b
--- /dev/null
+++ b/nodes/src/nodes/rocketride_vector/services.json
@@ -0,0 +1,193 @@
+{
+ //
+ // Required:
+ // The displayable name of this node
+ //
+ "title": "RocketRide Vector",
+ //
+ // Required:
+ // The protocol is the endpoint protocol
+ //
+ "protocol": "rocketride_vector://",
+ //
+ // Required:
+ // Class type of the node - what it does
+ //
+ "classType": ["store"],
+ //
+ // Required:
+ // Capabilities are flags that change the behavior of the underlying
+ // engine
+ //
+ "capabilities": [],
+ //
+ // Optional:
+ // Register is either filter, endpoint or ignored if not specified. If the
+ // type is specified, a factory is registered of that given type
+ //
+ "register": "filter",
+ //
+ // Optional:
+ // The node is the actual physical node to instantiate - if
+ // not specified, the protocol will be used
+ //
+ "node": "python",
+ //
+ // Optional:
+ // The path is the executable/script code - it is node dependent
+ // and is optional for most node
+ //
+ "path": "nodes.rocketride_vector",
+ //
+ // Required:
+ // The prefix map when added/removed when converting URLs <=> paths
+ //
+ "prefix": "RocketRide Vector Store",
+ //
+ // Optional:
+ // Description to of this driver
+ //
+ "description": ["A RocketRide-managed vector store backed by PostgreSQL + pgvector. Stores ", "and semantically searches high-dimensional embeddings in your own provisioned ", "RocketRide cloud database with zero setup — no host, user, or password to enter; ", "the connection is resolved automatically from your signed-in RocketRide identity. ", "An HNSW index is created on first write so semantic search is fast from the start."],
+ //
+ // Optional:
+ // The icon is the icon to display in the UI for this node
+ //
+ "icon": "rocketride_vector.svg",
+ "documentation": "https://docs.rocketride.org",
+ //
+ // Optional:
+ // Rendering hints to the UI which indicate which fields of
+ // the configuration should be used to display information
+ //
+ "tile": ["Table: ${parameters.rocketride_vector.${profile}.collection}", "Similarity: ${parameters.rocketride_vector.${profile}.similarity}"],
+ //
+ // Optional:
+ // As a pipe component, define what this pipe component takes
+ // and what it produces
+ //
+ "lanes": {
+ "documents": [],
+ "questions": ["documents", "answers", "questions"]
+ },
+ //
+ // Optional:
+ // Profile section are configuration options used by the driver
+ // itself
+ //
+ "preconfig": {
+ "default": "cloud",
+ "profiles": {
+ "cloud": {
+ "mode": "cloud",
+ "title": "RocketRide cloud",
+ "collection": "rocketride",
+ "score": 0.5,
+ "similarity": "cosine",
+ "hnsw_m": 16,
+ "hnsw_ef_construction": 64
+ }
+ }
+ },
+ //
+ // Optional:
+ // Local fields definitions - these define fields only for the
+ // current service. You may specify them here, or directly
+ // in the shape
+ //
+ "fields": {
+ "rrvector.collection": {
+ "title": "Table",
+ "description": "Name of the table to store vectors in your RocketRide cloud database.",
+ "type": "string",
+ "default": "rocketride"
+ },
+ "rrvector.score": {
+ "title": "Score threshold",
+ "description": "Minimum similarity score for a document to be returned by search.",
+ "type": "number",
+ "default": 0.5
+ },
+ "rrvector.similarity": {
+ "title": "Similarity Metric",
+ "description": "The similarity metric to use for vector search. Also selects the HNSW index operator class.",
+ "type": "string",
+ "default": "cosine",
+ "enum": ["cosine", "l2", "inner_product"]
+ },
+ "rrvector.hnsw_m": {
+ "title": "HNSW m",
+ "description": "HNSW graph degree (max connections per layer) used when the index is first created.",
+ "type": "integer",
+ "default": 16,
+ "minimum": 2,
+ "maximum": 100
+ },
+ "rrvector.hnsw_ef_construction": {
+ "title": "HNSW ef_construction",
+ "description": "HNSW build-time candidate list size used when the index is first created.",
+ "type": "integer",
+ "default": 64,
+ "minimum": 4,
+ "maximum": 1000
+ },
+ "rrvector.cloud": {
+ "object": "cloud",
+ "properties": ["rrvector.collection", "rrvector.score", "rrvector.similarity", "rrvector.hnsw_m", "rrvector.hnsw_ef_construction"]
+ },
+ "rrvector.profile": {
+ "hidden": true,
+ "title": "RocketRide cloud database",
+ "description": "Connect to...",
+ "type": "string",
+ "default": "cloud",
+ "enum": ["*>preconfig.profiles.*.title"],
+ "conditional": [
+ {
+ "value": "cloud",
+ "properties": ["rrvector.cloud"]
+ }
+ ]
+ },
+ "rrvector.provider": {
+ "type": "string",
+ "const": "rocketride_vector",
+ "default": "rocketride_vector"
+ },
+ "rrvector.rocketride_vector": {
+ "object": "rocketride_vector",
+ "properties": ["rrvector.profile"]
+ },
+ "rrvector.store": {
+ "object": "store",
+ "title": "Store",
+ "properties": ["rrvector.provider", "rrvector.rocketride_vector"]
+ },
+ "rrvector.target.autopipe": {
+ "object": "parameters",
+ "title": "Parameters",
+ "properties": ["remote", "rrvector.store", "all.embedding"]
+ },
+ "rrvector.target.parameters": {
+ "object": "parameters",
+ "title": "Parameters",
+ "properties": ["rrvector.target.autopipe"]
+ }
+ },
+ //
+ // Required:
+ // Defines the fields (shape) of the service. Either source or target
+ // map be specified, or both, but at least one is required
+ //
+ "shape": [
+ {
+ "section": "Pipe",
+ "title": "RocketRide Vector Store",
+ "properties": ["rrvector.profile"]
+ },
+ {
+ "section": "Transform",
+ "title": "RocketRide Vector",
+ "properties": ["type", "rrvector.target.parameters"]
+ }
+ ]
+}
diff --git a/nodes/test/test_age_translate.py b/nodes/test/test_age_translate.py
new file mode 100644
index 000000000..a1ec77a84
--- /dev/null
+++ b/nodes/test/test_age_translate.py
@@ -0,0 +1,359 @@
+# =============================================================================
+# RocketRide Engine
+# =============================================================================
+# MIT License
+# Copyright (c) 2026 Aparavi Software AG
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# =============================================================================
+
+"""Unit tests for the Cypher -> Apache AGE translation layer.
+
+The layer is a pure transform (no database), so everything here runs offline.
+It is loaded in isolation from disk (no ``ai.__init__`` chain) — its only
+external dependency is the antlr4 runtime.
+"""
+
+from __future__ import annotations
+
+import importlib.util
+import sys
+
+from pathlib import Path
+
+import pytest
+
+pytest.importorskip('antlr4')
+
+_AGE_DIR = Path(__file__).resolve().parents[2] / 'packages' / 'ai' / 'src' / 'ai' / 'common' / 'graph' / 'age'
+
+
+def _load_age():
+ name = '_test_rr_age'
+ spec = importlib.util.spec_from_file_location(
+ name, _AGE_DIR / '__init__.py', submodule_search_locations=[str(_AGE_DIR)]
+ )
+ assert spec is not None and spec.loader is not None
+ module = importlib.util.module_from_spec(spec)
+ sys.modules[name] = module
+ spec.loader.exec_module(module)
+ return module
+
+
+age = _load_age()
+SAFE = age.TranslateMode.SAFE
+RAW = age.TranslateMode.RAW
+
+
+# ---------------------------------------------------------------------------
+# analysis
+# ---------------------------------------------------------------------------
+
+
+class TestAnalyze:
+ def test_return_aliases_and_expressions(self):
+ facts = age.analyze('MATCH (a) RETURN a.name AS name, count(a) AS cnt, a.x')
+ assert [c.display_name for c in facts.return_columns] == ['name', 'cnt', 'a.x']
+
+ def test_backticked_alias_stripped(self):
+ facts = age.analyze('MATCH (n) RETURN n.a AS `weird name`')
+ assert facts.return_columns[0].display_name == 'weird name'
+
+ def test_with_projection_not_mistaken_for_return(self):
+ facts = age.analyze('MATCH (n) WITH n.a AS x, n.b AS y RETURN x')
+ assert [c.display_name for c in facts.return_columns] == ['x']
+
+ def test_union_uses_first_branch(self):
+ facts = age.analyze('MATCH (a) RETURN a.x UNION MATCH (b) RETURN b.y')
+ assert [c.display_name for c in facts.return_columns] == ['a.x']
+
+ def test_no_return_statement(self):
+ facts = age.analyze("CREATE (n:P {name: 'x'})")
+ assert facts.return_columns is None
+ assert facts.write_clauses == {'CREATE'}
+ assert facts.is_write
+
+ def test_return_star_detected(self):
+ assert age.analyze('MATCH (n) RETURN *').returns_star
+
+ def test_write_clauses_detected_as_typed_contexts(self):
+ facts = age.analyze('MATCH (n) SET n.a = 1 REMOVE n.b DELETE n')
+ assert facts.write_clauses == {'SET', 'REMOVE', 'DELETE'}
+
+ def test_merge_action_flag(self):
+ facts = age.analyze('MERGE (n:P {k: 1}) ON CREATE SET n.v = 1 RETURN n')
+ assert facts.has_merge_action
+ # A SET outside MERGE does not raise the flag.
+ assert not age.analyze('MATCH (n) SET n.a = 1').has_merge_action
+
+ def test_var_length_ranges(self):
+ facts = age.analyze('MATCH (a)-[*2..5]->(b)-[*3]->(c)-[*..4]->(d)-[*]->(e) RETURN a')
+ assert facts.var_length_ranges == [(2, 5), (3, 3), (None, 4), (None, None)]
+
+ def test_params_and_functions(self):
+ facts = age.analyze('MATCH (n) WHERE n.a = $x AND n.b = $y RETURN toUpper(n.c)')
+ assert facts.param_names == {'x', 'y'}
+ assert facts.function_names == {'toupper'}
+
+ def test_multi_label_and_where_label_check(self):
+ assert age.analyze('MATCH (n:A:B) RETURN n').has_multi_label
+ facts = age.analyze('MATCH (n) WHERE (n:Label) RETURN n')
+ assert facts.has_where_label_check
+ # Labels inside a MATCH pattern are not WHERE label checks.
+ assert not age.analyze('MATCH (n:Label) RETURN n').has_where_label_check
+
+ def test_syntax_error_raises_translation_error(self):
+ with pytest.raises(age.AgeTranslationError, match='syntax error'):
+ age.analyze('MATCHX (n RETURN n')
+
+ def test_empty_query_raises(self):
+ with pytest.raises(age.AgeTranslationError, match='Empty'):
+ age.analyze(' ')
+
+
+# ---------------------------------------------------------------------------
+# firewall
+# ---------------------------------------------------------------------------
+
+
+class TestFirewall:
+ def test_write_rejected_on_safe_path_only(self):
+ with pytest.raises(age.AgeFirewallRejected, match='write_clause'):
+ age.translate('CREATE (n)', mode=SAFE, graph_name='g')
+ # Same statement passes on the raw path.
+ assert age.translate('CREATE (n)', mode=RAW, graph_name='g').has_return is False
+
+ def test_call_rejected_on_safe_path(self):
+ with pytest.raises(age.AgeFirewallRejected, match='procedure_call'):
+ age.translate('CALL db.labels()', mode=SAFE, graph_name='g')
+
+ def test_unbounded_var_length_rejected_on_both_paths(self):
+ for mode in (SAFE, RAW):
+ with pytest.raises(age.AgeFirewallRejected, match='unbounded_var_length'):
+ age.translate('MATCH (a)-[*]->(b) RETURN a', mode=mode, graph_name='g')
+
+ def test_depth_cap_applies_to_raw_path_too(self):
+ with pytest.raises(age.AgeFirewallRejected, match='max_var_length_depth'):
+ age.translate('MATCH (a)-[*1..99]->(b) RETURN a', mode=RAW, graph_name='g')
+
+ def test_depth_cap_configurable(self):
+ config = age.FirewallConfig(max_var_length_depth=99)
+ plan = age.translate('MATCH (a)-[*1..99]->(b) RETURN a', mode=SAFE, graph_name='g', firewall=config)
+ assert plan.columns == ['a']
+
+ def test_query_length_cap(self):
+ config = age.FirewallConfig(max_query_length=30)
+ with pytest.raises(age.AgeFirewallRejected, match='max_query_length'):
+ age.translate('MATCH (averylongvariablename) RETURN averylongvariablename', graph_name='g', firewall=config)
+
+ def test_length_cap_checked_before_parse(self):
+ # An oversize query full of syntactically invalid text must produce the
+ # LENGTH error, not a syntax error — proving the cap precedes the parse
+ # (the parse is the expensive step the cap exists to bound).
+ config = age.FirewallConfig(max_query_length=100)
+ garbage = ':::not cypher at all::: ' * 50
+ with pytest.raises(age.AgeFirewallRejected, match='max_query_length'):
+ age.translate(garbage, graph_name='g', firewall=config)
+
+ def test_deep_nesting_rejected_pre_parse(self):
+ # ~14 stack frames per nesting level in the ANTLR parser; 100 levels
+ # used to escape as a bare RecursionError (reviewer-reproduced).
+ query = 'RETURN ' + '(' * 100 + '1' + ')' * 100
+ with pytest.raises(age.AgeFirewallRejected, match='max_nesting_depth'):
+ age.translate(query, graph_name='g')
+
+ def test_deep_nesting_backstop_is_translation_error(self):
+ # Even with the pre-parse cap lifted, deep nesting must surface as
+ # AgeTranslationError (the layer's contract), never RecursionError.
+ query = 'RETURN ' + '(' * 300 + '1' + ')' * 300
+ config = age.FirewallConfig(max_nesting_depth=10_000)
+ with pytest.raises(age.AgeTranslationError, match='nested too deeply'):
+ age.translate(query, graph_name='g', firewall=config)
+
+ def test_moderate_nesting_still_translates(self):
+ query = 'RETURN ' + '(' * 10 + '1' + ')' * 10
+ plan = age.translate(query, graph_name='g')
+ assert plan.has_return is True
+
+
+# ---------------------------------------------------------------------------
+# capabilities
+# ---------------------------------------------------------------------------
+
+
+class TestCapabilities:
+ def test_datetime_rejected_on_1_5_0(self):
+ with pytest.raises(age.AgeUnsupportedFeature, match='datetime'):
+ age.translate('MATCH (n) RETURN datetime()', graph_name='g')
+
+ def test_return_star_rejected(self):
+ with pytest.raises(age.AgeUnsupportedFeature, match='explicit'):
+ age.translate('MATCH (n) RETURN *', graph_name='g')
+
+ def test_order_by_alias_rejected(self):
+ with pytest.raises(age.AgeUnsupportedFeature, match='order by the expression'):
+ age.translate('MATCH (a)-[r:K]->(b) RETURN r.since AS since ORDER BY since', graph_name='g')
+ # Ordering by the expression itself is fine.
+ plan = age.translate('MATCH (a)-[r:K]->(b) RETURN r.since AS since ORDER BY r.since', graph_name='g')
+ assert plan.columns == ['since']
+ # A bare variable that is not an alias is not flagged.
+ plan2 = age.translate('MATCH (n) RETURN n.name AS name ORDER BY n', graph_name='g')
+ assert plan2.columns == ['name']
+
+ def test_promoted_cells_reject_with_guidance(self):
+ # Verified 2026-07-28 against the exact pin (PG 16.14 + AGE 1.5.0):
+ # all four former-TBD constructs are syntax-level failures, so the
+ # layer now rejects them pre-flight with an actionable alternative.
+ cases = [
+ ('MERGE (n:P {k: 1}) ON CREATE SET n.v = 1 RETURN n', 'separate SET'),
+ ('MATCH (n) WHERE (n:P) RETURN n', 'pattern instead'),
+ ('MATCH (n:A:B) RETURN n', 'category node'),
+ # NB: 'MATCH p = shortestPath(...)' is not openCypher grammar and is
+ # stopped even earlier, by the parser; the function-position form is
+ # what reaches the capability gate.
+ ('MATCH (a:P), (b:P) RETURN shortestPath((a)-[*..3]-(b))', 'variable-length'),
+ ]
+ for query, hint in cases:
+ with pytest.raises(age.AgeUnsupportedFeature, match=hint):
+ age.translate(query, mode=RAW, graph_name='g')
+
+ def test_table_structure(self):
+ assert age.DEFAULT_AGE_VERSION == '1.5.0'
+ table = age.CAPABILITY_TABLES['1.5.0']
+ assert table['datetime_function'].status is age.CellStatus.REJECT
+ # No cell remains unverified: every 1.5.0 cell has an empirical status.
+ tbd = {k for k, cap in table.items() if cap.status is age.CellStatus.TBD}
+ assert tbd == set()
+ for feature in ('merge_on_set', 'where_label_check', 'multi_label', 'shortest_path'):
+ assert table[feature].status is age.CellStatus.REJECT
+
+ def test_unknown_version_falls_back(self):
+ with pytest.raises(age.AgeUnsupportedFeature):
+ age.translate('MATCH (n) RETURN datetime()', graph_name='g', age_version='9.9.9')
+
+
+# ---------------------------------------------------------------------------
+# emit
+# ---------------------------------------------------------------------------
+
+
+class TestEmit:
+ def test_envelope_shape_without_params(self):
+ plan = age.translate('MATCH (n:P) RETURN n.name AS name, n', graph_name='mygraph', limit=7)
+ select = plan.statements[plan.result_index]
+ assert select.startswith("SELECT * FROM cypher('mygraph', $rr_cypher$")
+ assert select.endswith('AS (c0 agtype, c1 agtype) LIMIT 7')
+ assert plan.columns == ['name', 'n']
+ assert plan.read_only is True
+ # Session discipline: LOCAL-only settings precede the query.
+ assert plan.statements[0] == 'SET LOCAL search_path = ag_catalog,"$user",public'
+ assert plan.statements[1].startswith('SET LOCAL statement_timeout')
+
+ def test_no_return_synthesizes_single_column(self):
+ plan = age.translate('CREATE (n:P)', mode=RAW, graph_name='g')
+ assert 'AS (v agtype)' in plan.statements[plan.result_index]
+ assert plan.has_return is False
+
+ def test_params_use_prepare_execute_deallocate(self):
+ plan = age.translate('MATCH (n) WHERE n.a = $x RETURN n', params={'x': 1}, graph_name='g')
+ prepare, execute, deallocate = plan.statements[2], plan.statements[3], plan.statements[4]
+ assert prepare.startswith('PREPARE _rr_age_')
+ assert '(agtype) AS SELECT * FROM cypher(' in prepare
+ assert execute.startswith('EXECUTE _rr_age_') and execute.endswith('(%s::agtype)')
+ assert plan.binds[3] == ('{"x": 1}',)
+ assert deallocate.startswith('DEALLOCATE _rr_age_')
+ assert plan.result_index == 3
+
+ def test_params_without_placeholders_rejected(self):
+ with pytest.raises(age.AgeTranslationError, match='references no'):
+ age.translate('MATCH (n) RETURN n', params={'x': 1}, graph_name='g')
+
+ def test_referenced_params_without_values_rejected(self):
+ with pytest.raises(age.AgeTranslationError, match=r'no supplied values: \$x'):
+ age.translate('MATCH (n) WHERE n.a = $x RETURN n', graph_name='g')
+
+ def test_partially_supplied_params_rejected(self):
+ with pytest.raises(age.AgeTranslationError, match=r'no supplied values: \$y'):
+ age.translate(
+ 'MATCH (n) WHERE n.a = $x AND n.b = $y RETURN n',
+ params={'x': 1},
+ graph_name='g',
+ )
+
+ def test_dollar_tag_rotates_on_collision(self):
+ plan = age.translate("MATCH (n) WHERE n.note = '$rr_cypher$x' RETURN n.note", graph_name='g')
+ assert '$rr_cypher1$' in plan.statements[plan.result_index]
+
+ def test_invalid_graph_name_rejected(self):
+ with pytest.raises(age.AgeTranslationError, match='Invalid graph name'):
+ age.translate('MATCH (n) RETURN n', graph_name='bad-name; DROP')
+
+ def test_unserialisable_params_rejected(self):
+ with pytest.raises(age.AgeTranslationError, match='JSON-serialisable'):
+ age.translate('MATCH (n) WHERE n.a = $x RETURN n', params={'x': object()}, graph_name='g')
+
+
+# ---------------------------------------------------------------------------
+# decode
+# ---------------------------------------------------------------------------
+
+
+class TestDecode:
+ def test_vertex(self):
+ value = age.decode_agtype('{"id": 1, "label": "P", "properties": {"a": 3, "name": "x"}}::vertex')
+ assert value == {'id': 1, 'label': 'P', 'properties': {'a': 3, 'name': 'x'}}
+
+ def test_edge_carries_endpoints(self):
+ value = age.decode_agtype('{"id": 5, "label": "K", "end_id": 2, "start_id": 1, "properties": {"w": 2}}::edge')
+ assert value['start_id'] == 1
+ assert value['end_id'] == 2
+ assert value['properties'] == {'w': 2}
+
+ def test_scalars_and_null(self):
+ # Plain equality on purpose: decode converts agtype numerics through
+ # Decimal -> float, which is exact for these literals, and
+ # pytest.approx consults sys.modules['numpy'] at call time — under the
+ # full xdist gate another worker test can leave a stubbed numpy there,
+ # making approx itself blow up.
+ assert age.decode_agtype('"alice"') == 'alice'
+ assert age.decode_agtype('3.14') == 3.14
+ assert age.decode_agtype('42') == 42
+ assert age.decode_agtype('true') is True
+ assert age.decode_agtype(None) is None
+
+ def test_nested_containers(self):
+ assert age.decode_agtype('[1, {"a": [2, 3]}]') == [1, {'a': [2, 3]}]
+
+ def test_string_escapes_decoded(self):
+ # agtype STRING follows the JSON string grammar; escapes must decode
+ # to the real characters, not survive as literal backslash sequences.
+ assert age.decode_agtype('"a\\nb"') == 'a\nb'
+ assert age.decode_agtype('"say \\"hi\\""') == 'say "hi"'
+ assert age.decode_agtype('"caf\\u00e9"') == 'café'
+ value = age.decode_agtype('{"properties": {"k\\"ey": "v1\\tv2"}}::vertex')
+ assert value['properties'] == {'k"ey': 'v1\tv2'}
+
+ def test_decode_row_keys_by_display_names(self):
+ plan = age.translate('MATCH (n) RETURN n.name AS name, n.age', graph_name='g')
+ row = age.decode_row(plan, ('"alice"', '3'))
+ assert row == {'name': 'alice', 'n.age': 3}
+
+ def test_bad_agtype_raises(self):
+ with pytest.raises(age.AgeTranslationError, match='agtype syntax error'):
+ age.decode_agtype('{"unterminated')
diff --git a/nodes/test/test_rocketride_db.py b/nodes/test/test_rocketride_db.py
new file mode 100644
index 000000000..e9cbcb8f9
--- /dev/null
+++ b/nodes/test/test_rocketride_db.py
@@ -0,0 +1,579 @@
+# =============================================================================
+# RocketRide Engine
+# =============================================================================
+# MIT License
+# Copyright (c) 2026 Aparavi Software AG
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# =============================================================================
+
+"""Unit tests for the RocketRide cloud DB nodes (no database required).
+
+Covers the shared connection seam (``ai.common.rocketride_db``), the OSS
+``Account.resolve_db_dsn`` stub, the ``rocketride_sql`` connection override,
+and the ``rocketride_vector`` HNSW default-index logic. Real base classes are
+loaded from disk with ``rocketlib`` and heavy drivers stubbed, mirroring
+``test_graph_neo4j.py``.
+"""
+
+from __future__ import annotations
+
+import asyncio
+import importlib.util
+import sys
+import types
+
+from pathlib import Path
+
+import pytest
+
+_REPO = Path(__file__).resolve().parents[2]
+_AI_SRC = _REPO / 'packages' / 'ai' / 'src'
+_RRDB_PATH = _AI_SRC / 'ai' / 'common' / 'rocketride_db.py'
+_ACCOUNT_BASE = _AI_SRC / 'ai' / 'account' / 'base.py'
+_ACCOUNT_OSS = _AI_SRC / 'ai' / 'account' / 'oss' / '__init__.py'
+_SQL_NODE_DIR = _REPO / 'nodes' / 'src' / 'nodes' / 'rocketride_sql'
+_VEC_NODE_DIR = _REPO / 'nodes' / 'src' / 'nodes' / 'rocketride_vector'
+
+TEST_DSN = 'postgresql://rruser:rrpass@localhost:55432/rrtenant'
+
+
+def _load_from_path(name: str, path: Path, *, is_package: bool = False):
+ search = [str(path.parent)] if is_package else None
+ spec = importlib.util.spec_from_file_location(name, path, submodule_search_locations=search)
+ assert spec is not None and spec.loader is not None
+ module = importlib.util.module_from_spec(spec)
+ sys.modules[name] = module
+ spec.loader.exec_module(module)
+ return module
+
+
+def _load_rrdb():
+ """Load ai.common.rocketride_db in isolation (it only imports stdlib)."""
+ return _load_from_path('_test_rocketride_db_helpers', _RRDB_PATH)
+
+
+rrdb = _load_rrdb()
+
+
+# ---------------------------------------------------------------------------
+# Shared seam helpers
+# ---------------------------------------------------------------------------
+
+
+class TestDsnHelpers:
+ def test_url_gets_psycopg2_driver(self):
+ assert rrdb.to_sqlalchemy_url(TEST_DSN) == 'postgresql+psycopg2://rruser:rrpass@localhost:55432/rrtenant'
+
+ def test_postgres_scheme_gets_psycopg2_driver(self):
+ assert rrdb.to_sqlalchemy_url('postgres://u:p@h/db') == 'postgresql+psycopg2://u:p@h/db'
+
+ def test_explicit_driver_passes_through(self):
+ dsn = 'postgresql+psycopg2://u:p@h/db'
+ assert rrdb.to_sqlalchemy_url(dsn) == dsn
+
+ def test_keyvalue_dsn_passes_through(self):
+ dsn = 'host=localhost dbname=rrtenant user=rruser'
+ assert rrdb.to_sqlalchemy_url(dsn) == dsn
+
+ def test_parse_dsn_fields(self):
+ fields = rrdb.parse_dsn_fields(TEST_DSN)
+ assert fields == {
+ 'host': 'localhost',
+ 'port': 55432,
+ 'user': 'rruser',
+ 'password': 'rrpass',
+ 'database': 'rrtenant',
+ }
+
+ def test_parse_dsn_fields_unquotes_credentials(self):
+ fields = rrdb.parse_dsn_fields('postgresql://u%40x:p%23w@h:5432/db')
+ assert fields['user'] == 'u@x'
+ assert fields['password'] == 'p#w'
+
+ def test_parse_keyvalue_dsn_yields_empty_fields(self):
+ fields = rrdb.parse_dsn_fields('host=x dbname=y')
+ assert fields['host'] == ''
+ assert fields['database'] == ''
+
+ def test_parse_malformed_port_degrades_that_field_only(self):
+ fields = rrdb.parse_dsn_fields('postgresql://u:p@h:notaport/db')
+ assert fields['port'] is None
+ assert fields['host'] == 'h'
+ assert fields['database'] == 'db'
+
+ def test_parse_malformed_authority_yields_empty_fields(self):
+ # Mismatched IPv6 bracket — urlparse itself raises ValueError.
+ fields = rrdb.parse_dsn_fields('postgresql://[::1/db')
+ assert fields == {'host': '', 'port': None, 'user': '', 'password': '', 'database': ''}
+
+
+class TestClientId:
+ def test_missing_env_raises(self, monkeypatch):
+ monkeypatch.delenv(rrdb.CLIENT_ID_ENV, raising=False)
+ with pytest.raises(ValueError, match='signed-in RocketRide cloud identity'):
+ rrdb.current_client_id()
+
+ def test_present_env_returned_stripped(self, monkeypatch):
+ monkeypatch.setenv(rrdb.CLIENT_ID_ENV, ' tenant-42 ')
+ assert rrdb.current_client_id() == 'tenant-42'
+
+
+class TestResolveRocketrideDsn:
+ """The full seam: injected env DSN first, else account singleton via async bridge."""
+
+ def _install_fake_account(self, monkeypatch, resolver):
+ monkeypatch.delenv(rrdb.DB_DSN_ENV, raising=False)
+ ai_pkg = types.ModuleType('ai')
+ ai_pkg.__path__ = []
+ account_mod = types.ModuleType('ai.account')
+ account_mod.account = types.SimpleNamespace(resolve_db_dsn=resolver)
+ monkeypatch.setitem(sys.modules, 'ai', ai_pkg)
+ monkeypatch.setitem(sys.modules, 'ai.account', account_mod)
+
+ def test_injected_env_dsn_wins(self, monkeypatch):
+ """The task-engine-injected ROCKETRIDE_DB_DSN short-circuits everything."""
+
+ async def fake(client_id): # pragma: no cover — must not be reached
+ raise AssertionError('account must not be consulted when the env DSN is present')
+
+ self._install_fake_account(monkeypatch, fake)
+ monkeypatch.setenv(rrdb.DB_DSN_ENV, f' {TEST_DSN} ')
+ # No client id needed on the injected path either.
+ monkeypatch.delenv(rrdb.CLIENT_ID_ENV, raising=False)
+ assert rrdb.resolve_rocketride_dsn() == TEST_DSN
+
+ def test_resolve_error_env_surfaces_broker_failure(self, monkeypatch):
+ """A server-side resolution failure names the real cause, not sign-in."""
+
+ async def fake(client_id): # pragma: no cover — must not be reached
+ raise AssertionError('account must not be consulted when a resolve error is present')
+
+ self._install_fake_account(monkeypatch, fake)
+ monkeypatch.delenv(rrdb.DB_DSN_ENV, raising=False)
+ monkeypatch.setenv(rrdb.DB_RESOLVE_ERROR_ENV, 'DB broker request failed: HTTP 503')
+ with pytest.raises(ValueError, match='HTTP 503'):
+ rrdb.resolve_rocketride_dsn()
+
+ def test_empty_env_dsn_falls_back_to_account(self, monkeypatch):
+ async def fake(client_id):
+ return TEST_DSN
+
+ self._install_fake_account(monkeypatch, fake)
+ monkeypatch.setenv(rrdb.DB_DSN_ENV, ' ')
+ monkeypatch.setenv(rrdb.CLIENT_ID_ENV, 'tenant-42')
+ assert rrdb.resolve_rocketride_dsn() == TEST_DSN
+
+ def test_resolves_via_fake_account(self, monkeypatch):
+ seen = {}
+
+ async def fake(client_id):
+ seen['client_id'] = client_id
+ return TEST_DSN
+
+ self._install_fake_account(monkeypatch, fake)
+ monkeypatch.setenv(rrdb.CLIENT_ID_ENV, 'tenant-42')
+ assert rrdb.resolve_rocketride_dsn() == TEST_DSN
+ assert seen['client_id'] == 'tenant-42'
+
+ def test_empty_dsn_raises(self, monkeypatch):
+ async def fake(client_id):
+ return ''
+
+ self._install_fake_account(monkeypatch, fake)
+ monkeypatch.setenv(rrdb.CLIENT_ID_ENV, 'tenant-42')
+ with pytest.raises(ValueError, match='empty DSN'):
+ rrdb.resolve_rocketride_dsn()
+
+ def test_rejects_running_event_loop(self, monkeypatch):
+ async def fake(client_id):
+ return TEST_DSN
+
+ self._install_fake_account(monkeypatch, fake)
+ monkeypatch.setenv(rrdb.CLIENT_ID_ENV, 'tenant-42')
+
+ async def call_from_loop():
+ return rrdb.resolve_rocketride_dsn()
+
+ with pytest.raises(RuntimeError, match='running event loop'):
+ asyncio.run(call_from_loop())
+
+
+# ---------------------------------------------------------------------------
+# Account layer: OSS stub + base default
+# ---------------------------------------------------------------------------
+
+
+class TestAccountStub:
+ @pytest.fixture()
+ def account_cls(self, monkeypatch):
+ """Load the OSS Account with ``depends`` stubbed out."""
+ depends_mod = types.ModuleType('depends')
+ depends_mod.depends = lambda *a, **kw: None
+ monkeypatch.setitem(sys.modules, 'depends', depends_mod)
+
+ ai_pkg = types.ModuleType('ai')
+ ai_pkg.__path__ = [str(_AI_SRC / 'ai')]
+ monkeypatch.setitem(sys.modules, 'ai', ai_pkg)
+ account_pkg = types.ModuleType('ai.account')
+ account_pkg.__path__ = [str(_ACCOUNT_BASE.parent)]
+ monkeypatch.setitem(sys.modules, 'ai.account', account_pkg)
+ base_mod = _load_from_path('ai.account.base', _ACCOUNT_BASE)
+ monkeypatch.setitem(sys.modules, 'ai.account.base', base_mod)
+ oss_mod = _load_from_path('ai.account.oss', _ACCOUNT_OSS, is_package=True)
+ monkeypatch.setitem(sys.modules, 'ai.account.oss', oss_mod)
+ return base_mod.AccountBase, oss_mod.Account
+
+ def test_oss_stub_raises_cloud_signin_message(self, account_cls):
+ _, account = account_cls
+ with pytest.raises(NotImplementedError, match='require signing into RocketRide cloud'):
+ asyncio.run(account().resolve_db_dsn('tenant-42'))
+
+ def test_base_default_raises(self, account_cls):
+ base, _ = account_cls
+
+ class Minimal(base):
+ async def authenticate(self, credential):
+ return None
+
+ with pytest.raises(NotImplementedError):
+ asyncio.run(Minimal().resolve_db_dsn('tenant-42'))
+
+
+# ---------------------------------------------------------------------------
+# rocketride_sql: connection resolution override
+# ---------------------------------------------------------------------------
+
+
+def _sql_iglobal_cls(monkeypatch):
+ """Load rocketride_sql/IGlobal with the database base loaded from disk."""
+ pytest.importorskip('sqlalchemy')
+
+ rocketlib = types.ModuleType('rocketlib')
+ rocketlib.IGlobalBase = type('IGlobalBase', (), {})
+ rocketlib.error = lambda *a, **kw: None
+ rocketlib.warning = lambda *a, **kw: None
+ monkeypatch.setitem(sys.modules, 'rocketlib', rocketlib)
+
+ ai_pkg = types.ModuleType('ai')
+ ai_pkg.__path__ = []
+ common_pkg = types.ModuleType('ai.common')
+ common_pkg.__path__ = []
+ config = types.ModuleType('ai.common.config')
+ config.Config = types.SimpleNamespace(getNodeConfig=lambda *a, **kw: {})
+ monkeypatch.setitem(sys.modules, 'ai', ai_pkg)
+ monkeypatch.setitem(sys.modules, 'ai.common', common_pkg)
+ monkeypatch.setitem(sys.modules, 'ai.common.config', config)
+
+ rrdb_mod = _load_from_path('ai.common.rocketride_db', _RRDB_PATH)
+ monkeypatch.setitem(sys.modules, 'ai.common.rocketride_db', rrdb_mod)
+
+ db_global = _load_from_path(
+ 'ai.common.database.db_global_base',
+ _AI_SRC / 'ai' / 'common' / 'database' / 'db_global_base.py',
+ )
+ database_pkg = types.ModuleType('ai.common.database')
+ database_pkg.__path__ = []
+ database_pkg.DatabaseGlobalBase = db_global.DatabaseGlobalBase
+ monkeypatch.setitem(sys.modules, 'ai.common.database', database_pkg)
+ monkeypatch.setitem(sys.modules, 'ai.common.database.db_global_base', db_global)
+
+ iglobal = _load_from_path('nodes.rocketride_sql.IGlobal', _SQL_NODE_DIR / 'IGlobal.py')
+ return iglobal.IGlobal, rrdb_mod
+
+
+class TestRocketrideSqlConnection:
+ def _with_fake_account(self, monkeypatch):
+ account_mod = types.ModuleType('ai.account')
+
+ async def fake(client_id):
+ return TEST_DSN
+
+ account_mod.account = types.SimpleNamespace(resolve_db_dsn=fake)
+ monkeypatch.setitem(sys.modules, 'ai.account', account_mod)
+ monkeypatch.setenv('ROCKETRIDE_CLIENT_ID', 'tenant-42')
+ monkeypatch.delenv('ROCKETRIDE_DB_DSN', raising=False)
+
+ def test_connection_params_come_from_resolved_dsn(self, monkeypatch):
+ cls, _ = _sql_iglobal_cls(monkeypatch)
+ self._with_fake_account(monkeypatch)
+ glb = cls()
+ params = glb._connection_params({'table': ' widgets '})
+ assert params['host'] == 'localhost'
+ assert params['user'] == 'rruser'
+ assert params['password'] == 'rrpass'
+ assert params['database'] == 'rrtenant'
+ assert params['table'] == 'widgets'
+
+ def test_build_connection_url_uses_cached_dsn(self, monkeypatch):
+ cls, _ = _sql_iglobal_cls(monkeypatch)
+ self._with_fake_account(monkeypatch)
+ glb = cls()
+ params = glb._connection_params({})
+ url = glb._build_connection_url(params)
+ assert url == 'postgresql+psycopg2://rruser:rrpass@localhost:55432/rrtenant'
+
+ def test_no_connection_fields_read_from_config(self, monkeypatch):
+ """host/user/password in config must be ignored — the DSN wins."""
+ cls, _ = _sql_iglobal_cls(monkeypatch)
+ self._with_fake_account(monkeypatch)
+ glb = cls()
+ params = glb._connection_params({'host': 'evil', 'user': 'evil', 'password': 'evil'})
+ assert params['host'] == 'localhost'
+ assert params['user'] == 'rruser'
+ url = glb._build_connection_url(params)
+ assert 'evil' not in url
+
+
+# ---------------------------------------------------------------------------
+# rocketride_vector: HNSW default-index logic
+# ---------------------------------------------------------------------------
+
+
+class _FakeCursor:
+ def __init__(self, executed, fetch_batches):
+ self._executed = executed
+ self._fetch_batches = fetch_batches
+
+ def execute(self, sql, params=None):
+ self._executed.append(sql)
+
+ def fetchone(self):
+ # Existence probes (_doesCollectionExist) read a single boolean.
+ return (True,)
+
+ def fetchall(self):
+ return self._fetch_batches.pop(0) if self._fetch_batches else []
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, *exc):
+ return False
+
+
+class _FakeClient:
+ def __init__(self):
+ self.executed: list[str] = []
+ self.commits = 0
+ # Queue of row batches returned by successive fetchall() calls.
+ self.fetch_batches: list[list] = []
+
+ def cursor(self):
+ return _FakeCursor(self.executed, self.fetch_batches)
+
+ def commit(self):
+ self.commits += 1
+
+ def close(self):
+ pass
+
+
+def _vector_store_cls(monkeypatch):
+ """Load the rocketride_vector Store with heavy deps stubbed."""
+ warnings: list[str] = []
+
+ depends_mod = types.ModuleType('depends')
+ depends_mod.depends = lambda *a, **kw: None
+ monkeypatch.setitem(sys.modules, 'depends', depends_mod)
+
+ rocketlib = types.ModuleType('rocketlib')
+ rocketlib.warning = warnings.append
+ rocketlib.OPEN_MODE = types.SimpleNamespace(CONFIG=object())
+ rocketlib.Entry = type('Entry', (), {})
+ monkeypatch.setitem(sys.modules, 'rocketlib', rocketlib)
+
+ numpy_mod = types.ModuleType('numpy')
+ numpy_mod.array = lambda x: x
+ monkeypatch.setitem(sys.modules, 'numpy', numpy_mod)
+
+ psycopg2_mod = types.ModuleType('psycopg2')
+ psycopg2_mod.connect = lambda *a, **kw: _FakeClient()
+ monkeypatch.setitem(sys.modules, 'psycopg2', psycopg2_mod)
+ pgvector_mod = types.ModuleType('pgvector')
+ pgvector_psycopg2 = types.ModuleType('pgvector.psycopg2')
+ pgvector_psycopg2.register_vector = lambda client: None
+ pgvector_mod.psycopg2 = pgvector_psycopg2
+ monkeypatch.setitem(sys.modules, 'pgvector', pgvector_mod)
+ monkeypatch.setitem(sys.modules, 'pgvector.psycopg2', pgvector_psycopg2)
+
+ ai_pkg = types.ModuleType('ai')
+ ai_pkg.__path__ = []
+ common_pkg = types.ModuleType('ai.common')
+ common_pkg.__path__ = []
+ schema = types.ModuleType('ai.common.schema')
+ for name in ('Doc', 'DocFilter', 'DocMetadata', 'QuestionText'):
+ setattr(schema, name, type(name, (), {}))
+ store_mod = types.ModuleType('ai.common.store')
+
+ class _StubDocumentStoreBase:
+ def __init__(self, provider, connConfig, bag):
+ self.vectorSize = 0
+ self.modelName = ''
+ self.threshold_search = 0.5
+
+ store_mod.DocumentStoreBase = _StubDocumentStoreBase
+ config = types.ModuleType('ai.common.config')
+ config.Config = types.SimpleNamespace(getNodeConfig=lambda provider, connConfig: dict(connConfig))
+ transform = types.ModuleType('ai.common.transform')
+ for name in ('IGlobalTransform', 'IInstanceTransform', 'IEndpointTransform'):
+ setattr(transform, name, type(name, (), {}))
+ monkeypatch.setitem(sys.modules, 'ai', ai_pkg)
+ monkeypatch.setitem(sys.modules, 'ai.common', common_pkg)
+ monkeypatch.setitem(sys.modules, 'ai.common.schema', schema)
+ monkeypatch.setitem(sys.modules, 'ai.common.store', store_mod)
+ monkeypatch.setitem(sys.modules, 'ai.common.config', config)
+ monkeypatch.setitem(sys.modules, 'ai.common.transform', transform)
+
+ rrdb_mod = _load_from_path('ai.common.rocketride_db', _RRDB_PATH)
+ monkeypatch.setitem(sys.modules, 'ai.common.rocketride_db', rrdb_mod)
+
+ account_mod = types.ModuleType('ai.account')
+
+ async def fake(client_id):
+ return TEST_DSN
+
+ account_mod.account = types.SimpleNamespace(resolve_db_dsn=fake)
+ monkeypatch.setitem(sys.modules, 'ai.account', account_mod)
+ monkeypatch.setenv('ROCKETRIDE_CLIENT_ID', 'tenant-42')
+ monkeypatch.delenv('ROCKETRIDE_DB_DSN', raising=False)
+
+ iglobal = _load_from_path('nodes.rocketride_vector.IGlobal', _VEC_NODE_DIR / 'IGlobal.py')
+ pkg = types.ModuleType('nodes.rocketride_vector')
+ pkg.__path__ = [str(_VEC_NODE_DIR)]
+ pkg.IGlobal = iglobal
+ monkeypatch.setitem(sys.modules, 'nodes.rocketride_vector', pkg)
+ monkeypatch.setitem(sys.modules, 'nodes.rocketride_vector.IGlobal', iglobal)
+ store = _load_from_path('nodes.rocketride_vector.rocketride_vector', _VEC_NODE_DIR / 'rocketride_vector.py')
+ return store.Store, warnings
+
+
+def _make_store(cls, config):
+ return cls('rocketride_vector', config, {})
+
+
+class TestRocketrideVectorStore:
+ def test_connects_via_resolved_dsn(self, monkeypatch):
+ cls, _ = _vector_store_cls(monkeypatch)
+ store = _make_store(cls, {'collection': 'rr_vec', 'similarity': 'cosine'})
+ # No client_id on the store: identity rides the task file (#1686);
+ # tenant scoping keys on the DSN's database (see IGlobal's subKey).
+ assert store.host == 'localhost'
+ assert store.port == 55432
+ assert store.database == 'rrtenant'
+ assert isinstance(store.client, _FakeClient)
+
+ def test_invalid_collection_rejected(self, monkeypatch):
+ cls, _ = _vector_store_cls(monkeypatch)
+ with pytest.raises(ValueError, match='Invalid collection name'):
+ _make_store(cls, {'collection': 'bad-name;drop', 'similarity': 'cosine'})
+
+ @pytest.mark.parametrize(
+ ('similarity', 'opclass'),
+ [
+ ('cosine', 'vector_cosine_ops'),
+ ('l2', 'vector_l2_ops'),
+ ('inner_product', 'vector_ip_ops'),
+ ],
+ )
+ def test_hnsw_index_opclass_follows_similarity(self, monkeypatch, similarity, opclass):
+ cls, _ = _vector_store_cls(monkeypatch)
+ store = _make_store(cls, {'collection': 'rr_vec', 'similarity': similarity})
+ store._createCollection(vectorSize=5)
+ ddl = store.client.executed
+ assert any('CREATE TABLE IF NOT EXISTS rr_vec' in sql for sql in ddl)
+ index_sql = [sql for sql in ddl if 'USING hnsw' in sql]
+ assert len(index_sql) == 1
+ assert f'(embedding {opclass})' in index_sql[0]
+ assert 'rr_vec_embedding_hnsw' in index_sql[0]
+ assert 'm = 16' in index_sql[0]
+ assert 'ef_construction = 64' in index_sql[0]
+
+ def test_hnsw_params_overridable(self, monkeypatch):
+ cls, _ = _vector_store_cls(monkeypatch)
+ store = _make_store(
+ cls,
+ {'collection': 'rr_vec', 'similarity': 'cosine', 'hnsw_m': 32, 'hnsw_ef_construction': 128},
+ )
+ store._createCollection(vectorSize=5)
+ index_sql = [sql for sql in store.client.executed if 'USING hnsw' in sql]
+ assert 'm = 32' in index_sql[0]
+ assert 'ef_construction = 128' in index_sql[0]
+
+ def test_render_orders_chunks_and_bridges_gaps(self, monkeypatch):
+ """Chunks at 0, 1 and 3 (gap at 2): index order preserved, gap skipped."""
+ cls, _ = _vector_store_cls(monkeypatch)
+ store = _make_store(cls, {'collection': 'rr_vec', 'similarity': 'cosine'})
+ store.client.fetch_batches = [[('A', 0), ('D', 3), ('B', 1)]]
+ out: list[str] = []
+ store.render('obj-1', out.append)
+ assert out == ['ABD']
+
+ def test_render_empty_batch_emits_nothing(self, monkeypatch):
+ cls, _ = _vector_store_cls(monkeypatch)
+ store = _make_store(cls, {'collection': 'rr_vec', 'similarity': 'cosine'})
+ store.client.fetch_batches = [[]]
+ out: list[str] = []
+ store.render('obj-1', out.append)
+ assert out == []
+
+ def test_render_single_high_chunk_id(self, monkeypatch):
+ """One row with a high chunkId renders that row — allocation is bounded
+ by rows fetched, never by the chunkId value.
+ """
+ cls, _ = _vector_store_cls(monkeypatch)
+ store = _make_store(cls, {'collection': 'rr_vec', 'similarity': 'cosine'})
+ store.client.fetch_batches = [[('X', 999_999)]]
+ out: list[str] = []
+ store.render('obj-1', out.append)
+ assert out == ['X']
+
+ def test_hnsw_params_clamped_to_pgvector_bounds(self, monkeypatch):
+ cls, _ = _vector_store_cls(monkeypatch)
+ store = _make_store(
+ cls,
+ {'collection': 'rr_vec', 'similarity': 'cosine', 'hnsw_m': 1, 'hnsw_ef_construction': 1},
+ )
+ # pgvector requires m >= 2, ef_construction >= 4 and >= 2 * m.
+ assert store.hnsw_m == 2
+ assert store.hnsw_ef_construction == 4
+
+ def test_hnsw_ef_construction_raised_to_twice_m(self, monkeypatch):
+ cls, _ = _vector_store_cls(monkeypatch)
+ store = _make_store(
+ cls,
+ {'collection': 'rr_vec', 'similarity': 'cosine', 'hnsw_m': 64, 'hnsw_ef_construction': 64},
+ )
+ assert store.hnsw_ef_construction == 128
+
+ def test_hnsw_skipped_above_dimension_ceiling(self, monkeypatch):
+ cls, warnings = _vector_store_cls(monkeypatch)
+ store = _make_store(cls, {'collection': 'rr_vec', 'similarity': 'cosine'})
+ store._createCollection(vectorSize=2001)
+ ddl = store.client.executed
+ assert any('CREATE TABLE IF NOT EXISTS rr_vec' in sql for sql in ddl)
+ assert not any('USING hnsw' in sql for sql in ddl)
+ assert any('2000' in w and 'Skipping index' in w for w in warnings)
+
+ def test_hnsw_created_at_dimension_ceiling(self, monkeypatch):
+ cls, warnings = _vector_store_cls(monkeypatch)
+ store = _make_store(cls, {'collection': 'rr_vec', 'similarity': 'cosine'})
+ store._createCollection(vectorSize=2000)
+ assert any('USING hnsw' in sql for sql in store.client.executed)
+ assert not warnings
diff --git a/nodes/test/test_rocketride_db_full.py b/nodes/test/test_rocketride_db_full.py
new file mode 100644
index 000000000..7237bbcf4
--- /dev/null
+++ b/nodes/test/test_rocketride_db_full.py
@@ -0,0 +1,532 @@
+# =============================================================================
+# RocketRide Engine
+# =============================================================================
+# MIT License
+# Copyright (c) 2026 Aparavi Software AG
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# =============================================================================
+
+"""Integration tests for the RocketRide cloud DB nodes (real PostgreSQL).
+
+Runs the ``rocketride_sql`` and ``rocketride_vector`` nodes end-to-end against
+a local PostgreSQL container standing in for the cloud data-core, connected
+through the real seam (env ``ROCKETRIDE_CLIENT_ID`` + a fake, injectable
+``Account.resolve_db_dsn`` — exactly how tests are meant to swap the resolver;
+node code is identical regardless of which resolver is active).
+
+Requires a container on the live pin (PG16 + pgvector), e.g.::
+
+ docker run -d --name rr-pg-phase1 -p 55432:5432 \
+ -e POSTGRES_PASSWORD=rrpass -e POSTGRES_USER=rruser -e POSTGRES_DB=rrtenant \
+ pgvector/pgvector:pg16
+ docker exec rr-pg-phase1 psql -U rruser -d rrtenant -c 'CREATE EXTENSION IF NOT EXISTS vector;'
+
+Configuration via environment variables:
+ RR_TEST_PG_DSN — libpq URL of the test database
+ (default: postgresql://rruser:rrpass@localhost:55432/rrtenant)
+
+Skips cleanly when the database is unreachable.
+"""
+
+from __future__ import annotations
+
+import importlib.util
+import os
+import sys
+import urllib.parse
+import types
+
+from pathlib import Path
+
+import pytest
+
+_REPO = Path(__file__).resolve().parents[2]
+_AI_SRC = _REPO / 'packages' / 'ai' / 'src'
+_RRDB_PATH = _AI_SRC / 'ai' / 'common' / 'rocketride_db.py'
+_DB_BASE_DIR = _AI_SRC / 'ai' / 'common' / 'database'
+_SQL_NODE_DIR = _REPO / 'nodes' / 'src' / 'nodes' / 'rocketride_sql'
+_VEC_NODE_DIR = _REPO / 'nodes' / 'src' / 'nodes' / 'rocketride_vector'
+
+TEST_DSN = os.environ.get('RR_TEST_PG_DSN', 'postgresql://rruser:rrpass@localhost:55432/rrtenant')
+# Derived, not hardcoded: the suite must also run against a provisioned tenant
+# DSN (e.g. t__ through a pooler), not just the default container.
+TEST_DB_NAME = urllib.parse.urlparse(TEST_DSN).path.lstrip('/')
+TEST_CLIENT_ID = 'tenant-integration-test'
+
+# RR_REQUIRE_DB_TESTS (set by CI once its DB containers are healthy) turns
+# every skip in this module into a hard failure: a broken container or missing
+# dependency must never let the safety-control tests silently go green.
+_DB_TESTS_REQUIRED = bool(os.environ.get('RR_REQUIRE_DB_TESTS'))
+
+if _DB_TESTS_REQUIRED:
+ import numpy as np
+ import pgvector # noqa: F401
+ import psycopg2
+ import sqlalchemy # noqa: F401
+else:
+ psycopg2 = pytest.importorskip('psycopg2')
+ pytest.importorskip('pgvector')
+ pytest.importorskip('sqlalchemy')
+ np = pytest.importorskip('numpy')
+
+
+def _db_reachable() -> bool:
+ try:
+ conn = psycopg2.connect(TEST_DSN, connect_timeout=3)
+ conn.close()
+ return True
+ except Exception:
+ return False
+
+
+if _DB_TESTS_REQUIRED and not _db_reachable():
+ pytest.fail(f'RR_REQUIRE_DB_TESTS is set but the test database is not reachable at {TEST_DSN}', pytrace=False)
+
+pytestmark = pytest.mark.skipif(not _db_reachable(), reason=f'RocketRide test database not reachable at {TEST_DSN}')
+
+
+def _load_from_path(name: str, path: Path, *, is_package: bool = False):
+ search = [str(path.parent)] if is_package else None
+ spec = importlib.util.spec_from_file_location(name, path, submodule_search_locations=search)
+ assert spec is not None and spec.loader is not None
+ module = importlib.util.module_from_spec(spec)
+ sys.modules[name] = module
+ spec.loader.exec_module(module)
+ return module
+
+
+# ---------------------------------------------------------------------------
+# Environment: stubs + the fake resolver (the injectable test seam)
+# ---------------------------------------------------------------------------
+
+
+@pytest.fixture()
+def rr_env(monkeypatch):
+ """Stub the engine surface and install the fake DSN resolver."""
+ warnings: list[str] = []
+
+ depends_mod = types.ModuleType('depends')
+ depends_mod.depends = lambda *a, **kw: None
+ monkeypatch.setitem(sys.modules, 'depends', depends_mod)
+
+ rocketlib = types.ModuleType('rocketlib')
+ rocketlib.IGlobalBase = type('IGlobalBase', (), {})
+ rocketlib.IInstanceBase = type('IInstanceBase', (), {})
+ rocketlib.tool_function = lambda **kwargs: lambda f: f
+ rocketlib.debug = lambda *a, **kw: None
+ rocketlib.error = lambda *a, **kw: None
+ rocketlib.warning = warnings.append
+ rocketlib.OPEN_MODE = types.SimpleNamespace(CONFIG=object())
+ rocketlib.Entry = type('Entry', (), {})
+ rocketlib_types = types.ModuleType('rocketlib.types')
+ rocketlib_types.IInvokeLLM = types.SimpleNamespace(Ask=lambda **kw: kw)
+ rocketlib.types = rocketlib_types
+ monkeypatch.setitem(sys.modules, 'rocketlib', rocketlib)
+ monkeypatch.setitem(sys.modules, 'rocketlib.types', rocketlib_types)
+
+ ai_pkg = types.ModuleType('ai')
+ ai_pkg.__path__ = []
+ common_pkg = types.ModuleType('ai.common')
+ common_pkg.__path__ = []
+
+ schema = types.ModuleType('ai.common.schema')
+
+ class _Metadata:
+ def __init__(self, **kw):
+ self._data = kw
+ for k, v in kw.items():
+ setattr(self, k, v)
+
+ def model_dump(self):
+ return dict(self._data)
+
+ class _Doc:
+ def __init__(self, score=0.0, page_content='', metadata=None, embedding=None, embedding_model=''):
+ self.score = score
+ self.page_content = page_content
+ self.metadata = _Metadata(**metadata) if isinstance(metadata, dict) else metadata
+ self.embedding = embedding
+ self.embedding_model = embedding_model
+
+ class _DocFilter:
+ def __init__(self, **kw):
+ for name in (
+ 'nodeId',
+ 'isTable',
+ 'tableIds',
+ 'parent',
+ 'permissions',
+ 'objectIds',
+ 'isDeleted',
+ 'chunkIds',
+ 'minChunkId',
+ 'maxChunkId',
+ ):
+ setattr(self, name, kw.get(name))
+ self.limit = kw.get('limit', 10)
+
+ class _QuestionText(str):
+ embedding = None
+ embedding_model = ''
+
+ schema.Doc = _Doc
+ schema.DocFilter = _DocFilter
+ schema.DocMetadata = _Metadata
+ schema.QuestionText = _QuestionText
+ schema.Answer = type('Answer', (), {})
+ schema.Question = type('Question', (), {})
+ schema.QuestionType = types.SimpleNamespace(QUESTION=1, DIALECT=2, EXECUTE=3)
+
+ table = types.ModuleType('ai.common.table')
+ table.Table = types.SimpleNamespace(
+ generate_markdown_table=lambda data, headers=None: '\n'.join(' | '.join(map(str, r)) for r in data)
+ )
+
+ config = types.ModuleType('ai.common.config')
+ config.Config = types.SimpleNamespace(getNodeConfig=lambda provider, connConfig: dict(connConfig))
+
+ transform = types.ModuleType('ai.common.transform')
+ for name in ('IGlobalTransform', 'IInstanceTransform', 'IEndpointTransform'):
+ setattr(transform, name, type(name, (), {}))
+
+ store_mod = types.ModuleType('ai.common.store')
+
+ class _StubDocumentStoreBase:
+ """Minimal stand-in for DocumentStoreBase's first-write flow."""
+
+ def __init__(self, provider, connConfig, bag):
+ self.vectorSize = 0
+ self.modelName = ''
+ self.threshold_search = 0.5
+
+ def createCollection(self, documents) -> bool:
+ if not self._doesCollectionExist():
+ self._createCollection(len(documents[0].embedding))
+ return True
+
+ store_mod.DocumentStoreBase = _StubDocumentStoreBase
+
+ monkeypatch.setitem(sys.modules, 'ai', ai_pkg)
+ monkeypatch.setitem(sys.modules, 'ai.common', common_pkg)
+ monkeypatch.setitem(sys.modules, 'ai.common.schema', schema)
+ monkeypatch.setitem(sys.modules, 'ai.common.table', table)
+ monkeypatch.setitem(sys.modules, 'ai.common.config', config)
+ monkeypatch.setitem(sys.modules, 'ai.common.transform', transform)
+ monkeypatch.setitem(sys.modules, 'ai.common.store', store_mod)
+
+ rrdb_mod = _load_from_path('ai.common.rocketride_db', _RRDB_PATH)
+ monkeypatch.setitem(sys.modules, 'ai.common.rocketride_db', rrdb_mod)
+
+ # The injectable fake resolver: same seam a SaaS build fills in.
+ account_mod = types.ModuleType('ai.account')
+
+ async def fake_resolve_db_dsn(client_id):
+ assert client_id == TEST_CLIENT_ID
+ return TEST_DSN
+
+ account_mod.account = types.SimpleNamespace(resolve_db_dsn=fake_resolve_db_dsn)
+ monkeypatch.setitem(sys.modules, 'ai.account', account_mod)
+ monkeypatch.setenv('ROCKETRIDE_CLIENT_ID', TEST_CLIENT_ID)
+ monkeypatch.delenv('ROCKETRIDE_DB_DSN', raising=False)
+
+ return types.SimpleNamespace(schema=schema, warnings=warnings)
+
+
+@pytest.fixture()
+def raw_conn():
+ conn = psycopg2.connect(TEST_DSN)
+ yield conn
+ conn.close()
+
+
+# ---------------------------------------------------------------------------
+# rocketride_sql end-to-end
+# ---------------------------------------------------------------------------
+
+
+def _load_sql_node(monkeypatch):
+ db_global = _load_from_path('ai.common.database.db_global_base', _DB_BASE_DIR / 'db_global_base.py')
+ sql_safety = _load_from_path('ai.common.database.sql_safety', _DB_BASE_DIR / 'sql_safety.py')
+ database_pkg = types.ModuleType('ai.common.database')
+ database_pkg.__path__ = [str(_DB_BASE_DIR)]
+ database_pkg.DatabaseGlobalBase = db_global.DatabaseGlobalBase
+ monkeypatch.setitem(sys.modules, 'ai.common.database', database_pkg)
+ monkeypatch.setitem(sys.modules, 'ai.common.database.db_global_base', db_global)
+ monkeypatch.setitem(sys.modules, 'ai.common.database.sql_safety', sql_safety)
+ db_instance = _load_from_path('ai.common.database.db_instance_base', _DB_BASE_DIR / 'db_instance_base.py')
+ database_pkg.DatabaseInstanceBase = db_instance.DatabaseInstanceBase
+ monkeypatch.setitem(sys.modules, 'ai.common.database.db_instance_base', db_instance)
+
+ iglobal = _load_from_path('nodes.rocketride_sql.IGlobal', _SQL_NODE_DIR / 'IGlobal.py')
+ pkg = types.ModuleType('nodes.rocketride_sql')
+ pkg.__path__ = [str(_SQL_NODE_DIR)]
+ pkg.IGlobal = iglobal
+ monkeypatch.setitem(sys.modules, 'nodes.rocketride_sql', pkg)
+ monkeypatch.setitem(sys.modules, 'nodes.rocketride_sql.IGlobal', iglobal)
+ iinstance = _load_from_path('nodes.rocketride_sql.IInstance', _SQL_NODE_DIR / 'IInstance.py')
+ return iglobal.IGlobal, iinstance.IInstance
+
+
+class TestRocketrideSqlE2E:
+ @pytest.fixture()
+ def sql_table(self, raw_conn):
+ with raw_conn.cursor() as cur:
+ cur.execute('DROP TABLE IF EXISTS rr_sql_e2e')
+ cur.execute('CREATE TABLE rr_sql_e2e (id serial PRIMARY KEY, name text, qty int)')
+ cur.execute("INSERT INTO rr_sql_e2e (name, qty) VALUES ('bolt', 7), ('nut', 3)")
+ raw_conn.commit()
+ yield 'rr_sql_e2e'
+ with raw_conn.cursor() as cur:
+ cur.execute('DROP TABLE IF EXISTS rr_sql_e2e')
+ raw_conn.commit()
+
+ def _begin(self, monkeypatch, config):
+ iglobal_cls, iinstance_cls = _load_sql_node(monkeypatch)
+ glb = iglobal_cls()
+ glb.glb = types.SimpleNamespace(logicalType='rocketride_sql', connConfig=config)
+ glb.beginGlobal()
+ inst = iinstance_cls()
+ inst.IGlobal = glb
+ return glb, inst
+
+ def test_begin_global_connects_and_reflects(self, rr_env, monkeypatch, sql_table):
+ glb, _ = self._begin(monkeypatch, {'table': sql_table})
+ try:
+ assert glb.database == TEST_DB_NAME
+ assert glb.table == sql_table
+ assert sql_table in glb.db_schema
+ assert 'name' in glb.schema
+ ok, err = glb._validateQuery(f'SELECT * FROM {sql_table}')
+ assert ok, err
+ finally:
+ glb.endGlobal()
+
+ def test_execute_gated_by_allow_execute(self, rr_env, monkeypatch, sql_table):
+ glb, inst = self._begin(monkeypatch, {'table': sql_table, 'allow_execute': False})
+ try:
+ with pytest.raises(ValueError, match='execute tool is disabled'):
+ inst.execute({'sql': f'SELECT * FROM {sql_table}'})
+ finally:
+ glb.endGlobal()
+
+ def test_execute_runs_real_query(self, rr_env, monkeypatch, sql_table):
+ glb, inst = self._begin(monkeypatch, {'table': sql_table, 'allow_execute': True})
+ try:
+ result = inst.execute({'sql': f'SELECT name, qty FROM {sql_table} ORDER BY qty DESC'})
+ assert [r['name'] for r in result['rows']] == ['bolt', 'nut']
+ written = inst.execute({'sql': f"INSERT INTO {sql_table} (name, qty) VALUES ('washer', 11)"})
+ assert written['affected_rows'] == 1
+ back = inst.execute({'sql': f'SELECT count(*) AS n FROM {sql_table}'})
+ assert back['rows'][0]['n'] == 3
+ finally:
+ glb.endGlobal()
+
+ def test_begin_global_via_injected_env_dsn(self, rr_env, monkeypatch, sql_table):
+ """The production delivery path: the task engine resolves server-side and
+ injects ROCKETRIDE_DB_DSN; the node-side account is never consulted.
+ """
+ import types as _types
+
+ async def poisoned(client_id): # pragma: no cover — must not be reached
+ raise AssertionError('account must not be consulted when ROCKETRIDE_DB_DSN is injected')
+
+ monkeypatch.setitem(
+ sys.modules, 'ai.account', _types.SimpleNamespace(account=_types.SimpleNamespace(resolve_db_dsn=poisoned))
+ )
+ monkeypatch.setenv('ROCKETRIDE_DB_DSN', TEST_DSN)
+ glb, inst = self._begin(monkeypatch, {'table': sql_table, 'allow_execute': True})
+ try:
+ assert glb.database == TEST_DB_NAME
+ rows = inst.execute({'sql': f'SELECT count(*) AS n FROM {sql_table}'})
+ assert rows['rows'][0]['n'] == 2
+ finally:
+ glb.endGlobal()
+
+ def test_config_connection_fields_ignored(self, rr_env, monkeypatch, sql_table):
+ """The defining property: config cannot redirect the connection."""
+ glb, _ = self._begin(
+ monkeypatch,
+ {'table': sql_table, 'host': 'evil.example.com', 'user': 'evil', 'password': 'evil'},
+ )
+ try:
+ assert glb.database == TEST_DB_NAME
+ assert 'evil' not in str(glb.engine.url)
+ finally:
+ glb.endGlobal()
+
+
+# ---------------------------------------------------------------------------
+# rocketride_vector end-to-end (incl. HNSW index verification)
+# ---------------------------------------------------------------------------
+
+
+def _load_vector_store(monkeypatch):
+ iglobal = _load_from_path('nodes.rocketride_vector.IGlobal', _VEC_NODE_DIR / 'IGlobal.py')
+ pkg = types.ModuleType('nodes.rocketride_vector')
+ pkg.__path__ = [str(_VEC_NODE_DIR)]
+ pkg.IGlobal = iglobal
+ monkeypatch.setitem(sys.modules, 'nodes.rocketride_vector', pkg)
+ monkeypatch.setitem(sys.modules, 'nodes.rocketride_vector.IGlobal', iglobal)
+ store = _load_from_path('nodes.rocketride_vector.rocketride_vector', _VEC_NODE_DIR / 'rocketride_vector.py')
+ return store.Store
+
+
+VEC_TABLE = 'rr_vec_e2e'
+DIMS = 8
+
+
+def _unit_vec(rng):
+ v = rng.standard_normal(DIMS)
+ return (v / np.linalg.norm(v)).tolist()
+
+
+class TestRocketrideVectorE2E:
+ @pytest.fixture()
+ def vec_store(self, rr_env, monkeypatch, raw_conn):
+ with raw_conn.cursor() as cur:
+ cur.execute(f'DROP TABLE IF EXISTS {VEC_TABLE}')
+ raw_conn.commit()
+ store_cls = _load_vector_store(monkeypatch)
+ store = store_cls('rocketride_vector', {'collection': VEC_TABLE, 'similarity': 'cosine'}, {})
+ yield store
+ # Close the driver connection before dropping: the driver's SELECT
+ # paths (mirroring vectordb_postgres) leave the session idle in
+ # transaction, and its AccessShare lock would block the DROP.
+ if store.client is not None:
+ store.client.close()
+ store.client = None
+ with raw_conn.cursor() as cur:
+ cur.execute(f'DROP TABLE IF EXISTS {VEC_TABLE}')
+ raw_conn.commit()
+
+ def _chunks(self, schema, contents_and_embeddings):
+ docs = []
+ for i, (content, embedding) in enumerate(contents_and_embeddings):
+ docs.append(
+ schema.Doc(
+ page_content=content,
+ metadata={
+ 'objectId': f'obj-{i}',
+ 'nodeId': 'test-node',
+ 'parent': '/test',
+ 'permissionId': 0,
+ 'isDeleted': False,
+ 'chunkId': 0,
+ 'isTable': False,
+ 'tableId': 0,
+ 'vectorSize': len(embedding),
+ 'modelName': 'test-model',
+ },
+ embedding=embedding,
+ embedding_model='test-model',
+ )
+ )
+ return docs
+
+ def test_first_write_creates_table_and_hnsw_index(self, rr_env, vec_store, raw_conn):
+ rng = np.random.default_rng(7)
+ vec_store.addChunks(self._chunks(rr_env.schema, [(f'doc {i}', _unit_vec(rng)) for i in range(100)]))
+
+ with raw_conn.cursor() as cur:
+ cur.execute('SELECT indexname, indexdef FROM pg_indexes WHERE tablename = %s', (VEC_TABLE,))
+ indexes = {name: ddl for name, ddl in cur.fetchall()}
+
+ index_name = f'{VEC_TABLE}_embedding_hnsw'
+ assert index_name in indexes
+ assert 'USING hnsw (embedding vector_cosine_ops)' in indexes[index_name]
+ assert "m='16'" in indexes[index_name]
+ assert "ef_construction='64'" in indexes[index_name]
+
+ def test_semantic_search_uses_hnsw_index_not_seq_scan(self, rr_env, vec_store, raw_conn):
+ """The acceptance check: the node's own search SQL must use the index.
+
+ The planner rightly prefers a seq scan on tiny tables, so load enough
+ rows (and ANALYZE) that index cost wins; then EXPLAIN the exact
+ ``semantic_search`` statement the driver executes.
+ """
+ rng = np.random.default_rng(11)
+ # Insert in one bulk statement (the driver's row-at-a-time insert is
+ # too slow for a 2000-row planner fixture); create the table + index
+ # through the driver's first-write path first.
+ vec_store.addChunks(self._chunks(rr_env.schema, [('seed doc', _unit_vec(rng))]))
+ with raw_conn.cursor() as cur:
+ cur.execute(
+ f'INSERT INTO {VEC_TABLE} (content, objectId, chunkId, isDeleted, embedding) '
+ f"SELECT 'doc '||i, 'obj-'||i, 0, false, "
+ f"(SELECT ('['||string_agg((random()*2-1)::text, ',')||']')::vector "
+ f' FROM generate_series(1,{DIMS}) WHERE i=i) '
+ f'FROM generate_series(1, 2000) i'
+ )
+ cur.execute(f'ANALYZE {VEC_TABLE}')
+ raw_conn.commit()
+
+ # EXPLAIN the exact statement the driver runs for semantic search.
+ node_module = sys.modules['nodes.rocketride_vector.rocketride_vector']
+ search_sql = node_module.SQL_QUERIES['semantic_search'].format(
+ collection=VEC_TABLE, similarity_operator='<=>', where_clause=''
+ )
+ probe = _unit_vec(rng)
+ with raw_conn.cursor() as cur:
+ cur.execute(f'EXPLAIN {search_sql}', (str(probe), 5))
+ plan = '\n'.join(row[0] for row in cur.fetchall())
+
+ assert f'Index Scan using {VEC_TABLE}_embedding_hnsw' in plan, plan
+ assert 'Seq Scan' not in plan, plan
+
+ def test_semantic_search_returns_nearest_document(self, rr_env, vec_store):
+ rng = np.random.default_rng(23)
+ vectors = [(f'doc {i}', _unit_vec(rng)) for i in range(50)]
+ vec_store.addChunks(self._chunks(rr_env.schema, vectors))
+
+ # Query with (nearly) the exact embedding of doc 17 — it must come back first.
+ target = vectors[17][1]
+ query = rr_env.schema.QuestionText('find doc 17')
+ query.embedding = target
+ results = vec_store.searchSemantic(query, rr_env.schema.DocFilter(limit=5))
+ assert results, 'semantic search returned nothing'
+ assert results[0].page_content == 'doc 17'
+ assert results[0].score > 0.99
+
+ def test_upsert_replaces_same_object_id(self, rr_env, vec_store):
+ rng = np.random.default_rng(31)
+ first = self._chunks(rr_env.schema, [('original', _unit_vec(rng))])
+ vec_store.addChunks(first)
+ replacement = self._chunks(rr_env.schema, [('replaced', _unit_vec(rng))])
+ vec_store.addChunks(replacement)
+ assert vec_store.count_documents() == 1
+
+ def test_wide_vectors_skip_index_with_warning(self, rr_env, monkeypatch, raw_conn):
+ wide_table = 'rr_vec_wide_e2e'
+ with raw_conn.cursor() as cur:
+ cur.execute(f'DROP TABLE IF EXISTS {wide_table}')
+ raw_conn.commit()
+ store_cls = _load_vector_store(monkeypatch)
+ store = store_cls('rocketride_vector', {'collection': wide_table, 'similarity': 'cosine'}, {})
+ try:
+ store._createCollection(vectorSize=2001)
+ with raw_conn.cursor() as cur:
+ cur.execute('SELECT indexname FROM pg_indexes WHERE tablename = %s', (wide_table,))
+ index_names = [row[0] for row in cur.fetchall()]
+ assert not any('hnsw' in name for name in index_names)
+ assert any('Skipping index' in w for w in rr_env.warnings)
+ finally:
+ with raw_conn.cursor() as cur:
+ cur.execute(f'DROP TABLE IF EXISTS {wide_table}')
+ raw_conn.commit()
diff --git a/nodes/test/test_rocketride_graph_full.py b/nodes/test/test_rocketride_graph_full.py
new file mode 100644
index 000000000..337d1e76c
--- /dev/null
+++ b/nodes/test/test_rocketride_graph_full.py
@@ -0,0 +1,401 @@
+# =============================================================================
+# RocketRide Engine
+# =============================================================================
+# MIT License
+# Copyright (c) 2026 Aparavi Software AG
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# =============================================================================
+
+"""Integration tests for rocketride_graph against a real Postgres + Apache AGE.
+
+Runs the node end-to-end on the live pin (PG16 + AGE 1.5.0) through the real
+seam (env ``ROCKETRIDE_CLIENT_ID`` + a fake, injectable
+``Account.resolve_db_dsn``). Container:
+
+ docker run -d --name rr-pg-phase2 -p 55433:5432 \
+ -e POSTGRES_PASSWORD=rrpass -e POSTGRES_USER=rruser -e POSTGRES_DB=rrtenant \
+ rr-age-pin:pg16-age1.5.0-pgvector0.8.0
+ # image: apache/age:release_PG16_1.5.0 + pgvector v0.8.0 (see the layer README)
+
+Configuration via environment variables:
+ RR_TEST_AGE_DSN — libpq URL of the AGE test database
+ (default: postgresql://rruser:rrpass@localhost:55433/rrtenant)
+
+Skips cleanly when the database is unreachable.
+"""
+
+from __future__ import annotations
+
+import importlib.util
+import os
+import sys
+import types
+
+from pathlib import Path
+
+import pytest
+
+_REPO = Path(__file__).resolve().parents[2]
+_AI_SRC = _REPO / 'packages' / 'ai' / 'src'
+_GRAPH_PKG = _AI_SRC / 'ai' / 'common' / 'graph'
+_UTILS_DIR = _AI_SRC / 'ai' / 'common' / 'utils'
+_RRDB_PATH = _AI_SRC / 'ai' / 'common' / 'rocketride_db.py'
+_NODE_DIR = _REPO / 'nodes' / 'src' / 'nodes' / 'rocketride_graph'
+
+TEST_DSN = os.environ.get('RR_TEST_AGE_DSN', 'postgresql://rruser:rrpass@localhost:55433/rrtenant')
+TEST_CLIENT_ID = 'tenant-graph-test'
+GRAPH = 'rr_graph_e2e'
+
+# RR_REQUIRE_DB_TESTS (set by CI once its DB containers are healthy) turns
+# every skip in this module into a hard failure: a broken container or missing
+# dependency must never let the safety-control tests silently go green.
+_DB_TESTS_REQUIRED = bool(os.environ.get('RR_REQUIRE_DB_TESTS'))
+
+if _DB_TESTS_REQUIRED:
+ import antlr4 # noqa: F401
+ import psycopg2
+else:
+ psycopg2 = pytest.importorskip('psycopg2')
+ pytest.importorskip('antlr4')
+
+
+def _db_reachable() -> bool:
+ try:
+ conn = psycopg2.connect(TEST_DSN, connect_timeout=3)
+ conn.close()
+ return True
+ except Exception:
+ return False
+
+
+if _DB_TESTS_REQUIRED and not _db_reachable():
+ pytest.fail(f'RR_REQUIRE_DB_TESTS is set but the AGE test database is not reachable at {TEST_DSN}', pytrace=False)
+
+pytestmark = pytest.mark.skipif(not _db_reachable(), reason=f'RocketRide AGE test database not reachable at {TEST_DSN}')
+
+
+def _load_from_path(name: str, path: Path, *, is_package: bool = False):
+ search = [str(path.parent)] if is_package else None
+ spec = importlib.util.spec_from_file_location(name, path, submodule_search_locations=search)
+ assert spec is not None and spec.loader is not None
+ module = importlib.util.module_from_spec(spec)
+ sys.modules[name] = module
+ spec.loader.exec_module(module)
+ return module
+
+
+@pytest.fixture()
+def rr_env(monkeypatch):
+ """Stub the engine surface, load real graph base + age layer + node."""
+ warnings: list[str] = []
+
+ depends_mod = types.ModuleType('depends')
+ depends_mod.depends = lambda *a, **kw: None
+ monkeypatch.setitem(sys.modules, 'depends', depends_mod)
+
+ rocketlib = types.ModuleType('rocketlib')
+ rocketlib.IGlobalBase = type('IGlobalBase', (), {})
+ rocketlib.IInstanceBase = type('IInstanceBase', (), {})
+ rocketlib.tool_function = lambda **kwargs: lambda f: f
+ rocketlib.debug = lambda *a, **kw: None
+ rocketlib.error = lambda *a, **kw: None
+ rocketlib.warning = warnings.append
+ rocketlib.OPEN_MODE = types.SimpleNamespace(CONFIG=object())
+ rocketlib_types = types.ModuleType('rocketlib.types')
+ rocketlib_types.IInvokeLLM = types.SimpleNamespace(Ask=lambda **kw: kw)
+ rocketlib.types = rocketlib_types
+ monkeypatch.setitem(sys.modules, 'rocketlib', rocketlib)
+ monkeypatch.setitem(sys.modules, 'rocketlib.types', rocketlib_types)
+
+ ai_pkg = types.ModuleType('ai')
+ ai_pkg.__path__ = []
+ common_pkg = types.ModuleType('ai.common')
+ common_pkg.__path__ = [str(_AI_SRC / 'ai' / 'common')]
+
+ schema = types.ModuleType('ai.common.schema')
+ schema.Answer = type('Answer', (), {'setAnswer': lambda self, v: setattr(self, 'value', v)})
+ schema.Question = type('Question', (), {})
+ schema.QuestionType = types.SimpleNamespace(QUESTION=1, DIALECT=2, EXECUTE=3)
+
+ table = types.ModuleType('ai.common.table')
+ table.Table = types.SimpleNamespace(
+ generate_markdown_table=lambda data, headers=None: '\n'.join(' | '.join(map(str, r)) for r in data)
+ )
+
+ config = types.ModuleType('ai.common.config')
+ config.Config = types.SimpleNamespace(getNodeConfig=lambda provider, connConfig: dict(connConfig))
+
+ monkeypatch.setitem(sys.modules, 'ai', ai_pkg)
+ monkeypatch.setitem(sys.modules, 'ai.common', common_pkg)
+ monkeypatch.setitem(sys.modules, 'ai.common.schema', schema)
+ monkeypatch.setitem(sys.modules, 'ai.common.table', table)
+ monkeypatch.setitem(sys.modules, 'ai.common.config', config)
+
+ config_utils = _load_from_path('ai.common.config_utils', _UTILS_DIR / 'config_utils.py')
+ tool_args = _load_from_path('ai.common.tool_args', _UTILS_DIR / 'tool_args.py')
+ utils = types.ModuleType('ai.common.utils')
+ utils.parse_bool = config_utils.parse_bool
+ utils.config_int = config_utils.config_int
+ for fn in ('normalize_tool_input', 'require_str', 'require_dict', 'require_int', 'optional_int', 'optional_str'):
+ setattr(utils, fn, getattr(tool_args, fn))
+ monkeypatch.setitem(sys.modules, 'ai.common.utils', utils)
+
+ rrdb_mod = _load_from_path('ai.common.rocketride_db', _RRDB_PATH)
+ monkeypatch.setitem(sys.modules, 'ai.common.rocketride_db', rrdb_mod)
+
+ # Real graph base package (brings ai.common.graph.age with it).
+ graph_pkg = _load_from_path('ai.common.graph', _GRAPH_PKG / '__init__.py', is_package=True)
+ monkeypatch.setitem(sys.modules, 'ai.common.graph', graph_pkg)
+
+ # The injectable fake resolver — the same seam a SaaS build fills in.
+ account_mod = types.ModuleType('ai.account')
+
+ async def fake_resolve_db_dsn(client_id):
+ assert client_id == TEST_CLIENT_ID
+ return TEST_DSN
+
+ account_mod.account = types.SimpleNamespace(resolve_db_dsn=fake_resolve_db_dsn)
+ monkeypatch.setitem(sys.modules, 'ai.account', account_mod)
+ monkeypatch.setenv('ROCKETRIDE_CLIENT_ID', TEST_CLIENT_ID)
+ monkeypatch.delenv('ROCKETRIDE_DB_DSN', raising=False)
+
+ iglobal = _load_from_path('nodes.rocketride_graph.IGlobal', _NODE_DIR / 'IGlobal.py')
+ pkg = types.ModuleType('nodes.rocketride_graph')
+ pkg.__path__ = [str(_NODE_DIR)]
+ pkg.IGlobal = iglobal
+ monkeypatch.setitem(sys.modules, 'nodes.rocketride_graph', pkg)
+ monkeypatch.setitem(sys.modules, 'nodes.rocketride_graph.IGlobal', iglobal)
+ iinstance = _load_from_path('nodes.rocketride_graph.IInstance', _NODE_DIR / 'IInstance.py')
+
+ return types.SimpleNamespace(iglobal_cls=iglobal.IGlobal, iinstance_cls=iinstance.IInstance, warnings=warnings)
+
+
+@pytest.fixture()
+def age_graph(rr_env):
+ """Provision + seed the test graph out-of-band (provisioning is not the
+ node's job while create_graph ownership is open).
+ """
+ conn = psycopg2.connect(TEST_DSN)
+ conn.autocommit = True
+ with conn.cursor() as cur:
+ cur.execute('SET search_path = ag_catalog, "$user", public')
+ cur.execute('SELECT 1 FROM ag_graph WHERE name = %s', (GRAPH,))
+ if cur.fetchone():
+ cur.execute('SELECT drop_graph(%s, true)', (GRAPH,))
+ cur.execute('SELECT create_graph(%s)', (GRAPH,))
+ cur.execute(
+ f"SELECT * FROM cypher('{GRAPH}', $$ "
+ "CREATE (a:Person {name:'alice', age: 30})-[:KNOWS {since: 2019}]->(b:Person {name:'bob', age: 25}), "
+ "(b)-[:KNOWS {since: 2021}]->(c:Person {name:'carol', age: 41}), "
+ "(a)-[:WORKS_AT]->(x:Company {name:'RocketRide'}) "
+ '$$) AS (v agtype)'
+ )
+ yield conn
+ with conn.cursor() as cur:
+ cur.execute('SET search_path = ag_catalog, "$user", public')
+ cur.execute('SELECT drop_graph(%s, true)', (GRAPH,))
+ conn.close()
+
+
+def _begin(rr_env, config=None):
+ glb = rr_env.iglobal_cls()
+ merged = {'graph': GRAPH, **(config or {})}
+ glb.glb = types.SimpleNamespace(logicalType='rocketride_graph', connConfig=merged)
+ glb.beginGlobal()
+ return glb
+
+
+class TestLifecycle:
+ def test_begin_global_connects_and_reflects(self, rr_env, age_graph):
+ glb = _begin(rr_env)
+ try:
+ assert glb.age_version == '1.5.0'
+ assert glb.graph_name == GRAPH
+ assert set(glb.graph_schema['nodes']) == {'Person', 'Company'}
+ props = dict(glb.graph_schema['nodes']['Person'])
+ assert props.get('name') == 'str'
+ assert props.get('age') == 'int'
+ rel_types = {r['type'] for r in glb.graph_schema['relationships']}
+ assert rel_types == {'KNOWS', 'WORKS_AT'}
+ knows = next(r for r in glb.graph_schema['relationships'] if r['type'] == 'KNOWS')
+ assert knows['start'] == 'Person' and knows['end'] == 'Person'
+ finally:
+ glb.endGlobal()
+
+ def test_missing_graph_fails_fast(self, rr_env, age_graph):
+ glb = rr_env.iglobal_cls()
+ glb.glb = types.SimpleNamespace(logicalType='rocketride_graph', connConfig={'graph': 'rr_graph_missing'})
+ with pytest.raises(RuntimeError, match='does not exist'):
+ glb.beginGlobal()
+
+
+class TestSafePath:
+ def test_read_query_decodes_rows(self, rr_env, age_graph):
+ glb = _begin(rr_env)
+ try:
+ rows = glb._run_query(
+ 'MATCH (a:Person)-[r:KNOWS]->(b:Person) RETURN a.name AS who, r.since AS since, b ORDER BY r.since'
+ )
+ assert [r['who'] for r in rows] == ['alice', 'bob']
+ assert rows[0]['since'] == 2019
+ assert rows[0]['b']['label'] == 'Person'
+ assert rows[0]['b']['properties']['name'] == 'bob'
+ finally:
+ glb.endGlobal()
+
+ def test_params_round_trip(self, rr_env, age_graph):
+ glb = _begin(rr_env)
+ try:
+ rows = glb._run_query('MATCH (p:Person) WHERE p.name = $who RETURN p.age AS age', params={'who': 'carol'})
+ assert rows == [{'age': 41}]
+ finally:
+ glb.endGlobal()
+
+ def test_limit_contract_returns_at_most_limit_plus_one(self, rr_env, age_graph):
+ glb = _begin(rr_env)
+ try:
+ rows = glb._run_query('MATCH (p:Person) RETURN p.name AS name', limit=1)
+ assert len(rows) == 2 # limit + 1 => the base detects truncation
+ finally:
+ glb.endGlobal()
+
+ def test_read_only_transaction_enforced_at_database(self, rr_env, age_graph):
+ """Read back SHOW transaction_read_only inside a safe plan: it must be
+ 'on'. Outside a transaction both SET TRANSACTION READ ONLY and SET
+ LOCAL are silent no-ops, so this pins the precondition (autocommit
+ off -> implicit transaction) that the write protection and the
+ statement_timeout cap both hang on.
+ """
+ age = sys.modules['ai.common.graph.age']
+ glb = _begin(rr_env)
+ try:
+ assert glb.client.autocommit is False
+ plan = age.TranslatedQuery(columns=['ro'], has_return=True)
+ plan.statements.append('SHOW transaction_read_only')
+ plan.binds.append(())
+ plan.result_index = 0
+ plan.read_only = True
+ rows = glb._execute_plan(plan, fetch_cap=1)
+ assert rows and rows[0][0] == 'on'
+ finally:
+ glb.endGlobal()
+
+ def test_write_rejected_before_reaching_db(self, rr_env, age_graph):
+ glb = _begin(rr_env)
+ try:
+ with pytest.raises(Exception, match='write_clause'):
+ glb._run_query("CREATE (n:Person {name: 'evil'})")
+ finally:
+ glb.endGlobal()
+
+ def test_unsupported_datetime_rejected(self, rr_env, age_graph):
+ glb = _begin(rr_env)
+ try:
+ with pytest.raises(Exception, match='datetime'):
+ glb._run_query('MATCH (n) RETURN datetime()')
+ finally:
+ glb.endGlobal()
+
+
+class TestExecutePath:
+ def test_execute_gated_by_allow_execute(self, rr_env, age_graph):
+ glb = _begin(rr_env, {'allow_execute': False})
+ inst = rr_env.iinstance_cls()
+ inst.IGlobal = glb
+ try:
+ with pytest.raises(ValueError, match='execute tool is disabled'):
+ inst.execute({'query': 'MATCH (n) RETURN n.name'})
+ finally:
+ glb.endGlobal()
+
+ def test_execute_write_and_read_back(self, rr_env, age_graph):
+ glb = _begin(rr_env, {'allow_execute': True})
+ inst = rr_env.iinstance_cls()
+ inst.IGlobal = glb
+ try:
+ written = inst.execute({'query': "CREATE (d:Person {name: 'dave', age: 8}) RETURN d.name AS name"})
+ assert written['rows'] == [{'name': 'dave'}]
+ back = glb._run_query('MATCH (p:Person) WHERE p.name = $n RETURN p.age AS age', params={'n': 'dave'})
+ assert back == [{'age': 8}]
+ finally:
+ glb.endGlobal()
+
+ def test_execute_still_enforces_resource_caps(self, rr_env, age_graph):
+ glb = _begin(rr_env, {'allow_execute': True})
+ inst = rr_env.iinstance_cls()
+ inst.IGlobal = glb
+ try:
+ with pytest.raises(Exception, match='unbounded_var_length'):
+ inst.execute({'query': 'MATCH (a)-[*]->(b) RETURN a'})
+ finally:
+ glb.endGlobal()
+
+
+class TestValidateQuery:
+ def test_valid_query_passes(self, rr_env, age_graph):
+ glb = _begin(rr_env)
+ try:
+ ok, err = glb._validate_query('MATCH (p:Person) RETURN p.name')
+ assert ok, err
+ finally:
+ glb.endGlobal()
+
+ def test_syntax_error_reported_for_repair_loop(self, rr_env, age_graph):
+ glb = _begin(rr_env)
+ try:
+ ok, err = glb._validate_query('MATCHX (p RETURN p')
+ assert not ok
+ assert 'syntax error' in err
+ finally:
+ glb.endGlobal()
+
+ def test_firewall_rejection_reported(self, rr_env, age_graph):
+ glb = _begin(rr_env)
+ try:
+ ok, err = glb._validate_query('MATCH (a)-[*]->(b) RETURN a')
+ assert not ok
+ assert 'unbounded_var_length' in err
+ finally:
+ glb.endGlobal()
+
+ def test_validate_does_not_execute(self, rr_env, age_graph):
+ glb = _begin(rr_env)
+ try:
+ before = glb._run_query('MATCH (p:Person) RETURN count(p) AS n')[0]['n']
+ # EXPLAIN of a read; graph must be unchanged afterwards.
+ ok, _ = glb._validate_query('MATCH (p:Person) RETURN p.name')
+ assert ok
+ after = glb._run_query('MATCH (p:Person) RETURN count(p) AS n')[0]['n']
+ assert before == after
+ finally:
+ glb.endGlobal()
+
+
+class TestInstanceTools:
+ def test_dialect_and_schema_tools(self, rr_env, age_graph):
+ glb = _begin(rr_env)
+ inst = rr_env.iinstance_cls()
+ inst.IGlobal = glb
+ try:
+ assert inst.dialect({}) == {'dialect': 'age'}
+ schema = inst.get_schema({})
+ assert 'Person' in schema['labels']
+ finally:
+ glb.endGlobal()
diff --git a/packages/ai/src/ai/account/base.py b/packages/ai/src/ai/account/base.py
index 7ccbe0c30..75190a0ed 100644
--- a/packages/ai/src/ai/account/base.py
+++ b/packages/ai/src/ai/account/base.py
@@ -318,6 +318,139 @@ async def get_transactions(
"""
return {'transactions': [], 'total': 0, 'page': page, 'pageSize': page_size}
+ # =========================================================================
+ # CLOUD DATABASE — env-gated broker call; raises when unconfigured
+ # =========================================================================
+
+ async def resolve_db_dsn(self, client_id: str) -> str:
+ """
+ Resolve the per-tenant database DSN for the RocketRide cloud DB nodes.
+
+ The ``rocketride_sql`` / ``rocketride_vector`` / ``rocketride_graph``
+ nodes take no connection configuration. Instead of reading
+ host/user/password, they resolve a ready connection string for the
+ caller's own provisioned cloud database, keyed by the authenticated
+ ``client_id`` (``userId``). One database per tenant backs all three.
+
+ Default implementation (both editions): call the data-core provisioner
+ configured via server-side environment —
+
+ ROCKETRIDE_DB_BROKER_URL the /provision endpoint URL
+ ROCKETRIDE_DB_BROKER_TOKEN its Bearer token (from ASM/k8s; never
+ hardcoded)
+
+ ``POST {url} {"tenant_id": client_id}`` -> ``{"database", "role",
+ "dsn", "created"}`` (only ``dsn`` is read here).
+ The endpoint is idempotent (same tenant -> same DSN; the per-tenant
+ password is derived, not stored), so this method is safe to call on
+ every task start with no caching or persistence on this side.
+
+ When the environment is not configured (the open-source default), this
+ raises: the cloud databases require a RocketRide cloud deployment.
+ A SaaS overlay may still override this method entirely.
+
+ Args:
+ client_id: The authenticated connection identity (``userId``).
+ Passed verbatim as the broker's ``tenant_id`` (the provisioner
+ owns slugging/hashing it into a database name).
+
+ Returns:
+ A libpq/SQLAlchemy-compatible PostgreSQL DSN for the tenant DB
+ (e.g. ``postgresql://role:pass@pooler:5432/t_?sslmode=require``).
+
+ Raises:
+ NotImplementedError: broker environment not configured.
+ RuntimeError: the broker rejected the request or returned no DSN.
+ """
+ broker_url = os.environ.get('ROCKETRIDE_DB_BROKER_URL', '').strip()
+ broker_token = os.environ.get('ROCKETRIDE_DB_BROKER_TOKEN', '').strip()
+ if not broker_url or not broker_token:
+ raise NotImplementedError('RocketRide cloud DB nodes require signing into RocketRide cloud')
+ if not client_id or not client_id.strip():
+ raise ValueError('resolve_db_dsn requires a non-empty client_id')
+ self._check_broker_url(broker_url)
+
+ import asyncio
+
+ return await asyncio.to_thread(self._call_db_broker, broker_url, broker_token, client_id.strip())
+
+ @staticmethod
+ def _check_broker_url(url: str) -> None:
+ """Refuse to send the broker token over anything but HTTPS.
+
+ The token can resolve ANY tenant's DSN, so a deployment typo like an
+ ``http://`` broker URL must fail loudly, not silently ship the token
+ (and every returned DSN) in cleartext. Plain http is allowed only for
+ localhost — the local dev/test rig runs the provisioner there.
+ """
+ from urllib.parse import urlparse
+
+ try:
+ parsed = urlparse(url)
+ scheme, host = parsed.scheme.lower(), parsed.hostname
+ except ValueError:
+ scheme, host = '', None
+ if scheme == 'https':
+ return
+ if scheme == 'http' and host in ('localhost', '127.0.0.1', '::1'):
+ return
+ raise RuntimeError(
+ f'ROCKETRIDE_DB_BROKER_URL must use https (got {scheme or "no"} scheme): '
+ 'the broker token must never travel unencrypted; plain http is allowed only for localhost'
+ )
+
+ @staticmethod
+ def _call_db_broker(url: str, token: str, tenant_id: str) -> str:
+ """Blocking POST to the provisioner; runs in a worker thread.
+
+ Stdlib-only on purpose: the account layer has no async-HTTP dependency,
+ and one small request per task start does not justify adding one.
+ """
+ import json
+ import urllib.error
+ import urllib.request
+
+ request = urllib.request.Request(
+ url,
+ data=json.dumps({'tenant_id': tenant_id}).encode('utf-8'),
+ headers={
+ 'Authorization': f'Bearer {token}',
+ 'Content-Type': 'application/json',
+ },
+ method='POST',
+ )
+ try:
+ with urllib.request.urlopen(request, timeout=15) as response:
+ body = json.loads(response.read().decode('utf-8'))
+ except urllib.error.HTTPError as e:
+ # 4xx = unknown/invalid tenant per the broker contract; 5xx = broker fault.
+ raise RuntimeError(f'DB broker rejected provision for this account (HTTP {e.code})') from e
+ except Exception as e:
+ raise RuntimeError(f'DB broker unreachable: {e}') from e
+
+ dsn = body.get('dsn') if isinstance(body, dict) else None
+ if not dsn or not isinstance(dsn, str):
+ raise RuntimeError('DB broker response did not include a DSN')
+ return AccountBase._pin_dsn_tls(dsn)
+
+ @staticmethod
+ def _pin_dsn_tls(dsn: str) -> str:
+ """Validate the broker's DSN and guarantee it carries an sslmode.
+
+ The broker contract sends ``sslmode=require``; this pins that
+ guarantee client-side so a broker regression cannot silently
+ downgrade every tenant connection to cleartext.
+ """
+ from urllib.parse import parse_qs, urlparse
+
+ parsed = urlparse(dsn)
+ if parsed.scheme.lower() not in ('postgres', 'postgresql'):
+ raise RuntimeError(f'DB broker returned a non-PostgreSQL DSN (scheme {parsed.scheme!r})')
+ if 'sslmode' in parse_qs(parsed.query):
+ return dsn
+ separator = '&' if parsed.query else '?'
+ return f'{dsn}{separator}sslmode=require'
+
# =========================================================================
# DAP COMMAND DISPATCH — SaaS overrides all three
# =========================================================================
diff --git a/packages/ai/src/ai/account/oss/__init__.py b/packages/ai/src/ai/account/oss/__init__.py
index 8f0bad986..86424f7fd 100644
--- a/packages/ai/src/ai/account/oss/__init__.py
+++ b/packages/ai/src/ai/account/oss/__init__.py
@@ -233,6 +233,10 @@ async def remove_team_member(self, **kw):
# audit() is inherited from AccountBase as a no-op — OSS has no database.
+ # resolve_db_dsn is inherited from AccountBase: env-gated broker call
+ # (ROCKETRIDE_DB_BROKER_URL/_TOKEN); raises the cloud-sign-in error when
+ # the environment is not configured — the open-source default.
+
# =========================================================================
# APP MANIFEST — read from static apps.json
# =========================================================================
diff --git a/packages/ai/src/ai/common/graph/age/README.md b/packages/ai/src/ai/common/graph/age/README.md
new file mode 100644
index 000000000..2edf5a2a8
--- /dev/null
+++ b/packages/ai/src/ai/common/graph/age/README.md
@@ -0,0 +1,100 @@
+# Cypher → Apache AGE translation layer
+
+Apache AGE cannot run bare Cypher: every query needs an SQL `cypher()`
+envelope, a synthesized result-column list, prepared-statement parameter
+binding, and agtype result decoding — plus a firewall and a dialect gate for
+the version gaps. This package is that translation, as a **pure transform**
+(no database access), so graph nodes targeting the RocketRide cloud data-core
+(PG16 + AGE 1.5.0) can speak Cypher end to end.
+
+## Pipeline
+
+`translate(cypher, params, limit, mode, graph_name, age_version, firewall)`
+→ [`TranslatedQuery`](emit.py):
+
+1. **Parse** ([analysis.py](analysis.py)) — openCypher ANTLR grammar (M23),
+ not regex. Extracts RETURN projection, write clauses, `$params`,
+ variable-length depth bounds, invoked functions.
+2. **Firewall** ([firewall.py](firewall.py)) — resource caps on **both**
+ paths (query length, variable-length depth, statement timeout); semantic
+ read-only rules (no writes, no CALL) on the **safe** path only.
+3. **Dialect** ([capabilities.py](capabilities.py)) — capability table keyed
+ by AGE version: `SUPPORTED` / `EMULATE` (rewrite hook; framework only in
+ v1) / `REJECT` (fail loud pre-flight) / `TBD` (unverified: passes through,
+ AGE's own error surfaces via EXPLAIN/execute). Verify TBD cells against
+ the live instance and promote them.
+4. **Emit** ([emit.py](emit.py)) — `cypher()` envelope with a
+ collision-proof dollar-quote tag, synthesized `AS (c0 agtype, …)` list,
+ `SET LOCAL search_path`/`statement_timeout` preamble, and — when params
+ are present — `PREPARE …(agtype)` / `EXECUTE …(%s::agtype)` /
+ `DEALLOCATE` (AGE rejects inline `::agtype` literals as the params
+ argument).
+5. **Decode** ([decode.py](decode.py)) — agtype text → plain Python via the
+ vendored Apache AGE driver parser; vertices/edges/paths flatten to dicts.
+
+The caller (the `rocketride_graph` node) executes `TranslatedQuery.statements`
+in order inside one transaction — `BEGIN READ ONLY` on the safe path — and
+decodes rows with `decode_row`. Everything is `SET LOCAL` because the cloud
+endpoint is a transaction-mode pooler: plain `SET` would bleed across tenants.
+
+Errors: `AgeTranslationError` (parse/emit), `AgeUnsupportedFeature`
+(capability REJECT), `AgeFirewallRejected` (caps/semantics) — all fail loud.
+
+## Verified AGE 1.5.0 mechanics
+
+Probed against a container on the live pin (PG 16.14 + AGE 1.5.0 + pgvector
+0.8.0, image `apache/age:release_PG16_1.5.0` + pgvector v0.8.0):
+
+- AS-list column **count** must equal the RETURN count (names arbitrary);
+ no-RETURN statements accept one synthesized column and yield 0 rows.
+- `RETURN *` requires scope analysis to synthesize columns → REJECT in v1.
+- cypher()'s third argument must be a **prepared-statement parameter**
+ (inline `'…'::agtype` fails: "third argument of cypher function must be a
+ parameter").
+- `age` is in `shared_preload_libraries` → no `LOAD 'age'` needed.
+- `EXPLAIN` works over the envelope (incl. `EXPLAIN EXECUTE`) and surfaces
+ Cypher syntax errors cleanly — that is the `_validate_query` mechanism.
+- `BEGIN READ ONLY` blocks writes through `cypher()` server-side ("cannot
+ execute CREATE TABLE in a read-only transaction").
+- `datetime()` does not exist on 1.5.0 (`ag_catalog.age_datetime` missing)
+ → capability REJECT.
+
+## Vendored code
+
+| Path | Origin | License |
+|---|---|---|
+| `_cypher/Cypher.g4` + `_cypher/gen/` | openCypher **M23** grammar artifact (`https://s3.amazonaws.com/artifacts.opencypher.org/M23/Cypher.g4`), parser generated with ANTLR 4.13.2 | Apache-2.0 (© Neo Technology 2015-2023) |
+| `_agtype/` (`builder.py`, `models.py`, `exceptions.py`, `Agtype.g4`) | `apache/age` master `drivers/python/age/` + `drivers/Agtype.g4` at commit `5a254d6869d8b2c271f025ea158c0fee2cfacfa3` (PyPI `apache-age-python` 0.0.7 is stale and hard-pins antlr 4.11.1 — deliberately not a dependency) | Apache-2.0 |
+| `_agtype/gen/` | regenerated from `Agtype.g4` with ANTLR 4.13.2 (upstream ships 4.11-generated code that warns per-parse under the 4.13 runtime) | Apache-2.0 |
+
+Local deviations are documented in each `_agtype`/`_cypher` `__init__.py`
+(notably: dropped `_agtype/exceptions.py`'s `from psycopg.errors import *`
+psycopg-v3 re-export).
+
+Runtime dependency: `antlr4-python3-runtime>=4.13.2,<4.14`
+(declared in `packages/ai/src/ai/common/requirements.txt`).
+
+## Regenerating the parsers
+
+Only needed when a grammar file changes. Requires the pip package
+`antlr4-tools` (auto-downloads the ANTLR jar; needs a JRE — `install-jdk`
+can fetch one without touching the system):
+
+```bash
+antlr4 -v 4.13.2 -Dlanguage=Python3 -visitor -o _cypher/gen _cypher/Cypher.g4
+antlr4 -v 4.13.2 -Dlanguage=Python3 -visitor -o _agtype/gen _agtype/Agtype.g4
+# then: delete the *.interp/*.tokens artifacts, restore gen/__init__.py,
+# and normalize the "Generated from " headers to bare filenames.
+```
+
+## Deliberately out of scope (v1)
+
+- ~~Capability cell values for `merge_on_set`, `where_label_check`,
+ `multi_label`, `shortest_path`~~ — **verified 2026-07-28** against the exact
+ pin container (PG 16.14 + AGE 1.5.0): all four fail at AGE's parser
+ (`syntax error at or near ...`) while plain `MERGE` on the same graph
+ succeeds, so all four are promoted to `REJECT` with actionable messages.
+ No `TBD` cells remain in the 1.5.0 table.
+- `EMULATE` rewrites (framework hook exists, no emulations implemented).
+- Capability routing to FalkorDB/Neo4j for AGE-can't-do workloads (own
+ effort, per the design).
diff --git a/packages/ai/src/ai/common/graph/age/__init__.py b/packages/ai/src/ai/common/graph/age/__init__.py
new file mode 100644
index 000000000..dd3f655c2
--- /dev/null
+++ b/packages/ai/src/ai/common/graph/age/__init__.py
@@ -0,0 +1,56 @@
+# =============================================================================
+# MIT License
+# Copyright (c) 2026 Aparavi Software AG
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# =============================================================================
+
+"""Cypher -> Apache AGE translation layer.
+
+Public surface for graph nodes targeting the RocketRide cloud data-core
+(Postgres + Apache AGE). See README.md in this directory for the pipeline
+description, verified AGE mechanics, and vendored-code provenance.
+"""
+
+from .analysis import CypherFacts, analyze
+from .capabilities import AGE_1_5_0, CAPABILITY_TABLES, Capability, CellStatus, DEFAULT_AGE_VERSION
+from .decode import decode_agtype
+from .emit import TranslatedQuery
+from .errors import AgeFirewallRejected, AgeTranslationError, AgeUnsupportedFeature
+from .firewall import FirewallConfig
+from .translate import TranslateMode, decode_row, translate
+
+__all__ = [
+ 'AGE_1_5_0',
+ 'AgeFirewallRejected',
+ 'AgeTranslationError',
+ 'AgeUnsupportedFeature',
+ 'CAPABILITY_TABLES',
+ 'Capability',
+ 'CellStatus',
+ 'CypherFacts',
+ 'DEFAULT_AGE_VERSION',
+ 'FirewallConfig',
+ 'TranslateMode',
+ 'TranslatedQuery',
+ 'analyze',
+ 'decode_agtype',
+ 'decode_row',
+ 'translate',
+]
diff --git a/packages/ai/src/ai/common/graph/age/_agtype/Agtype.g4 b/packages/ai/src/ai/common/graph/age/_agtype/Agtype.g4
new file mode 100644
index 000000000..1d75c1c3d
--- /dev/null
+++ b/packages/ai/src/ai/common/graph/age/_agtype/Agtype.g4
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+grammar Agtype;
+
+agType
+ : agValue EOF
+ ;
+
+agValue
+ : value typeAnnotation?
+ ;
+
+value
+ : STRING #StringValue
+ | INTEGER #IntegerValue
+ | floatLiteral #FloatValue
+ | 'true' #TrueBoolean
+ | 'false' #FalseBoolean
+ | 'null' #NullValue
+ | obj #ObjectValue
+ | array #ArrayValue
+ ;
+
+obj
+ : '{' pair (',' pair)* '}'
+ | '{' '}'
+ ;
+
+pair
+ : STRING ':' agValue
+ ;
+
+array
+ : '[' agValue (',' agValue)* ']'
+ | '[' ']'
+ ;
+
+typeAnnotation
+ : '::' IDENT
+ ;
+
+IDENT
+ : [A-Z_a-z][$0-9A-Z_a-z]*
+ ;
+
+STRING
+ : '"' (ESC | SAFECODEPOINT)* '"'
+ ;
+
+fragment ESC
+ : '\\' (["\\/bfnrt] | UNICODE)
+ ;
+
+fragment UNICODE
+ : 'u' HEX HEX HEX HEX
+ ;
+
+fragment HEX
+ : [0-9a-fA-F]
+ ;
+
+fragment SAFECODEPOINT
+ : ~ ["\\\u0000-\u001F]
+ ;
+
+INTEGER
+ : '-'? INT
+ ;
+
+fragment INT
+ : '0' | [1-9] [0-9]*
+ ;
+
+floatLiteral
+ : RegularFloat
+ | ExponentFloat
+ | '-'? 'Infinity'
+ | 'NaN'
+ ;
+
+RegularFloat
+ : '-'? INT DECIMAL
+ ;
+
+ExponentFloat
+ : '-'? INT DECIMAL? SCIENTIFIC
+ ;
+
+fragment DECIMAL
+ : '.' [0-9]+
+ ;
+
+fragment SCIENTIFIC
+ : [Ee][+-]? [0-9]+
+ ;
+
+WS
+ : [ \t\n\r] + -> skip
+ ;
diff --git a/packages/ai/src/ai/common/graph/age/_agtype/__init__.py b/packages/ai/src/ai/common/graph/age/_agtype/__init__.py
new file mode 100644
index 000000000..84e0061df
--- /dev/null
+++ b/packages/ai/src/ai/common/graph/age/_agtype/__init__.py
@@ -0,0 +1,19 @@
+# =============================================================================
+# VENDORED — Apache AGE Python driver agtype decoder (Apache-2.0).
+# Source: apache/age master drivers/python/age/ at commit
+# 5a254d6869d8b2c271f025ea158c0fee2cfacfa3
+# Local deviations:
+# - exceptions.py: dropped 'from psycopg.errors import *' (psycopg v3
+# convenience re-export; this repo standardises on psycopg2 and the local
+# exception classes never used it).
+# - gen/: regenerated from drivers/Agtype.g4 with ANTLR 4.13.2 (upstream
+# ships 4.11.1-generated code, which warns per-parse under the 4.13
+# runtime). See ../README.md for the regeneration step.
+# - builder.py: _stripStringDelimiters decodes JSON escapes with json.loads
+# (upstream strips only the quote delimiters, so '"a\nb"' round-tripped as
+# a literal backslash-n; the STRING token is grammar-guaranteed to be a
+# valid JSON string literal).
+# =============================================================================
+
+from .builder import newResultHandler, parseAgeValue # noqa: F401
+from .models import Edge, Path, Vertex # noqa: F401
diff --git a/packages/ai/src/ai/common/graph/age/_agtype/builder.py b/packages/ai/src/ai/common/graph/age/_agtype/builder.py
new file mode 100644
index 000000000..b223634e9
--- /dev/null
+++ b/packages/ai/src/ai/common/graph/age/_agtype/builder.py
@@ -0,0 +1,254 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+# http://www.apache.org/licenses/LICENSE-2.0
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from . import gen
+from .gen.AgtypeLexer import AgtypeLexer
+from .gen.AgtypeParser import AgtypeParser
+from .gen.AgtypeVisitor import AgtypeVisitor
+from .models import *
+from .exceptions import *
+from antlr4 import InputStream, CommonTokenStream, ParserRuleContext
+from antlr4.tree.Tree import TerminalNode
+from decimal import Decimal
+import json
+
+resultHandler = None
+
+class ResultHandler:
+ def parse(ageData):
+ pass
+
+def newResultHandler(query=""):
+ resultHandler = Antlr4ResultHandler(None, query)
+ return resultHandler
+
+def parseAgeValue(value, cursor=None):
+ if value is None:
+ return None
+
+ global resultHandler
+ if (resultHandler == None):
+ resultHandler = Antlr4ResultHandler(None)
+ try:
+ return resultHandler.parse(value)
+ except Exception as ex:
+ raise AGTypeError(value, ex)
+
+
+class Antlr4ResultHandler(ResultHandler):
+ def __init__(self, vertexCache, query=None):
+ self.lexer = AgtypeLexer()
+ self.parser = AgtypeParser(None)
+ self.visitor = ResultVisitor(vertexCache)
+
+ def parse(self, ageData):
+ if not ageData:
+ return None
+ # print("Parse::", ageData)
+
+ self.lexer.inputStream = InputStream(ageData)
+ self.parser.setTokenStream(CommonTokenStream(self.lexer))
+ self.parser.reset()
+ tree = self.parser.agType()
+ parsed = tree.accept(self.visitor)
+ return parsed
+
+
+# print raw result String
+class DummyResultHandler(ResultHandler):
+ def parse(self, ageData):
+ print(ageData)
+
+# default agType visitor
+class ResultVisitor(AgtypeVisitor):
+ vertexCache = None
+
+ def __init__(self, cache) -> None:
+ super().__init__()
+ self.vertexCache = cache
+
+
+ def visitAgType(self, ctx:AgtypeParser.AgTypeContext):
+ agVal = ctx.agValue()
+ if agVal != None:
+ obj = ctx.agValue().accept(self)
+ return obj
+
+ return None
+
+ def visitAgValue(self, ctx:AgtypeParser.AgValueContext):
+ annoCtx = ctx.typeAnnotation()
+ valueCtx = ctx.value()
+
+ if annoCtx is not None:
+ annoCtx.accept(self)
+ identNode = annoCtx.IDENT()
+ if identNode is None:
+ raise AGTypeError(ctx.getText(), "Missing type annotation identifier")
+ anno = identNode.getText()
+ if valueCtx is None:
+ raise AGTypeError(ctx.getText(), "Missing value for annotated type")
+ return self.handleAnnotatedValue(anno, valueCtx)
+ else:
+ if valueCtx is None:
+ return None
+ return valueCtx.accept(self)
+
+
+ @staticmethod
+ def _stripStringDelimiters(stringToken):
+ # The Agtype STRING token follows the JSON string grammar exactly
+ # (surrounding '"' plus \-escapes incl. \uXXXX), so json.loads both
+ # strips the delimiters and decodes the escapes — slicing alone would
+ # return 'a\\nb' as a literal backslash-n.
+ return json.loads(stringToken.getText())
+
+ # Visit a parse tree produced by AgtypeParser#StringValue.
+ def visitStringValue(self, ctx:AgtypeParser.StringValueContext):
+ return self._stripStringDelimiters(ctx.STRING())
+
+
+ # Visit a parse tree produced by AgtypeParser#IntegerValue.
+ def visitIntegerValue(self, ctx:AgtypeParser.IntegerValueContext):
+ return int(ctx.INTEGER().getText())
+
+ # Visit a parse tree produced by AgtypeParser#floatLiteral.
+ def visitFloatLiteral(self, ctx:AgtypeParser.FloatLiteralContext):
+ text = ctx.getText()
+ c = ctx.getChild(0)
+ if c is None or not hasattr(c, 'symbol') or c.symbol is None:
+ raise AGTypeError(
+ str(text),
+ "Malformed float literal: missing or invalid child node"
+ )
+ tp = c.symbol.type
+ if tp == AgtypeParser.RegularFloat:
+ return float(text)
+ elif tp == AgtypeParser.ExponentFloat:
+ return float(text)
+ else:
+ if text == 'NaN':
+ return float('nan')
+ elif text == '-Infinity':
+ return float('-inf')
+ elif text == 'Infinity':
+ return float('inf')
+ else:
+ raise ValueError("Unknown float expression: " + text)
+
+
+ # Visit a parse tree produced by AgtypeParser#TrueBoolean.
+ def visitTrueBoolean(self, ctx:AgtypeParser.TrueBooleanContext):
+ return True
+
+
+ # Visit a parse tree produced by AgtypeParser#FalseBoolean.
+ def visitFalseBoolean(self, ctx:AgtypeParser.FalseBooleanContext):
+ return False
+
+
+ # Visit a parse tree produced by AgtypeParser#NullValue.
+ def visitNullValue(self, ctx:AgtypeParser.NullValueContext):
+ return None
+
+
+ # Visit a parse tree produced by AgtypeParser#obj.
+ def visitObj(self, ctx:AgtypeParser.ObjContext):
+ obj = dict()
+ for c in ctx.getChildren():
+ if isinstance(c, AgtypeParser.PairContext):
+ namVal = self.visitPair(c)
+ name = namVal[0]
+ valCtx = namVal[1]
+ # visitPair() raises AGTypeError when the value node is
+ # missing, so valCtx should never be None here. The
+ # guard is kept as a defensive fallback only.
+ if valCtx is not None:
+ val = valCtx.accept(self)
+ obj[name] = val
+ else:
+ obj[name] = None
+ return obj
+
+
+ # Visit a parse tree produced by AgtypeParser#pair.
+ def visitPair(self, ctx:AgtypeParser.PairContext):
+ self.visitChildren(ctx)
+ strNode = ctx.STRING()
+ agValNode = ctx.agValue()
+ if strNode is None:
+ raise AGTypeError(ctx.getText(), "Missing key in object pair")
+ if agValNode is None:
+ raise AGTypeError(ctx.getText(), "Missing value in object pair")
+ return (self._stripStringDelimiters(strNode), agValNode)
+
+
+ # Visit a parse tree produced by AgtypeParser#array.
+ def visitArray(self, ctx:AgtypeParser.ArrayContext):
+ li = list()
+ for c in ctx.getChildren():
+ if not isinstance(c, TerminalNode):
+ val = c.accept(self)
+ li.append(val)
+ return li
+
+ def handleAnnotatedValue(self, anno:str, ctx:ParserRuleContext):
+ # Each branch below constructs a model object (Vertex, Edge, Path)
+ # and populates it from the parsed dict/list. If a type check
+ # fails (e.g. the parsed value is not a dict), AGTypeError is
+ # raised and the partially-constructed object is discarded — no
+ # cleanup is needed because the caller propagates the exception.
+ if anno == "numeric":
+ return Decimal(ctx.getText())
+ elif anno == "vertex":
+ d = ctx.accept(self)
+ if not isinstance(d, dict):
+ raise AGTypeError(str(ctx.getText()), "Expected dict for vertex, got " + type(d).__name__)
+ vid = d.get("id")
+ vertex = None
+ if self.vertexCache is not None and vid in self.vertexCache:
+ vertex = self.vertexCache[vid]
+ else:
+ vertex = Vertex()
+ vertex.id = d.get("id")
+ vertex.label = d.get("label")
+ vertex.properties = d.get("properties") or {}
+
+ if self.vertexCache is not None:
+ self.vertexCache[vid] = vertex
+
+ return vertex
+
+ elif anno == "edge":
+ edge = Edge()
+ d = ctx.accept(self)
+ if not isinstance(d, dict):
+ raise AGTypeError(str(ctx.getText()), "Expected dict for edge, got " + type(d).__name__)
+ edge.id = d.get("id")
+ edge.label = d.get("label")
+ edge.end_id = d.get("end_id")
+ edge.start_id = d.get("start_id")
+ edge.properties = d.get("properties") or {}
+
+ return edge
+
+ elif anno == "path":
+ arr = ctx.accept(self)
+ if not isinstance(arr, list):
+ raise AGTypeError(str(ctx.getText()), "Expected list for path, got " + type(arr).__name__)
+ path = Path(arr)
+
+ return path
+
+ return ctx.accept(self)
diff --git a/packages/ai/src/ai/common/graph/age/_agtype/exceptions.py b/packages/ai/src/ai/common/graph/age/_agtype/exceptions.py
new file mode 100644
index 000000000..f17b6a3c2
--- /dev/null
+++ b/packages/ai/src/ai/common/graph/age/_agtype/exceptions.py
@@ -0,0 +1,101 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+# http://www.apache.org/licenses/LICENSE-2.0
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+
+class AgeNotSet(Exception):
+ def __init__(self, name=None):
+ self.name = name
+ super().__init__(name or 'AGE extension is not set.')
+
+ def __repr__(self):
+ return 'AGE extension is not set.'
+
+class GraphNotFound(Exception):
+ def __init__(self, name=None):
+ self.name = name
+ super().__init__(f'Graph[{name}] does not exist.' if name else 'Graph does not exist.')
+
+ def __repr__(self):
+ if self.name:
+ return 'Graph[' + self.name + '] does not exist.'
+ return 'Graph does not exist.'
+
+
+class GraphAlreadyExists(Exception):
+ def __init__(self, name=None):
+ self.name = name
+ super().__init__(f'Graph[{name}] already exists.' if name else 'Graph already exists.')
+
+ def __repr__(self):
+ if self.name:
+ return 'Graph[' + self.name + '] already exists.'
+ return 'Graph already exists.'
+
+
+class InvalidGraphName(Exception):
+ """Raised when a graph name contains invalid characters."""
+ def __init__(self, name, reason=None):
+ self.name = name
+ self.reason = reason
+ msg = f"Invalid graph name: '{name}'."
+ if reason:
+ msg += f" {reason}"
+ super().__init__(msg)
+
+ def __repr__(self):
+ return f"InvalidGraphName('{self.name}')"
+
+
+class InvalidIdentifier(Exception):
+ """Raised when an identifier (column, label, etc.) is invalid."""
+ def __init__(self, name, context=None):
+ self.name = name
+ self.context = context
+ msg = f"Invalid identifier: '{name}'."
+ if context:
+ msg += f" {context}"
+ super().__init__(msg)
+
+ def __repr__(self):
+ return f"InvalidIdentifier('{self.name}')"
+
+
+class GraphNotSet(Exception):
+ def __repr__(self):
+ return 'Graph name is not set.'
+
+
+class NoConnection(Exception):
+ def __repr__(self):
+ return 'No Connection'
+
+class NoCursor(Exception):
+ def __repr__(self):
+ return 'No Cursor'
+
+class SqlExecutionError(Exception):
+ def __init__(self, msg, cause):
+ self.msg = msg
+ self.cause = cause
+ super().__init__(msg, cause)
+
+ def __repr__(self):
+ return 'SqlExecution [' + self.msg + ']'
+
+class AGTypeError(Exception):
+ def __init__(self, msg, cause):
+ self.msg = msg
+ self.cause = cause
+ super().__init__(msg, cause)
diff --git a/packages/ai/src/ai/common/graph/age/_agtype/gen/AgtypeLexer.py b/packages/ai/src/ai/common/graph/age/_agtype/gen/AgtypeLexer.py
new file mode 100644
index 000000000..71b0851cc
--- /dev/null
+++ b/packages/ai/src/ai/common/graph/age/_agtype/gen/AgtypeLexer.py
@@ -0,0 +1,135 @@
+# Generated from Agtype.g4 by ANTLR 4.13.2
+from antlr4 import *
+from io import StringIO
+import sys
+if sys.version_info[1] > 5:
+ from typing import TextIO
+else:
+ from typing.io import TextIO
+
+
+def serializedATN():
+ return [
+ 4,0,19,183,6,-1,2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,
+ 2,6,7,6,2,7,7,7,2,8,7,8,2,9,7,9,2,10,7,10,2,11,7,11,2,12,7,12,2,
+ 13,7,13,2,14,7,14,2,15,7,15,2,16,7,16,2,17,7,17,2,18,7,18,2,19,7,
+ 19,2,20,7,20,2,21,7,21,2,22,7,22,2,23,7,23,2,24,7,24,2,25,7,25,1,
+ 0,1,0,1,0,1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,2,1,2,1,2,1,2,1,
+ 3,1,3,1,4,1,4,1,5,1,5,1,6,1,6,1,7,1,7,1,8,1,8,1,9,1,9,1,9,1,10,1,
+ 10,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,12,1,12,1,12,1,
+ 12,1,13,1,13,5,13,102,8,13,10,13,12,13,105,9,13,1,14,1,14,1,14,5,
+ 14,110,8,14,10,14,12,14,113,9,14,1,14,1,14,1,15,1,15,1,15,3,15,120,
+ 8,15,1,16,1,16,1,16,1,16,1,16,1,16,1,17,1,17,1,18,1,18,1,19,3,19,
+ 133,8,19,1,19,1,19,1,20,1,20,1,20,5,20,140,8,20,10,20,12,20,143,
+ 9,20,3,20,145,8,20,1,21,3,21,148,8,21,1,21,1,21,1,21,1,22,3,22,154,
+ 8,22,1,22,1,22,3,22,158,8,22,1,22,1,22,1,23,1,23,4,23,164,8,23,11,
+ 23,12,23,165,1,24,1,24,3,24,170,8,24,1,24,4,24,173,8,24,11,24,12,
+ 24,174,1,25,4,25,178,8,25,11,25,12,25,179,1,25,1,25,0,0,26,1,1,3,
+ 2,5,3,7,4,9,5,11,6,13,7,15,8,17,9,19,10,21,11,23,12,25,13,27,14,
+ 29,15,31,0,33,0,35,0,37,0,39,16,41,0,43,17,45,18,47,0,49,0,51,19,
+ 1,0,10,3,0,65,90,95,95,97,122,5,0,36,36,48,57,65,90,95,95,97,122,
+ 8,0,34,34,47,47,92,92,98,98,102,102,110,110,114,114,116,116,3,0,
+ 48,57,65,70,97,102,3,0,0,31,34,34,92,92,1,0,49,57,1,0,48,57,2,0,
+ 69,69,101,101,2,0,43,43,45,45,3,0,9,10,13,13,32,32,189,0,1,1,0,0,
+ 0,0,3,1,0,0,0,0,5,1,0,0,0,0,7,1,0,0,0,0,9,1,0,0,0,0,11,1,0,0,0,0,
+ 13,1,0,0,0,0,15,1,0,0,0,0,17,1,0,0,0,0,19,1,0,0,0,0,21,1,0,0,0,0,
+ 23,1,0,0,0,0,25,1,0,0,0,0,27,1,0,0,0,0,29,1,0,0,0,0,39,1,0,0,0,0,
+ 43,1,0,0,0,0,45,1,0,0,0,0,51,1,0,0,0,1,53,1,0,0,0,3,58,1,0,0,0,5,
+ 64,1,0,0,0,7,69,1,0,0,0,9,71,1,0,0,0,11,73,1,0,0,0,13,75,1,0,0,0,
+ 15,77,1,0,0,0,17,79,1,0,0,0,19,81,1,0,0,0,21,84,1,0,0,0,23,86,1,
+ 0,0,0,25,95,1,0,0,0,27,99,1,0,0,0,29,106,1,0,0,0,31,116,1,0,0,0,
+ 33,121,1,0,0,0,35,127,1,0,0,0,37,129,1,0,0,0,39,132,1,0,0,0,41,144,
+ 1,0,0,0,43,147,1,0,0,0,45,153,1,0,0,0,47,161,1,0,0,0,49,167,1,0,
+ 0,0,51,177,1,0,0,0,53,54,5,116,0,0,54,55,5,114,0,0,55,56,5,117,0,
+ 0,56,57,5,101,0,0,57,2,1,0,0,0,58,59,5,102,0,0,59,60,5,97,0,0,60,
+ 61,5,108,0,0,61,62,5,115,0,0,62,63,5,101,0,0,63,4,1,0,0,0,64,65,
+ 5,110,0,0,65,66,5,117,0,0,66,67,5,108,0,0,67,68,5,108,0,0,68,6,1,
+ 0,0,0,69,70,5,123,0,0,70,8,1,0,0,0,71,72,5,44,0,0,72,10,1,0,0,0,
+ 73,74,5,125,0,0,74,12,1,0,0,0,75,76,5,58,0,0,76,14,1,0,0,0,77,78,
+ 5,91,0,0,78,16,1,0,0,0,79,80,5,93,0,0,80,18,1,0,0,0,81,82,5,58,0,
+ 0,82,83,5,58,0,0,83,20,1,0,0,0,84,85,5,45,0,0,85,22,1,0,0,0,86,87,
+ 5,73,0,0,87,88,5,110,0,0,88,89,5,102,0,0,89,90,5,105,0,0,90,91,5,
+ 110,0,0,91,92,5,105,0,0,92,93,5,116,0,0,93,94,5,121,0,0,94,24,1,
+ 0,0,0,95,96,5,78,0,0,96,97,5,97,0,0,97,98,5,78,0,0,98,26,1,0,0,0,
+ 99,103,7,0,0,0,100,102,7,1,0,0,101,100,1,0,0,0,102,105,1,0,0,0,103,
+ 101,1,0,0,0,103,104,1,0,0,0,104,28,1,0,0,0,105,103,1,0,0,0,106,111,
+ 5,34,0,0,107,110,3,31,15,0,108,110,3,37,18,0,109,107,1,0,0,0,109,
+ 108,1,0,0,0,110,113,1,0,0,0,111,109,1,0,0,0,111,112,1,0,0,0,112,
+ 114,1,0,0,0,113,111,1,0,0,0,114,115,5,34,0,0,115,30,1,0,0,0,116,
+ 119,5,92,0,0,117,120,7,2,0,0,118,120,3,33,16,0,119,117,1,0,0,0,119,
+ 118,1,0,0,0,120,32,1,0,0,0,121,122,5,117,0,0,122,123,3,35,17,0,123,
+ 124,3,35,17,0,124,125,3,35,17,0,125,126,3,35,17,0,126,34,1,0,0,0,
+ 127,128,7,3,0,0,128,36,1,0,0,0,129,130,8,4,0,0,130,38,1,0,0,0,131,
+ 133,5,45,0,0,132,131,1,0,0,0,132,133,1,0,0,0,133,134,1,0,0,0,134,
+ 135,3,41,20,0,135,40,1,0,0,0,136,145,5,48,0,0,137,141,7,5,0,0,138,
+ 140,7,6,0,0,139,138,1,0,0,0,140,143,1,0,0,0,141,139,1,0,0,0,141,
+ 142,1,0,0,0,142,145,1,0,0,0,143,141,1,0,0,0,144,136,1,0,0,0,144,
+ 137,1,0,0,0,145,42,1,0,0,0,146,148,5,45,0,0,147,146,1,0,0,0,147,
+ 148,1,0,0,0,148,149,1,0,0,0,149,150,3,41,20,0,150,151,3,47,23,0,
+ 151,44,1,0,0,0,152,154,5,45,0,0,153,152,1,0,0,0,153,154,1,0,0,0,
+ 154,155,1,0,0,0,155,157,3,41,20,0,156,158,3,47,23,0,157,156,1,0,
+ 0,0,157,158,1,0,0,0,158,159,1,0,0,0,159,160,3,49,24,0,160,46,1,0,
+ 0,0,161,163,5,46,0,0,162,164,7,6,0,0,163,162,1,0,0,0,164,165,1,0,
+ 0,0,165,163,1,0,0,0,165,166,1,0,0,0,166,48,1,0,0,0,167,169,7,7,0,
+ 0,168,170,7,8,0,0,169,168,1,0,0,0,169,170,1,0,0,0,170,172,1,0,0,
+ 0,171,173,7,6,0,0,172,171,1,0,0,0,173,174,1,0,0,0,174,172,1,0,0,
+ 0,174,175,1,0,0,0,175,50,1,0,0,0,176,178,7,9,0,0,177,176,1,0,0,0,
+ 178,179,1,0,0,0,179,177,1,0,0,0,179,180,1,0,0,0,180,181,1,0,0,0,
+ 181,182,6,25,0,0,182,52,1,0,0,0,15,0,103,109,111,119,132,141,144,
+ 147,153,157,165,169,174,179,1,6,0,0
+ ]
+
+class AgtypeLexer(Lexer):
+
+ atn = ATNDeserializer().deserialize(serializedATN())
+
+ decisionsToDFA = [ DFA(ds, i) for i, ds in enumerate(atn.decisionToState) ]
+
+ T__0 = 1
+ T__1 = 2
+ T__2 = 3
+ T__3 = 4
+ T__4 = 5
+ T__5 = 6
+ T__6 = 7
+ T__7 = 8
+ T__8 = 9
+ T__9 = 10
+ T__10 = 11
+ T__11 = 12
+ T__12 = 13
+ IDENT = 14
+ STRING = 15
+ INTEGER = 16
+ RegularFloat = 17
+ ExponentFloat = 18
+ WS = 19
+
+ channelNames = [ u"DEFAULT_TOKEN_CHANNEL", u"HIDDEN" ]
+
+ modeNames = [ "DEFAULT_MODE" ]
+
+ literalNames = [ "",
+ "'true'", "'false'", "'null'", "'{'", "','", "'}'", "':'", "'['",
+ "']'", "'::'", "'-'", "'Infinity'", "'NaN'" ]
+
+ symbolicNames = [ "",
+ "IDENT", "STRING", "INTEGER", "RegularFloat", "ExponentFloat",
+ "WS" ]
+
+ ruleNames = [ "T__0", "T__1", "T__2", "T__3", "T__4", "T__5", "T__6",
+ "T__7", "T__8", "T__9", "T__10", "T__11", "T__12", "IDENT",
+ "STRING", "ESC", "UNICODE", "HEX", "SAFECODEPOINT", "INTEGER",
+ "INT", "RegularFloat", "ExponentFloat", "DECIMAL", "SCIENTIFIC",
+ "WS" ]
+
+ grammarFileName = "Agtype.g4"
+
+ def __init__(self, input=None, output:TextIO = sys.stdout):
+ super().__init__(input, output)
+ self.checkVersion("4.13.2")
+ self._interp = LexerATNSimulator(self, self.atn, self.decisionsToDFA, PredictionContextCache())
+ self._actions = None
+ self._predicates = None
+
+
diff --git a/packages/ai/src/ai/common/graph/age/_agtype/gen/AgtypeListener.py b/packages/ai/src/ai/common/graph/age/_agtype/gen/AgtypeListener.py
new file mode 100644
index 000000000..982dfaaf5
--- /dev/null
+++ b/packages/ai/src/ai/common/graph/age/_agtype/gen/AgtypeListener.py
@@ -0,0 +1,147 @@
+# Generated from Agtype.g4 by ANTLR 4.13.2
+from antlr4 import *
+if "." in __name__:
+ from .AgtypeParser import AgtypeParser
+else:
+ from AgtypeParser import AgtypeParser
+
+# This class defines a complete listener for a parse tree produced by AgtypeParser.
+class AgtypeListener(ParseTreeListener):
+
+ # Enter a parse tree produced by AgtypeParser#agType.
+ def enterAgType(self, ctx:AgtypeParser.AgTypeContext):
+ pass
+
+ # Exit a parse tree produced by AgtypeParser#agType.
+ def exitAgType(self, ctx:AgtypeParser.AgTypeContext):
+ pass
+
+
+ # Enter a parse tree produced by AgtypeParser#agValue.
+ def enterAgValue(self, ctx:AgtypeParser.AgValueContext):
+ pass
+
+ # Exit a parse tree produced by AgtypeParser#agValue.
+ def exitAgValue(self, ctx:AgtypeParser.AgValueContext):
+ pass
+
+
+ # Enter a parse tree produced by AgtypeParser#StringValue.
+ def enterStringValue(self, ctx:AgtypeParser.StringValueContext):
+ pass
+
+ # Exit a parse tree produced by AgtypeParser#StringValue.
+ def exitStringValue(self, ctx:AgtypeParser.StringValueContext):
+ pass
+
+
+ # Enter a parse tree produced by AgtypeParser#IntegerValue.
+ def enterIntegerValue(self, ctx:AgtypeParser.IntegerValueContext):
+ pass
+
+ # Exit a parse tree produced by AgtypeParser#IntegerValue.
+ def exitIntegerValue(self, ctx:AgtypeParser.IntegerValueContext):
+ pass
+
+
+ # Enter a parse tree produced by AgtypeParser#FloatValue.
+ def enterFloatValue(self, ctx:AgtypeParser.FloatValueContext):
+ pass
+
+ # Exit a parse tree produced by AgtypeParser#FloatValue.
+ def exitFloatValue(self, ctx:AgtypeParser.FloatValueContext):
+ pass
+
+
+ # Enter a parse tree produced by AgtypeParser#TrueBoolean.
+ def enterTrueBoolean(self, ctx:AgtypeParser.TrueBooleanContext):
+ pass
+
+ # Exit a parse tree produced by AgtypeParser#TrueBoolean.
+ def exitTrueBoolean(self, ctx:AgtypeParser.TrueBooleanContext):
+ pass
+
+
+ # Enter a parse tree produced by AgtypeParser#FalseBoolean.
+ def enterFalseBoolean(self, ctx:AgtypeParser.FalseBooleanContext):
+ pass
+
+ # Exit a parse tree produced by AgtypeParser#FalseBoolean.
+ def exitFalseBoolean(self, ctx:AgtypeParser.FalseBooleanContext):
+ pass
+
+
+ # Enter a parse tree produced by AgtypeParser#NullValue.
+ def enterNullValue(self, ctx:AgtypeParser.NullValueContext):
+ pass
+
+ # Exit a parse tree produced by AgtypeParser#NullValue.
+ def exitNullValue(self, ctx:AgtypeParser.NullValueContext):
+ pass
+
+
+ # Enter a parse tree produced by AgtypeParser#ObjectValue.
+ def enterObjectValue(self, ctx:AgtypeParser.ObjectValueContext):
+ pass
+
+ # Exit a parse tree produced by AgtypeParser#ObjectValue.
+ def exitObjectValue(self, ctx:AgtypeParser.ObjectValueContext):
+ pass
+
+
+ # Enter a parse tree produced by AgtypeParser#ArrayValue.
+ def enterArrayValue(self, ctx:AgtypeParser.ArrayValueContext):
+ pass
+
+ # Exit a parse tree produced by AgtypeParser#ArrayValue.
+ def exitArrayValue(self, ctx:AgtypeParser.ArrayValueContext):
+ pass
+
+
+ # Enter a parse tree produced by AgtypeParser#obj.
+ def enterObj(self, ctx:AgtypeParser.ObjContext):
+ pass
+
+ # Exit a parse tree produced by AgtypeParser#obj.
+ def exitObj(self, ctx:AgtypeParser.ObjContext):
+ pass
+
+
+ # Enter a parse tree produced by AgtypeParser#pair.
+ def enterPair(self, ctx:AgtypeParser.PairContext):
+ pass
+
+ # Exit a parse tree produced by AgtypeParser#pair.
+ def exitPair(self, ctx:AgtypeParser.PairContext):
+ pass
+
+
+ # Enter a parse tree produced by AgtypeParser#array.
+ def enterArray(self, ctx:AgtypeParser.ArrayContext):
+ pass
+
+ # Exit a parse tree produced by AgtypeParser#array.
+ def exitArray(self, ctx:AgtypeParser.ArrayContext):
+ pass
+
+
+ # Enter a parse tree produced by AgtypeParser#typeAnnotation.
+ def enterTypeAnnotation(self, ctx:AgtypeParser.TypeAnnotationContext):
+ pass
+
+ # Exit a parse tree produced by AgtypeParser#typeAnnotation.
+ def exitTypeAnnotation(self, ctx:AgtypeParser.TypeAnnotationContext):
+ pass
+
+
+ # Enter a parse tree produced by AgtypeParser#floatLiteral.
+ def enterFloatLiteral(self, ctx:AgtypeParser.FloatLiteralContext):
+ pass
+
+ # Exit a parse tree produced by AgtypeParser#floatLiteral.
+ def exitFloatLiteral(self, ctx:AgtypeParser.FloatLiteralContext):
+ pass
+
+
+
+del AgtypeParser
\ No newline at end of file
diff --git a/packages/ai/src/ai/common/graph/age/_agtype/gen/AgtypeParser.py b/packages/ai/src/ai/common/graph/age/_agtype/gen/AgtypeParser.py
new file mode 100644
index 000000000..e34c60f5f
--- /dev/null
+++ b/packages/ai/src/ai/common/graph/age/_agtype/gen/AgtypeParser.py
@@ -0,0 +1,846 @@
+# Generated from Agtype.g4 by ANTLR 4.13.2
+# encoding: utf-8
+from antlr4 import *
+from io import StringIO
+import sys
+if sys.version_info[1] > 5:
+ from typing import TextIO
+else:
+ from typing.io import TextIO
+
+def serializedATN():
+ return [
+ 4,1,19,80,2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,2,6,7,
+ 6,2,7,7,7,1,0,1,0,1,0,1,1,1,1,3,1,22,8,1,1,2,1,2,1,2,1,2,1,2,1,2,
+ 1,2,1,2,3,2,32,8,2,1,3,1,3,1,3,1,3,5,3,38,8,3,10,3,12,3,41,9,3,1,
+ 3,1,3,1,3,1,3,3,3,47,8,3,1,4,1,4,1,4,1,4,1,5,1,5,1,5,1,5,5,5,57,
+ 8,5,10,5,12,5,60,9,5,1,5,1,5,1,5,1,5,3,5,66,8,5,1,6,1,6,1,6,1,7,
+ 1,7,1,7,3,7,74,8,7,1,7,1,7,3,7,78,8,7,1,7,0,0,8,0,2,4,6,8,10,12,
+ 14,0,0,87,0,16,1,0,0,0,2,19,1,0,0,0,4,31,1,0,0,0,6,46,1,0,0,0,8,
+ 48,1,0,0,0,10,65,1,0,0,0,12,67,1,0,0,0,14,77,1,0,0,0,16,17,3,2,1,
+ 0,17,18,5,0,0,1,18,1,1,0,0,0,19,21,3,4,2,0,20,22,3,12,6,0,21,20,
+ 1,0,0,0,21,22,1,0,0,0,22,3,1,0,0,0,23,32,5,15,0,0,24,32,5,16,0,0,
+ 25,32,3,14,7,0,26,32,5,1,0,0,27,32,5,2,0,0,28,32,5,3,0,0,29,32,3,
+ 6,3,0,30,32,3,10,5,0,31,23,1,0,0,0,31,24,1,0,0,0,31,25,1,0,0,0,31,
+ 26,1,0,0,0,31,27,1,0,0,0,31,28,1,0,0,0,31,29,1,0,0,0,31,30,1,0,0,
+ 0,32,5,1,0,0,0,33,34,5,4,0,0,34,39,3,8,4,0,35,36,5,5,0,0,36,38,3,
+ 8,4,0,37,35,1,0,0,0,38,41,1,0,0,0,39,37,1,0,0,0,39,40,1,0,0,0,40,
+ 42,1,0,0,0,41,39,1,0,0,0,42,43,5,6,0,0,43,47,1,0,0,0,44,45,5,4,0,
+ 0,45,47,5,6,0,0,46,33,1,0,0,0,46,44,1,0,0,0,47,7,1,0,0,0,48,49,5,
+ 15,0,0,49,50,5,7,0,0,50,51,3,2,1,0,51,9,1,0,0,0,52,53,5,8,0,0,53,
+ 58,3,2,1,0,54,55,5,5,0,0,55,57,3,2,1,0,56,54,1,0,0,0,57,60,1,0,0,
+ 0,58,56,1,0,0,0,58,59,1,0,0,0,59,61,1,0,0,0,60,58,1,0,0,0,61,62,
+ 5,9,0,0,62,66,1,0,0,0,63,64,5,8,0,0,64,66,5,9,0,0,65,52,1,0,0,0,
+ 65,63,1,0,0,0,66,11,1,0,0,0,67,68,5,10,0,0,68,69,5,14,0,0,69,13,
+ 1,0,0,0,70,78,5,17,0,0,71,78,5,18,0,0,72,74,5,11,0,0,73,72,1,0,0,
+ 0,73,74,1,0,0,0,74,75,1,0,0,0,75,78,5,12,0,0,76,78,5,13,0,0,77,70,
+ 1,0,0,0,77,71,1,0,0,0,77,73,1,0,0,0,77,76,1,0,0,0,78,15,1,0,0,0,
+ 8,21,31,39,46,58,65,73,77
+ ]
+
+class AgtypeParser ( Parser ):
+
+ grammarFileName = "Agtype.g4"
+
+ atn = ATNDeserializer().deserialize(serializedATN())
+
+ decisionsToDFA = [ DFA(ds, i) for i, ds in enumerate(atn.decisionToState) ]
+
+ sharedContextCache = PredictionContextCache()
+
+ literalNames = [ "", "'true'", "'false'", "'null'", "'{'",
+ "','", "'}'", "':'", "'['", "']'", "'::'", "'-'", "'Infinity'",
+ "'NaN'" ]
+
+ symbolicNames = [ "", "", "", "",
+ "", "", "", "",
+ "", "", "", "",
+ "", "", "IDENT", "STRING", "INTEGER",
+ "RegularFloat", "ExponentFloat", "WS" ]
+
+ RULE_agType = 0
+ RULE_agValue = 1
+ RULE_value = 2
+ RULE_obj = 3
+ RULE_pair = 4
+ RULE_array = 5
+ RULE_typeAnnotation = 6
+ RULE_floatLiteral = 7
+
+ ruleNames = [ "agType", "agValue", "value", "obj", "pair", "array",
+ "typeAnnotation", "floatLiteral" ]
+
+ EOF = Token.EOF
+ T__0=1
+ T__1=2
+ T__2=3
+ T__3=4
+ T__4=5
+ T__5=6
+ T__6=7
+ T__7=8
+ T__8=9
+ T__9=10
+ T__10=11
+ T__11=12
+ T__12=13
+ IDENT=14
+ STRING=15
+ INTEGER=16
+ RegularFloat=17
+ ExponentFloat=18
+ WS=19
+
+ def __init__(self, input:TokenStream, output:TextIO = sys.stdout):
+ super().__init__(input, output)
+ self.checkVersion("4.13.2")
+ self._interp = ParserATNSimulator(self, self.atn, self.decisionsToDFA, self.sharedContextCache)
+ self._predicates = None
+
+
+
+
+ class AgTypeContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def agValue(self):
+ return self.getTypedRuleContext(AgtypeParser.AgValueContext,0)
+
+
+ def EOF(self):
+ return self.getToken(AgtypeParser.EOF, 0)
+
+ def getRuleIndex(self):
+ return AgtypeParser.RULE_agType
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterAgType" ):
+ listener.enterAgType(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitAgType" ):
+ listener.exitAgType(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitAgType" ):
+ return visitor.visitAgType(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def agType(self):
+
+ localctx = AgtypeParser.AgTypeContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 0, self.RULE_agType)
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 16
+ self.agValue()
+ self.state = 17
+ self.match(AgtypeParser.EOF)
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class AgValueContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def value(self):
+ return self.getTypedRuleContext(AgtypeParser.ValueContext,0)
+
+
+ def typeAnnotation(self):
+ return self.getTypedRuleContext(AgtypeParser.TypeAnnotationContext,0)
+
+
+ def getRuleIndex(self):
+ return AgtypeParser.RULE_agValue
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterAgValue" ):
+ listener.enterAgValue(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitAgValue" ):
+ listener.exitAgValue(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitAgValue" ):
+ return visitor.visitAgValue(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def agValue(self):
+
+ localctx = AgtypeParser.AgValueContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 2, self.RULE_agValue)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 19
+ self.value()
+ self.state = 21
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==10:
+ self.state = 20
+ self.typeAnnotation()
+
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class ValueContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+
+ def getRuleIndex(self):
+ return AgtypeParser.RULE_value
+
+
+ def copyFrom(self, ctx:ParserRuleContext):
+ super().copyFrom(ctx)
+
+
+
+ class NullValueContext(ValueContext):
+
+ def __init__(self, parser, ctx:ParserRuleContext): # actually a AgtypeParser.ValueContext
+ super().__init__(parser)
+ self.copyFrom(ctx)
+
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterNullValue" ):
+ listener.enterNullValue(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitNullValue" ):
+ listener.exitNullValue(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitNullValue" ):
+ return visitor.visitNullValue(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+ class ObjectValueContext(ValueContext):
+
+ def __init__(self, parser, ctx:ParserRuleContext): # actually a AgtypeParser.ValueContext
+ super().__init__(parser)
+ self.copyFrom(ctx)
+
+ def obj(self):
+ return self.getTypedRuleContext(AgtypeParser.ObjContext,0)
+
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterObjectValue" ):
+ listener.enterObjectValue(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitObjectValue" ):
+ listener.exitObjectValue(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitObjectValue" ):
+ return visitor.visitObjectValue(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+ class IntegerValueContext(ValueContext):
+
+ def __init__(self, parser, ctx:ParserRuleContext): # actually a AgtypeParser.ValueContext
+ super().__init__(parser)
+ self.copyFrom(ctx)
+
+ def INTEGER(self):
+ return self.getToken(AgtypeParser.INTEGER, 0)
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterIntegerValue" ):
+ listener.enterIntegerValue(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitIntegerValue" ):
+ listener.exitIntegerValue(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitIntegerValue" ):
+ return visitor.visitIntegerValue(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+ class TrueBooleanContext(ValueContext):
+
+ def __init__(self, parser, ctx:ParserRuleContext): # actually a AgtypeParser.ValueContext
+ super().__init__(parser)
+ self.copyFrom(ctx)
+
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterTrueBoolean" ):
+ listener.enterTrueBoolean(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitTrueBoolean" ):
+ listener.exitTrueBoolean(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitTrueBoolean" ):
+ return visitor.visitTrueBoolean(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+ class FalseBooleanContext(ValueContext):
+
+ def __init__(self, parser, ctx:ParserRuleContext): # actually a AgtypeParser.ValueContext
+ super().__init__(parser)
+ self.copyFrom(ctx)
+
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterFalseBoolean" ):
+ listener.enterFalseBoolean(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitFalseBoolean" ):
+ listener.exitFalseBoolean(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitFalseBoolean" ):
+ return visitor.visitFalseBoolean(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+ class FloatValueContext(ValueContext):
+
+ def __init__(self, parser, ctx:ParserRuleContext): # actually a AgtypeParser.ValueContext
+ super().__init__(parser)
+ self.copyFrom(ctx)
+
+ def floatLiteral(self):
+ return self.getTypedRuleContext(AgtypeParser.FloatLiteralContext,0)
+
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterFloatValue" ):
+ listener.enterFloatValue(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitFloatValue" ):
+ listener.exitFloatValue(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitFloatValue" ):
+ return visitor.visitFloatValue(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+ class StringValueContext(ValueContext):
+
+ def __init__(self, parser, ctx:ParserRuleContext): # actually a AgtypeParser.ValueContext
+ super().__init__(parser)
+ self.copyFrom(ctx)
+
+ def STRING(self):
+ return self.getToken(AgtypeParser.STRING, 0)
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterStringValue" ):
+ listener.enterStringValue(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitStringValue" ):
+ listener.exitStringValue(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitStringValue" ):
+ return visitor.visitStringValue(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+ class ArrayValueContext(ValueContext):
+
+ def __init__(self, parser, ctx:ParserRuleContext): # actually a AgtypeParser.ValueContext
+ super().__init__(parser)
+ self.copyFrom(ctx)
+
+ def array(self):
+ return self.getTypedRuleContext(AgtypeParser.ArrayContext,0)
+
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterArrayValue" ):
+ listener.enterArrayValue(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitArrayValue" ):
+ listener.exitArrayValue(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitArrayValue" ):
+ return visitor.visitArrayValue(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+ def value(self):
+
+ localctx = AgtypeParser.ValueContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 4, self.RULE_value)
+ try:
+ self.state = 31
+ self._errHandler.sync(self)
+ token = self._input.LA(1)
+ if token in [15]:
+ localctx = AgtypeParser.StringValueContext(self, localctx)
+ self.enterOuterAlt(localctx, 1)
+ self.state = 23
+ self.match(AgtypeParser.STRING)
+ pass
+ elif token in [16]:
+ localctx = AgtypeParser.IntegerValueContext(self, localctx)
+ self.enterOuterAlt(localctx, 2)
+ self.state = 24
+ self.match(AgtypeParser.INTEGER)
+ pass
+ elif token in [11, 12, 13, 17, 18]:
+ localctx = AgtypeParser.FloatValueContext(self, localctx)
+ self.enterOuterAlt(localctx, 3)
+ self.state = 25
+ self.floatLiteral()
+ pass
+ elif token in [1]:
+ localctx = AgtypeParser.TrueBooleanContext(self, localctx)
+ self.enterOuterAlt(localctx, 4)
+ self.state = 26
+ self.match(AgtypeParser.T__0)
+ pass
+ elif token in [2]:
+ localctx = AgtypeParser.FalseBooleanContext(self, localctx)
+ self.enterOuterAlt(localctx, 5)
+ self.state = 27
+ self.match(AgtypeParser.T__1)
+ pass
+ elif token in [3]:
+ localctx = AgtypeParser.NullValueContext(self, localctx)
+ self.enterOuterAlt(localctx, 6)
+ self.state = 28
+ self.match(AgtypeParser.T__2)
+ pass
+ elif token in [4]:
+ localctx = AgtypeParser.ObjectValueContext(self, localctx)
+ self.enterOuterAlt(localctx, 7)
+ self.state = 29
+ self.obj()
+ pass
+ elif token in [8]:
+ localctx = AgtypeParser.ArrayValueContext(self, localctx)
+ self.enterOuterAlt(localctx, 8)
+ self.state = 30
+ self.array()
+ pass
+ else:
+ raise NoViableAltException(self)
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class ObjContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def pair(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(AgtypeParser.PairContext)
+ else:
+ return self.getTypedRuleContext(AgtypeParser.PairContext,i)
+
+
+ def getRuleIndex(self):
+ return AgtypeParser.RULE_obj
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterObj" ):
+ listener.enterObj(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitObj" ):
+ listener.exitObj(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitObj" ):
+ return visitor.visitObj(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def obj(self):
+
+ localctx = AgtypeParser.ObjContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 6, self.RULE_obj)
+ self._la = 0 # Token type
+ try:
+ self.state = 46
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,3,self._ctx)
+ if la_ == 1:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 33
+ self.match(AgtypeParser.T__3)
+ self.state = 34
+ self.pair()
+ self.state = 39
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ while _la==5:
+ self.state = 35
+ self.match(AgtypeParser.T__4)
+ self.state = 36
+ self.pair()
+ self.state = 41
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+
+ self.state = 42
+ self.match(AgtypeParser.T__5)
+ pass
+
+ elif la_ == 2:
+ self.enterOuterAlt(localctx, 2)
+ self.state = 44
+ self.match(AgtypeParser.T__3)
+ self.state = 45
+ self.match(AgtypeParser.T__5)
+ pass
+
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class PairContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def STRING(self):
+ return self.getToken(AgtypeParser.STRING, 0)
+
+ def agValue(self):
+ return self.getTypedRuleContext(AgtypeParser.AgValueContext,0)
+
+
+ def getRuleIndex(self):
+ return AgtypeParser.RULE_pair
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterPair" ):
+ listener.enterPair(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitPair" ):
+ listener.exitPair(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitPair" ):
+ return visitor.visitPair(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def pair(self):
+
+ localctx = AgtypeParser.PairContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 8, self.RULE_pair)
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 48
+ self.match(AgtypeParser.STRING)
+ self.state = 49
+ self.match(AgtypeParser.T__6)
+ self.state = 50
+ self.agValue()
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class ArrayContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def agValue(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(AgtypeParser.AgValueContext)
+ else:
+ return self.getTypedRuleContext(AgtypeParser.AgValueContext,i)
+
+
+ def getRuleIndex(self):
+ return AgtypeParser.RULE_array
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterArray" ):
+ listener.enterArray(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitArray" ):
+ listener.exitArray(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitArray" ):
+ return visitor.visitArray(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def array(self):
+
+ localctx = AgtypeParser.ArrayContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 10, self.RULE_array)
+ self._la = 0 # Token type
+ try:
+ self.state = 65
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,5,self._ctx)
+ if la_ == 1:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 52
+ self.match(AgtypeParser.T__7)
+ self.state = 53
+ self.agValue()
+ self.state = 58
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ while _la==5:
+ self.state = 54
+ self.match(AgtypeParser.T__4)
+ self.state = 55
+ self.agValue()
+ self.state = 60
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+
+ self.state = 61
+ self.match(AgtypeParser.T__8)
+ pass
+
+ elif la_ == 2:
+ self.enterOuterAlt(localctx, 2)
+ self.state = 63
+ self.match(AgtypeParser.T__7)
+ self.state = 64
+ self.match(AgtypeParser.T__8)
+ pass
+
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class TypeAnnotationContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def IDENT(self):
+ return self.getToken(AgtypeParser.IDENT, 0)
+
+ def getRuleIndex(self):
+ return AgtypeParser.RULE_typeAnnotation
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterTypeAnnotation" ):
+ listener.enterTypeAnnotation(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitTypeAnnotation" ):
+ listener.exitTypeAnnotation(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitTypeAnnotation" ):
+ return visitor.visitTypeAnnotation(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def typeAnnotation(self):
+
+ localctx = AgtypeParser.TypeAnnotationContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 12, self.RULE_typeAnnotation)
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 67
+ self.match(AgtypeParser.T__9)
+ self.state = 68
+ self.match(AgtypeParser.IDENT)
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class FloatLiteralContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def RegularFloat(self):
+ return self.getToken(AgtypeParser.RegularFloat, 0)
+
+ def ExponentFloat(self):
+ return self.getToken(AgtypeParser.ExponentFloat, 0)
+
+ def getRuleIndex(self):
+ return AgtypeParser.RULE_floatLiteral
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterFloatLiteral" ):
+ listener.enterFloatLiteral(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitFloatLiteral" ):
+ listener.exitFloatLiteral(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitFloatLiteral" ):
+ return visitor.visitFloatLiteral(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def floatLiteral(self):
+
+ localctx = AgtypeParser.FloatLiteralContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 14, self.RULE_floatLiteral)
+ self._la = 0 # Token type
+ try:
+ self.state = 77
+ self._errHandler.sync(self)
+ token = self._input.LA(1)
+ if token in [17]:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 70
+ self.match(AgtypeParser.RegularFloat)
+ pass
+ elif token in [18]:
+ self.enterOuterAlt(localctx, 2)
+ self.state = 71
+ self.match(AgtypeParser.ExponentFloat)
+ pass
+ elif token in [11, 12]:
+ self.enterOuterAlt(localctx, 3)
+ self.state = 73
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==11:
+ self.state = 72
+ self.match(AgtypeParser.T__10)
+
+
+ self.state = 75
+ self.match(AgtypeParser.T__11)
+ pass
+ elif token in [13]:
+ self.enterOuterAlt(localctx, 4)
+ self.state = 76
+ self.match(AgtypeParser.T__12)
+ pass
+ else:
+ raise NoViableAltException(self)
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+
+
+
diff --git a/packages/ai/src/ai/common/graph/age/_agtype/gen/AgtypeVisitor.py b/packages/ai/src/ai/common/graph/age/_agtype/gen/AgtypeVisitor.py
new file mode 100644
index 000000000..9d81ef974
--- /dev/null
+++ b/packages/ai/src/ai/common/graph/age/_agtype/gen/AgtypeVisitor.py
@@ -0,0 +1,88 @@
+# Generated from Agtype.g4 by ANTLR 4.13.2
+from antlr4 import *
+if "." in __name__:
+ from .AgtypeParser import AgtypeParser
+else:
+ from AgtypeParser import AgtypeParser
+
+# This class defines a complete generic visitor for a parse tree produced by AgtypeParser.
+
+class AgtypeVisitor(ParseTreeVisitor):
+
+ # Visit a parse tree produced by AgtypeParser#agType.
+ def visitAgType(self, ctx:AgtypeParser.AgTypeContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by AgtypeParser#agValue.
+ def visitAgValue(self, ctx:AgtypeParser.AgValueContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by AgtypeParser#StringValue.
+ def visitStringValue(self, ctx:AgtypeParser.StringValueContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by AgtypeParser#IntegerValue.
+ def visitIntegerValue(self, ctx:AgtypeParser.IntegerValueContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by AgtypeParser#FloatValue.
+ def visitFloatValue(self, ctx:AgtypeParser.FloatValueContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by AgtypeParser#TrueBoolean.
+ def visitTrueBoolean(self, ctx:AgtypeParser.TrueBooleanContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by AgtypeParser#FalseBoolean.
+ def visitFalseBoolean(self, ctx:AgtypeParser.FalseBooleanContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by AgtypeParser#NullValue.
+ def visitNullValue(self, ctx:AgtypeParser.NullValueContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by AgtypeParser#ObjectValue.
+ def visitObjectValue(self, ctx:AgtypeParser.ObjectValueContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by AgtypeParser#ArrayValue.
+ def visitArrayValue(self, ctx:AgtypeParser.ArrayValueContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by AgtypeParser#obj.
+ def visitObj(self, ctx:AgtypeParser.ObjContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by AgtypeParser#pair.
+ def visitPair(self, ctx:AgtypeParser.PairContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by AgtypeParser#array.
+ def visitArray(self, ctx:AgtypeParser.ArrayContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by AgtypeParser#typeAnnotation.
+ def visitTypeAnnotation(self, ctx:AgtypeParser.TypeAnnotationContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by AgtypeParser#floatLiteral.
+ def visitFloatLiteral(self, ctx:AgtypeParser.FloatLiteralContext):
+ return self.visitChildren(ctx)
+
+
+
+del AgtypeParser
\ No newline at end of file
diff --git a/packages/ai/src/ai/common/graph/age/_agtype/gen/__init__.py b/packages/ai/src/ai/common/graph/age/_agtype/gen/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/ai/src/ai/common/graph/age/_agtype/models.py b/packages/ai/src/ai/common/graph/age/_agtype/models.py
new file mode 100644
index 000000000..62215c160
--- /dev/null
+++ b/packages/ai/src/ai/common/graph/age/_agtype/models.py
@@ -0,0 +1,334 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+# http://www.apache.org/licenses/LICENSE-2.0
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import json
+from io import StringIO
+
+
+TP_NONE = 0
+TP_VERTEX = 1
+TP_EDGE = 2
+TP_PATH = 3
+
+
+class Graph():
+ def __init__(self, stmt=None) -> None:
+ self.statement = stmt
+ self.rows = list()
+ self.vertices = dict()
+
+ def __iter__(self):
+ return self.rows.__iter__()
+
+ def __len__(self):
+ return self.rows.__len__()
+
+ def __getitem__(self,index):
+ return self.rows[index]
+
+ def size(self):
+ return self.rows.__len__()
+
+ def append(self, agObj):
+ self.rows.append(agObj)
+
+ def getVertices(self):
+ return self.vertices
+
+ def getVertex(self, id):
+ if id in self.vertices:
+ return self.vertices[id]
+ else:
+ return None
+
+class AGObj:
+ @property
+ def gtype(self):
+ return TP_NONE
+
+
+class Path(AGObj):
+ entities = []
+ def __init__(self, entities=None) -> None:
+ self.entities = entities
+
+ @property
+ def gtype(self):
+ return TP_PATH
+
+ def __iter__(self):
+ return self.entities.__iter__()
+
+ def __len__(self):
+ return self.entities.__len__()
+
+ def __getitem__(self,index):
+ return self.entities[index]
+
+ def size(self):
+ return self.entities.__len__()
+
+ def append(self, agObj:AGObj ):
+ self.entities.append(agObj)
+
+ def __str__(self) -> str:
+ return self.toString()
+
+ def __repr__(self) -> str:
+ return self.toString()
+
+ def toString(self) -> str:
+ buf = StringIO()
+ buf.write("[")
+ max = len(self.entities)
+ idx = 0
+ while idx < max:
+ if idx > 0:
+ buf.write(",")
+ self.entities[idx]._toString(buf)
+ idx += 1
+ buf.write("]::PATH")
+
+ return buf.getvalue()
+
+ def toJson(self) -> str:
+ buf = StringIO()
+ buf.write("{\"gtype\": \"path\", \"elements\": [")
+
+ max = len(self.entities)
+ idx = 0
+ while idx < max:
+ if idx > 0:
+ buf.write(",")
+ self.entities[idx]._toJson(buf)
+ idx += 1
+ buf.write("]}")
+
+ return buf.getvalue()
+
+ def to_dict(self) -> list:
+ # AGObj elements are recursively converted; JSON-native types
+ # (dict, list, str, int, float, bool, None) pass through unchanged.
+ # Non-serializable objects fall back to str() as a safety net.
+ result = []
+ for e in (self.entities or []):
+ if isinstance(e, AGObj):
+ result.append(e.to_dict())
+ elif isinstance(e, (dict, list, str, int, float, bool, type(None))):
+ result.append(e)
+ else:
+ result.append(str(e))
+ return result
+
+
+
+
+class Vertex(AGObj):
+ def __init__(self, id=None, label=None, properties=None) -> None:
+ self.id = id
+ self.label = label
+ self.properties = properties
+
+ @property
+ def gtype(self):
+ return TP_VERTEX
+
+ def __setitem__(self,name, value):
+ self.properties[name]=value
+
+ def __getitem__(self,name):
+ if name in self.properties:
+ return self.properties[name]
+ else:
+ return None
+
+ def __str__(self) -> str:
+ return self.toString()
+
+ def __repr__(self) -> str:
+ return self.toString()
+
+ def to_dict(self) -> dict:
+ """Return a plain dict suitable for JSON serialization.
+
+ Properties are shallow-copied; nested mutable values will share
+ references with the original Vertex.
+ """
+ return {
+ "id": self.id,
+ "label": self.label,
+ "properties": dict(self.properties) if self.properties else {},
+ }
+
+ def toString(self) -> str:
+ return nodeToString(self)
+
+ def _toString(self, buf):
+ _nodeToString(self, buf)
+
+ def toJson(self) -> str:
+ return nodeToJson(self)
+
+ def _toJson(self, buf):
+ _nodeToJson(self, buf)
+
+
+class Edge(AGObj):
+ def __init__(self, id=None, label=None, properties=None) -> None:
+ self.id = id
+ self.label = label
+ self.start_id = None
+ self.end_id = None
+ self.properties = properties
+
+ @property
+ def gtype(self):
+ return TP_EDGE
+
+ def __setitem__(self,name, value):
+ self.properties[name]=value
+
+ def __getitem__(self,name):
+ if name in self.properties:
+ return self.properties[name]
+ else:
+ return None
+
+ def __str__(self) -> str:
+ return self.toString()
+
+ def __repr__(self) -> str:
+ return self.toString()
+
+ def to_dict(self) -> dict:
+ """Return a plain dict suitable for JSON serialization.
+
+ Properties are shallow-copied; nested mutable values will share
+ references with the original Edge.
+ """
+ return {
+ "id": self.id,
+ "label": self.label,
+ "start_id": self.start_id,
+ "end_id": self.end_id,
+ "properties": dict(self.properties) if self.properties else {},
+ }
+
+ def extraStrFormat(node, buf):
+ if node.start_id != None:
+ buf.write(", start_id:")
+ buf.write(str(node.start_id))
+
+ if node.end_id != None:
+ buf.write(", end_id:")
+ buf.write(str(node.end_id))
+
+
+ def toString(self) -> str:
+ return nodeToString(self, Edge.extraStrFormat)
+
+ def _toString(self, buf):
+ _nodeToString(self, buf, Edge.extraStrFormat)
+
+ def extraJsonFormat(node, buf):
+ if node.start_id != None:
+ buf.write(", \"start_id\": \"")
+ buf.write(str(node.start_id))
+ buf.write("\"")
+
+ if node.end_id != None:
+ buf.write(", \"end_id\": \"")
+ buf.write(str(node.end_id))
+ buf.write("\"")
+
+ def toJson(self) -> str:
+ return nodeToJson(self, Edge.extraJsonFormat)
+
+ def _toJson(self, buf):
+ _nodeToJson(self, buf, Edge.extraJsonFormat)
+
+
+def nodeToString(node, extraFormatter=None):
+ buf = StringIO()
+ _nodeToString(node,buf,extraFormatter=extraFormatter)
+ return buf.getvalue()
+
+
+def _nodeToString(node, buf, extraFormatter=None):
+ buf.write("{")
+ if node.label != None:
+ buf.write("label:")
+ buf.write(node.label)
+
+ if node.id != None:
+ buf.write(", id:")
+ buf.write(str(node.id))
+
+ if node.properties != None:
+ buf.write(", properties:{")
+ prop_list = []
+ for k, v in node.properties.items():
+ prop_list.append(f"{k}: {str(v)}")
+
+ # Join properties with comma and write to buffer
+ buf.write(", ".join(prop_list))
+ buf.write("}")
+
+ if extraFormatter != None:
+ extraFormatter(node, buf)
+
+ if node.gtype == TP_VERTEX:
+ buf.write("}::VERTEX")
+ if node.gtype == TP_EDGE:
+ buf.write("}::EDGE")
+
+
+def nodeToJson(node, extraFormatter=None):
+ buf = StringIO()
+ _nodeToJson(node, buf, extraFormatter=extraFormatter)
+ return buf.getvalue()
+
+
+def _nodeToJson(node, buf, extraFormatter=None):
+ buf.write("{\"gtype\": ")
+ if node.gtype == TP_VERTEX:
+ buf.write("\"vertex\", ")
+ if node.gtype == TP_EDGE:
+ buf.write("\"edge\", ")
+
+ if node.label != None:
+ buf.write("\"label\":\"")
+ buf.write(node.label)
+ buf.write("\"")
+
+ if node.id != None:
+ buf.write(", \"id\":")
+ buf.write(str(node.id))
+
+ if extraFormatter != None:
+ extraFormatter(node, buf)
+
+ if node.properties != None:
+ buf.write(", \"properties\":{")
+
+ prop_list = []
+ for k, v in node.properties.items():
+ prop_list.append(f"\"{k}\": \"{str(v)}\"")
+
+ # Join properties with comma and write to buffer
+ buf.write(", ".join(prop_list))
+ buf.write("}")
+ buf.write("}")
+
\ No newline at end of file
diff --git a/packages/ai/src/ai/common/graph/age/_cypher/Cypher.g4 b/packages/ai/src/ai/common/graph/age/_cypher/Cypher.g4
new file mode 100644
index 000000000..549736b5c
--- /dev/null
+++ b/packages/ai/src/ai/common/graph/age/_cypher/Cypher.g4
@@ -0,0 +1,805 @@
+/**
+ * Copyright (c) 2015-2023 "Neo Technology,"
+ * Network Engine for Objects in Lund AB [http://neotechnology.com]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Attribution Notice under the terms of the Apache License 2.0
+ *
+ * This work was created by the collective efforts of the openCypher community.
+ * Without limiting the terms of Section 6, any Derivative Work that is not
+ * approved by the public consensus process of the openCypher Implementers Group
+ * should not be described as “Cypher” (and Cypher® is a registered trademark of
+ * Neo4j Inc.) or as "openCypher". Extensions by implementers or prototypes or
+ * proposals for change that have been documented or implemented should only be
+ * described as "implementation extensions to Cypher" or as "proposed changes to
+ * Cypher that are not yet approved by the openCypher community".
+ */
+grammar Cypher;
+
+oC_Cypher
+ : SP? oC_Statement ( SP? ';' )? SP? EOF ;
+
+oC_Statement
+ : oC_Query ;
+
+oC_Query
+ : oC_RegularQuery
+ | oC_StandaloneCall
+ ;
+
+oC_RegularQuery
+ : oC_SingleQuery ( SP? oC_Union )* ;
+
+oC_Union
+ : ( UNION SP ALL SP? oC_SingleQuery )
+ | ( UNION SP? oC_SingleQuery )
+ ;
+
+UNION : ( 'U' | 'u' ) ( 'N' | 'n' ) ( 'I' | 'i' ) ( 'O' | 'o' ) ( 'N' | 'n' ) ;
+
+ALL : ( 'A' | 'a' ) ( 'L' | 'l' ) ( 'L' | 'l' ) ;
+
+oC_SingleQuery
+ : oC_SinglePartQuery
+ | oC_MultiPartQuery
+ ;
+
+oC_SinglePartQuery
+ : ( ( oC_ReadingClause SP? )* oC_Return )
+ | ( ( oC_ReadingClause SP? )* oC_UpdatingClause ( SP? oC_UpdatingClause )* ( SP? oC_Return )? )
+ ;
+
+oC_MultiPartQuery
+ : ( ( oC_ReadingClause SP? )* ( oC_UpdatingClause SP? )* oC_With SP? )+ oC_SinglePartQuery ;
+
+oC_UpdatingClause
+ : oC_Create
+ | oC_Merge
+ | oC_Delete
+ | oC_Set
+ | oC_Remove
+ ;
+
+oC_ReadingClause
+ : oC_Match
+ | oC_Unwind
+ | oC_InQueryCall
+ ;
+
+oC_Match
+ : ( OPTIONAL SP )? MATCH SP? oC_Pattern ( SP? oC_Where )? ;
+
+OPTIONAL : ( 'O' | 'o' ) ( 'P' | 'p' ) ( 'T' | 't' ) ( 'I' | 'i' ) ( 'O' | 'o' ) ( 'N' | 'n' ) ( 'A' | 'a' ) ( 'L' | 'l' ) ;
+
+MATCH : ( 'M' | 'm' ) ( 'A' | 'a' ) ( 'T' | 't' ) ( 'C' | 'c' ) ( 'H' | 'h' ) ;
+
+oC_Unwind
+ : UNWIND SP? oC_Expression SP AS SP oC_Variable ;
+
+UNWIND : ( 'U' | 'u' ) ( 'N' | 'n' ) ( 'W' | 'w' ) ( 'I' | 'i' ) ( 'N' | 'n' ) ( 'D' | 'd' ) ;
+
+AS : ( 'A' | 'a' ) ( 'S' | 's' ) ;
+
+oC_Merge
+ : MERGE SP? oC_PatternPart ( SP oC_MergeAction )* ;
+
+MERGE : ( 'M' | 'm' ) ( 'E' | 'e' ) ( 'R' | 'r' ) ( 'G' | 'g' ) ( 'E' | 'e' ) ;
+
+oC_MergeAction
+ : ( ON SP MATCH SP oC_Set )
+ | ( ON SP CREATE SP oC_Set )
+ ;
+
+ON : ( 'O' | 'o' ) ( 'N' | 'n' ) ;
+
+CREATE : ( 'C' | 'c' ) ( 'R' | 'r' ) ( 'E' | 'e' ) ( 'A' | 'a' ) ( 'T' | 't' ) ( 'E' | 'e' ) ;
+
+oC_Create
+ : CREATE SP? oC_Pattern ;
+
+oC_Set
+ : SET SP? oC_SetItem ( SP? ',' SP? oC_SetItem )* ;
+
+SET : ( 'S' | 's' ) ( 'E' | 'e' ) ( 'T' | 't' ) ;
+
+oC_SetItem
+ : ( oC_PropertyExpression SP? '=' SP? oC_Expression )
+ | ( oC_Variable SP? '=' SP? oC_Expression )
+ | ( oC_Variable SP? '+=' SP? oC_Expression )
+ | ( oC_Variable SP? oC_NodeLabels )
+ ;
+
+oC_Delete
+ : ( DETACH SP )? DELETE SP? oC_Expression ( SP? ',' SP? oC_Expression )* ;
+
+DETACH : ( 'D' | 'd' ) ( 'E' | 'e' ) ( 'T' | 't' ) ( 'A' | 'a' ) ( 'C' | 'c' ) ( 'H' | 'h' ) ;
+
+DELETE : ( 'D' | 'd' ) ( 'E' | 'e' ) ( 'L' | 'l' ) ( 'E' | 'e' ) ( 'T' | 't' ) ( 'E' | 'e' ) ;
+
+oC_Remove
+ : REMOVE SP oC_RemoveItem ( SP? ',' SP? oC_RemoveItem )* ;
+
+REMOVE : ( 'R' | 'r' ) ( 'E' | 'e' ) ( 'M' | 'm' ) ( 'O' | 'o' ) ( 'V' | 'v' ) ( 'E' | 'e' ) ;
+
+oC_RemoveItem
+ : ( oC_Variable oC_NodeLabels )
+ | oC_PropertyExpression
+ ;
+
+oC_InQueryCall
+ : CALL SP oC_ExplicitProcedureInvocation ( SP? YIELD SP oC_YieldItems )? ;
+
+CALL : ( 'C' | 'c' ) ( 'A' | 'a' ) ( 'L' | 'l' ) ( 'L' | 'l' ) ;
+
+YIELD : ( 'Y' | 'y' ) ( 'I' | 'i' ) ( 'E' | 'e' ) ( 'L' | 'l' ) ( 'D' | 'd' ) ;
+
+oC_StandaloneCall
+ : CALL SP ( oC_ExplicitProcedureInvocation | oC_ImplicitProcedureInvocation ) ( SP? YIELD SP ( '*' | oC_YieldItems ) )? ;
+
+oC_YieldItems
+ : oC_YieldItem ( SP? ',' SP? oC_YieldItem )* ( SP? oC_Where )? ;
+
+oC_YieldItem
+ : ( oC_ProcedureResultField SP AS SP )? oC_Variable ;
+
+oC_With
+ : WITH oC_ProjectionBody ( SP? oC_Where )? ;
+
+WITH : ( 'W' | 'w' ) ( 'I' | 'i' ) ( 'T' | 't' ) ( 'H' | 'h' ) ;
+
+oC_Return
+ : RETURN oC_ProjectionBody ;
+
+RETURN : ( 'R' | 'r' ) ( 'E' | 'e' ) ( 'T' | 't' ) ( 'U' | 'u' ) ( 'R' | 'r' ) ( 'N' | 'n' ) ;
+
+oC_ProjectionBody
+ : ( SP? DISTINCT )? SP oC_ProjectionItems ( SP oC_Order )? ( SP oC_Skip )? ( SP oC_Limit )? ;
+
+DISTINCT : ( 'D' | 'd' ) ( 'I' | 'i' ) ( 'S' | 's' ) ( 'T' | 't' ) ( 'I' | 'i' ) ( 'N' | 'n' ) ( 'C' | 'c' ) ( 'T' | 't' ) ;
+
+oC_ProjectionItems
+ : ( '*' ( SP? ',' SP? oC_ProjectionItem )* )
+ | ( oC_ProjectionItem ( SP? ',' SP? oC_ProjectionItem )* )
+ ;
+
+oC_ProjectionItem
+ : ( oC_Expression SP AS SP oC_Variable )
+ | oC_Expression
+ ;
+
+oC_Order
+ : ORDER SP BY SP oC_SortItem ( ',' SP? oC_SortItem )* ;
+
+ORDER : ( 'O' | 'o' ) ( 'R' | 'r' ) ( 'D' | 'd' ) ( 'E' | 'e' ) ( 'R' | 'r' ) ;
+
+BY : ( 'B' | 'b' ) ( 'Y' | 'y' ) ;
+
+oC_Skip
+ : L_SKIP SP oC_Expression ;
+
+L_SKIP : ( 'S' | 's' ) ( 'K' | 'k' ) ( 'I' | 'i' ) ( 'P' | 'p' ) ;
+
+oC_Limit
+ : LIMIT SP oC_Expression ;
+
+LIMIT : ( 'L' | 'l' ) ( 'I' | 'i' ) ( 'M' | 'm' ) ( 'I' | 'i' ) ( 'T' | 't' ) ;
+
+oC_SortItem
+ : oC_Expression ( SP? ( ASCENDING | ASC | DESCENDING | DESC ) )? ;
+
+ASCENDING : ( 'A' | 'a' ) ( 'S' | 's' ) ( 'C' | 'c' ) ( 'E' | 'e' ) ( 'N' | 'n' ) ( 'D' | 'd' ) ( 'I' | 'i' ) ( 'N' | 'n' ) ( 'G' | 'g' ) ;
+
+ASC : ( 'A' | 'a' ) ( 'S' | 's' ) ( 'C' | 'c' ) ;
+
+DESCENDING : ( 'D' | 'd' ) ( 'E' | 'e' ) ( 'S' | 's' ) ( 'C' | 'c' ) ( 'E' | 'e' ) ( 'N' | 'n' ) ( 'D' | 'd' ) ( 'I' | 'i' ) ( 'N' | 'n' ) ( 'G' | 'g' ) ;
+
+DESC : ( 'D' | 'd' ) ( 'E' | 'e' ) ( 'S' | 's' ) ( 'C' | 'c' ) ;
+
+oC_Where
+ : WHERE SP oC_Expression ;
+
+WHERE : ( 'W' | 'w' ) ( 'H' | 'h' ) ( 'E' | 'e' ) ( 'R' | 'r' ) ( 'E' | 'e' ) ;
+
+oC_Pattern
+ : oC_PatternPart ( SP? ',' SP? oC_PatternPart )* ;
+
+oC_PatternPart
+ : ( oC_Variable SP? '=' SP? oC_AnonymousPatternPart )
+ | oC_AnonymousPatternPart
+ ;
+
+oC_AnonymousPatternPart
+ : oC_PatternElement ;
+
+oC_PatternElement
+ : ( oC_NodePattern ( SP? oC_PatternElementChain )* )
+ | ( '(' oC_PatternElement ')' )
+ ;
+
+oC_RelationshipsPattern
+ : oC_NodePattern ( SP? oC_PatternElementChain )+ ;
+
+oC_NodePattern
+ : '(' SP? ( oC_Variable SP? )? ( oC_NodeLabels SP? )? ( oC_Properties SP? )? ')' ;
+
+oC_PatternElementChain
+ : oC_RelationshipPattern SP? oC_NodePattern ;
+
+oC_RelationshipPattern
+ : ( oC_LeftArrowHead SP? oC_Dash SP? oC_RelationshipDetail? SP? oC_Dash SP? oC_RightArrowHead )
+ | ( oC_LeftArrowHead SP? oC_Dash SP? oC_RelationshipDetail? SP? oC_Dash )
+ | ( oC_Dash SP? oC_RelationshipDetail? SP? oC_Dash SP? oC_RightArrowHead )
+ | ( oC_Dash SP? oC_RelationshipDetail? SP? oC_Dash )
+ ;
+
+oC_RelationshipDetail
+ : '[' SP? ( oC_Variable SP? )? ( oC_RelationshipTypes SP? )? oC_RangeLiteral? ( oC_Properties SP? )? ']' ;
+
+oC_Properties
+ : oC_MapLiteral
+ | oC_Parameter
+ ;
+
+oC_RelationshipTypes
+ : ':' SP? oC_RelTypeName ( SP? '|' ':'? SP? oC_RelTypeName )* ;
+
+oC_NodeLabels
+ : oC_NodeLabel ( SP? oC_NodeLabel )* ;
+
+oC_NodeLabel
+ : ':' SP? oC_LabelName ;
+
+oC_RangeLiteral
+ : '*' SP? ( oC_IntegerLiteral SP? )? ( '..' SP? ( oC_IntegerLiteral SP? )? )? ;
+
+oC_LabelName
+ : oC_SchemaName ;
+
+oC_RelTypeName
+ : oC_SchemaName ;
+
+oC_PropertyExpression
+ : oC_Atom ( SP? oC_PropertyLookup )+ ;
+
+oC_Expression
+ : oC_OrExpression ;
+
+oC_OrExpression
+ : oC_XorExpression ( SP OR SP oC_XorExpression )* ;
+
+OR : ( 'O' | 'o' ) ( 'R' | 'r' ) ;
+
+oC_XorExpression
+ : oC_AndExpression ( SP XOR SP oC_AndExpression )* ;
+
+XOR : ( 'X' | 'x' ) ( 'O' | 'o' ) ( 'R' | 'r' ) ;
+
+oC_AndExpression
+ : oC_NotExpression ( SP AND SP oC_NotExpression )* ;
+
+AND : ( 'A' | 'a' ) ( 'N' | 'n' ) ( 'D' | 'd' ) ;
+
+oC_NotExpression
+ : ( NOT SP? )* oC_ComparisonExpression ;
+
+NOT : ( 'N' | 'n' ) ( 'O' | 'o' ) ( 'T' | 't' ) ;
+
+oC_ComparisonExpression
+ : oC_StringListNullPredicateExpression ( SP? oC_PartialComparisonExpression )* ;
+
+oC_PartialComparisonExpression
+ : ( '=' SP? oC_StringListNullPredicateExpression )
+ | ( '<>' SP? oC_StringListNullPredicateExpression )
+ | ( '<' SP? oC_StringListNullPredicateExpression )
+ | ( '>' SP? oC_StringListNullPredicateExpression )
+ | ( '<=' SP? oC_StringListNullPredicateExpression )
+ | ( '>=' SP? oC_StringListNullPredicateExpression )
+ ;
+
+oC_StringListNullPredicateExpression
+ : oC_AddOrSubtractExpression ( oC_StringPredicateExpression | oC_ListPredicateExpression | oC_NullPredicateExpression )* ;
+
+oC_StringPredicateExpression
+ : ( ( SP STARTS SP WITH ) | ( SP ENDS SP WITH ) | ( SP CONTAINS ) ) SP? oC_AddOrSubtractExpression ;
+
+STARTS : ( 'S' | 's' ) ( 'T' | 't' ) ( 'A' | 'a' ) ( 'R' | 'r' ) ( 'T' | 't' ) ( 'S' | 's' ) ;
+
+ENDS : ( 'E' | 'e' ) ( 'N' | 'n' ) ( 'D' | 'd' ) ( 'S' | 's' ) ;
+
+CONTAINS : ( 'C' | 'c' ) ( 'O' | 'o' ) ( 'N' | 'n' ) ( 'T' | 't' ) ( 'A' | 'a' ) ( 'I' | 'i' ) ( 'N' | 'n' ) ( 'S' | 's' ) ;
+
+oC_ListPredicateExpression
+ : SP IN SP? oC_AddOrSubtractExpression ;
+
+IN : ( 'I' | 'i' ) ( 'N' | 'n' ) ;
+
+oC_NullPredicateExpression
+ : ( SP IS SP NULL )
+ | ( SP IS SP NOT SP NULL )
+ ;
+
+IS : ( 'I' | 'i' ) ( 'S' | 's' ) ;
+
+NULL : ( 'N' | 'n' ) ( 'U' | 'u' ) ( 'L' | 'l' ) ( 'L' | 'l' ) ;
+
+oC_AddOrSubtractExpression
+ : oC_MultiplyDivideModuloExpression ( ( SP? '+' SP? oC_MultiplyDivideModuloExpression ) | ( SP? '-' SP? oC_MultiplyDivideModuloExpression ) )* ;
+
+oC_MultiplyDivideModuloExpression
+ : oC_PowerOfExpression ( ( SP? '*' SP? oC_PowerOfExpression ) | ( SP? '/' SP? oC_PowerOfExpression ) | ( SP? '%' SP? oC_PowerOfExpression ) )* ;
+
+oC_PowerOfExpression
+ : oC_UnaryAddOrSubtractExpression ( SP? '^' SP? oC_UnaryAddOrSubtractExpression )* ;
+
+oC_UnaryAddOrSubtractExpression
+ : oC_NonArithmeticOperatorExpression
+ | ( ( '+' | '-' ) SP? oC_NonArithmeticOperatorExpression )
+ ;
+
+oC_NonArithmeticOperatorExpression
+ : oC_Atom ( ( SP? oC_ListOperatorExpression ) | ( SP? oC_PropertyLookup ) )* ( SP? oC_NodeLabels )? ;
+
+oC_ListOperatorExpression
+ : ( '[' oC_Expression ']' )
+ | ( '[' oC_Expression? '..' oC_Expression? ']' )
+ ;
+
+oC_PropertyLookup
+ : '.' SP? ( oC_PropertyKeyName ) ;
+
+oC_Atom
+ : oC_Literal
+ | oC_Parameter
+ | oC_CaseExpression
+ | ( COUNT SP? '(' SP? '*' SP? ')' )
+ | oC_ListComprehension
+ | oC_PatternComprehension
+ | oC_Quantifier
+ | oC_PatternPredicate
+ | oC_ParenthesizedExpression
+ | oC_FunctionInvocation
+ | oC_ExistentialSubquery
+ | oC_Variable
+ ;
+
+COUNT : ( 'C' | 'c' ) ( 'O' | 'o' ) ( 'U' | 'u' ) ( 'N' | 'n' ) ( 'T' | 't' ) ;
+
+oC_CaseExpression
+ : ( ( CASE ( SP? oC_CaseAlternative )+ ) | ( CASE SP? oC_Expression ( SP? oC_CaseAlternative )+ ) ) ( SP? ELSE SP? oC_Expression )? SP? END ;
+
+CASE : ( 'C' | 'c' ) ( 'A' | 'a' ) ( 'S' | 's' ) ( 'E' | 'e' ) ;
+
+ELSE : ( 'E' | 'e' ) ( 'L' | 'l' ) ( 'S' | 's' ) ( 'E' | 'e' ) ;
+
+END : ( 'E' | 'e' ) ( 'N' | 'n' ) ( 'D' | 'd' ) ;
+
+oC_CaseAlternative
+ : WHEN SP? oC_Expression SP? THEN SP? oC_Expression ;
+
+WHEN : ( 'W' | 'w' ) ( 'H' | 'h' ) ( 'E' | 'e' ) ( 'N' | 'n' ) ;
+
+THEN : ( 'T' | 't' ) ( 'H' | 'h' ) ( 'E' | 'e' ) ( 'N' | 'n' ) ;
+
+oC_ListComprehension
+ : '[' SP? oC_FilterExpression ( SP? '|' SP? oC_Expression )? SP? ']' ;
+
+oC_PatternComprehension
+ : '[' SP? ( oC_Variable SP? '=' SP? )? oC_RelationshipsPattern SP? ( oC_Where SP? )? '|' SP? oC_Expression SP? ']' ;
+
+oC_Quantifier
+ : ( ALL SP? '(' SP? oC_FilterExpression SP? ')' )
+ | ( ANY SP? '(' SP? oC_FilterExpression SP? ')' )
+ | ( NONE SP? '(' SP? oC_FilterExpression SP? ')' )
+ | ( SINGLE SP? '(' SP? oC_FilterExpression SP? ')' )
+ ;
+
+ANY : ( 'A' | 'a' ) ( 'N' | 'n' ) ( 'Y' | 'y' ) ;
+
+NONE : ( 'N' | 'n' ) ( 'O' | 'o' ) ( 'N' | 'n' ) ( 'E' | 'e' ) ;
+
+SINGLE : ( 'S' | 's' ) ( 'I' | 'i' ) ( 'N' | 'n' ) ( 'G' | 'g' ) ( 'L' | 'l' ) ( 'E' | 'e' ) ;
+
+oC_FilterExpression
+ : oC_IdInColl ( SP? oC_Where )? ;
+
+oC_PatternPredicate
+ : oC_RelationshipsPattern ;
+
+oC_ParenthesizedExpression
+ : '(' SP? oC_Expression SP? ')' ;
+
+oC_IdInColl
+ : oC_Variable SP IN SP oC_Expression ;
+
+oC_FunctionInvocation
+ : oC_FunctionName SP? '(' SP? ( DISTINCT SP? )? ( oC_Expression SP? ( ',' SP? oC_Expression SP? )* )? ')' ;
+
+oC_FunctionName
+ : oC_Namespace oC_SymbolicName ;
+
+oC_ExistentialSubquery
+ : EXISTS SP? '{' SP? ( oC_RegularQuery | ( oC_Pattern ( SP? oC_Where )? ) ) SP? '}' ;
+
+EXISTS : ( 'E' | 'e' ) ( 'X' | 'x' ) ( 'I' | 'i' ) ( 'S' | 's' ) ( 'T' | 't' ) ( 'S' | 's' ) ;
+
+oC_ExplicitProcedureInvocation
+ : oC_ProcedureName SP? '(' SP? ( oC_Expression SP? ( ',' SP? oC_Expression SP? )* )? ')' ;
+
+oC_ImplicitProcedureInvocation
+ : oC_ProcedureName ;
+
+oC_ProcedureResultField
+ : oC_SymbolicName ;
+
+oC_ProcedureName
+ : oC_Namespace oC_SymbolicName ;
+
+oC_Namespace
+ : ( oC_SymbolicName '.' )* ;
+
+oC_Variable
+ : oC_SymbolicName ;
+
+oC_Literal
+ : oC_BooleanLiteral
+ | NULL
+ | oC_NumberLiteral
+ | StringLiteral
+ | oC_ListLiteral
+ | oC_MapLiteral
+ ;
+
+oC_BooleanLiteral
+ : TRUE
+ | FALSE
+ ;
+
+TRUE : ( 'T' | 't' ) ( 'R' | 'r' ) ( 'U' | 'u' ) ( 'E' | 'e' ) ;
+
+FALSE : ( 'F' | 'f' ) ( 'A' | 'a' ) ( 'L' | 'l' ) ( 'S' | 's' ) ( 'E' | 'e' ) ;
+
+oC_NumberLiteral
+ : oC_DoubleLiteral
+ | oC_IntegerLiteral
+ ;
+
+oC_IntegerLiteral
+ : HexInteger
+ | OctalInteger
+ | DecimalInteger
+ ;
+
+HexInteger
+ : '0x' ( HexDigit )+ ;
+
+DecimalInteger
+ : ZeroDigit
+ | ( NonZeroDigit ( Digit )* )
+ ;
+
+OctalInteger
+ : '0o' ( OctDigit )+ ;
+
+HexLetter
+ : ( ( 'A' | 'a' ) )
+ | ( ( 'B' | 'b' ) )
+ | ( ( 'C' | 'c' ) )
+ | ( ( 'D' | 'd' ) )
+ | ( ( 'E' | 'e' ) )
+ | ( ( 'F' | 'f' ) )
+ ;
+
+HexDigit
+ : Digit
+ | HexLetter
+ ;
+
+Digit
+ : ZeroDigit
+ | NonZeroDigit
+ ;
+
+NonZeroDigit
+ : NonZeroOctDigit
+ | '8'
+ | '9'
+ ;
+
+NonZeroOctDigit
+ : '1'
+ | '2'
+ | '3'
+ | '4'
+ | '5'
+ | '6'
+ | '7'
+ ;
+
+OctDigit
+ : ZeroDigit
+ | NonZeroOctDigit
+ ;
+
+ZeroDigit
+ : '0' ;
+
+oC_DoubleLiteral
+ : ExponentDecimalReal
+ | RegularDecimalReal
+ ;
+
+ExponentDecimalReal
+ : ( ( Digit )+ | ( ( Digit )+ '.' ( Digit )+ ) | ( '.' ( Digit )+ ) ) ( ( 'E' | 'e' ) ) '-'? ( Digit )+ ;
+
+RegularDecimalReal
+ : ( Digit )* '.' ( Digit )+ ;
+
+StringLiteral
+ : ( '"' ( StringLiteral_0 | EscapedChar )* '"' )
+ | ( '\'' ( StringLiteral_1 | EscapedChar )* '\'' )
+ ;
+
+EscapedChar
+ : '\\' ( '\\' | '\'' | '"' | ( ( 'B' | 'b' ) ) | ( ( 'F' | 'f' ) ) | ( ( 'N' | 'n' ) ) | ( ( 'R' | 'r' ) ) | ( ( 'T' | 't' ) ) | ( ( ( 'U' | 'u' ) ) ( HexDigit HexDigit HexDigit HexDigit ) ) | ( ( ( 'U' | 'u' ) ) ( HexDigit HexDigit HexDigit HexDigit HexDigit HexDigit HexDigit HexDigit ) ) ) ;
+
+oC_ListLiteral
+ : '[' SP? ( oC_Expression SP? ( ',' SP? oC_Expression SP? )* )? ']' ;
+
+oC_MapLiteral
+ : '{' SP? ( oC_PropertyKeyName SP? ':' SP? oC_Expression SP? ( ',' SP? oC_PropertyKeyName SP? ':' SP? oC_Expression SP? )* )? '}' ;
+
+oC_PropertyKeyName
+ : oC_SchemaName ;
+
+oC_Parameter
+ : '$' ( oC_SymbolicName | DecimalInteger ) ;
+
+oC_SchemaName
+ : oC_SymbolicName
+ | oC_ReservedWord
+ ;
+
+oC_ReservedWord
+ : ALL
+ | ASC
+ | ASCENDING
+ | BY
+ | CREATE
+ | DELETE
+ | DESC
+ | DESCENDING
+ | DETACH
+ | EXISTS
+ | LIMIT
+ | MATCH
+ | MERGE
+ | ON
+ | OPTIONAL
+ | ORDER
+ | REMOVE
+ | RETURN
+ | SET
+ | L_SKIP
+ | WHERE
+ | WITH
+ | UNION
+ | UNWIND
+ | AND
+ | AS
+ | CONTAINS
+ | DISTINCT
+ | ENDS
+ | IN
+ | IS
+ | NOT
+ | OR
+ | STARTS
+ | XOR
+ | FALSE
+ | TRUE
+ | NULL
+ | CONSTRAINT
+ | DO
+ | FOR
+ | REQUIRE
+ | UNIQUE
+ | CASE
+ | WHEN
+ | THEN
+ | ELSE
+ | END
+ | MANDATORY
+ | SCALAR
+ | OF
+ | ADD
+ | DROP
+ ;
+
+CONSTRAINT : ( 'C' | 'c' ) ( 'O' | 'o' ) ( 'N' | 'n' ) ( 'S' | 's' ) ( 'T' | 't' ) ( 'R' | 'r' ) ( 'A' | 'a' ) ( 'I' | 'i' ) ( 'N' | 'n' ) ( 'T' | 't' ) ;
+
+DO : ( 'D' | 'd' ) ( 'O' | 'o' ) ;
+
+FOR : ( 'F' | 'f' ) ( 'O' | 'o' ) ( 'R' | 'r' ) ;
+
+REQUIRE : ( 'R' | 'r' ) ( 'E' | 'e' ) ( 'Q' | 'q' ) ( 'U' | 'u' ) ( 'I' | 'i' ) ( 'R' | 'r' ) ( 'E' | 'e' ) ;
+
+UNIQUE : ( 'U' | 'u' ) ( 'N' | 'n' ) ( 'I' | 'i' ) ( 'Q' | 'q' ) ( 'U' | 'u' ) ( 'E' | 'e' ) ;
+
+MANDATORY : ( 'M' | 'm' ) ( 'A' | 'a' ) ( 'N' | 'n' ) ( 'D' | 'd' ) ( 'A' | 'a' ) ( 'T' | 't' ) ( 'O' | 'o' ) ( 'R' | 'r' ) ( 'Y' | 'y' ) ;
+
+SCALAR : ( 'S' | 's' ) ( 'C' | 'c' ) ( 'A' | 'a' ) ( 'L' | 'l' ) ( 'A' | 'a' ) ( 'R' | 'r' ) ;
+
+OF : ( 'O' | 'o' ) ( 'F' | 'f' ) ;
+
+ADD : ( 'A' | 'a' ) ( 'D' | 'd' ) ( 'D' | 'd' ) ;
+
+DROP : ( 'D' | 'd' ) ( 'R' | 'r' ) ( 'O' | 'o' ) ( 'P' | 'p' ) ;
+
+oC_SymbolicName
+ : UnescapedSymbolicName
+ | EscapedSymbolicName
+ | HexLetter
+ | COUNT
+ | FILTER
+ | EXTRACT
+ | ANY
+ | NONE
+ | SINGLE
+ ;
+
+FILTER : ( 'F' | 'f' ) ( 'I' | 'i' ) ( 'L' | 'l' ) ( 'T' | 't' ) ( 'E' | 'e' ) ( 'R' | 'r' ) ;
+
+EXTRACT : ( 'E' | 'e' ) ( 'X' | 'x' ) ( 'T' | 't' ) ( 'R' | 'r' ) ( 'A' | 'a' ) ( 'C' | 'c' ) ( 'T' | 't' ) ;
+
+UnescapedSymbolicName
+ : IdentifierStart ( IdentifierPart )* ;
+
+/**
+ * Based on the unicode identifier and pattern syntax
+ * (http://www.unicode.org/reports/tr31/)
+ * And extended with a few characters.
+ */
+IdentifierStart
+ : ID_Start
+ | Pc
+ ;
+
+/**
+ * Based on the unicode identifier and pattern syntax
+ * (http://www.unicode.org/reports/tr31/)
+ * And extended with a few characters.
+ */
+IdentifierPart
+ : ID_Continue
+ | Sc
+ ;
+
+/**
+ * Any character except "`", enclosed within `backticks`. Backticks are escaped with double backticks.
+ */
+EscapedSymbolicName
+ : ( '`' ( EscapedSymbolicName_0 )* '`' )+ ;
+
+SP
+ : ( WHITESPACE )+ ;
+
+WHITESPACE
+ : SPACE
+ | TAB
+ | LF
+ | VT
+ | FF
+ | CR
+ | FS
+ | GS
+ | RS
+ | US
+ | '\u1680'
+ | '\u180e'
+ | '\u2000'
+ | '\u2001'
+ | '\u2002'
+ | '\u2003'
+ | '\u2004'
+ | '\u2005'
+ | '\u2006'
+ | '\u2008'
+ | '\u2009'
+ | '\u200a'
+ | '\u2028'
+ | '\u2029'
+ | '\u205f'
+ | '\u3000'
+ | '\u00a0'
+ | '\u2007'
+ | '\u202f'
+ | Comment
+ ;
+
+Comment
+ : ( '/*' ( Comment_1 | ( '*' Comment_2 ) )* '*/' )
+ | ( '//' ( Comment_3 )* CR? ( LF | EOF ) )
+ ;
+
+oC_LeftArrowHead
+ : '<'
+ | '\u27e8'
+ | '\u3008'
+ | '\ufe64'
+ | '\uff1c'
+ ;
+
+oC_RightArrowHead
+ : '>'
+ | '\u27e9'
+ | '\u3009'
+ | '\ufe65'
+ | '\uff1e'
+ ;
+
+oC_Dash
+ : '-'
+ | '\u00ad'
+ | '\u2010'
+ | '\u2011'
+ | '\u2012'
+ | '\u2013'
+ | '\u2014'
+ | '\u2015'
+ | '\u2212'
+ | '\ufe58'
+ | '\ufe63'
+ | '\uff0d'
+ ;
+
+fragment FF : [\f] ;
+
+fragment EscapedSymbolicName_0 : ~[`] ;
+
+fragment RS : [\u001E] ;
+
+fragment ID_Continue : [\p{ID_Continue}] ;
+
+fragment Comment_1 : ~[*] ;
+
+fragment StringLiteral_1 : ~['\\] ;
+
+fragment Comment_3 : ~[\n\r] ;
+
+fragment Comment_2 : ~[/] ;
+
+fragment GS : [\u001D] ;
+
+fragment FS : [\u001C] ;
+
+fragment CR : [\r] ;
+
+fragment Sc : [\p{Sc}] ;
+
+fragment SPACE : [ ] ;
+
+fragment Pc : [\p{Pc}] ;
+
+fragment TAB : [\t] ;
+
+fragment StringLiteral_0 : ~["\\] ;
+
+fragment LF : [\n] ;
+
+fragment VT : [\u000B] ;
+
+fragment US : [\u001F] ;
+
+fragment ID_Start : [\p{ID_Start}] ;
+
diff --git a/packages/ai/src/ai/common/graph/age/_cypher/__init__.py b/packages/ai/src/ai/common/graph/age/_cypher/__init__.py
new file mode 100644
index 000000000..afab632a7
--- /dev/null
+++ b/packages/ai/src/ai/common/graph/age/_cypher/__init__.py
@@ -0,0 +1,6 @@
+# =============================================================================
+# VENDORED — openCypher grammar M23 (Apache-2.0, (c) Neo Technology 2015-2023).
+# Source grammar: https://s3.amazonaws.com/artifacts.opencypher.org/M23/Cypher.g4
+# gen/: generated with ANTLR 4.13.2 (-Dlanguage=Python3 -visitor). Committed so
+# developers never need Java; see ../README.md for the regeneration step.
+# =============================================================================
diff --git a/packages/ai/src/ai/common/graph/age/_cypher/gen/CypherLexer.py b/packages/ai/src/ai/common/graph/age/_cypher/gen/CypherLexer.py
new file mode 100644
index 000000000..9f9170af2
--- /dev/null
+++ b/packages/ai/src/ai/common/graph/age/_cypher/gen/CypherLexer.py
@@ -0,0 +1,832 @@
+# Generated from Cypher.g4 by ANTLR 4.13.2
+from antlr4 import *
+from io import StringIO
+import sys
+if sys.version_info[1] > 5:
+ from typing import TextIO
+else:
+ from typing.io import TextIO
+
+
+def serializedATN():
+ return [
+ 4,0,127,993,6,-1,2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,
+ 2,6,7,6,2,7,7,7,2,8,7,8,2,9,7,9,2,10,7,10,2,11,7,11,2,12,7,12,2,
+ 13,7,13,2,14,7,14,2,15,7,15,2,16,7,16,2,17,7,17,2,18,7,18,2,19,7,
+ 19,2,20,7,20,2,21,7,21,2,22,7,22,2,23,7,23,2,24,7,24,2,25,7,25,2,
+ 26,7,26,2,27,7,27,2,28,7,28,2,29,7,29,2,30,7,30,2,31,7,31,2,32,7,
+ 32,2,33,7,33,2,34,7,34,2,35,7,35,2,36,7,36,2,37,7,37,2,38,7,38,2,
+ 39,7,39,2,40,7,40,2,41,7,41,2,42,7,42,2,43,7,43,2,44,7,44,2,45,7,
+ 45,2,46,7,46,2,47,7,47,2,48,7,48,2,49,7,49,2,50,7,50,2,51,7,51,2,
+ 52,7,52,2,53,7,53,2,54,7,54,2,55,7,55,2,56,7,56,2,57,7,57,2,58,7,
+ 58,2,59,7,59,2,60,7,60,2,61,7,61,2,62,7,62,2,63,7,63,2,64,7,64,2,
+ 65,7,65,2,66,7,66,2,67,7,67,2,68,7,68,2,69,7,69,2,70,7,70,2,71,7,
+ 71,2,72,7,72,2,73,7,73,2,74,7,74,2,75,7,75,2,76,7,76,2,77,7,77,2,
+ 78,7,78,2,79,7,79,2,80,7,80,2,81,7,81,2,82,7,82,2,83,7,83,2,84,7,
+ 84,2,85,7,85,2,86,7,86,2,87,7,87,2,88,7,88,2,89,7,89,2,90,7,90,2,
+ 91,7,91,2,92,7,92,2,93,7,93,2,94,7,94,2,95,7,95,2,96,7,96,2,97,7,
+ 97,2,98,7,98,2,99,7,99,2,100,7,100,2,101,7,101,2,102,7,102,2,103,
+ 7,103,2,104,7,104,2,105,7,105,2,106,7,106,2,107,7,107,2,108,7,108,
+ 2,109,7,109,2,110,7,110,2,111,7,111,2,112,7,112,2,113,7,113,2,114,
+ 7,114,2,115,7,115,2,116,7,116,2,117,7,117,2,118,7,118,2,119,7,119,
+ 2,120,7,120,2,121,7,121,2,122,7,122,2,123,7,123,2,124,7,124,2,125,
+ 7,125,2,126,7,126,2,127,7,127,2,128,7,128,2,129,7,129,2,130,7,130,
+ 2,131,7,131,2,132,7,132,2,133,7,133,2,134,7,134,2,135,7,135,2,136,
+ 7,136,2,137,7,137,2,138,7,138,2,139,7,139,2,140,7,140,2,141,7,141,
+ 2,142,7,142,2,143,7,143,2,144,7,144,2,145,7,145,2,146,7,146,1,0,
+ 1,0,1,1,1,1,1,2,1,2,1,3,1,3,1,3,1,4,1,4,1,5,1,5,1,6,1,6,1,7,1,7,
+ 1,8,1,8,1,9,1,9,1,10,1,10,1,11,1,11,1,11,1,12,1,12,1,12,1,13,1,13,
+ 1,14,1,14,1,15,1,15,1,15,1,16,1,16,1,16,1,17,1,17,1,18,1,18,1,19,
+ 1,19,1,20,1,20,1,21,1,21,1,22,1,22,1,23,1,23,1,24,1,24,1,25,1,25,
+ 1,26,1,26,1,27,1,27,1,28,1,28,1,29,1,29,1,30,1,30,1,31,1,31,1,32,
+ 1,32,1,33,1,33,1,34,1,34,1,35,1,35,1,36,1,36,1,37,1,37,1,38,1,38,
+ 1,39,1,39,1,40,1,40,1,41,1,41,1,42,1,42,1,43,1,43,1,44,1,44,1,45,
+ 1,45,1,45,1,45,1,45,1,45,1,46,1,46,1,46,1,46,1,47,1,47,1,47,1,47,
+ 1,47,1,47,1,47,1,47,1,47,1,48,1,48,1,48,1,48,1,48,1,48,1,49,1,49,
+ 1,49,1,49,1,49,1,49,1,49,1,50,1,50,1,50,1,51,1,51,1,51,1,51,1,51,
+ 1,51,1,52,1,52,1,52,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,54,1,54,
+ 1,54,1,54,1,55,1,55,1,55,1,55,1,55,1,55,1,55,1,56,1,56,1,56,1,56,
+ 1,56,1,56,1,56,1,57,1,57,1,57,1,57,1,57,1,57,1,57,1,58,1,58,1,58,
+ 1,58,1,58,1,59,1,59,1,59,1,59,1,59,1,59,1,60,1,60,1,60,1,60,1,60,
+ 1,61,1,61,1,61,1,61,1,61,1,61,1,61,1,62,1,62,1,62,1,62,1,62,1,62,
+ 1,62,1,62,1,62,1,63,1,63,1,63,1,63,1,63,1,63,1,64,1,64,1,64,1,65,
+ 1,65,1,65,1,65,1,65,1,66,1,66,1,66,1,66,1,66,1,66,1,67,1,67,1,67,
+ 1,67,1,67,1,67,1,67,1,67,1,67,1,67,1,68,1,68,1,68,1,68,1,69,1,69,
+ 1,69,1,69,1,69,1,69,1,69,1,69,1,69,1,69,1,69,1,70,1,70,1,70,1,70,
+ 1,70,1,71,1,71,1,71,1,71,1,71,1,71,1,72,1,72,1,72,1,73,1,73,1,73,
+ 1,73,1,74,1,74,1,74,1,74,1,75,1,75,1,75,1,75,1,76,1,76,1,76,1,76,
+ 1,76,1,76,1,76,1,77,1,77,1,77,1,77,1,77,1,78,1,78,1,78,1,78,1,78,
+ 1,78,1,78,1,78,1,78,1,79,1,79,1,79,1,80,1,80,1,80,1,81,1,81,1,81,
+ 1,81,1,81,1,82,1,82,1,82,1,82,1,82,1,82,1,83,1,83,1,83,1,83,1,83,
+ 1,84,1,84,1,84,1,84,1,84,1,85,1,85,1,85,1,85,1,86,1,86,1,86,1,86,
+ 1,86,1,87,1,87,1,87,1,87,1,87,1,88,1,88,1,88,1,88,1,89,1,89,1,89,
+ 1,89,1,89,1,90,1,90,1,90,1,90,1,90,1,90,1,90,1,91,1,91,1,91,1,91,
+ 1,91,1,91,1,91,1,92,1,92,1,92,1,92,1,92,1,93,1,93,1,93,1,93,1,93,
+ 1,93,1,94,1,94,1,94,1,94,4,94,670,8,94,11,94,12,94,671,1,95,1,95,
+ 1,95,5,95,677,8,95,10,95,12,95,680,9,95,3,95,682,8,95,1,96,1,96,
+ 1,96,1,96,4,96,688,8,96,11,96,12,96,689,1,97,3,97,693,8,97,1,98,
+ 1,98,3,98,697,8,98,1,99,1,99,3,99,701,8,99,1,100,1,100,3,100,705,
+ 8,100,1,101,1,101,1,102,1,102,3,102,711,8,102,1,103,1,103,1,104,
+ 4,104,716,8,104,11,104,12,104,717,1,104,4,104,721,8,104,11,104,12,
+ 104,722,1,104,1,104,4,104,727,8,104,11,104,12,104,728,1,104,1,104,
+ 4,104,733,8,104,11,104,12,104,734,3,104,737,8,104,1,104,1,104,3,
+ 104,741,8,104,1,104,4,104,744,8,104,11,104,12,104,745,1,105,5,105,
+ 749,8,105,10,105,12,105,752,9,105,1,105,1,105,4,105,756,8,105,11,
+ 105,12,105,757,1,106,1,106,1,106,5,106,763,8,106,10,106,12,106,766,
+ 9,106,1,106,1,106,1,106,1,106,5,106,772,8,106,10,106,12,106,775,
+ 9,106,1,106,3,106,778,8,106,1,107,1,107,1,107,1,107,1,107,1,107,
+ 1,107,1,107,1,107,1,107,1,107,1,107,1,107,1,107,1,107,1,107,1,107,
+ 1,107,3,107,798,8,107,1,108,1,108,1,108,1,108,1,108,1,108,1,108,
+ 1,108,1,108,1,108,1,108,1,109,1,109,1,109,1,110,1,110,1,110,1,110,
+ 1,111,1,111,1,111,1,111,1,111,1,111,1,111,1,111,1,112,1,112,1,112,
+ 1,112,1,112,1,112,1,112,1,113,1,113,1,113,1,113,1,113,1,113,1,113,
+ 1,113,1,113,1,113,1,114,1,114,1,114,1,114,1,114,1,114,1,114,1,115,
+ 1,115,1,115,1,116,1,116,1,116,1,116,1,117,1,117,1,117,1,117,1,117,
+ 1,118,1,118,1,118,1,118,1,118,1,118,1,118,1,119,1,119,1,119,1,119,
+ 1,119,1,119,1,119,1,119,1,120,1,120,5,120,879,8,120,10,120,12,120,
+ 882,9,120,1,121,1,121,3,121,886,8,121,1,122,1,122,3,122,890,8,122,
+ 1,123,1,123,5,123,894,8,123,10,123,12,123,897,9,123,1,123,4,123,
+ 900,8,123,11,123,12,123,901,1,124,4,124,905,8,124,11,124,12,124,
+ 906,1,125,1,125,1,125,1,125,1,125,1,125,1,125,1,125,1,125,1,125,
+ 1,125,1,125,3,125,921,8,125,1,126,1,126,1,126,1,126,1,126,1,126,
+ 5,126,929,8,126,10,126,12,126,932,9,126,1,126,1,126,1,126,1,126,
+ 1,126,1,126,5,126,940,8,126,10,126,12,126,943,9,126,1,126,3,126,
+ 946,8,126,1,126,1,126,3,126,950,8,126,3,126,952,8,126,1,127,1,127,
+ 1,128,1,128,1,129,1,129,1,130,1,130,1,131,1,131,1,132,1,132,1,133,
+ 1,133,1,134,1,134,1,135,1,135,1,136,1,136,1,137,1,137,1,138,1,138,
+ 1,139,1,139,1,140,1,140,1,141,1,141,1,142,1,142,1,143,1,143,1,144,
+ 1,144,1,145,1,145,1,146,1,146,0,0,147,1,1,3,2,5,3,7,4,9,5,11,6,13,
+ 7,15,8,17,9,19,10,21,11,23,12,25,13,27,14,29,15,31,16,33,17,35,18,
+ 37,19,39,20,41,21,43,22,45,23,47,24,49,25,51,26,53,27,55,28,57,29,
+ 59,30,61,31,63,32,65,33,67,34,69,35,71,36,73,37,75,38,77,39,79,40,
+ 81,41,83,42,85,43,87,44,89,45,91,46,93,47,95,48,97,49,99,50,101,
+ 51,103,52,105,53,107,54,109,55,111,56,113,57,115,58,117,59,119,60,
+ 121,61,123,62,125,63,127,64,129,65,131,66,133,67,135,68,137,69,139,
+ 70,141,71,143,72,145,73,147,74,149,75,151,76,153,77,155,78,157,79,
+ 159,80,161,81,163,82,165,83,167,84,169,85,171,86,173,87,175,88,177,
+ 89,179,90,181,91,183,92,185,93,187,94,189,95,191,96,193,97,195,98,
+ 197,99,199,100,201,101,203,102,205,103,207,104,209,105,211,106,213,
+ 107,215,108,217,109,219,110,221,111,223,112,225,113,227,114,229,
+ 115,231,116,233,117,235,118,237,119,239,120,241,121,243,122,245,
+ 123,247,124,249,125,251,126,253,127,255,0,257,0,259,0,261,0,263,
+ 0,265,0,267,0,269,0,271,0,273,0,275,0,277,0,279,0,281,0,283,0,285,
+ 0,287,0,289,0,291,0,293,0,1,0,47,2,0,85,85,117,117,2,0,78,78,110,
+ 110,2,0,73,73,105,105,2,0,79,79,111,111,2,0,65,65,97,97,2,0,76,76,
+ 108,108,2,0,80,80,112,112,2,0,84,84,116,116,2,0,77,77,109,109,2,
+ 0,67,67,99,99,2,0,72,72,104,104,2,0,87,87,119,119,2,0,68,68,100,
+ 100,2,0,83,83,115,115,2,0,69,69,101,101,2,0,82,82,114,114,2,0,71,
+ 71,103,103,2,0,86,86,118,118,2,0,89,89,121,121,2,0,66,66,98,98,2,
+ 0,75,75,107,107,2,0,88,88,120,120,2,0,70,70,102,102,2,0,65,70,97,
+ 102,13,0,34,34,39,39,66,66,70,70,78,78,82,82,84,84,92,92,98,98,102,
+ 102,110,110,114,114,116,116,2,0,81,81,113,113,8,0,160,160,5760,5760,
+ 6158,6158,8192,8202,8232,8233,8239,8239,8287,8287,12288,12288,1,
+ 0,12,12,1,0,96,96,1,0,30,30,768,0,48,57,65,90,95,95,97,122,170,170,
+ 181,181,183,183,186,186,192,214,216,246,248,705,710,721,736,740,
+ 748,748,750,750,768,884,886,887,890,893,895,895,902,906,908,908,
+ 910,929,931,1013,1015,1153,1155,1159,1162,1327,1329,1366,1369,1369,
+ 1376,1416,1425,1469,1471,1471,1473,1474,1476,1477,1479,1479,1488,
+ 1514,1519,1522,1552,1562,1568,1641,1646,1747,1749,1756,1759,1768,
+ 1770,1788,1791,1791,1808,1866,1869,1969,1984,2037,2042,2042,2045,
+ 2045,2048,2093,2112,2139,2144,2154,2160,2183,2185,2190,2200,2273,
+ 2275,2403,2406,2415,2417,2435,2437,2444,2447,2448,2451,2472,2474,
+ 2480,2482,2482,2486,2489,2492,2500,2503,2504,2507,2510,2519,2519,
+ 2524,2525,2527,2531,2534,2545,2556,2556,2558,2558,2561,2563,2565,
+ 2570,2575,2576,2579,2600,2602,2608,2610,2611,2613,2614,2616,2617,
+ 2620,2620,2622,2626,2631,2632,2635,2637,2641,2641,2649,2652,2654,
+ 2654,2662,2677,2689,2691,2693,2701,2703,2705,2707,2728,2730,2736,
+ 2738,2739,2741,2745,2748,2757,2759,2761,2763,2765,2768,2768,2784,
+ 2787,2790,2799,2809,2815,2817,2819,2821,2828,2831,2832,2835,2856,
+ 2858,2864,2866,2867,2869,2873,2876,2884,2887,2888,2891,2893,2901,
+ 2903,2908,2909,2911,2915,2918,2927,2929,2929,2946,2947,2949,2954,
+ 2958,2960,2962,2965,2969,2970,2972,2972,2974,2975,2979,2980,2984,
+ 2986,2990,3001,3006,3010,3014,3016,3018,3021,3024,3024,3031,3031,
+ 3046,3055,3072,3084,3086,3088,3090,3112,3114,3129,3132,3140,3142,
+ 3144,3146,3149,3157,3158,3160,3162,3165,3165,3168,3171,3174,3183,
+ 3200,3203,3205,3212,3214,3216,3218,3240,3242,3251,3253,3257,3260,
+ 3268,3270,3272,3274,3277,3285,3286,3293,3294,3296,3299,3302,3311,
+ 3313,3315,3328,3340,3342,3344,3346,3396,3398,3400,3402,3406,3412,
+ 3415,3423,3427,3430,3439,3450,3455,3457,3459,3461,3478,3482,3505,
+ 3507,3515,3517,3517,3520,3526,3530,3530,3535,3540,3542,3542,3544,
+ 3551,3558,3567,3570,3571,3585,3642,3648,3662,3664,3673,3713,3714,
+ 3716,3716,3718,3722,3724,3747,3749,3749,3751,3773,3776,3780,3782,
+ 3782,3784,3790,3792,3801,3804,3807,3840,3840,3864,3865,3872,3881,
+ 3893,3893,3895,3895,3897,3897,3902,3911,3913,3948,3953,3972,3974,
+ 3991,3993,4028,4038,4038,4096,4169,4176,4253,4256,4293,4295,4295,
+ 4301,4301,4304,4346,4348,4680,4682,4685,4688,4694,4696,4696,4698,
+ 4701,4704,4744,4746,4749,4752,4784,4786,4789,4792,4798,4800,4800,
+ 4802,4805,4808,4822,4824,4880,4882,4885,4888,4954,4957,4959,4969,
+ 4977,4992,5007,5024,5109,5112,5117,5121,5740,5743,5759,5761,5786,
+ 5792,5866,5870,5880,5888,5909,5919,5940,5952,5971,5984,5996,5998,
+ 6000,6002,6003,6016,6099,6103,6103,6108,6109,6112,6121,6155,6157,
+ 6159,6169,6176,6264,6272,6314,6320,6389,6400,6430,6432,6443,6448,
+ 6459,6470,6509,6512,6516,6528,6571,6576,6601,6608,6618,6656,6683,
+ 6688,6750,6752,6780,6783,6793,6800,6809,6823,6823,6832,6845,6847,
+ 6862,6912,6988,6992,7001,7019,7027,7040,7155,7168,7223,7232,7241,
+ 7245,7293,7296,7304,7312,7354,7357,7359,7376,7378,7380,7418,7424,
+ 7957,7960,7965,7968,8005,8008,8013,8016,8023,8025,8025,8027,8027,
+ 8029,8029,8031,8061,8064,8116,8118,8124,8126,8126,8130,8132,8134,
+ 8140,8144,8147,8150,8155,8160,8172,8178,8180,8182,8188,8255,8256,
+ 8276,8276,8305,8305,8319,8319,8336,8348,8400,8412,8417,8417,8421,
+ 8432,8450,8450,8455,8455,8458,8467,8469,8469,8472,8477,8484,8484,
+ 8486,8486,8488,8488,8490,8505,8508,8511,8517,8521,8526,8526,8544,
+ 8584,11264,11492,11499,11507,11520,11557,11559,11559,11565,11565,
+ 11568,11623,11631,11631,11647,11670,11680,11686,11688,11694,11696,
+ 11702,11704,11710,11712,11718,11720,11726,11728,11734,11736,11742,
+ 11744,11775,12293,12295,12321,12335,12337,12341,12344,12348,12353,
+ 12438,12441,12447,12449,12538,12540,12543,12549,12591,12593,12686,
+ 12704,12735,12784,12799,13312,19903,19968,42124,42192,42237,42240,
+ 42508,42512,42539,42560,42607,42612,42621,42623,42737,42775,42783,
+ 42786,42888,42891,42954,42960,42961,42963,42963,42965,42969,42994,
+ 43047,43052,43052,43072,43123,43136,43205,43216,43225,43232,43255,
+ 43259,43259,43261,43309,43312,43347,43360,43388,43392,43456,43471,
+ 43481,43488,43518,43520,43574,43584,43597,43600,43609,43616,43638,
+ 43642,43714,43739,43741,43744,43759,43762,43766,43777,43782,43785,
+ 43790,43793,43798,43808,43814,43816,43822,43824,43866,43868,43881,
+ 43888,44010,44012,44013,44016,44025,44032,55203,55216,55238,55243,
+ 55291,63744,64109,64112,64217,64256,64262,64275,64279,64285,64296,
+ 64298,64310,64312,64316,64318,64318,64320,64321,64323,64324,64326,
+ 64433,64467,64829,64848,64911,64914,64967,65008,65019,65024,65039,
+ 65056,65071,65075,65076,65101,65103,65136,65140,65142,65276,65296,
+ 65305,65313,65338,65343,65343,65345,65370,65382,65470,65474,65479,
+ 65482,65487,65490,65495,65498,65500,65536,65547,65549,65574,65576,
+ 65594,65596,65597,65599,65613,65616,65629,65664,65786,65856,65908,
+ 66045,66045,66176,66204,66208,66256,66272,66272,66304,66335,66349,
+ 66378,66384,66426,66432,66461,66464,66499,66504,66511,66513,66517,
+ 66560,66717,66720,66729,66736,66771,66776,66811,66816,66855,66864,
+ 66915,66928,66938,66940,66954,66956,66962,66964,66965,66967,66977,
+ 66979,66993,66995,67001,67003,67004,67072,67382,67392,67413,67424,
+ 67431,67456,67461,67463,67504,67506,67514,67584,67589,67592,67592,
+ 67594,67637,67639,67640,67644,67644,67647,67669,67680,67702,67712,
+ 67742,67808,67826,67828,67829,67840,67861,67872,67897,67968,68023,
+ 68030,68031,68096,68099,68101,68102,68108,68115,68117,68119,68121,
+ 68149,68152,68154,68159,68159,68192,68220,68224,68252,68288,68295,
+ 68297,68326,68352,68405,68416,68437,68448,68466,68480,68497,68608,
+ 68680,68736,68786,68800,68850,68864,68903,68912,68921,69248,69289,
+ 69291,69292,69296,69297,69373,69404,69415,69415,69424,69456,69488,
+ 69509,69552,69572,69600,69622,69632,69702,69734,69749,69759,69818,
+ 69826,69826,69840,69864,69872,69881,69888,69940,69942,69951,69956,
+ 69959,69968,70003,70006,70006,70016,70084,70089,70092,70094,70106,
+ 70108,70108,70144,70161,70163,70199,70206,70209,70272,70278,70280,
+ 70280,70282,70285,70287,70301,70303,70312,70320,70378,70384,70393,
+ 70400,70403,70405,70412,70415,70416,70419,70440,70442,70448,70450,
+ 70451,70453,70457,70459,70468,70471,70472,70475,70477,70480,70480,
+ 70487,70487,70493,70499,70502,70508,70512,70516,70656,70730,70736,
+ 70745,70750,70753,70784,70853,70855,70855,70864,70873,71040,71093,
+ 71096,71104,71128,71133,71168,71232,71236,71236,71248,71257,71296,
+ 71352,71360,71369,71424,71450,71453,71467,71472,71481,71488,71494,
+ 71680,71738,71840,71913,71935,71942,71945,71945,71948,71955,71957,
+ 71958,71960,71989,71991,71992,71995,72003,72016,72025,72096,72103,
+ 72106,72151,72154,72161,72163,72164,72192,72254,72263,72263,72272,
+ 72345,72349,72349,72368,72440,72704,72712,72714,72758,72760,72768,
+ 72784,72793,72818,72847,72850,72871,72873,72886,72960,72966,72968,
+ 72969,72971,73014,73018,73018,73020,73021,73023,73031,73040,73049,
+ 73056,73061,73063,73064,73066,73102,73104,73105,73107,73112,73120,
+ 73129,73440,73462,73472,73488,73490,73530,73534,73538,73552,73561,
+ 73648,73648,73728,74649,74752,74862,74880,75075,77712,77808,77824,
+ 78895,78912,78933,82944,83526,92160,92728,92736,92766,92768,92777,
+ 92784,92862,92864,92873,92880,92909,92912,92916,92928,92982,92992,
+ 92995,93008,93017,93027,93047,93053,93071,93760,93823,93952,94026,
+ 94031,94087,94095,94111,94176,94177,94179,94180,94192,94193,94208,
+ 100343,100352,101589,101632,101640,110576,110579,110581,110587,110589,
+ 110590,110592,110882,110898,110898,110928,110930,110933,110933,110948,
+ 110951,110960,111355,113664,113770,113776,113788,113792,113800,113808,
+ 113817,113821,113822,118528,118573,118576,118598,119141,119145,119149,
+ 119154,119163,119170,119173,119179,119210,119213,119362,119364,119808,
+ 119892,119894,119964,119966,119967,119970,119970,119973,119974,119977,
+ 119980,119982,119993,119995,119995,119997,120003,120005,120069,120071,
+ 120074,120077,120084,120086,120092,120094,120121,120123,120126,120128,
+ 120132,120134,120134,120138,120144,120146,120485,120488,120512,120514,
+ 120538,120540,120570,120572,120596,120598,120628,120630,120654,120656,
+ 120686,120688,120712,120714,120744,120746,120770,120772,120779,120782,
+ 120831,121344,121398,121403,121452,121461,121461,121476,121476,121499,
+ 121503,121505,121519,122624,122654,122661,122666,122880,122886,122888,
+ 122904,122907,122913,122915,122916,122918,122922,122928,122989,123023,
+ 123023,123136,123180,123184,123197,123200,123209,123214,123214,123536,
+ 123566,123584,123641,124112,124153,124896,124902,124904,124907,124909,
+ 124910,124912,124926,124928,125124,125136,125142,125184,125259,125264,
+ 125273,126464,126467,126469,126495,126497,126498,126500,126500,126503,
+ 126503,126505,126514,126516,126519,126521,126521,126523,126523,126530,
+ 126530,126535,126535,126537,126537,126539,126539,126541,126543,126545,
+ 126546,126548,126548,126551,126551,126553,126553,126555,126555,126557,
+ 126557,126559,126559,126561,126562,126564,126564,126567,126570,126572,
+ 126578,126580,126583,126585,126588,126590,126590,126592,126601,126603,
+ 126619,126625,126627,126629,126633,126635,126651,130032,130041,131072,
+ 173791,173824,177977,177984,178205,178208,183969,183984,191456,194560,
+ 195101,196608,201546,201552,205743,917760,917999,1,0,42,42,2,0,39,
+ 39,92,92,2,0,10,10,13,13,1,0,47,47,1,0,29,29,1,0,28,28,1,0,13,13,
+ 21,0,36,36,162,165,1423,1423,1547,1547,2046,2047,2546,2547,2555,
+ 2555,2801,2801,3065,3065,3647,3647,6107,6107,8352,8384,43064,43064,
+ 65020,65020,65129,65129,65284,65284,65504,65505,65509,65510,73693,
+ 73696,123647,123647,126128,126128,1,0,32,32,6,0,95,95,8255,8256,
+ 8276,8276,65075,65076,65101,65103,65343,65343,1,0,9,9,2,0,34,34,
+ 92,92,1,0,10,10,1,0,11,11,1,0,31,31,659,0,65,90,97,122,170,170,181,
+ 181,186,186,192,214,216,246,248,705,710,721,736,740,748,748,750,
+ 750,880,884,886,887,890,893,895,895,902,902,904,906,908,908,910,
+ 929,931,1013,1015,1153,1162,1327,1329,1366,1369,1369,1376,1416,1488,
+ 1514,1519,1522,1568,1610,1646,1647,1649,1747,1749,1749,1765,1766,
+ 1774,1775,1786,1788,1791,1791,1808,1808,1810,1839,1869,1957,1969,
+ 1969,1994,2026,2036,2037,2042,2042,2048,2069,2074,2074,2084,2084,
+ 2088,2088,2112,2136,2144,2154,2160,2183,2185,2190,2208,2249,2308,
+ 2361,2365,2365,2384,2384,2392,2401,2417,2432,2437,2444,2447,2448,
+ 2451,2472,2474,2480,2482,2482,2486,2489,2493,2493,2510,2510,2524,
+ 2525,2527,2529,2544,2545,2556,2556,2565,2570,2575,2576,2579,2600,
+ 2602,2608,2610,2611,2613,2614,2616,2617,2649,2652,2654,2654,2674,
+ 2676,2693,2701,2703,2705,2707,2728,2730,2736,2738,2739,2741,2745,
+ 2749,2749,2768,2768,2784,2785,2809,2809,2821,2828,2831,2832,2835,
+ 2856,2858,2864,2866,2867,2869,2873,2877,2877,2908,2909,2911,2913,
+ 2929,2929,2947,2947,2949,2954,2958,2960,2962,2965,2969,2970,2972,
+ 2972,2974,2975,2979,2980,2984,2986,2990,3001,3024,3024,3077,3084,
+ 3086,3088,3090,3112,3114,3129,3133,3133,3160,3162,3165,3165,3168,
+ 3169,3200,3200,3205,3212,3214,3216,3218,3240,3242,3251,3253,3257,
+ 3261,3261,3293,3294,3296,3297,3313,3314,3332,3340,3342,3344,3346,
+ 3386,3389,3389,3406,3406,3412,3414,3423,3425,3450,3455,3461,3478,
+ 3482,3505,3507,3515,3517,3517,3520,3526,3585,3632,3634,3635,3648,
+ 3654,3713,3714,3716,3716,3718,3722,3724,3747,3749,3749,3751,3760,
+ 3762,3763,3773,3773,3776,3780,3782,3782,3804,3807,3840,3840,3904,
+ 3911,3913,3948,3976,3980,4096,4138,4159,4159,4176,4181,4186,4189,
+ 4193,4193,4197,4198,4206,4208,4213,4225,4238,4238,4256,4293,4295,
+ 4295,4301,4301,4304,4346,4348,4680,4682,4685,4688,4694,4696,4696,
+ 4698,4701,4704,4744,4746,4749,4752,4784,4786,4789,4792,4798,4800,
+ 4800,4802,4805,4808,4822,4824,4880,4882,4885,4888,4954,4992,5007,
+ 5024,5109,5112,5117,5121,5740,5743,5759,5761,5786,5792,5866,5870,
+ 5880,5888,5905,5919,5937,5952,5969,5984,5996,5998,6000,6016,6067,
+ 6103,6103,6108,6108,6176,6264,6272,6312,6314,6314,6320,6389,6400,
+ 6430,6480,6509,6512,6516,6528,6571,6576,6601,6656,6678,6688,6740,
+ 6823,6823,6917,6963,6981,6988,7043,7072,7086,7087,7098,7141,7168,
+ 7203,7245,7247,7258,7293,7296,7304,7312,7354,7357,7359,7401,7404,
+ 7406,7411,7413,7414,7418,7418,7424,7615,7680,7957,7960,7965,7968,
+ 8005,8008,8013,8016,8023,8025,8025,8027,8027,8029,8029,8031,8061,
+ 8064,8116,8118,8124,8126,8126,8130,8132,8134,8140,8144,8147,8150,
+ 8155,8160,8172,8178,8180,8182,8188,8305,8305,8319,8319,8336,8348,
+ 8450,8450,8455,8455,8458,8467,8469,8469,8472,8477,8484,8484,8486,
+ 8486,8488,8488,8490,8505,8508,8511,8517,8521,8526,8526,8544,8584,
+ 11264,11492,11499,11502,11506,11507,11520,11557,11559,11559,11565,
+ 11565,11568,11623,11631,11631,11648,11670,11680,11686,11688,11694,
+ 11696,11702,11704,11710,11712,11718,11720,11726,11728,11734,11736,
+ 11742,12293,12295,12321,12329,12337,12341,12344,12348,12353,12438,
+ 12443,12447,12449,12538,12540,12543,12549,12591,12593,12686,12704,
+ 12735,12784,12799,13312,19903,19968,42124,42192,42237,42240,42508,
+ 42512,42527,42538,42539,42560,42606,42623,42653,42656,42735,42775,
+ 42783,42786,42888,42891,42954,42960,42961,42963,42963,42965,42969,
+ 42994,43009,43011,43013,43015,43018,43020,43042,43072,43123,43138,
+ 43187,43250,43255,43259,43259,43261,43262,43274,43301,43312,43334,
+ 43360,43388,43396,43442,43471,43471,43488,43492,43494,43503,43514,
+ 43518,43520,43560,43584,43586,43588,43595,43616,43638,43642,43642,
+ 43646,43695,43697,43697,43701,43702,43705,43709,43712,43712,43714,
+ 43714,43739,43741,43744,43754,43762,43764,43777,43782,43785,43790,
+ 43793,43798,43808,43814,43816,43822,43824,43866,43868,43881,43888,
+ 44002,44032,55203,55216,55238,55243,55291,63744,64109,64112,64217,
+ 64256,64262,64275,64279,64285,64285,64287,64296,64298,64310,64312,
+ 64316,64318,64318,64320,64321,64323,64324,64326,64433,64467,64829,
+ 64848,64911,64914,64967,65008,65019,65136,65140,65142,65276,65313,
+ 65338,65345,65370,65382,65470,65474,65479,65482,65487,65490,65495,
+ 65498,65500,65536,65547,65549,65574,65576,65594,65596,65597,65599,
+ 65613,65616,65629,65664,65786,65856,65908,66176,66204,66208,66256,
+ 66304,66335,66349,66378,66384,66421,66432,66461,66464,66499,66504,
+ 66511,66513,66517,66560,66717,66736,66771,66776,66811,66816,66855,
+ 66864,66915,66928,66938,66940,66954,66956,66962,66964,66965,66967,
+ 66977,66979,66993,66995,67001,67003,67004,67072,67382,67392,67413,
+ 67424,67431,67456,67461,67463,67504,67506,67514,67584,67589,67592,
+ 67592,67594,67637,67639,67640,67644,67644,67647,67669,67680,67702,
+ 67712,67742,67808,67826,67828,67829,67840,67861,67872,67897,67968,
+ 68023,68030,68031,68096,68096,68112,68115,68117,68119,68121,68149,
+ 68192,68220,68224,68252,68288,68295,68297,68324,68352,68405,68416,
+ 68437,68448,68466,68480,68497,68608,68680,68736,68786,68800,68850,
+ 68864,68899,69248,69289,69296,69297,69376,69404,69415,69415,69424,
+ 69445,69488,69505,69552,69572,69600,69622,69635,69687,69745,69746,
+ 69749,69749,69763,69807,69840,69864,69891,69926,69956,69956,69959,
+ 69959,69968,70002,70006,70006,70019,70066,70081,70084,70106,70106,
+ 70108,70108,70144,70161,70163,70187,70207,70208,70272,70278,70280,
+ 70280,70282,70285,70287,70301,70303,70312,70320,70366,70405,70412,
+ 70415,70416,70419,70440,70442,70448,70450,70451,70453,70457,70461,
+ 70461,70480,70480,70493,70497,70656,70708,70727,70730,70751,70753,
+ 70784,70831,70852,70853,70855,70855,71040,71086,71128,71131,71168,
+ 71215,71236,71236,71296,71338,71352,71352,71424,71450,71488,71494,
+ 71680,71723,71840,71903,71935,71942,71945,71945,71948,71955,71957,
+ 71958,71960,71983,71999,71999,72001,72001,72096,72103,72106,72144,
+ 72161,72161,72163,72163,72192,72192,72203,72242,72250,72250,72272,
+ 72272,72284,72329,72349,72349,72368,72440,72704,72712,72714,72750,
+ 72768,72768,72818,72847,72960,72966,72968,72969,72971,73008,73030,
+ 73030,73056,73061,73063,73064,73066,73097,73112,73112,73440,73458,
+ 73474,73474,73476,73488,73490,73523,73648,73648,73728,74649,74752,
+ 74862,74880,75075,77712,77808,77824,78895,78913,78918,82944,83526,
+ 92160,92728,92736,92766,92784,92862,92880,92909,92928,92975,92992,
+ 92995,93027,93047,93053,93071,93760,93823,93952,94026,94032,94032,
+ 94099,94111,94176,94177,94179,94179,94208,100343,100352,101589,101632,
+ 101640,110576,110579,110581,110587,110589,110590,110592,110882,110898,
+ 110898,110928,110930,110933,110933,110948,110951,110960,111355,113664,
+ 113770,113776,113788,113792,113800,113808,113817,119808,119892,119894,
+ 119964,119966,119967,119970,119970,119973,119974,119977,119980,119982,
+ 119993,119995,119995,119997,120003,120005,120069,120071,120074,120077,
+ 120084,120086,120092,120094,120121,120123,120126,120128,120132,120134,
+ 120134,120138,120144,120146,120485,120488,120512,120514,120538,120540,
+ 120570,120572,120596,120598,120628,120630,120654,120656,120686,120688,
+ 120712,120714,120744,120746,120770,120772,120779,122624,122654,122661,
+ 122666,122928,122989,123136,123180,123191,123197,123214,123214,123536,
+ 123565,123584,123627,124112,124139,124896,124902,124904,124907,124909,
+ 124910,124912,124926,124928,125124,125184,125251,125259,125259,126464,
+ 126467,126469,126495,126497,126498,126500,126500,126503,126503,126505,
+ 126514,126516,126519,126521,126521,126523,126523,126530,126530,126535,
+ 126535,126537,126537,126539,126539,126541,126543,126545,126546,126548,
+ 126548,126551,126551,126553,126553,126555,126555,126557,126557,126559,
+ 126559,126561,126562,126564,126564,126567,126570,126572,126578,126580,
+ 126583,126585,126588,126590,126590,126592,126601,126603,126619,126625,
+ 126627,126629,126633,126635,126651,131072,173791,173824,177977,177984,
+ 178205,178208,183969,183984,191456,194560,195101,196608,201546,201552,
+ 205743,1020,0,1,1,0,0,0,0,3,1,0,0,0,0,5,1,0,0,0,0,7,1,0,0,0,0,9,
+ 1,0,0,0,0,11,1,0,0,0,0,13,1,0,0,0,0,15,1,0,0,0,0,17,1,0,0,0,0,19,
+ 1,0,0,0,0,21,1,0,0,0,0,23,1,0,0,0,0,25,1,0,0,0,0,27,1,0,0,0,0,29,
+ 1,0,0,0,0,31,1,0,0,0,0,33,1,0,0,0,0,35,1,0,0,0,0,37,1,0,0,0,0,39,
+ 1,0,0,0,0,41,1,0,0,0,0,43,1,0,0,0,0,45,1,0,0,0,0,47,1,0,0,0,0,49,
+ 1,0,0,0,0,51,1,0,0,0,0,53,1,0,0,0,0,55,1,0,0,0,0,57,1,0,0,0,0,59,
+ 1,0,0,0,0,61,1,0,0,0,0,63,1,0,0,0,0,65,1,0,0,0,0,67,1,0,0,0,0,69,
+ 1,0,0,0,0,71,1,0,0,0,0,73,1,0,0,0,0,75,1,0,0,0,0,77,1,0,0,0,0,79,
+ 1,0,0,0,0,81,1,0,0,0,0,83,1,0,0,0,0,85,1,0,0,0,0,87,1,0,0,0,0,89,
+ 1,0,0,0,0,91,1,0,0,0,0,93,1,0,0,0,0,95,1,0,0,0,0,97,1,0,0,0,0,99,
+ 1,0,0,0,0,101,1,0,0,0,0,103,1,0,0,0,0,105,1,0,0,0,0,107,1,0,0,0,
+ 0,109,1,0,0,0,0,111,1,0,0,0,0,113,1,0,0,0,0,115,1,0,0,0,0,117,1,
+ 0,0,0,0,119,1,0,0,0,0,121,1,0,0,0,0,123,1,0,0,0,0,125,1,0,0,0,0,
+ 127,1,0,0,0,0,129,1,0,0,0,0,131,1,0,0,0,0,133,1,0,0,0,0,135,1,0,
+ 0,0,0,137,1,0,0,0,0,139,1,0,0,0,0,141,1,0,0,0,0,143,1,0,0,0,0,145,
+ 1,0,0,0,0,147,1,0,0,0,0,149,1,0,0,0,0,151,1,0,0,0,0,153,1,0,0,0,
+ 0,155,1,0,0,0,0,157,1,0,0,0,0,159,1,0,0,0,0,161,1,0,0,0,0,163,1,
+ 0,0,0,0,165,1,0,0,0,0,167,1,0,0,0,0,169,1,0,0,0,0,171,1,0,0,0,0,
+ 173,1,0,0,0,0,175,1,0,0,0,0,177,1,0,0,0,0,179,1,0,0,0,0,181,1,0,
+ 0,0,0,183,1,0,0,0,0,185,1,0,0,0,0,187,1,0,0,0,0,189,1,0,0,0,0,191,
+ 1,0,0,0,0,193,1,0,0,0,0,195,1,0,0,0,0,197,1,0,0,0,0,199,1,0,0,0,
+ 0,201,1,0,0,0,0,203,1,0,0,0,0,205,1,0,0,0,0,207,1,0,0,0,0,209,1,
+ 0,0,0,0,211,1,0,0,0,0,213,1,0,0,0,0,215,1,0,0,0,0,217,1,0,0,0,0,
+ 219,1,0,0,0,0,221,1,0,0,0,0,223,1,0,0,0,0,225,1,0,0,0,0,227,1,0,
+ 0,0,0,229,1,0,0,0,0,231,1,0,0,0,0,233,1,0,0,0,0,235,1,0,0,0,0,237,
+ 1,0,0,0,0,239,1,0,0,0,0,241,1,0,0,0,0,243,1,0,0,0,0,245,1,0,0,0,
+ 0,247,1,0,0,0,0,249,1,0,0,0,0,251,1,0,0,0,0,253,1,0,0,0,1,295,1,
+ 0,0,0,3,297,1,0,0,0,5,299,1,0,0,0,7,301,1,0,0,0,9,304,1,0,0,0,11,
+ 306,1,0,0,0,13,308,1,0,0,0,15,310,1,0,0,0,17,312,1,0,0,0,19,314,
+ 1,0,0,0,21,316,1,0,0,0,23,318,1,0,0,0,25,321,1,0,0,0,27,324,1,0,
+ 0,0,29,326,1,0,0,0,31,328,1,0,0,0,33,331,1,0,0,0,35,334,1,0,0,0,
+ 37,336,1,0,0,0,39,338,1,0,0,0,41,340,1,0,0,0,43,342,1,0,0,0,45,344,
+ 1,0,0,0,47,346,1,0,0,0,49,348,1,0,0,0,51,350,1,0,0,0,53,352,1,0,
+ 0,0,55,354,1,0,0,0,57,356,1,0,0,0,59,358,1,0,0,0,61,360,1,0,0,0,
+ 63,362,1,0,0,0,65,364,1,0,0,0,67,366,1,0,0,0,69,368,1,0,0,0,71,370,
+ 1,0,0,0,73,372,1,0,0,0,75,374,1,0,0,0,77,376,1,0,0,0,79,378,1,0,
+ 0,0,81,380,1,0,0,0,83,382,1,0,0,0,85,384,1,0,0,0,87,386,1,0,0,0,
+ 89,388,1,0,0,0,91,390,1,0,0,0,93,396,1,0,0,0,95,400,1,0,0,0,97,409,
+ 1,0,0,0,99,415,1,0,0,0,101,422,1,0,0,0,103,425,1,0,0,0,105,431,1,
+ 0,0,0,107,434,1,0,0,0,109,441,1,0,0,0,111,445,1,0,0,0,113,452,1,
+ 0,0,0,115,459,1,0,0,0,117,466,1,0,0,0,119,471,1,0,0,0,121,477,1,
+ 0,0,0,123,482,1,0,0,0,125,489,1,0,0,0,127,498,1,0,0,0,129,504,1,
+ 0,0,0,131,507,1,0,0,0,133,512,1,0,0,0,135,518,1,0,0,0,137,528,1,
+ 0,0,0,139,532,1,0,0,0,141,543,1,0,0,0,143,548,1,0,0,0,145,554,1,
+ 0,0,0,147,557,1,0,0,0,149,561,1,0,0,0,151,565,1,0,0,0,153,569,1,
+ 0,0,0,155,576,1,0,0,0,157,581,1,0,0,0,159,590,1,0,0,0,161,593,1,
+ 0,0,0,163,596,1,0,0,0,165,601,1,0,0,0,167,607,1,0,0,0,169,612,1,
+ 0,0,0,171,617,1,0,0,0,173,621,1,0,0,0,175,626,1,0,0,0,177,631,1,
+ 0,0,0,179,635,1,0,0,0,181,640,1,0,0,0,183,647,1,0,0,0,185,654,1,
+ 0,0,0,187,659,1,0,0,0,189,665,1,0,0,0,191,681,1,0,0,0,193,683,1,
+ 0,0,0,195,692,1,0,0,0,197,696,1,0,0,0,199,700,1,0,0,0,201,704,1,
+ 0,0,0,203,706,1,0,0,0,205,710,1,0,0,0,207,712,1,0,0,0,209,736,1,
+ 0,0,0,211,750,1,0,0,0,213,777,1,0,0,0,215,779,1,0,0,0,217,799,1,
+ 0,0,0,219,810,1,0,0,0,221,813,1,0,0,0,223,817,1,0,0,0,225,825,1,
+ 0,0,0,227,832,1,0,0,0,229,842,1,0,0,0,231,849,1,0,0,0,233,852,1,
+ 0,0,0,235,856,1,0,0,0,237,861,1,0,0,0,239,868,1,0,0,0,241,876,1,
+ 0,0,0,243,885,1,0,0,0,245,889,1,0,0,0,247,899,1,0,0,0,249,904,1,
+ 0,0,0,251,920,1,0,0,0,253,951,1,0,0,0,255,953,1,0,0,0,257,955,1,
+ 0,0,0,259,957,1,0,0,0,261,959,1,0,0,0,263,961,1,0,0,0,265,963,1,
+ 0,0,0,267,965,1,0,0,0,269,967,1,0,0,0,271,969,1,0,0,0,273,971,1,
+ 0,0,0,275,973,1,0,0,0,277,975,1,0,0,0,279,977,1,0,0,0,281,979,1,
+ 0,0,0,283,981,1,0,0,0,285,983,1,0,0,0,287,985,1,0,0,0,289,987,1,
+ 0,0,0,291,989,1,0,0,0,293,991,1,0,0,0,295,296,5,59,0,0,296,2,1,0,
+ 0,0,297,298,5,44,0,0,298,4,1,0,0,0,299,300,5,61,0,0,300,6,1,0,0,
+ 0,301,302,5,43,0,0,302,303,5,61,0,0,303,8,1,0,0,0,304,305,5,42,0,
+ 0,305,10,1,0,0,0,306,307,5,40,0,0,307,12,1,0,0,0,308,309,5,41,0,
+ 0,309,14,1,0,0,0,310,311,5,91,0,0,311,16,1,0,0,0,312,313,5,93,0,
+ 0,313,18,1,0,0,0,314,315,5,58,0,0,315,20,1,0,0,0,316,317,5,124,0,
+ 0,317,22,1,0,0,0,318,319,5,46,0,0,319,320,5,46,0,0,320,24,1,0,0,
+ 0,321,322,5,60,0,0,322,323,5,62,0,0,323,26,1,0,0,0,324,325,5,60,
+ 0,0,325,28,1,0,0,0,326,327,5,62,0,0,327,30,1,0,0,0,328,329,5,60,
+ 0,0,329,330,5,61,0,0,330,32,1,0,0,0,331,332,5,62,0,0,332,333,5,61,
+ 0,0,333,34,1,0,0,0,334,335,5,43,0,0,335,36,1,0,0,0,336,337,5,45,
+ 0,0,337,38,1,0,0,0,338,339,5,47,0,0,339,40,1,0,0,0,340,341,5,37,
+ 0,0,341,42,1,0,0,0,342,343,5,94,0,0,343,44,1,0,0,0,344,345,5,46,
+ 0,0,345,46,1,0,0,0,346,347,5,123,0,0,347,48,1,0,0,0,348,349,5,125,
+ 0,0,349,50,1,0,0,0,350,351,5,36,0,0,351,52,1,0,0,0,352,353,5,10216,
+ 0,0,353,54,1,0,0,0,354,355,5,12296,0,0,355,56,1,0,0,0,356,357,5,
+ 65124,0,0,357,58,1,0,0,0,358,359,5,65308,0,0,359,60,1,0,0,0,360,
+ 361,5,10217,0,0,361,62,1,0,0,0,362,363,5,12297,0,0,363,64,1,0,0,
+ 0,364,365,5,65125,0,0,365,66,1,0,0,0,366,367,5,65310,0,0,367,68,
+ 1,0,0,0,368,369,5,173,0,0,369,70,1,0,0,0,370,371,5,8208,0,0,371,
+ 72,1,0,0,0,372,373,5,8209,0,0,373,74,1,0,0,0,374,375,5,8210,0,0,
+ 375,76,1,0,0,0,376,377,5,8211,0,0,377,78,1,0,0,0,378,379,5,8212,
+ 0,0,379,80,1,0,0,0,380,381,5,8213,0,0,381,82,1,0,0,0,382,383,5,8722,
+ 0,0,383,84,1,0,0,0,384,385,5,65112,0,0,385,86,1,0,0,0,386,387,5,
+ 65123,0,0,387,88,1,0,0,0,388,389,5,65293,0,0,389,90,1,0,0,0,390,
+ 391,7,0,0,0,391,392,7,1,0,0,392,393,7,2,0,0,393,394,7,3,0,0,394,
+ 395,7,1,0,0,395,92,1,0,0,0,396,397,7,4,0,0,397,398,7,5,0,0,398,399,
+ 7,5,0,0,399,94,1,0,0,0,400,401,7,3,0,0,401,402,7,6,0,0,402,403,7,
+ 7,0,0,403,404,7,2,0,0,404,405,7,3,0,0,405,406,7,1,0,0,406,407,7,
+ 4,0,0,407,408,7,5,0,0,408,96,1,0,0,0,409,410,7,8,0,0,410,411,7,4,
+ 0,0,411,412,7,7,0,0,412,413,7,9,0,0,413,414,7,10,0,0,414,98,1,0,
+ 0,0,415,416,7,0,0,0,416,417,7,1,0,0,417,418,7,11,0,0,418,419,7,2,
+ 0,0,419,420,7,1,0,0,420,421,7,12,0,0,421,100,1,0,0,0,422,423,7,4,
+ 0,0,423,424,7,13,0,0,424,102,1,0,0,0,425,426,7,8,0,0,426,427,7,14,
+ 0,0,427,428,7,15,0,0,428,429,7,16,0,0,429,430,7,14,0,0,430,104,1,
+ 0,0,0,431,432,7,3,0,0,432,433,7,1,0,0,433,106,1,0,0,0,434,435,7,
+ 9,0,0,435,436,7,15,0,0,436,437,7,14,0,0,437,438,7,4,0,0,438,439,
+ 7,7,0,0,439,440,7,14,0,0,440,108,1,0,0,0,441,442,7,13,0,0,442,443,
+ 7,14,0,0,443,444,7,7,0,0,444,110,1,0,0,0,445,446,7,12,0,0,446,447,
+ 7,14,0,0,447,448,7,7,0,0,448,449,7,4,0,0,449,450,7,9,0,0,450,451,
+ 7,10,0,0,451,112,1,0,0,0,452,453,7,12,0,0,453,454,7,14,0,0,454,455,
+ 7,5,0,0,455,456,7,14,0,0,456,457,7,7,0,0,457,458,7,14,0,0,458,114,
+ 1,0,0,0,459,460,7,15,0,0,460,461,7,14,0,0,461,462,7,8,0,0,462,463,
+ 7,3,0,0,463,464,7,17,0,0,464,465,7,14,0,0,465,116,1,0,0,0,466,467,
+ 7,9,0,0,467,468,7,4,0,0,468,469,7,5,0,0,469,470,7,5,0,0,470,118,
+ 1,0,0,0,471,472,7,18,0,0,472,473,7,2,0,0,473,474,7,14,0,0,474,475,
+ 7,5,0,0,475,476,7,12,0,0,476,120,1,0,0,0,477,478,7,11,0,0,478,479,
+ 7,2,0,0,479,480,7,7,0,0,480,481,7,10,0,0,481,122,1,0,0,0,482,483,
+ 7,15,0,0,483,484,7,14,0,0,484,485,7,7,0,0,485,486,7,0,0,0,486,487,
+ 7,15,0,0,487,488,7,1,0,0,488,124,1,0,0,0,489,490,7,12,0,0,490,491,
+ 7,2,0,0,491,492,7,13,0,0,492,493,7,7,0,0,493,494,7,2,0,0,494,495,
+ 7,1,0,0,495,496,7,9,0,0,496,497,7,7,0,0,497,126,1,0,0,0,498,499,
+ 7,3,0,0,499,500,7,15,0,0,500,501,7,12,0,0,501,502,7,14,0,0,502,503,
+ 7,15,0,0,503,128,1,0,0,0,504,505,7,19,0,0,505,506,7,18,0,0,506,130,
+ 1,0,0,0,507,508,7,13,0,0,508,509,7,20,0,0,509,510,7,2,0,0,510,511,
+ 7,6,0,0,511,132,1,0,0,0,512,513,7,5,0,0,513,514,7,2,0,0,514,515,
+ 7,8,0,0,515,516,7,2,0,0,516,517,7,7,0,0,517,134,1,0,0,0,518,519,
+ 7,4,0,0,519,520,7,13,0,0,520,521,7,9,0,0,521,522,7,14,0,0,522,523,
+ 7,1,0,0,523,524,7,12,0,0,524,525,7,2,0,0,525,526,7,1,0,0,526,527,
+ 7,16,0,0,527,136,1,0,0,0,528,529,7,4,0,0,529,530,7,13,0,0,530,531,
+ 7,9,0,0,531,138,1,0,0,0,532,533,7,12,0,0,533,534,7,14,0,0,534,535,
+ 7,13,0,0,535,536,7,9,0,0,536,537,7,14,0,0,537,538,7,1,0,0,538,539,
+ 7,12,0,0,539,540,7,2,0,0,540,541,7,1,0,0,541,542,7,16,0,0,542,140,
+ 1,0,0,0,543,544,7,12,0,0,544,545,7,14,0,0,545,546,7,13,0,0,546,547,
+ 7,9,0,0,547,142,1,0,0,0,548,549,7,11,0,0,549,550,7,10,0,0,550,551,
+ 7,14,0,0,551,552,7,15,0,0,552,553,7,14,0,0,553,144,1,0,0,0,554,555,
+ 7,3,0,0,555,556,7,15,0,0,556,146,1,0,0,0,557,558,7,21,0,0,558,559,
+ 7,3,0,0,559,560,7,15,0,0,560,148,1,0,0,0,561,562,7,4,0,0,562,563,
+ 7,1,0,0,563,564,7,12,0,0,564,150,1,0,0,0,565,566,7,1,0,0,566,567,
+ 7,3,0,0,567,568,7,7,0,0,568,152,1,0,0,0,569,570,7,13,0,0,570,571,
+ 7,7,0,0,571,572,7,4,0,0,572,573,7,15,0,0,573,574,7,7,0,0,574,575,
+ 7,13,0,0,575,154,1,0,0,0,576,577,7,14,0,0,577,578,7,1,0,0,578,579,
+ 7,12,0,0,579,580,7,13,0,0,580,156,1,0,0,0,581,582,7,9,0,0,582,583,
+ 7,3,0,0,583,584,7,1,0,0,584,585,7,7,0,0,585,586,7,4,0,0,586,587,
+ 7,2,0,0,587,588,7,1,0,0,588,589,7,13,0,0,589,158,1,0,0,0,590,591,
+ 7,2,0,0,591,592,7,1,0,0,592,160,1,0,0,0,593,594,7,2,0,0,594,595,
+ 7,13,0,0,595,162,1,0,0,0,596,597,7,1,0,0,597,598,7,0,0,0,598,599,
+ 7,5,0,0,599,600,7,5,0,0,600,164,1,0,0,0,601,602,7,9,0,0,602,603,
+ 7,3,0,0,603,604,7,0,0,0,604,605,7,1,0,0,605,606,7,7,0,0,606,166,
+ 1,0,0,0,607,608,7,9,0,0,608,609,7,4,0,0,609,610,7,13,0,0,610,611,
+ 7,14,0,0,611,168,1,0,0,0,612,613,7,14,0,0,613,614,7,5,0,0,614,615,
+ 7,13,0,0,615,616,7,14,0,0,616,170,1,0,0,0,617,618,7,14,0,0,618,619,
+ 7,1,0,0,619,620,7,12,0,0,620,172,1,0,0,0,621,622,7,11,0,0,622,623,
+ 7,10,0,0,623,624,7,14,0,0,624,625,7,1,0,0,625,174,1,0,0,0,626,627,
+ 7,7,0,0,627,628,7,10,0,0,628,629,7,14,0,0,629,630,7,1,0,0,630,176,
+ 1,0,0,0,631,632,7,4,0,0,632,633,7,1,0,0,633,634,7,18,0,0,634,178,
+ 1,0,0,0,635,636,7,1,0,0,636,637,7,3,0,0,637,638,7,1,0,0,638,639,
+ 7,14,0,0,639,180,1,0,0,0,640,641,7,13,0,0,641,642,7,2,0,0,642,643,
+ 7,1,0,0,643,644,7,16,0,0,644,645,7,5,0,0,645,646,7,14,0,0,646,182,
+ 1,0,0,0,647,648,7,14,0,0,648,649,7,21,0,0,649,650,7,2,0,0,650,651,
+ 7,13,0,0,651,652,7,7,0,0,652,653,7,13,0,0,653,184,1,0,0,0,654,655,
+ 7,7,0,0,655,656,7,15,0,0,656,657,7,0,0,0,657,658,7,14,0,0,658,186,
+ 1,0,0,0,659,660,7,22,0,0,660,661,7,4,0,0,661,662,7,5,0,0,662,663,
+ 7,13,0,0,663,664,7,14,0,0,664,188,1,0,0,0,665,666,5,48,0,0,666,667,
+ 5,120,0,0,667,669,1,0,0,0,668,670,3,197,98,0,669,668,1,0,0,0,670,
+ 671,1,0,0,0,671,669,1,0,0,0,671,672,1,0,0,0,672,190,1,0,0,0,673,
+ 682,3,207,103,0,674,678,3,201,100,0,675,677,3,199,99,0,676,675,1,
+ 0,0,0,677,680,1,0,0,0,678,676,1,0,0,0,678,679,1,0,0,0,679,682,1,
+ 0,0,0,680,678,1,0,0,0,681,673,1,0,0,0,681,674,1,0,0,0,682,192,1,
+ 0,0,0,683,684,5,48,0,0,684,685,5,111,0,0,685,687,1,0,0,0,686,688,
+ 3,205,102,0,687,686,1,0,0,0,688,689,1,0,0,0,689,687,1,0,0,0,689,
+ 690,1,0,0,0,690,194,1,0,0,0,691,693,7,23,0,0,692,691,1,0,0,0,693,
+ 196,1,0,0,0,694,697,3,199,99,0,695,697,3,195,97,0,696,694,1,0,0,
+ 0,696,695,1,0,0,0,697,198,1,0,0,0,698,701,3,207,103,0,699,701,3,
+ 201,100,0,700,698,1,0,0,0,700,699,1,0,0,0,701,200,1,0,0,0,702,705,
+ 3,203,101,0,703,705,2,56,57,0,704,702,1,0,0,0,704,703,1,0,0,0,705,
+ 202,1,0,0,0,706,707,2,49,55,0,707,204,1,0,0,0,708,711,3,207,103,
+ 0,709,711,3,203,101,0,710,708,1,0,0,0,710,709,1,0,0,0,711,206,1,
+ 0,0,0,712,713,5,48,0,0,713,208,1,0,0,0,714,716,3,199,99,0,715,714,
+ 1,0,0,0,716,717,1,0,0,0,717,715,1,0,0,0,717,718,1,0,0,0,718,737,
+ 1,0,0,0,719,721,3,199,99,0,720,719,1,0,0,0,721,722,1,0,0,0,722,720,
+ 1,0,0,0,722,723,1,0,0,0,723,724,1,0,0,0,724,726,5,46,0,0,725,727,
+ 3,199,99,0,726,725,1,0,0,0,727,728,1,0,0,0,728,726,1,0,0,0,728,729,
+ 1,0,0,0,729,737,1,0,0,0,730,732,5,46,0,0,731,733,3,199,99,0,732,
+ 731,1,0,0,0,733,734,1,0,0,0,734,732,1,0,0,0,734,735,1,0,0,0,735,
+ 737,1,0,0,0,736,715,1,0,0,0,736,720,1,0,0,0,736,730,1,0,0,0,737,
+ 738,1,0,0,0,738,740,7,14,0,0,739,741,5,45,0,0,740,739,1,0,0,0,740,
+ 741,1,0,0,0,741,743,1,0,0,0,742,744,3,199,99,0,743,742,1,0,0,0,744,
+ 745,1,0,0,0,745,743,1,0,0,0,745,746,1,0,0,0,746,210,1,0,0,0,747,
+ 749,3,199,99,0,748,747,1,0,0,0,749,752,1,0,0,0,750,748,1,0,0,0,750,
+ 751,1,0,0,0,751,753,1,0,0,0,752,750,1,0,0,0,753,755,5,46,0,0,754,
+ 756,3,199,99,0,755,754,1,0,0,0,756,757,1,0,0,0,757,755,1,0,0,0,757,
+ 758,1,0,0,0,758,212,1,0,0,0,759,764,5,34,0,0,760,763,3,285,142,0,
+ 761,763,3,215,107,0,762,760,1,0,0,0,762,761,1,0,0,0,763,766,1,0,
+ 0,0,764,762,1,0,0,0,764,765,1,0,0,0,765,767,1,0,0,0,766,764,1,0,
+ 0,0,767,778,5,34,0,0,768,773,5,39,0,0,769,772,3,265,132,0,770,772,
+ 3,215,107,0,771,769,1,0,0,0,771,770,1,0,0,0,772,775,1,0,0,0,773,
+ 771,1,0,0,0,773,774,1,0,0,0,774,776,1,0,0,0,775,773,1,0,0,0,776,
+ 778,5,39,0,0,777,759,1,0,0,0,777,768,1,0,0,0,778,214,1,0,0,0,779,
+ 797,5,92,0,0,780,798,7,24,0,0,781,782,7,0,0,0,782,783,3,197,98,0,
+ 783,784,3,197,98,0,784,785,3,197,98,0,785,786,3,197,98,0,786,798,
+ 1,0,0,0,787,788,7,0,0,0,788,789,3,197,98,0,789,790,3,197,98,0,790,
+ 791,3,197,98,0,791,792,3,197,98,0,792,793,3,197,98,0,793,794,3,197,
+ 98,0,794,795,3,197,98,0,795,796,3,197,98,0,796,798,1,0,0,0,797,780,
+ 1,0,0,0,797,781,1,0,0,0,797,787,1,0,0,0,798,216,1,0,0,0,799,800,
+ 7,9,0,0,800,801,7,3,0,0,801,802,7,1,0,0,802,803,7,13,0,0,803,804,
+ 7,7,0,0,804,805,7,15,0,0,805,806,7,4,0,0,806,807,7,2,0,0,807,808,
+ 7,1,0,0,808,809,7,7,0,0,809,218,1,0,0,0,810,811,7,12,0,0,811,812,
+ 7,3,0,0,812,220,1,0,0,0,813,814,7,22,0,0,814,815,7,3,0,0,815,816,
+ 7,15,0,0,816,222,1,0,0,0,817,818,7,15,0,0,818,819,7,14,0,0,819,820,
+ 7,25,0,0,820,821,7,0,0,0,821,822,7,2,0,0,822,823,7,15,0,0,823,824,
+ 7,14,0,0,824,224,1,0,0,0,825,826,7,0,0,0,826,827,7,1,0,0,827,828,
+ 7,2,0,0,828,829,7,25,0,0,829,830,7,0,0,0,830,831,7,14,0,0,831,226,
+ 1,0,0,0,832,833,7,8,0,0,833,834,7,4,0,0,834,835,7,1,0,0,835,836,
+ 7,12,0,0,836,837,7,4,0,0,837,838,7,7,0,0,838,839,7,3,0,0,839,840,
+ 7,15,0,0,840,841,7,18,0,0,841,228,1,0,0,0,842,843,7,13,0,0,843,844,
+ 7,9,0,0,844,845,7,4,0,0,845,846,7,5,0,0,846,847,7,4,0,0,847,848,
+ 7,15,0,0,848,230,1,0,0,0,849,850,7,3,0,0,850,851,7,22,0,0,851,232,
+ 1,0,0,0,852,853,7,4,0,0,853,854,7,12,0,0,854,855,7,12,0,0,855,234,
+ 1,0,0,0,856,857,7,12,0,0,857,858,7,15,0,0,858,859,7,3,0,0,859,860,
+ 7,6,0,0,860,236,1,0,0,0,861,862,7,22,0,0,862,863,7,2,0,0,863,864,
+ 7,5,0,0,864,865,7,7,0,0,865,866,7,14,0,0,866,867,7,15,0,0,867,238,
+ 1,0,0,0,868,869,7,14,0,0,869,870,7,21,0,0,870,871,7,7,0,0,871,872,
+ 7,15,0,0,872,873,7,4,0,0,873,874,7,9,0,0,874,875,7,7,0,0,875,240,
+ 1,0,0,0,876,880,3,243,121,0,877,879,3,245,122,0,878,877,1,0,0,0,
+ 879,882,1,0,0,0,880,878,1,0,0,0,880,881,1,0,0,0,881,242,1,0,0,0,
+ 882,880,1,0,0,0,883,886,3,293,146,0,884,886,3,281,140,0,885,883,
+ 1,0,0,0,885,884,1,0,0,0,886,244,1,0,0,0,887,890,3,261,130,0,888,
+ 890,3,277,138,0,889,887,1,0,0,0,889,888,1,0,0,0,890,246,1,0,0,0,
+ 891,895,5,96,0,0,892,894,3,257,128,0,893,892,1,0,0,0,894,897,1,0,
+ 0,0,895,893,1,0,0,0,895,896,1,0,0,0,896,898,1,0,0,0,897,895,1,0,
+ 0,0,898,900,5,96,0,0,899,891,1,0,0,0,900,901,1,0,0,0,901,899,1,0,
+ 0,0,901,902,1,0,0,0,902,248,1,0,0,0,903,905,3,251,125,0,904,903,
+ 1,0,0,0,905,906,1,0,0,0,906,904,1,0,0,0,906,907,1,0,0,0,907,250,
+ 1,0,0,0,908,921,3,279,139,0,909,921,3,283,141,0,910,921,3,287,143,
+ 0,911,921,3,289,144,0,912,921,3,255,127,0,913,921,3,275,137,0,914,
+ 921,3,273,136,0,915,921,3,271,135,0,916,921,3,259,129,0,917,921,
+ 3,291,145,0,918,921,7,26,0,0,919,921,3,253,126,0,920,908,1,0,0,0,
+ 920,909,1,0,0,0,920,910,1,0,0,0,920,911,1,0,0,0,920,912,1,0,0,0,
+ 920,913,1,0,0,0,920,914,1,0,0,0,920,915,1,0,0,0,920,916,1,0,0,0,
+ 920,917,1,0,0,0,920,918,1,0,0,0,920,919,1,0,0,0,921,252,1,0,0,0,
+ 922,923,5,47,0,0,923,924,5,42,0,0,924,930,1,0,0,0,925,929,3,263,
+ 131,0,926,927,5,42,0,0,927,929,3,269,134,0,928,925,1,0,0,0,928,926,
+ 1,0,0,0,929,932,1,0,0,0,930,928,1,0,0,0,930,931,1,0,0,0,931,933,
+ 1,0,0,0,932,930,1,0,0,0,933,934,5,42,0,0,934,952,5,47,0,0,935,936,
+ 5,47,0,0,936,937,5,47,0,0,937,941,1,0,0,0,938,940,3,267,133,0,939,
+ 938,1,0,0,0,940,943,1,0,0,0,941,939,1,0,0,0,941,942,1,0,0,0,942,
+ 945,1,0,0,0,943,941,1,0,0,0,944,946,3,275,137,0,945,944,1,0,0,0,
+ 945,946,1,0,0,0,946,949,1,0,0,0,947,950,3,287,143,0,948,950,5,0,
+ 0,1,949,947,1,0,0,0,949,948,1,0,0,0,950,952,1,0,0,0,951,922,1,0,
+ 0,0,951,935,1,0,0,0,952,254,1,0,0,0,953,954,7,27,0,0,954,256,1,0,
+ 0,0,955,956,8,28,0,0,956,258,1,0,0,0,957,958,7,29,0,0,958,260,1,
+ 0,0,0,959,960,7,30,0,0,960,262,1,0,0,0,961,962,8,31,0,0,962,264,
+ 1,0,0,0,963,964,8,32,0,0,964,266,1,0,0,0,965,966,8,33,0,0,966,268,
+ 1,0,0,0,967,968,8,34,0,0,968,270,1,0,0,0,969,970,7,35,0,0,970,272,
+ 1,0,0,0,971,972,7,36,0,0,972,274,1,0,0,0,973,974,7,37,0,0,974,276,
+ 1,0,0,0,975,976,7,38,0,0,976,278,1,0,0,0,977,978,7,39,0,0,978,280,
+ 1,0,0,0,979,980,7,40,0,0,980,282,1,0,0,0,981,982,7,41,0,0,982,284,
+ 1,0,0,0,983,984,8,42,0,0,984,286,1,0,0,0,985,986,7,43,0,0,986,288,
+ 1,0,0,0,987,988,7,44,0,0,988,290,1,0,0,0,989,990,7,45,0,0,990,292,
+ 1,0,0,0,991,992,7,46,0,0,992,294,1,0,0,0,38,0,671,678,681,689,692,
+ 696,700,704,710,717,722,728,734,736,740,745,750,757,762,764,771,
+ 773,777,797,880,885,889,895,901,906,920,928,930,941,945,949,951,
+ 0
+ ]
+
+class CypherLexer(Lexer):
+
+ atn = ATNDeserializer().deserialize(serializedATN())
+
+ decisionsToDFA = [ DFA(ds, i) for i, ds in enumerate(atn.decisionToState) ]
+
+ T__0 = 1
+ T__1 = 2
+ T__2 = 3
+ T__3 = 4
+ T__4 = 5
+ T__5 = 6
+ T__6 = 7
+ T__7 = 8
+ T__8 = 9
+ T__9 = 10
+ T__10 = 11
+ T__11 = 12
+ T__12 = 13
+ T__13 = 14
+ T__14 = 15
+ T__15 = 16
+ T__16 = 17
+ T__17 = 18
+ T__18 = 19
+ T__19 = 20
+ T__20 = 21
+ T__21 = 22
+ T__22 = 23
+ T__23 = 24
+ T__24 = 25
+ T__25 = 26
+ T__26 = 27
+ T__27 = 28
+ T__28 = 29
+ T__29 = 30
+ T__30 = 31
+ T__31 = 32
+ T__32 = 33
+ T__33 = 34
+ T__34 = 35
+ T__35 = 36
+ T__36 = 37
+ T__37 = 38
+ T__38 = 39
+ T__39 = 40
+ T__40 = 41
+ T__41 = 42
+ T__42 = 43
+ T__43 = 44
+ T__44 = 45
+ UNION = 46
+ ALL = 47
+ OPTIONAL = 48
+ MATCH = 49
+ UNWIND = 50
+ AS = 51
+ MERGE = 52
+ ON = 53
+ CREATE = 54
+ SET = 55
+ DETACH = 56
+ DELETE = 57
+ REMOVE = 58
+ CALL = 59
+ YIELD = 60
+ WITH = 61
+ RETURN = 62
+ DISTINCT = 63
+ ORDER = 64
+ BY = 65
+ L_SKIP = 66
+ LIMIT = 67
+ ASCENDING = 68
+ ASC = 69
+ DESCENDING = 70
+ DESC = 71
+ WHERE = 72
+ OR = 73
+ XOR = 74
+ AND = 75
+ NOT = 76
+ STARTS = 77
+ ENDS = 78
+ CONTAINS = 79
+ IN = 80
+ IS = 81
+ NULL = 82
+ COUNT = 83
+ CASE = 84
+ ELSE = 85
+ END = 86
+ WHEN = 87
+ THEN = 88
+ ANY = 89
+ NONE = 90
+ SINGLE = 91
+ EXISTS = 92
+ TRUE = 93
+ FALSE = 94
+ HexInteger = 95
+ DecimalInteger = 96
+ OctalInteger = 97
+ HexLetter = 98
+ HexDigit = 99
+ Digit = 100
+ NonZeroDigit = 101
+ NonZeroOctDigit = 102
+ OctDigit = 103
+ ZeroDigit = 104
+ ExponentDecimalReal = 105
+ RegularDecimalReal = 106
+ StringLiteral = 107
+ EscapedChar = 108
+ CONSTRAINT = 109
+ DO = 110
+ FOR = 111
+ REQUIRE = 112
+ UNIQUE = 113
+ MANDATORY = 114
+ SCALAR = 115
+ OF = 116
+ ADD = 117
+ DROP = 118
+ FILTER = 119
+ EXTRACT = 120
+ UnescapedSymbolicName = 121
+ IdentifierStart = 122
+ IdentifierPart = 123
+ EscapedSymbolicName = 124
+ SP = 125
+ WHITESPACE = 126
+ Comment = 127
+
+ channelNames = [ u"DEFAULT_TOKEN_CHANNEL", u"HIDDEN" ]
+
+ modeNames = [ "DEFAULT_MODE" ]
+
+ literalNames = [ "",
+ "';'", "','", "'='", "'+='", "'*'", "'('", "')'", "'['", "']'",
+ "':'", "'|'", "'..'", "'<>'", "'<'", "'>'", "'<='", "'>='",
+ "'+'", "'-'", "'/'", "'%'", "'^'", "'.'", "'{'", "'}'", "'$'",
+ "'\\u27E8'", "'\\u3008'", "'\\uFE64'", "'\\uFF1C'", "'\\u27E9'",
+ "'\\u3009'", "'\\uFE65'", "'\\uFF1E'", "'\\u00AD'", "'\\u2010'",
+ "'\\u2011'", "'\\u2012'", "'\\u2013'", "'\\u2014'", "'\\u2015'",
+ "'\\u2212'", "'\\uFE58'", "'\\uFE63'", "'\\uFF0D'", "'0'" ]
+
+ symbolicNames = [ "",
+ "UNION", "ALL", "OPTIONAL", "MATCH", "UNWIND", "AS", "MERGE",
+ "ON", "CREATE", "SET", "DETACH", "DELETE", "REMOVE", "CALL",
+ "YIELD", "WITH", "RETURN", "DISTINCT", "ORDER", "BY", "L_SKIP",
+ "LIMIT", "ASCENDING", "ASC", "DESCENDING", "DESC", "WHERE",
+ "OR", "XOR", "AND", "NOT", "STARTS", "ENDS", "CONTAINS", "IN",
+ "IS", "NULL", "COUNT", "CASE", "ELSE", "END", "WHEN", "THEN",
+ "ANY", "NONE", "SINGLE", "EXISTS", "TRUE", "FALSE", "HexInteger",
+ "DecimalInteger", "OctalInteger", "HexLetter", "HexDigit", "Digit",
+ "NonZeroDigit", "NonZeroOctDigit", "OctDigit", "ZeroDigit",
+ "ExponentDecimalReal", "RegularDecimalReal", "StringLiteral",
+ "EscapedChar", "CONSTRAINT", "DO", "FOR", "REQUIRE", "UNIQUE",
+ "MANDATORY", "SCALAR", "OF", "ADD", "DROP", "FILTER", "EXTRACT",
+ "UnescapedSymbolicName", "IdentifierStart", "IdentifierPart",
+ "EscapedSymbolicName", "SP", "WHITESPACE", "Comment" ]
+
+ ruleNames = [ "T__0", "T__1", "T__2", "T__3", "T__4", "T__5", "T__6",
+ "T__7", "T__8", "T__9", "T__10", "T__11", "T__12", "T__13",
+ "T__14", "T__15", "T__16", "T__17", "T__18", "T__19",
+ "T__20", "T__21", "T__22", "T__23", "T__24", "T__25",
+ "T__26", "T__27", "T__28", "T__29", "T__30", "T__31",
+ "T__32", "T__33", "T__34", "T__35", "T__36", "T__37",
+ "T__38", "T__39", "T__40", "T__41", "T__42", "T__43",
+ "T__44", "UNION", "ALL", "OPTIONAL", "MATCH", "UNWIND",
+ "AS", "MERGE", "ON", "CREATE", "SET", "DETACH", "DELETE",
+ "REMOVE", "CALL", "YIELD", "WITH", "RETURN", "DISTINCT",
+ "ORDER", "BY", "L_SKIP", "LIMIT", "ASCENDING", "ASC",
+ "DESCENDING", "DESC", "WHERE", "OR", "XOR", "AND", "NOT",
+ "STARTS", "ENDS", "CONTAINS", "IN", "IS", "NULL", "COUNT",
+ "CASE", "ELSE", "END", "WHEN", "THEN", "ANY", "NONE",
+ "SINGLE", "EXISTS", "TRUE", "FALSE", "HexInteger", "DecimalInteger",
+ "OctalInteger", "HexLetter", "HexDigit", "Digit", "NonZeroDigit",
+ "NonZeroOctDigit", "OctDigit", "ZeroDigit", "ExponentDecimalReal",
+ "RegularDecimalReal", "StringLiteral", "EscapedChar",
+ "CONSTRAINT", "DO", "FOR", "REQUIRE", "UNIQUE", "MANDATORY",
+ "SCALAR", "OF", "ADD", "DROP", "FILTER", "EXTRACT", "UnescapedSymbolicName",
+ "IdentifierStart", "IdentifierPart", "EscapedSymbolicName",
+ "SP", "WHITESPACE", "Comment", "FF", "EscapedSymbolicName_0",
+ "RS", "ID_Continue", "Comment_1", "StringLiteral_1", "Comment_3",
+ "Comment_2", "GS", "FS", "CR", "Sc", "SPACE", "Pc", "TAB",
+ "StringLiteral_0", "LF", "VT", "US", "ID_Start" ]
+
+ grammarFileName = "Cypher.g4"
+
+ def __init__(self, input=None, output:TextIO = sys.stdout):
+ super().__init__(input, output)
+ self.checkVersion("4.13.2")
+ self._interp = LexerATNSimulator(self, self.atn, self.decisionsToDFA, PredictionContextCache())
+ self._actions = None
+ self._predicates = None
+
+
diff --git a/packages/ai/src/ai/common/graph/age/_cypher/gen/CypherListener.py b/packages/ai/src/ai/common/graph/age/_cypher/gen/CypherListener.py
new file mode 100644
index 000000000..88ea5db13
--- /dev/null
+++ b/packages/ai/src/ai/common/graph/age/_cypher/gen/CypherListener.py
@@ -0,0 +1,939 @@
+# Generated from Cypher.g4 by ANTLR 4.13.2
+from antlr4 import *
+if "." in __name__:
+ from .CypherParser import CypherParser
+else:
+ from CypherParser import CypherParser
+
+# This class defines a complete listener for a parse tree produced by CypherParser.
+class CypherListener(ParseTreeListener):
+
+ # Enter a parse tree produced by CypherParser#oC_Cypher.
+ def enterOC_Cypher(self, ctx:CypherParser.OC_CypherContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_Cypher.
+ def exitOC_Cypher(self, ctx:CypherParser.OC_CypherContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_Statement.
+ def enterOC_Statement(self, ctx:CypherParser.OC_StatementContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_Statement.
+ def exitOC_Statement(self, ctx:CypherParser.OC_StatementContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_Query.
+ def enterOC_Query(self, ctx:CypherParser.OC_QueryContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_Query.
+ def exitOC_Query(self, ctx:CypherParser.OC_QueryContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_RegularQuery.
+ def enterOC_RegularQuery(self, ctx:CypherParser.OC_RegularQueryContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_RegularQuery.
+ def exitOC_RegularQuery(self, ctx:CypherParser.OC_RegularQueryContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_Union.
+ def enterOC_Union(self, ctx:CypherParser.OC_UnionContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_Union.
+ def exitOC_Union(self, ctx:CypherParser.OC_UnionContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_SingleQuery.
+ def enterOC_SingleQuery(self, ctx:CypherParser.OC_SingleQueryContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_SingleQuery.
+ def exitOC_SingleQuery(self, ctx:CypherParser.OC_SingleQueryContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_SinglePartQuery.
+ def enterOC_SinglePartQuery(self, ctx:CypherParser.OC_SinglePartQueryContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_SinglePartQuery.
+ def exitOC_SinglePartQuery(self, ctx:CypherParser.OC_SinglePartQueryContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_MultiPartQuery.
+ def enterOC_MultiPartQuery(self, ctx:CypherParser.OC_MultiPartQueryContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_MultiPartQuery.
+ def exitOC_MultiPartQuery(self, ctx:CypherParser.OC_MultiPartQueryContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_UpdatingClause.
+ def enterOC_UpdatingClause(self, ctx:CypherParser.OC_UpdatingClauseContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_UpdatingClause.
+ def exitOC_UpdatingClause(self, ctx:CypherParser.OC_UpdatingClauseContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_ReadingClause.
+ def enterOC_ReadingClause(self, ctx:CypherParser.OC_ReadingClauseContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_ReadingClause.
+ def exitOC_ReadingClause(self, ctx:CypherParser.OC_ReadingClauseContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_Match.
+ def enterOC_Match(self, ctx:CypherParser.OC_MatchContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_Match.
+ def exitOC_Match(self, ctx:CypherParser.OC_MatchContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_Unwind.
+ def enterOC_Unwind(self, ctx:CypherParser.OC_UnwindContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_Unwind.
+ def exitOC_Unwind(self, ctx:CypherParser.OC_UnwindContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_Merge.
+ def enterOC_Merge(self, ctx:CypherParser.OC_MergeContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_Merge.
+ def exitOC_Merge(self, ctx:CypherParser.OC_MergeContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_MergeAction.
+ def enterOC_MergeAction(self, ctx:CypherParser.OC_MergeActionContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_MergeAction.
+ def exitOC_MergeAction(self, ctx:CypherParser.OC_MergeActionContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_Create.
+ def enterOC_Create(self, ctx:CypherParser.OC_CreateContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_Create.
+ def exitOC_Create(self, ctx:CypherParser.OC_CreateContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_Set.
+ def enterOC_Set(self, ctx:CypherParser.OC_SetContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_Set.
+ def exitOC_Set(self, ctx:CypherParser.OC_SetContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_SetItem.
+ def enterOC_SetItem(self, ctx:CypherParser.OC_SetItemContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_SetItem.
+ def exitOC_SetItem(self, ctx:CypherParser.OC_SetItemContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_Delete.
+ def enterOC_Delete(self, ctx:CypherParser.OC_DeleteContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_Delete.
+ def exitOC_Delete(self, ctx:CypherParser.OC_DeleteContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_Remove.
+ def enterOC_Remove(self, ctx:CypherParser.OC_RemoveContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_Remove.
+ def exitOC_Remove(self, ctx:CypherParser.OC_RemoveContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_RemoveItem.
+ def enterOC_RemoveItem(self, ctx:CypherParser.OC_RemoveItemContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_RemoveItem.
+ def exitOC_RemoveItem(self, ctx:CypherParser.OC_RemoveItemContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_InQueryCall.
+ def enterOC_InQueryCall(self, ctx:CypherParser.OC_InQueryCallContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_InQueryCall.
+ def exitOC_InQueryCall(self, ctx:CypherParser.OC_InQueryCallContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_StandaloneCall.
+ def enterOC_StandaloneCall(self, ctx:CypherParser.OC_StandaloneCallContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_StandaloneCall.
+ def exitOC_StandaloneCall(self, ctx:CypherParser.OC_StandaloneCallContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_YieldItems.
+ def enterOC_YieldItems(self, ctx:CypherParser.OC_YieldItemsContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_YieldItems.
+ def exitOC_YieldItems(self, ctx:CypherParser.OC_YieldItemsContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_YieldItem.
+ def enterOC_YieldItem(self, ctx:CypherParser.OC_YieldItemContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_YieldItem.
+ def exitOC_YieldItem(self, ctx:CypherParser.OC_YieldItemContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_With.
+ def enterOC_With(self, ctx:CypherParser.OC_WithContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_With.
+ def exitOC_With(self, ctx:CypherParser.OC_WithContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_Return.
+ def enterOC_Return(self, ctx:CypherParser.OC_ReturnContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_Return.
+ def exitOC_Return(self, ctx:CypherParser.OC_ReturnContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_ProjectionBody.
+ def enterOC_ProjectionBody(self, ctx:CypherParser.OC_ProjectionBodyContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_ProjectionBody.
+ def exitOC_ProjectionBody(self, ctx:CypherParser.OC_ProjectionBodyContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_ProjectionItems.
+ def enterOC_ProjectionItems(self, ctx:CypherParser.OC_ProjectionItemsContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_ProjectionItems.
+ def exitOC_ProjectionItems(self, ctx:CypherParser.OC_ProjectionItemsContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_ProjectionItem.
+ def enterOC_ProjectionItem(self, ctx:CypherParser.OC_ProjectionItemContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_ProjectionItem.
+ def exitOC_ProjectionItem(self, ctx:CypherParser.OC_ProjectionItemContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_Order.
+ def enterOC_Order(self, ctx:CypherParser.OC_OrderContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_Order.
+ def exitOC_Order(self, ctx:CypherParser.OC_OrderContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_Skip.
+ def enterOC_Skip(self, ctx:CypherParser.OC_SkipContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_Skip.
+ def exitOC_Skip(self, ctx:CypherParser.OC_SkipContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_Limit.
+ def enterOC_Limit(self, ctx:CypherParser.OC_LimitContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_Limit.
+ def exitOC_Limit(self, ctx:CypherParser.OC_LimitContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_SortItem.
+ def enterOC_SortItem(self, ctx:CypherParser.OC_SortItemContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_SortItem.
+ def exitOC_SortItem(self, ctx:CypherParser.OC_SortItemContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_Where.
+ def enterOC_Where(self, ctx:CypherParser.OC_WhereContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_Where.
+ def exitOC_Where(self, ctx:CypherParser.OC_WhereContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_Pattern.
+ def enterOC_Pattern(self, ctx:CypherParser.OC_PatternContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_Pattern.
+ def exitOC_Pattern(self, ctx:CypherParser.OC_PatternContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_PatternPart.
+ def enterOC_PatternPart(self, ctx:CypherParser.OC_PatternPartContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_PatternPart.
+ def exitOC_PatternPart(self, ctx:CypherParser.OC_PatternPartContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_AnonymousPatternPart.
+ def enterOC_AnonymousPatternPart(self, ctx:CypherParser.OC_AnonymousPatternPartContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_AnonymousPatternPart.
+ def exitOC_AnonymousPatternPart(self, ctx:CypherParser.OC_AnonymousPatternPartContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_PatternElement.
+ def enterOC_PatternElement(self, ctx:CypherParser.OC_PatternElementContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_PatternElement.
+ def exitOC_PatternElement(self, ctx:CypherParser.OC_PatternElementContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_RelationshipsPattern.
+ def enterOC_RelationshipsPattern(self, ctx:CypherParser.OC_RelationshipsPatternContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_RelationshipsPattern.
+ def exitOC_RelationshipsPattern(self, ctx:CypherParser.OC_RelationshipsPatternContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_NodePattern.
+ def enterOC_NodePattern(self, ctx:CypherParser.OC_NodePatternContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_NodePattern.
+ def exitOC_NodePattern(self, ctx:CypherParser.OC_NodePatternContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_PatternElementChain.
+ def enterOC_PatternElementChain(self, ctx:CypherParser.OC_PatternElementChainContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_PatternElementChain.
+ def exitOC_PatternElementChain(self, ctx:CypherParser.OC_PatternElementChainContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_RelationshipPattern.
+ def enterOC_RelationshipPattern(self, ctx:CypherParser.OC_RelationshipPatternContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_RelationshipPattern.
+ def exitOC_RelationshipPattern(self, ctx:CypherParser.OC_RelationshipPatternContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_RelationshipDetail.
+ def enterOC_RelationshipDetail(self, ctx:CypherParser.OC_RelationshipDetailContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_RelationshipDetail.
+ def exitOC_RelationshipDetail(self, ctx:CypherParser.OC_RelationshipDetailContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_Properties.
+ def enterOC_Properties(self, ctx:CypherParser.OC_PropertiesContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_Properties.
+ def exitOC_Properties(self, ctx:CypherParser.OC_PropertiesContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_RelationshipTypes.
+ def enterOC_RelationshipTypes(self, ctx:CypherParser.OC_RelationshipTypesContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_RelationshipTypes.
+ def exitOC_RelationshipTypes(self, ctx:CypherParser.OC_RelationshipTypesContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_NodeLabels.
+ def enterOC_NodeLabels(self, ctx:CypherParser.OC_NodeLabelsContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_NodeLabels.
+ def exitOC_NodeLabels(self, ctx:CypherParser.OC_NodeLabelsContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_NodeLabel.
+ def enterOC_NodeLabel(self, ctx:CypherParser.OC_NodeLabelContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_NodeLabel.
+ def exitOC_NodeLabel(self, ctx:CypherParser.OC_NodeLabelContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_RangeLiteral.
+ def enterOC_RangeLiteral(self, ctx:CypherParser.OC_RangeLiteralContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_RangeLiteral.
+ def exitOC_RangeLiteral(self, ctx:CypherParser.OC_RangeLiteralContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_LabelName.
+ def enterOC_LabelName(self, ctx:CypherParser.OC_LabelNameContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_LabelName.
+ def exitOC_LabelName(self, ctx:CypherParser.OC_LabelNameContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_RelTypeName.
+ def enterOC_RelTypeName(self, ctx:CypherParser.OC_RelTypeNameContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_RelTypeName.
+ def exitOC_RelTypeName(self, ctx:CypherParser.OC_RelTypeNameContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_PropertyExpression.
+ def enterOC_PropertyExpression(self, ctx:CypherParser.OC_PropertyExpressionContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_PropertyExpression.
+ def exitOC_PropertyExpression(self, ctx:CypherParser.OC_PropertyExpressionContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_Expression.
+ def enterOC_Expression(self, ctx:CypherParser.OC_ExpressionContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_Expression.
+ def exitOC_Expression(self, ctx:CypherParser.OC_ExpressionContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_OrExpression.
+ def enterOC_OrExpression(self, ctx:CypherParser.OC_OrExpressionContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_OrExpression.
+ def exitOC_OrExpression(self, ctx:CypherParser.OC_OrExpressionContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_XorExpression.
+ def enterOC_XorExpression(self, ctx:CypherParser.OC_XorExpressionContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_XorExpression.
+ def exitOC_XorExpression(self, ctx:CypherParser.OC_XorExpressionContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_AndExpression.
+ def enterOC_AndExpression(self, ctx:CypherParser.OC_AndExpressionContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_AndExpression.
+ def exitOC_AndExpression(self, ctx:CypherParser.OC_AndExpressionContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_NotExpression.
+ def enterOC_NotExpression(self, ctx:CypherParser.OC_NotExpressionContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_NotExpression.
+ def exitOC_NotExpression(self, ctx:CypherParser.OC_NotExpressionContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_ComparisonExpression.
+ def enterOC_ComparisonExpression(self, ctx:CypherParser.OC_ComparisonExpressionContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_ComparisonExpression.
+ def exitOC_ComparisonExpression(self, ctx:CypherParser.OC_ComparisonExpressionContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_PartialComparisonExpression.
+ def enterOC_PartialComparisonExpression(self, ctx:CypherParser.OC_PartialComparisonExpressionContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_PartialComparisonExpression.
+ def exitOC_PartialComparisonExpression(self, ctx:CypherParser.OC_PartialComparisonExpressionContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_StringListNullPredicateExpression.
+ def enterOC_StringListNullPredicateExpression(self, ctx:CypherParser.OC_StringListNullPredicateExpressionContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_StringListNullPredicateExpression.
+ def exitOC_StringListNullPredicateExpression(self, ctx:CypherParser.OC_StringListNullPredicateExpressionContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_StringPredicateExpression.
+ def enterOC_StringPredicateExpression(self, ctx:CypherParser.OC_StringPredicateExpressionContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_StringPredicateExpression.
+ def exitOC_StringPredicateExpression(self, ctx:CypherParser.OC_StringPredicateExpressionContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_ListPredicateExpression.
+ def enterOC_ListPredicateExpression(self, ctx:CypherParser.OC_ListPredicateExpressionContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_ListPredicateExpression.
+ def exitOC_ListPredicateExpression(self, ctx:CypherParser.OC_ListPredicateExpressionContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_NullPredicateExpression.
+ def enterOC_NullPredicateExpression(self, ctx:CypherParser.OC_NullPredicateExpressionContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_NullPredicateExpression.
+ def exitOC_NullPredicateExpression(self, ctx:CypherParser.OC_NullPredicateExpressionContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_AddOrSubtractExpression.
+ def enterOC_AddOrSubtractExpression(self, ctx:CypherParser.OC_AddOrSubtractExpressionContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_AddOrSubtractExpression.
+ def exitOC_AddOrSubtractExpression(self, ctx:CypherParser.OC_AddOrSubtractExpressionContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_MultiplyDivideModuloExpression.
+ def enterOC_MultiplyDivideModuloExpression(self, ctx:CypherParser.OC_MultiplyDivideModuloExpressionContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_MultiplyDivideModuloExpression.
+ def exitOC_MultiplyDivideModuloExpression(self, ctx:CypherParser.OC_MultiplyDivideModuloExpressionContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_PowerOfExpression.
+ def enterOC_PowerOfExpression(self, ctx:CypherParser.OC_PowerOfExpressionContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_PowerOfExpression.
+ def exitOC_PowerOfExpression(self, ctx:CypherParser.OC_PowerOfExpressionContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_UnaryAddOrSubtractExpression.
+ def enterOC_UnaryAddOrSubtractExpression(self, ctx:CypherParser.OC_UnaryAddOrSubtractExpressionContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_UnaryAddOrSubtractExpression.
+ def exitOC_UnaryAddOrSubtractExpression(self, ctx:CypherParser.OC_UnaryAddOrSubtractExpressionContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_NonArithmeticOperatorExpression.
+ def enterOC_NonArithmeticOperatorExpression(self, ctx:CypherParser.OC_NonArithmeticOperatorExpressionContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_NonArithmeticOperatorExpression.
+ def exitOC_NonArithmeticOperatorExpression(self, ctx:CypherParser.OC_NonArithmeticOperatorExpressionContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_ListOperatorExpression.
+ def enterOC_ListOperatorExpression(self, ctx:CypherParser.OC_ListOperatorExpressionContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_ListOperatorExpression.
+ def exitOC_ListOperatorExpression(self, ctx:CypherParser.OC_ListOperatorExpressionContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_PropertyLookup.
+ def enterOC_PropertyLookup(self, ctx:CypherParser.OC_PropertyLookupContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_PropertyLookup.
+ def exitOC_PropertyLookup(self, ctx:CypherParser.OC_PropertyLookupContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_Atom.
+ def enterOC_Atom(self, ctx:CypherParser.OC_AtomContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_Atom.
+ def exitOC_Atom(self, ctx:CypherParser.OC_AtomContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_CaseExpression.
+ def enterOC_CaseExpression(self, ctx:CypherParser.OC_CaseExpressionContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_CaseExpression.
+ def exitOC_CaseExpression(self, ctx:CypherParser.OC_CaseExpressionContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_CaseAlternative.
+ def enterOC_CaseAlternative(self, ctx:CypherParser.OC_CaseAlternativeContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_CaseAlternative.
+ def exitOC_CaseAlternative(self, ctx:CypherParser.OC_CaseAlternativeContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_ListComprehension.
+ def enterOC_ListComprehension(self, ctx:CypherParser.OC_ListComprehensionContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_ListComprehension.
+ def exitOC_ListComprehension(self, ctx:CypherParser.OC_ListComprehensionContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_PatternComprehension.
+ def enterOC_PatternComprehension(self, ctx:CypherParser.OC_PatternComprehensionContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_PatternComprehension.
+ def exitOC_PatternComprehension(self, ctx:CypherParser.OC_PatternComprehensionContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_Quantifier.
+ def enterOC_Quantifier(self, ctx:CypherParser.OC_QuantifierContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_Quantifier.
+ def exitOC_Quantifier(self, ctx:CypherParser.OC_QuantifierContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_FilterExpression.
+ def enterOC_FilterExpression(self, ctx:CypherParser.OC_FilterExpressionContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_FilterExpression.
+ def exitOC_FilterExpression(self, ctx:CypherParser.OC_FilterExpressionContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_PatternPredicate.
+ def enterOC_PatternPredicate(self, ctx:CypherParser.OC_PatternPredicateContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_PatternPredicate.
+ def exitOC_PatternPredicate(self, ctx:CypherParser.OC_PatternPredicateContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_ParenthesizedExpression.
+ def enterOC_ParenthesizedExpression(self, ctx:CypherParser.OC_ParenthesizedExpressionContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_ParenthesizedExpression.
+ def exitOC_ParenthesizedExpression(self, ctx:CypherParser.OC_ParenthesizedExpressionContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_IdInColl.
+ def enterOC_IdInColl(self, ctx:CypherParser.OC_IdInCollContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_IdInColl.
+ def exitOC_IdInColl(self, ctx:CypherParser.OC_IdInCollContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_FunctionInvocation.
+ def enterOC_FunctionInvocation(self, ctx:CypherParser.OC_FunctionInvocationContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_FunctionInvocation.
+ def exitOC_FunctionInvocation(self, ctx:CypherParser.OC_FunctionInvocationContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_FunctionName.
+ def enterOC_FunctionName(self, ctx:CypherParser.OC_FunctionNameContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_FunctionName.
+ def exitOC_FunctionName(self, ctx:CypherParser.OC_FunctionNameContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_ExistentialSubquery.
+ def enterOC_ExistentialSubquery(self, ctx:CypherParser.OC_ExistentialSubqueryContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_ExistentialSubquery.
+ def exitOC_ExistentialSubquery(self, ctx:CypherParser.OC_ExistentialSubqueryContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_ExplicitProcedureInvocation.
+ def enterOC_ExplicitProcedureInvocation(self, ctx:CypherParser.OC_ExplicitProcedureInvocationContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_ExplicitProcedureInvocation.
+ def exitOC_ExplicitProcedureInvocation(self, ctx:CypherParser.OC_ExplicitProcedureInvocationContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_ImplicitProcedureInvocation.
+ def enterOC_ImplicitProcedureInvocation(self, ctx:CypherParser.OC_ImplicitProcedureInvocationContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_ImplicitProcedureInvocation.
+ def exitOC_ImplicitProcedureInvocation(self, ctx:CypherParser.OC_ImplicitProcedureInvocationContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_ProcedureResultField.
+ def enterOC_ProcedureResultField(self, ctx:CypherParser.OC_ProcedureResultFieldContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_ProcedureResultField.
+ def exitOC_ProcedureResultField(self, ctx:CypherParser.OC_ProcedureResultFieldContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_ProcedureName.
+ def enterOC_ProcedureName(self, ctx:CypherParser.OC_ProcedureNameContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_ProcedureName.
+ def exitOC_ProcedureName(self, ctx:CypherParser.OC_ProcedureNameContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_Namespace.
+ def enterOC_Namespace(self, ctx:CypherParser.OC_NamespaceContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_Namespace.
+ def exitOC_Namespace(self, ctx:CypherParser.OC_NamespaceContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_Variable.
+ def enterOC_Variable(self, ctx:CypherParser.OC_VariableContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_Variable.
+ def exitOC_Variable(self, ctx:CypherParser.OC_VariableContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_Literal.
+ def enterOC_Literal(self, ctx:CypherParser.OC_LiteralContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_Literal.
+ def exitOC_Literal(self, ctx:CypherParser.OC_LiteralContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_BooleanLiteral.
+ def enterOC_BooleanLiteral(self, ctx:CypherParser.OC_BooleanLiteralContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_BooleanLiteral.
+ def exitOC_BooleanLiteral(self, ctx:CypherParser.OC_BooleanLiteralContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_NumberLiteral.
+ def enterOC_NumberLiteral(self, ctx:CypherParser.OC_NumberLiteralContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_NumberLiteral.
+ def exitOC_NumberLiteral(self, ctx:CypherParser.OC_NumberLiteralContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_IntegerLiteral.
+ def enterOC_IntegerLiteral(self, ctx:CypherParser.OC_IntegerLiteralContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_IntegerLiteral.
+ def exitOC_IntegerLiteral(self, ctx:CypherParser.OC_IntegerLiteralContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_DoubleLiteral.
+ def enterOC_DoubleLiteral(self, ctx:CypherParser.OC_DoubleLiteralContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_DoubleLiteral.
+ def exitOC_DoubleLiteral(self, ctx:CypherParser.OC_DoubleLiteralContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_ListLiteral.
+ def enterOC_ListLiteral(self, ctx:CypherParser.OC_ListLiteralContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_ListLiteral.
+ def exitOC_ListLiteral(self, ctx:CypherParser.OC_ListLiteralContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_MapLiteral.
+ def enterOC_MapLiteral(self, ctx:CypherParser.OC_MapLiteralContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_MapLiteral.
+ def exitOC_MapLiteral(self, ctx:CypherParser.OC_MapLiteralContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_PropertyKeyName.
+ def enterOC_PropertyKeyName(self, ctx:CypherParser.OC_PropertyKeyNameContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_PropertyKeyName.
+ def exitOC_PropertyKeyName(self, ctx:CypherParser.OC_PropertyKeyNameContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_Parameter.
+ def enterOC_Parameter(self, ctx:CypherParser.OC_ParameterContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_Parameter.
+ def exitOC_Parameter(self, ctx:CypherParser.OC_ParameterContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_SchemaName.
+ def enterOC_SchemaName(self, ctx:CypherParser.OC_SchemaNameContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_SchemaName.
+ def exitOC_SchemaName(self, ctx:CypherParser.OC_SchemaNameContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_ReservedWord.
+ def enterOC_ReservedWord(self, ctx:CypherParser.OC_ReservedWordContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_ReservedWord.
+ def exitOC_ReservedWord(self, ctx:CypherParser.OC_ReservedWordContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_SymbolicName.
+ def enterOC_SymbolicName(self, ctx:CypherParser.OC_SymbolicNameContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_SymbolicName.
+ def exitOC_SymbolicName(self, ctx:CypherParser.OC_SymbolicNameContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_LeftArrowHead.
+ def enterOC_LeftArrowHead(self, ctx:CypherParser.OC_LeftArrowHeadContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_LeftArrowHead.
+ def exitOC_LeftArrowHead(self, ctx:CypherParser.OC_LeftArrowHeadContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_RightArrowHead.
+ def enterOC_RightArrowHead(self, ctx:CypherParser.OC_RightArrowHeadContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_RightArrowHead.
+ def exitOC_RightArrowHead(self, ctx:CypherParser.OC_RightArrowHeadContext):
+ pass
+
+
+ # Enter a parse tree produced by CypherParser#oC_Dash.
+ def enterOC_Dash(self, ctx:CypherParser.OC_DashContext):
+ pass
+
+ # Exit a parse tree produced by CypherParser#oC_Dash.
+ def exitOC_Dash(self, ctx:CypherParser.OC_DashContext):
+ pass
+
+
+
+del CypherParser
\ No newline at end of file
diff --git a/packages/ai/src/ai/common/graph/age/_cypher/gen/CypherParser.py b/packages/ai/src/ai/common/graph/age/_cypher/gen/CypherParser.py
new file mode 100644
index 000000000..168c61e20
--- /dev/null
+++ b/packages/ai/src/ai/common/graph/age/_cypher/gen/CypherParser.py
@@ -0,0 +1,10440 @@
+# Generated from Cypher.g4 by ANTLR 4.13.2
+# encoding: utf-8
+from antlr4 import *
+from io import StringIO
+import sys
+if sys.version_info[1] > 5:
+ from typing import TextIO
+else:
+ from typing.io import TextIO
+
+def serializedATN():
+ return [
+ 4,1,127,1575,2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,2,6,
+ 7,6,2,7,7,7,2,8,7,8,2,9,7,9,2,10,7,10,2,11,7,11,2,12,7,12,2,13,7,
+ 13,2,14,7,14,2,15,7,15,2,16,7,16,2,17,7,17,2,18,7,18,2,19,7,19,2,
+ 20,7,20,2,21,7,21,2,22,7,22,2,23,7,23,2,24,7,24,2,25,7,25,2,26,7,
+ 26,2,27,7,27,2,28,7,28,2,29,7,29,2,30,7,30,2,31,7,31,2,32,7,32,2,
+ 33,7,33,2,34,7,34,2,35,7,35,2,36,7,36,2,37,7,37,2,38,7,38,2,39,7,
+ 39,2,40,7,40,2,41,7,41,2,42,7,42,2,43,7,43,2,44,7,44,2,45,7,45,2,
+ 46,7,46,2,47,7,47,2,48,7,48,2,49,7,49,2,50,7,50,2,51,7,51,2,52,7,
+ 52,2,53,7,53,2,54,7,54,2,55,7,55,2,56,7,56,2,57,7,57,2,58,7,58,2,
+ 59,7,59,2,60,7,60,2,61,7,61,2,62,7,62,2,63,7,63,2,64,7,64,2,65,7,
+ 65,2,66,7,66,2,67,7,67,2,68,7,68,2,69,7,69,2,70,7,70,2,71,7,71,2,
+ 72,7,72,2,73,7,73,2,74,7,74,2,75,7,75,2,76,7,76,2,77,7,77,2,78,7,
+ 78,2,79,7,79,2,80,7,80,2,81,7,81,2,82,7,82,2,83,7,83,2,84,7,84,2,
+ 85,7,85,2,86,7,86,2,87,7,87,2,88,7,88,2,89,7,89,2,90,7,90,2,91,7,
+ 91,2,92,7,92,2,93,7,93,2,94,7,94,2,95,7,95,2,96,7,96,2,97,7,97,2,
+ 98,7,98,2,99,7,99,2,100,7,100,2,101,7,101,2,102,7,102,1,0,3,0,208,
+ 8,0,1,0,1,0,3,0,212,8,0,1,0,3,0,215,8,0,1,0,3,0,218,8,0,1,0,1,0,
+ 1,1,1,1,1,2,1,2,3,2,226,8,2,1,3,1,3,3,3,230,8,3,1,3,5,3,233,8,3,
+ 10,3,12,3,236,9,3,1,4,1,4,1,4,1,4,3,4,242,8,4,1,4,1,4,1,4,3,4,247,
+ 8,4,1,4,3,4,250,8,4,1,5,1,5,3,5,254,8,5,1,6,1,6,3,6,258,8,6,5,6,
+ 260,8,6,10,6,12,6,263,9,6,1,6,1,6,1,6,3,6,268,8,6,5,6,270,8,6,10,
+ 6,12,6,273,9,6,1,6,1,6,3,6,277,8,6,1,6,5,6,280,8,6,10,6,12,6,283,
+ 9,6,1,6,3,6,286,8,6,1,6,3,6,289,8,6,3,6,291,8,6,1,7,1,7,3,7,295,
+ 8,7,5,7,297,8,7,10,7,12,7,300,9,7,1,7,1,7,3,7,304,8,7,5,7,306,8,
+ 7,10,7,12,7,309,9,7,1,7,1,7,3,7,313,8,7,4,7,315,8,7,11,7,12,7,316,
+ 1,7,1,7,1,8,1,8,1,8,1,8,1,8,3,8,326,8,8,1,9,1,9,1,9,3,9,331,8,9,
+ 1,10,1,10,3,10,335,8,10,1,10,1,10,3,10,339,8,10,1,10,1,10,3,10,343,
+ 8,10,1,10,3,10,346,8,10,1,11,1,11,3,11,350,8,11,1,11,1,11,1,11,1,
+ 11,1,11,1,11,1,12,1,12,3,12,360,8,12,1,12,1,12,1,12,5,12,365,8,12,
+ 10,12,12,12,368,9,12,1,13,1,13,1,13,1,13,1,13,1,13,1,13,1,13,1,13,
+ 1,13,3,13,380,8,13,1,14,1,14,3,14,384,8,14,1,14,1,14,1,15,1,15,3,
+ 15,390,8,15,1,15,1,15,3,15,394,8,15,1,15,1,15,3,15,398,8,15,1,15,
+ 5,15,401,8,15,10,15,12,15,404,9,15,1,16,1,16,3,16,408,8,16,1,16,
+ 1,16,3,16,412,8,16,1,16,1,16,1,16,1,16,3,16,418,8,16,1,16,1,16,3,
+ 16,422,8,16,1,16,1,16,1,16,1,16,3,16,428,8,16,1,16,1,16,3,16,432,
+ 8,16,1,16,1,16,1,16,1,16,3,16,438,8,16,1,16,1,16,3,16,442,8,16,1,
+ 17,1,17,3,17,446,8,17,1,17,1,17,3,17,450,8,17,1,17,1,17,3,17,454,
+ 8,17,1,17,1,17,3,17,458,8,17,1,17,5,17,461,8,17,10,17,12,17,464,
+ 9,17,1,18,1,18,1,18,1,18,3,18,470,8,18,1,18,1,18,3,18,474,8,18,1,
+ 18,5,18,477,8,18,10,18,12,18,480,9,18,1,19,1,19,1,19,1,19,3,19,486,
+ 8,19,1,20,1,20,1,20,1,20,3,20,492,8,20,1,20,1,20,1,20,3,20,497,8,
+ 20,1,21,1,21,1,21,1,21,3,21,503,8,21,1,21,3,21,506,8,21,1,21,1,21,
+ 1,21,1,21,3,21,512,8,21,3,21,514,8,21,1,22,1,22,3,22,518,8,22,1,
+ 22,1,22,3,22,522,8,22,1,22,5,22,525,8,22,10,22,12,22,528,9,22,1,
+ 22,3,22,531,8,22,1,22,3,22,534,8,22,1,23,1,23,1,23,1,23,1,23,3,23,
+ 541,8,23,1,23,1,23,1,24,1,24,1,24,3,24,548,8,24,1,24,3,24,551,8,
+ 24,1,25,1,25,1,25,1,26,3,26,557,8,26,1,26,3,26,560,8,26,1,26,1,26,
+ 1,26,1,26,3,26,566,8,26,1,26,1,26,3,26,570,8,26,1,26,1,26,3,26,574,
+ 8,26,1,27,1,27,3,27,578,8,27,1,27,1,27,3,27,582,8,27,1,27,5,27,585,
+ 8,27,10,27,12,27,588,9,27,1,27,1,27,3,27,592,8,27,1,27,1,27,3,27,
+ 596,8,27,1,27,5,27,599,8,27,10,27,12,27,602,9,27,3,27,604,8,27,1,
+ 28,1,28,1,28,1,28,1,28,1,28,1,28,3,28,613,8,28,1,29,1,29,1,29,1,
+ 29,1,29,1,29,1,29,3,29,622,8,29,1,29,5,29,625,8,29,10,29,12,29,628,
+ 9,29,1,30,1,30,1,30,1,30,1,31,1,31,1,31,1,31,1,32,1,32,3,32,640,
+ 8,32,1,32,3,32,643,8,32,1,33,1,33,1,33,1,33,1,34,1,34,3,34,651,8,
+ 34,1,34,1,34,3,34,655,8,34,1,34,5,34,658,8,34,10,34,12,34,661,9,
+ 34,1,35,1,35,3,35,665,8,35,1,35,1,35,3,35,669,8,35,1,35,1,35,1,35,
+ 3,35,674,8,35,1,36,1,36,1,37,1,37,3,37,680,8,37,1,37,5,37,683,8,
+ 37,10,37,12,37,686,9,37,1,37,1,37,1,37,1,37,3,37,692,8,37,1,38,1,
+ 38,3,38,696,8,38,1,38,4,38,699,8,38,11,38,12,38,700,1,39,1,39,3,
+ 39,705,8,39,1,39,1,39,3,39,709,8,39,3,39,711,8,39,1,39,1,39,3,39,
+ 715,8,39,3,39,717,8,39,1,39,1,39,3,39,721,8,39,3,39,723,8,39,1,39,
+ 1,39,1,40,1,40,3,40,729,8,40,1,40,1,40,1,41,1,41,3,41,735,8,41,1,
+ 41,1,41,3,41,739,8,41,1,41,3,41,742,8,41,1,41,3,41,745,8,41,1,41,
+ 1,41,3,41,749,8,41,1,41,1,41,1,41,1,41,3,41,755,8,41,1,41,1,41,3,
+ 41,759,8,41,1,41,3,41,762,8,41,1,41,3,41,765,8,41,1,41,1,41,1,41,
+ 1,41,3,41,771,8,41,1,41,3,41,774,8,41,1,41,3,41,777,8,41,1,41,1,
+ 41,3,41,781,8,41,1,41,1,41,1,41,1,41,3,41,787,8,41,1,41,3,41,790,
+ 8,41,1,41,3,41,793,8,41,1,41,1,41,3,41,797,8,41,1,42,1,42,3,42,801,
+ 8,42,1,42,1,42,3,42,805,8,42,3,42,807,8,42,1,42,1,42,3,42,811,8,
+ 42,3,42,813,8,42,1,42,3,42,816,8,42,1,42,1,42,3,42,820,8,42,3,42,
+ 822,8,42,1,42,1,42,1,43,1,43,3,43,828,8,43,1,44,1,44,3,44,832,8,
+ 44,1,44,1,44,3,44,836,8,44,1,44,1,44,3,44,840,8,44,1,44,3,44,843,
+ 8,44,1,44,5,44,846,8,44,10,44,12,44,849,9,44,1,45,1,45,3,45,853,
+ 8,45,1,45,5,45,856,8,45,10,45,12,45,859,9,45,1,46,1,46,3,46,863,
+ 8,46,1,46,1,46,1,47,1,47,3,47,869,8,47,1,47,1,47,3,47,873,8,47,3,
+ 47,875,8,47,1,47,1,47,3,47,879,8,47,1,47,1,47,3,47,883,8,47,3,47,
+ 885,8,47,3,47,887,8,47,1,48,1,48,1,49,1,49,1,50,1,50,3,50,895,8,
+ 50,1,50,4,50,898,8,50,11,50,12,50,899,1,51,1,51,1,52,1,52,1,52,1,
+ 52,1,52,5,52,909,8,52,10,52,12,52,912,9,52,1,53,1,53,1,53,1,53,1,
+ 53,5,53,919,8,53,10,53,12,53,922,9,53,1,54,1,54,1,54,1,54,1,54,5,
+ 54,929,8,54,10,54,12,54,932,9,54,1,55,1,55,3,55,936,8,55,5,55,938,
+ 8,55,10,55,12,55,941,9,55,1,55,1,55,1,56,1,56,3,56,947,8,56,1,56,
+ 5,56,950,8,56,10,56,12,56,953,9,56,1,57,1,57,3,57,957,8,57,1,57,
+ 1,57,1,57,3,57,962,8,57,1,57,1,57,1,57,3,57,967,8,57,1,57,1,57,1,
+ 57,3,57,972,8,57,1,57,1,57,1,57,3,57,977,8,57,1,57,1,57,1,57,3,57,
+ 982,8,57,1,57,3,57,985,8,57,1,58,1,58,1,58,1,58,5,58,991,8,58,10,
+ 58,12,58,994,9,58,1,59,1,59,1,59,1,59,1,59,1,59,1,59,1,59,1,59,1,
+ 59,3,59,1006,8,59,1,59,3,59,1009,8,59,1,59,1,59,1,60,1,60,1,60,3,
+ 60,1016,8,60,1,60,1,60,1,61,1,61,1,61,1,61,1,61,1,61,1,61,1,61,1,
+ 61,1,61,3,61,1030,8,61,1,62,1,62,3,62,1034,8,62,1,62,1,62,3,62,1038,
+ 8,62,1,62,1,62,3,62,1042,8,62,1,62,1,62,3,62,1046,8,62,1,62,5,62,
+ 1049,8,62,10,62,12,62,1052,9,62,1,63,1,63,3,63,1056,8,63,1,63,1,
+ 63,3,63,1060,8,63,1,63,1,63,3,63,1064,8,63,1,63,1,63,3,63,1068,8,
+ 63,1,63,1,63,3,63,1072,8,63,1,63,1,63,3,63,1076,8,63,1,63,5,63,1079,
+ 8,63,10,63,12,63,1082,9,63,1,64,1,64,3,64,1086,8,64,1,64,1,64,3,
+ 64,1090,8,64,1,64,5,64,1093,8,64,10,64,12,64,1096,9,64,1,65,1,65,
+ 1,65,3,65,1101,8,65,1,65,3,65,1104,8,65,1,66,1,66,3,66,1108,8,66,
+ 1,66,1,66,3,66,1112,8,66,1,66,5,66,1115,8,66,10,66,12,66,1118,9,
+ 66,1,66,3,66,1121,8,66,1,66,3,66,1124,8,66,1,67,1,67,1,67,1,67,1,
+ 67,1,67,3,67,1132,8,67,1,67,1,67,3,67,1136,8,67,1,67,3,67,1139,8,
+ 67,1,68,1,68,3,68,1143,8,68,1,68,1,68,1,69,1,69,1,69,1,69,1,69,3,
+ 69,1152,8,69,1,69,1,69,3,69,1156,8,69,1,69,1,69,3,69,1160,8,69,1,
+ 69,1,69,1,69,1,69,1,69,1,69,1,69,1,69,1,69,3,69,1171,8,69,1,70,1,
+ 70,3,70,1175,8,70,1,70,4,70,1178,8,70,11,70,12,70,1179,1,70,1,70,
+ 3,70,1184,8,70,1,70,1,70,3,70,1188,8,70,1,70,4,70,1191,8,70,11,70,
+ 12,70,1192,3,70,1195,8,70,1,70,3,70,1198,8,70,1,70,1,70,3,70,1202,
+ 8,70,1,70,3,70,1205,8,70,1,70,3,70,1208,8,70,1,70,1,70,1,71,1,71,
+ 3,71,1214,8,71,1,71,1,71,3,71,1218,8,71,1,71,1,71,3,71,1222,8,71,
+ 1,71,1,71,1,72,1,72,3,72,1228,8,72,1,72,1,72,3,72,1232,8,72,1,72,
+ 1,72,3,72,1236,8,72,1,72,3,72,1239,8,72,1,72,3,72,1242,8,72,1,72,
+ 1,72,1,73,1,73,3,73,1248,8,73,1,73,1,73,3,73,1252,8,73,1,73,1,73,
+ 3,73,1256,8,73,3,73,1258,8,73,1,73,1,73,3,73,1262,8,73,1,73,1,73,
+ 3,73,1266,8,73,3,73,1268,8,73,1,73,1,73,3,73,1272,8,73,1,73,1,73,
+ 3,73,1276,8,73,1,73,1,73,1,74,1,74,3,74,1282,8,74,1,74,1,74,3,74,
+ 1286,8,74,1,74,1,74,3,74,1290,8,74,1,74,1,74,1,74,1,74,3,74,1296,
+ 8,74,1,74,1,74,3,74,1300,8,74,1,74,1,74,3,74,1304,8,74,1,74,1,74,
+ 1,74,1,74,3,74,1310,8,74,1,74,1,74,3,74,1314,8,74,1,74,1,74,3,74,
+ 1318,8,74,1,74,1,74,1,74,1,74,3,74,1324,8,74,1,74,1,74,3,74,1328,
+ 8,74,1,74,1,74,3,74,1332,8,74,1,74,1,74,3,74,1336,8,74,1,75,1,75,
+ 3,75,1340,8,75,1,75,3,75,1343,8,75,1,76,1,76,1,77,1,77,3,77,1349,
+ 8,77,1,77,1,77,3,77,1353,8,77,1,77,1,77,1,78,1,78,1,78,1,78,1,78,
+ 1,78,1,79,1,79,3,79,1365,8,79,1,79,1,79,3,79,1369,8,79,1,79,1,79,
+ 3,79,1373,8,79,3,79,1375,8,79,1,79,1,79,3,79,1379,8,79,1,79,1,79,
+ 3,79,1383,8,79,1,79,1,79,3,79,1387,8,79,5,79,1389,8,79,10,79,12,
+ 79,1392,9,79,3,79,1394,8,79,1,79,1,79,1,80,1,80,1,80,1,81,1,81,3,
+ 81,1403,8,81,1,81,1,81,3,81,1407,8,81,1,81,1,81,1,81,3,81,1412,8,
+ 81,1,81,3,81,1415,8,81,3,81,1417,8,81,1,81,3,81,1420,8,81,1,81,1,
+ 81,1,82,1,82,3,82,1426,8,82,1,82,1,82,3,82,1430,8,82,1,82,1,82,3,
+ 82,1434,8,82,1,82,1,82,3,82,1438,8,82,1,82,1,82,3,82,1442,8,82,5,
+ 82,1444,8,82,10,82,12,82,1447,9,82,3,82,1449,8,82,1,82,1,82,1,83,
+ 1,83,1,84,1,84,1,85,1,85,1,85,1,86,1,86,1,86,5,86,1463,8,86,10,86,
+ 12,86,1466,9,86,1,87,1,87,1,88,1,88,1,88,1,88,1,88,1,88,3,88,1476,
+ 8,88,1,89,1,89,1,90,1,90,3,90,1482,8,90,1,91,1,91,1,92,1,92,1,93,
+ 1,93,3,93,1490,8,93,1,93,1,93,3,93,1494,8,93,1,93,1,93,3,93,1498,
+ 8,93,1,93,1,93,3,93,1502,8,93,5,93,1504,8,93,10,93,12,93,1507,9,
+ 93,3,93,1509,8,93,1,93,1,93,1,94,1,94,3,94,1515,8,94,1,94,1,94,3,
+ 94,1519,8,94,1,94,1,94,3,94,1523,8,94,1,94,1,94,3,94,1527,8,94,1,
+ 94,1,94,3,94,1531,8,94,1,94,1,94,3,94,1535,8,94,1,94,1,94,3,94,1539,
+ 8,94,1,94,1,94,3,94,1543,8,94,5,94,1545,8,94,10,94,12,94,1548,9,
+ 94,3,94,1550,8,94,1,94,1,94,1,95,1,95,1,96,1,96,1,96,3,96,1559,8,
+ 96,1,97,1,97,3,97,1563,8,97,1,98,1,98,1,99,1,99,1,100,1,100,1,101,
+ 1,101,1,102,1,102,1,102,0,0,103,0,2,4,6,8,10,12,14,16,18,20,22,24,
+ 26,28,30,32,34,36,38,40,42,44,46,48,50,52,54,56,58,60,62,64,66,68,
+ 70,72,74,76,78,80,82,84,86,88,90,92,94,96,98,100,102,104,106,108,
+ 110,112,114,116,118,120,122,124,126,128,130,132,134,136,138,140,
+ 142,144,146,148,150,152,154,156,158,160,162,164,166,168,170,172,
+ 174,176,178,180,182,184,186,188,190,192,194,196,198,200,202,204,
+ 0,10,1,0,68,71,1,0,18,19,1,0,93,94,1,0,95,97,1,0,105,106,5,0,46,
+ 58,61,82,84,88,92,94,109,118,5,0,83,83,89,91,98,98,119,121,124,124,
+ 2,0,14,14,27,30,2,0,15,15,31,34,2,0,19,19,35,45,1793,0,207,1,0,0,
+ 0,2,221,1,0,0,0,4,225,1,0,0,0,6,227,1,0,0,0,8,249,1,0,0,0,10,253,
+ 1,0,0,0,12,290,1,0,0,0,14,314,1,0,0,0,16,325,1,0,0,0,18,330,1,0,
+ 0,0,20,334,1,0,0,0,22,347,1,0,0,0,24,357,1,0,0,0,26,379,1,0,0,0,
+ 28,381,1,0,0,0,30,387,1,0,0,0,32,441,1,0,0,0,34,445,1,0,0,0,36,465,
+ 1,0,0,0,38,485,1,0,0,0,40,487,1,0,0,0,42,498,1,0,0,0,44,515,1,0,
+ 0,0,46,540,1,0,0,0,48,544,1,0,0,0,50,552,1,0,0,0,52,559,1,0,0,0,
+ 54,603,1,0,0,0,56,612,1,0,0,0,58,614,1,0,0,0,60,629,1,0,0,0,62,633,
+ 1,0,0,0,64,637,1,0,0,0,66,644,1,0,0,0,68,648,1,0,0,0,70,673,1,0,
+ 0,0,72,675,1,0,0,0,74,691,1,0,0,0,76,693,1,0,0,0,78,702,1,0,0,0,
+ 80,726,1,0,0,0,82,796,1,0,0,0,84,798,1,0,0,0,86,827,1,0,0,0,88,829,
+ 1,0,0,0,90,850,1,0,0,0,92,860,1,0,0,0,94,866,1,0,0,0,96,888,1,0,
+ 0,0,98,890,1,0,0,0,100,892,1,0,0,0,102,901,1,0,0,0,104,903,1,0,0,
+ 0,106,913,1,0,0,0,108,923,1,0,0,0,110,939,1,0,0,0,112,944,1,0,0,
+ 0,114,984,1,0,0,0,116,986,1,0,0,0,118,1005,1,0,0,0,120,1012,1,0,
+ 0,0,122,1029,1,0,0,0,124,1031,1,0,0,0,126,1053,1,0,0,0,128,1083,
+ 1,0,0,0,130,1103,1,0,0,0,132,1105,1,0,0,0,134,1138,1,0,0,0,136,1140,
+ 1,0,0,0,138,1170,1,0,0,0,140,1194,1,0,0,0,142,1211,1,0,0,0,144,1225,
+ 1,0,0,0,146,1245,1,0,0,0,148,1335,1,0,0,0,150,1337,1,0,0,0,152,1344,
+ 1,0,0,0,154,1346,1,0,0,0,156,1356,1,0,0,0,158,1362,1,0,0,0,160,1397,
+ 1,0,0,0,162,1400,1,0,0,0,164,1423,1,0,0,0,166,1452,1,0,0,0,168,1454,
+ 1,0,0,0,170,1456,1,0,0,0,172,1464,1,0,0,0,174,1467,1,0,0,0,176,1475,
+ 1,0,0,0,178,1477,1,0,0,0,180,1481,1,0,0,0,182,1483,1,0,0,0,184,1485,
+ 1,0,0,0,186,1487,1,0,0,0,188,1512,1,0,0,0,190,1553,1,0,0,0,192,1555,
+ 1,0,0,0,194,1562,1,0,0,0,196,1564,1,0,0,0,198,1566,1,0,0,0,200,1568,
+ 1,0,0,0,202,1570,1,0,0,0,204,1572,1,0,0,0,206,208,5,125,0,0,207,
+ 206,1,0,0,0,207,208,1,0,0,0,208,209,1,0,0,0,209,214,3,2,1,0,210,
+ 212,5,125,0,0,211,210,1,0,0,0,211,212,1,0,0,0,212,213,1,0,0,0,213,
+ 215,5,1,0,0,214,211,1,0,0,0,214,215,1,0,0,0,215,217,1,0,0,0,216,
+ 218,5,125,0,0,217,216,1,0,0,0,217,218,1,0,0,0,218,219,1,0,0,0,219,
+ 220,5,0,0,1,220,1,1,0,0,0,221,222,3,4,2,0,222,3,1,0,0,0,223,226,
+ 3,6,3,0,224,226,3,42,21,0,225,223,1,0,0,0,225,224,1,0,0,0,226,5,
+ 1,0,0,0,227,234,3,10,5,0,228,230,5,125,0,0,229,228,1,0,0,0,229,230,
+ 1,0,0,0,230,231,1,0,0,0,231,233,3,8,4,0,232,229,1,0,0,0,233,236,
+ 1,0,0,0,234,232,1,0,0,0,234,235,1,0,0,0,235,7,1,0,0,0,236,234,1,
+ 0,0,0,237,238,5,46,0,0,238,239,5,125,0,0,239,241,5,47,0,0,240,242,
+ 5,125,0,0,241,240,1,0,0,0,241,242,1,0,0,0,242,243,1,0,0,0,243,250,
+ 3,10,5,0,244,246,5,46,0,0,245,247,5,125,0,0,246,245,1,0,0,0,246,
+ 247,1,0,0,0,247,248,1,0,0,0,248,250,3,10,5,0,249,237,1,0,0,0,249,
+ 244,1,0,0,0,250,9,1,0,0,0,251,254,3,12,6,0,252,254,3,14,7,0,253,
+ 251,1,0,0,0,253,252,1,0,0,0,254,11,1,0,0,0,255,257,3,18,9,0,256,
+ 258,5,125,0,0,257,256,1,0,0,0,257,258,1,0,0,0,258,260,1,0,0,0,259,
+ 255,1,0,0,0,260,263,1,0,0,0,261,259,1,0,0,0,261,262,1,0,0,0,262,
+ 264,1,0,0,0,263,261,1,0,0,0,264,291,3,50,25,0,265,267,3,18,9,0,266,
+ 268,5,125,0,0,267,266,1,0,0,0,267,268,1,0,0,0,268,270,1,0,0,0,269,
+ 265,1,0,0,0,270,273,1,0,0,0,271,269,1,0,0,0,271,272,1,0,0,0,272,
+ 274,1,0,0,0,273,271,1,0,0,0,274,281,3,16,8,0,275,277,5,125,0,0,276,
+ 275,1,0,0,0,276,277,1,0,0,0,277,278,1,0,0,0,278,280,3,16,8,0,279,
+ 276,1,0,0,0,280,283,1,0,0,0,281,279,1,0,0,0,281,282,1,0,0,0,282,
+ 288,1,0,0,0,283,281,1,0,0,0,284,286,5,125,0,0,285,284,1,0,0,0,285,
+ 286,1,0,0,0,286,287,1,0,0,0,287,289,3,50,25,0,288,285,1,0,0,0,288,
+ 289,1,0,0,0,289,291,1,0,0,0,290,261,1,0,0,0,290,271,1,0,0,0,291,
+ 13,1,0,0,0,292,294,3,18,9,0,293,295,5,125,0,0,294,293,1,0,0,0,294,
+ 295,1,0,0,0,295,297,1,0,0,0,296,292,1,0,0,0,297,300,1,0,0,0,298,
+ 296,1,0,0,0,298,299,1,0,0,0,299,307,1,0,0,0,300,298,1,0,0,0,301,
+ 303,3,16,8,0,302,304,5,125,0,0,303,302,1,0,0,0,303,304,1,0,0,0,304,
+ 306,1,0,0,0,305,301,1,0,0,0,306,309,1,0,0,0,307,305,1,0,0,0,307,
+ 308,1,0,0,0,308,310,1,0,0,0,309,307,1,0,0,0,310,312,3,48,24,0,311,
+ 313,5,125,0,0,312,311,1,0,0,0,312,313,1,0,0,0,313,315,1,0,0,0,314,
+ 298,1,0,0,0,315,316,1,0,0,0,316,314,1,0,0,0,316,317,1,0,0,0,317,
+ 318,1,0,0,0,318,319,3,12,6,0,319,15,1,0,0,0,320,326,3,28,14,0,321,
+ 326,3,24,12,0,322,326,3,34,17,0,323,326,3,30,15,0,324,326,3,36,18,
+ 0,325,320,1,0,0,0,325,321,1,0,0,0,325,322,1,0,0,0,325,323,1,0,0,
+ 0,325,324,1,0,0,0,326,17,1,0,0,0,327,331,3,20,10,0,328,331,3,22,
+ 11,0,329,331,3,40,20,0,330,327,1,0,0,0,330,328,1,0,0,0,330,329,1,
+ 0,0,0,331,19,1,0,0,0,332,333,5,48,0,0,333,335,5,125,0,0,334,332,
+ 1,0,0,0,334,335,1,0,0,0,335,336,1,0,0,0,336,338,5,49,0,0,337,339,
+ 5,125,0,0,338,337,1,0,0,0,338,339,1,0,0,0,339,340,1,0,0,0,340,345,
+ 3,68,34,0,341,343,5,125,0,0,342,341,1,0,0,0,342,343,1,0,0,0,343,
+ 344,1,0,0,0,344,346,3,66,33,0,345,342,1,0,0,0,345,346,1,0,0,0,346,
+ 21,1,0,0,0,347,349,5,50,0,0,348,350,5,125,0,0,349,348,1,0,0,0,349,
+ 350,1,0,0,0,350,351,1,0,0,0,351,352,3,102,51,0,352,353,5,125,0,0,
+ 353,354,5,51,0,0,354,355,5,125,0,0,355,356,3,174,87,0,356,23,1,0,
+ 0,0,357,359,5,52,0,0,358,360,5,125,0,0,359,358,1,0,0,0,359,360,1,
+ 0,0,0,360,361,1,0,0,0,361,366,3,70,35,0,362,363,5,125,0,0,363,365,
+ 3,26,13,0,364,362,1,0,0,0,365,368,1,0,0,0,366,364,1,0,0,0,366,367,
+ 1,0,0,0,367,25,1,0,0,0,368,366,1,0,0,0,369,370,5,53,0,0,370,371,
+ 5,125,0,0,371,372,5,49,0,0,372,373,5,125,0,0,373,380,3,30,15,0,374,
+ 375,5,53,0,0,375,376,5,125,0,0,376,377,5,54,0,0,377,378,5,125,0,
+ 0,378,380,3,30,15,0,379,369,1,0,0,0,379,374,1,0,0,0,380,27,1,0,0,
+ 0,381,383,5,54,0,0,382,384,5,125,0,0,383,382,1,0,0,0,383,384,1,0,
+ 0,0,384,385,1,0,0,0,385,386,3,68,34,0,386,29,1,0,0,0,387,389,5,55,
+ 0,0,388,390,5,125,0,0,389,388,1,0,0,0,389,390,1,0,0,0,390,391,1,
+ 0,0,0,391,402,3,32,16,0,392,394,5,125,0,0,393,392,1,0,0,0,393,394,
+ 1,0,0,0,394,395,1,0,0,0,395,397,5,2,0,0,396,398,5,125,0,0,397,396,
+ 1,0,0,0,397,398,1,0,0,0,398,399,1,0,0,0,399,401,3,32,16,0,400,393,
+ 1,0,0,0,401,404,1,0,0,0,402,400,1,0,0,0,402,403,1,0,0,0,403,31,1,
+ 0,0,0,404,402,1,0,0,0,405,407,3,100,50,0,406,408,5,125,0,0,407,406,
+ 1,0,0,0,407,408,1,0,0,0,408,409,1,0,0,0,409,411,5,3,0,0,410,412,
+ 5,125,0,0,411,410,1,0,0,0,411,412,1,0,0,0,412,413,1,0,0,0,413,414,
+ 3,102,51,0,414,442,1,0,0,0,415,417,3,174,87,0,416,418,5,125,0,0,
+ 417,416,1,0,0,0,417,418,1,0,0,0,418,419,1,0,0,0,419,421,5,3,0,0,
+ 420,422,5,125,0,0,421,420,1,0,0,0,421,422,1,0,0,0,422,423,1,0,0,
+ 0,423,424,3,102,51,0,424,442,1,0,0,0,425,427,3,174,87,0,426,428,
+ 5,125,0,0,427,426,1,0,0,0,427,428,1,0,0,0,428,429,1,0,0,0,429,431,
+ 5,4,0,0,430,432,5,125,0,0,431,430,1,0,0,0,431,432,1,0,0,0,432,433,
+ 1,0,0,0,433,434,3,102,51,0,434,442,1,0,0,0,435,437,3,174,87,0,436,
+ 438,5,125,0,0,437,436,1,0,0,0,437,438,1,0,0,0,438,439,1,0,0,0,439,
+ 440,3,90,45,0,440,442,1,0,0,0,441,405,1,0,0,0,441,415,1,0,0,0,441,
+ 425,1,0,0,0,441,435,1,0,0,0,442,33,1,0,0,0,443,444,5,56,0,0,444,
+ 446,5,125,0,0,445,443,1,0,0,0,445,446,1,0,0,0,446,447,1,0,0,0,447,
+ 449,5,57,0,0,448,450,5,125,0,0,449,448,1,0,0,0,449,450,1,0,0,0,450,
+ 451,1,0,0,0,451,462,3,102,51,0,452,454,5,125,0,0,453,452,1,0,0,0,
+ 453,454,1,0,0,0,454,455,1,0,0,0,455,457,5,2,0,0,456,458,5,125,0,
+ 0,457,456,1,0,0,0,457,458,1,0,0,0,458,459,1,0,0,0,459,461,3,102,
+ 51,0,460,453,1,0,0,0,461,464,1,0,0,0,462,460,1,0,0,0,462,463,1,0,
+ 0,0,463,35,1,0,0,0,464,462,1,0,0,0,465,466,5,58,0,0,466,467,5,125,
+ 0,0,467,478,3,38,19,0,468,470,5,125,0,0,469,468,1,0,0,0,469,470,
+ 1,0,0,0,470,471,1,0,0,0,471,473,5,2,0,0,472,474,5,125,0,0,473,472,
+ 1,0,0,0,473,474,1,0,0,0,474,475,1,0,0,0,475,477,3,38,19,0,476,469,
+ 1,0,0,0,477,480,1,0,0,0,478,476,1,0,0,0,478,479,1,0,0,0,479,37,1,
+ 0,0,0,480,478,1,0,0,0,481,482,3,174,87,0,482,483,3,90,45,0,483,486,
+ 1,0,0,0,484,486,3,100,50,0,485,481,1,0,0,0,485,484,1,0,0,0,486,39,
+ 1,0,0,0,487,488,5,59,0,0,488,489,5,125,0,0,489,496,3,164,82,0,490,
+ 492,5,125,0,0,491,490,1,0,0,0,491,492,1,0,0,0,492,493,1,0,0,0,493,
+ 494,5,60,0,0,494,495,5,125,0,0,495,497,3,44,22,0,496,491,1,0,0,0,
+ 496,497,1,0,0,0,497,41,1,0,0,0,498,499,5,59,0,0,499,502,5,125,0,
+ 0,500,503,3,164,82,0,501,503,3,166,83,0,502,500,1,0,0,0,502,501,
+ 1,0,0,0,503,513,1,0,0,0,504,506,5,125,0,0,505,504,1,0,0,0,505,506,
+ 1,0,0,0,506,507,1,0,0,0,507,508,5,60,0,0,508,511,5,125,0,0,509,512,
+ 5,5,0,0,510,512,3,44,22,0,511,509,1,0,0,0,511,510,1,0,0,0,512,514,
+ 1,0,0,0,513,505,1,0,0,0,513,514,1,0,0,0,514,43,1,0,0,0,515,526,3,
+ 46,23,0,516,518,5,125,0,0,517,516,1,0,0,0,517,518,1,0,0,0,518,519,
+ 1,0,0,0,519,521,5,2,0,0,520,522,5,125,0,0,521,520,1,0,0,0,521,522,
+ 1,0,0,0,522,523,1,0,0,0,523,525,3,46,23,0,524,517,1,0,0,0,525,528,
+ 1,0,0,0,526,524,1,0,0,0,526,527,1,0,0,0,527,533,1,0,0,0,528,526,
+ 1,0,0,0,529,531,5,125,0,0,530,529,1,0,0,0,530,531,1,0,0,0,531,532,
+ 1,0,0,0,532,534,3,66,33,0,533,530,1,0,0,0,533,534,1,0,0,0,534,45,
+ 1,0,0,0,535,536,3,168,84,0,536,537,5,125,0,0,537,538,5,51,0,0,538,
+ 539,5,125,0,0,539,541,1,0,0,0,540,535,1,0,0,0,540,541,1,0,0,0,541,
+ 542,1,0,0,0,542,543,3,174,87,0,543,47,1,0,0,0,544,545,5,61,0,0,545,
+ 550,3,52,26,0,546,548,5,125,0,0,547,546,1,0,0,0,547,548,1,0,0,0,
+ 548,549,1,0,0,0,549,551,3,66,33,0,550,547,1,0,0,0,550,551,1,0,0,
+ 0,551,49,1,0,0,0,552,553,5,62,0,0,553,554,3,52,26,0,554,51,1,0,0,
+ 0,555,557,5,125,0,0,556,555,1,0,0,0,556,557,1,0,0,0,557,558,1,0,
+ 0,0,558,560,5,63,0,0,559,556,1,0,0,0,559,560,1,0,0,0,560,561,1,0,
+ 0,0,561,562,5,125,0,0,562,565,3,54,27,0,563,564,5,125,0,0,564,566,
+ 3,58,29,0,565,563,1,0,0,0,565,566,1,0,0,0,566,569,1,0,0,0,567,568,
+ 5,125,0,0,568,570,3,60,30,0,569,567,1,0,0,0,569,570,1,0,0,0,570,
+ 573,1,0,0,0,571,572,5,125,0,0,572,574,3,62,31,0,573,571,1,0,0,0,
+ 573,574,1,0,0,0,574,53,1,0,0,0,575,586,5,5,0,0,576,578,5,125,0,0,
+ 577,576,1,0,0,0,577,578,1,0,0,0,578,579,1,0,0,0,579,581,5,2,0,0,
+ 580,582,5,125,0,0,581,580,1,0,0,0,581,582,1,0,0,0,582,583,1,0,0,
+ 0,583,585,3,56,28,0,584,577,1,0,0,0,585,588,1,0,0,0,586,584,1,0,
+ 0,0,586,587,1,0,0,0,587,604,1,0,0,0,588,586,1,0,0,0,589,600,3,56,
+ 28,0,590,592,5,125,0,0,591,590,1,0,0,0,591,592,1,0,0,0,592,593,1,
+ 0,0,0,593,595,5,2,0,0,594,596,5,125,0,0,595,594,1,0,0,0,595,596,
+ 1,0,0,0,596,597,1,0,0,0,597,599,3,56,28,0,598,591,1,0,0,0,599,602,
+ 1,0,0,0,600,598,1,0,0,0,600,601,1,0,0,0,601,604,1,0,0,0,602,600,
+ 1,0,0,0,603,575,1,0,0,0,603,589,1,0,0,0,604,55,1,0,0,0,605,606,3,
+ 102,51,0,606,607,5,125,0,0,607,608,5,51,0,0,608,609,5,125,0,0,609,
+ 610,3,174,87,0,610,613,1,0,0,0,611,613,3,102,51,0,612,605,1,0,0,
+ 0,612,611,1,0,0,0,613,57,1,0,0,0,614,615,5,64,0,0,615,616,5,125,
+ 0,0,616,617,5,65,0,0,617,618,5,125,0,0,618,626,3,64,32,0,619,621,
+ 5,2,0,0,620,622,5,125,0,0,621,620,1,0,0,0,621,622,1,0,0,0,622,623,
+ 1,0,0,0,623,625,3,64,32,0,624,619,1,0,0,0,625,628,1,0,0,0,626,624,
+ 1,0,0,0,626,627,1,0,0,0,627,59,1,0,0,0,628,626,1,0,0,0,629,630,5,
+ 66,0,0,630,631,5,125,0,0,631,632,3,102,51,0,632,61,1,0,0,0,633,634,
+ 5,67,0,0,634,635,5,125,0,0,635,636,3,102,51,0,636,63,1,0,0,0,637,
+ 642,3,102,51,0,638,640,5,125,0,0,639,638,1,0,0,0,639,640,1,0,0,0,
+ 640,641,1,0,0,0,641,643,7,0,0,0,642,639,1,0,0,0,642,643,1,0,0,0,
+ 643,65,1,0,0,0,644,645,5,72,0,0,645,646,5,125,0,0,646,647,3,102,
+ 51,0,647,67,1,0,0,0,648,659,3,70,35,0,649,651,5,125,0,0,650,649,
+ 1,0,0,0,650,651,1,0,0,0,651,652,1,0,0,0,652,654,5,2,0,0,653,655,
+ 5,125,0,0,654,653,1,0,0,0,654,655,1,0,0,0,655,656,1,0,0,0,656,658,
+ 3,70,35,0,657,650,1,0,0,0,658,661,1,0,0,0,659,657,1,0,0,0,659,660,
+ 1,0,0,0,660,69,1,0,0,0,661,659,1,0,0,0,662,664,3,174,87,0,663,665,
+ 5,125,0,0,664,663,1,0,0,0,664,665,1,0,0,0,665,666,1,0,0,0,666,668,
+ 5,3,0,0,667,669,5,125,0,0,668,667,1,0,0,0,668,669,1,0,0,0,669,670,
+ 1,0,0,0,670,671,3,72,36,0,671,674,1,0,0,0,672,674,3,72,36,0,673,
+ 662,1,0,0,0,673,672,1,0,0,0,674,71,1,0,0,0,675,676,3,74,37,0,676,
+ 73,1,0,0,0,677,684,3,78,39,0,678,680,5,125,0,0,679,678,1,0,0,0,679,
+ 680,1,0,0,0,680,681,1,0,0,0,681,683,3,80,40,0,682,679,1,0,0,0,683,
+ 686,1,0,0,0,684,682,1,0,0,0,684,685,1,0,0,0,685,692,1,0,0,0,686,
+ 684,1,0,0,0,687,688,5,6,0,0,688,689,3,74,37,0,689,690,5,7,0,0,690,
+ 692,1,0,0,0,691,677,1,0,0,0,691,687,1,0,0,0,692,75,1,0,0,0,693,698,
+ 3,78,39,0,694,696,5,125,0,0,695,694,1,0,0,0,695,696,1,0,0,0,696,
+ 697,1,0,0,0,697,699,3,80,40,0,698,695,1,0,0,0,699,700,1,0,0,0,700,
+ 698,1,0,0,0,700,701,1,0,0,0,701,77,1,0,0,0,702,704,5,6,0,0,703,705,
+ 5,125,0,0,704,703,1,0,0,0,704,705,1,0,0,0,705,710,1,0,0,0,706,708,
+ 3,174,87,0,707,709,5,125,0,0,708,707,1,0,0,0,708,709,1,0,0,0,709,
+ 711,1,0,0,0,710,706,1,0,0,0,710,711,1,0,0,0,711,716,1,0,0,0,712,
+ 714,3,90,45,0,713,715,5,125,0,0,714,713,1,0,0,0,714,715,1,0,0,0,
+ 715,717,1,0,0,0,716,712,1,0,0,0,716,717,1,0,0,0,717,722,1,0,0,0,
+ 718,720,3,86,43,0,719,721,5,125,0,0,720,719,1,0,0,0,720,721,1,0,
+ 0,0,721,723,1,0,0,0,722,718,1,0,0,0,722,723,1,0,0,0,723,724,1,0,
+ 0,0,724,725,5,7,0,0,725,79,1,0,0,0,726,728,3,82,41,0,727,729,5,125,
+ 0,0,728,727,1,0,0,0,728,729,1,0,0,0,729,730,1,0,0,0,730,731,3,78,
+ 39,0,731,81,1,0,0,0,732,734,3,200,100,0,733,735,5,125,0,0,734,733,
+ 1,0,0,0,734,735,1,0,0,0,735,736,1,0,0,0,736,738,3,204,102,0,737,
+ 739,5,125,0,0,738,737,1,0,0,0,738,739,1,0,0,0,739,741,1,0,0,0,740,
+ 742,3,84,42,0,741,740,1,0,0,0,741,742,1,0,0,0,742,744,1,0,0,0,743,
+ 745,5,125,0,0,744,743,1,0,0,0,744,745,1,0,0,0,745,746,1,0,0,0,746,
+ 748,3,204,102,0,747,749,5,125,0,0,748,747,1,0,0,0,748,749,1,0,0,
+ 0,749,750,1,0,0,0,750,751,3,202,101,0,751,797,1,0,0,0,752,754,3,
+ 200,100,0,753,755,5,125,0,0,754,753,1,0,0,0,754,755,1,0,0,0,755,
+ 756,1,0,0,0,756,758,3,204,102,0,757,759,5,125,0,0,758,757,1,0,0,
+ 0,758,759,1,0,0,0,759,761,1,0,0,0,760,762,3,84,42,0,761,760,1,0,
+ 0,0,761,762,1,0,0,0,762,764,1,0,0,0,763,765,5,125,0,0,764,763,1,
+ 0,0,0,764,765,1,0,0,0,765,766,1,0,0,0,766,767,3,204,102,0,767,797,
+ 1,0,0,0,768,770,3,204,102,0,769,771,5,125,0,0,770,769,1,0,0,0,770,
+ 771,1,0,0,0,771,773,1,0,0,0,772,774,3,84,42,0,773,772,1,0,0,0,773,
+ 774,1,0,0,0,774,776,1,0,0,0,775,777,5,125,0,0,776,775,1,0,0,0,776,
+ 777,1,0,0,0,777,778,1,0,0,0,778,780,3,204,102,0,779,781,5,125,0,
+ 0,780,779,1,0,0,0,780,781,1,0,0,0,781,782,1,0,0,0,782,783,3,202,
+ 101,0,783,797,1,0,0,0,784,786,3,204,102,0,785,787,5,125,0,0,786,
+ 785,1,0,0,0,786,787,1,0,0,0,787,789,1,0,0,0,788,790,3,84,42,0,789,
+ 788,1,0,0,0,789,790,1,0,0,0,790,792,1,0,0,0,791,793,5,125,0,0,792,
+ 791,1,0,0,0,792,793,1,0,0,0,793,794,1,0,0,0,794,795,3,204,102,0,
+ 795,797,1,0,0,0,796,732,1,0,0,0,796,752,1,0,0,0,796,768,1,0,0,0,
+ 796,784,1,0,0,0,797,83,1,0,0,0,798,800,5,8,0,0,799,801,5,125,0,0,
+ 800,799,1,0,0,0,800,801,1,0,0,0,801,806,1,0,0,0,802,804,3,174,87,
+ 0,803,805,5,125,0,0,804,803,1,0,0,0,804,805,1,0,0,0,805,807,1,0,
+ 0,0,806,802,1,0,0,0,806,807,1,0,0,0,807,812,1,0,0,0,808,810,3,88,
+ 44,0,809,811,5,125,0,0,810,809,1,0,0,0,810,811,1,0,0,0,811,813,1,
+ 0,0,0,812,808,1,0,0,0,812,813,1,0,0,0,813,815,1,0,0,0,814,816,3,
+ 94,47,0,815,814,1,0,0,0,815,816,1,0,0,0,816,821,1,0,0,0,817,819,
+ 3,86,43,0,818,820,5,125,0,0,819,818,1,0,0,0,819,820,1,0,0,0,820,
+ 822,1,0,0,0,821,817,1,0,0,0,821,822,1,0,0,0,822,823,1,0,0,0,823,
+ 824,5,9,0,0,824,85,1,0,0,0,825,828,3,188,94,0,826,828,3,192,96,0,
+ 827,825,1,0,0,0,827,826,1,0,0,0,828,87,1,0,0,0,829,831,5,10,0,0,
+ 830,832,5,125,0,0,831,830,1,0,0,0,831,832,1,0,0,0,832,833,1,0,0,
+ 0,833,847,3,98,49,0,834,836,5,125,0,0,835,834,1,0,0,0,835,836,1,
+ 0,0,0,836,837,1,0,0,0,837,839,5,11,0,0,838,840,5,10,0,0,839,838,
+ 1,0,0,0,839,840,1,0,0,0,840,842,1,0,0,0,841,843,5,125,0,0,842,841,
+ 1,0,0,0,842,843,1,0,0,0,843,844,1,0,0,0,844,846,3,98,49,0,845,835,
+ 1,0,0,0,846,849,1,0,0,0,847,845,1,0,0,0,847,848,1,0,0,0,848,89,1,
+ 0,0,0,849,847,1,0,0,0,850,857,3,92,46,0,851,853,5,125,0,0,852,851,
+ 1,0,0,0,852,853,1,0,0,0,853,854,1,0,0,0,854,856,3,92,46,0,855,852,
+ 1,0,0,0,856,859,1,0,0,0,857,855,1,0,0,0,857,858,1,0,0,0,858,91,1,
+ 0,0,0,859,857,1,0,0,0,860,862,5,10,0,0,861,863,5,125,0,0,862,861,
+ 1,0,0,0,862,863,1,0,0,0,863,864,1,0,0,0,864,865,3,96,48,0,865,93,
+ 1,0,0,0,866,868,5,5,0,0,867,869,5,125,0,0,868,867,1,0,0,0,868,869,
+ 1,0,0,0,869,874,1,0,0,0,870,872,3,182,91,0,871,873,5,125,0,0,872,
+ 871,1,0,0,0,872,873,1,0,0,0,873,875,1,0,0,0,874,870,1,0,0,0,874,
+ 875,1,0,0,0,875,886,1,0,0,0,876,878,5,12,0,0,877,879,5,125,0,0,878,
+ 877,1,0,0,0,878,879,1,0,0,0,879,884,1,0,0,0,880,882,3,182,91,0,881,
+ 883,5,125,0,0,882,881,1,0,0,0,882,883,1,0,0,0,883,885,1,0,0,0,884,
+ 880,1,0,0,0,884,885,1,0,0,0,885,887,1,0,0,0,886,876,1,0,0,0,886,
+ 887,1,0,0,0,887,95,1,0,0,0,888,889,3,194,97,0,889,97,1,0,0,0,890,
+ 891,3,194,97,0,891,99,1,0,0,0,892,897,3,138,69,0,893,895,5,125,0,
+ 0,894,893,1,0,0,0,894,895,1,0,0,0,895,896,1,0,0,0,896,898,3,136,
+ 68,0,897,894,1,0,0,0,898,899,1,0,0,0,899,897,1,0,0,0,899,900,1,0,
+ 0,0,900,101,1,0,0,0,901,902,3,104,52,0,902,103,1,0,0,0,903,910,3,
+ 106,53,0,904,905,5,125,0,0,905,906,5,73,0,0,906,907,5,125,0,0,907,
+ 909,3,106,53,0,908,904,1,0,0,0,909,912,1,0,0,0,910,908,1,0,0,0,910,
+ 911,1,0,0,0,911,105,1,0,0,0,912,910,1,0,0,0,913,920,3,108,54,0,914,
+ 915,5,125,0,0,915,916,5,74,0,0,916,917,5,125,0,0,917,919,3,108,54,
+ 0,918,914,1,0,0,0,919,922,1,0,0,0,920,918,1,0,0,0,920,921,1,0,0,
+ 0,921,107,1,0,0,0,922,920,1,0,0,0,923,930,3,110,55,0,924,925,5,125,
+ 0,0,925,926,5,75,0,0,926,927,5,125,0,0,927,929,3,110,55,0,928,924,
+ 1,0,0,0,929,932,1,0,0,0,930,928,1,0,0,0,930,931,1,0,0,0,931,109,
+ 1,0,0,0,932,930,1,0,0,0,933,935,5,76,0,0,934,936,5,125,0,0,935,934,
+ 1,0,0,0,935,936,1,0,0,0,936,938,1,0,0,0,937,933,1,0,0,0,938,941,
+ 1,0,0,0,939,937,1,0,0,0,939,940,1,0,0,0,940,942,1,0,0,0,941,939,
+ 1,0,0,0,942,943,3,112,56,0,943,111,1,0,0,0,944,951,3,116,58,0,945,
+ 947,5,125,0,0,946,945,1,0,0,0,946,947,1,0,0,0,947,948,1,0,0,0,948,
+ 950,3,114,57,0,949,946,1,0,0,0,950,953,1,0,0,0,951,949,1,0,0,0,951,
+ 952,1,0,0,0,952,113,1,0,0,0,953,951,1,0,0,0,954,956,5,3,0,0,955,
+ 957,5,125,0,0,956,955,1,0,0,0,956,957,1,0,0,0,957,958,1,0,0,0,958,
+ 985,3,116,58,0,959,961,5,13,0,0,960,962,5,125,0,0,961,960,1,0,0,
+ 0,961,962,1,0,0,0,962,963,1,0,0,0,963,985,3,116,58,0,964,966,5,14,
+ 0,0,965,967,5,125,0,0,966,965,1,0,0,0,966,967,1,0,0,0,967,968,1,
+ 0,0,0,968,985,3,116,58,0,969,971,5,15,0,0,970,972,5,125,0,0,971,
+ 970,1,0,0,0,971,972,1,0,0,0,972,973,1,0,0,0,973,985,3,116,58,0,974,
+ 976,5,16,0,0,975,977,5,125,0,0,976,975,1,0,0,0,976,977,1,0,0,0,977,
+ 978,1,0,0,0,978,985,3,116,58,0,979,981,5,17,0,0,980,982,5,125,0,
+ 0,981,980,1,0,0,0,981,982,1,0,0,0,982,983,1,0,0,0,983,985,3,116,
+ 58,0,984,954,1,0,0,0,984,959,1,0,0,0,984,964,1,0,0,0,984,969,1,0,
+ 0,0,984,974,1,0,0,0,984,979,1,0,0,0,985,115,1,0,0,0,986,992,3,124,
+ 62,0,987,991,3,118,59,0,988,991,3,120,60,0,989,991,3,122,61,0,990,
+ 987,1,0,0,0,990,988,1,0,0,0,990,989,1,0,0,0,991,994,1,0,0,0,992,
+ 990,1,0,0,0,992,993,1,0,0,0,993,117,1,0,0,0,994,992,1,0,0,0,995,
+ 996,5,125,0,0,996,997,5,77,0,0,997,998,5,125,0,0,998,1006,5,61,0,
+ 0,999,1000,5,125,0,0,1000,1001,5,78,0,0,1001,1002,5,125,0,0,1002,
+ 1006,5,61,0,0,1003,1004,5,125,0,0,1004,1006,5,79,0,0,1005,995,1,
+ 0,0,0,1005,999,1,0,0,0,1005,1003,1,0,0,0,1006,1008,1,0,0,0,1007,
+ 1009,5,125,0,0,1008,1007,1,0,0,0,1008,1009,1,0,0,0,1009,1010,1,0,
+ 0,0,1010,1011,3,124,62,0,1011,119,1,0,0,0,1012,1013,5,125,0,0,1013,
+ 1015,5,80,0,0,1014,1016,5,125,0,0,1015,1014,1,0,0,0,1015,1016,1,
+ 0,0,0,1016,1017,1,0,0,0,1017,1018,3,124,62,0,1018,121,1,0,0,0,1019,
+ 1020,5,125,0,0,1020,1021,5,81,0,0,1021,1022,5,125,0,0,1022,1030,
+ 5,82,0,0,1023,1024,5,125,0,0,1024,1025,5,81,0,0,1025,1026,5,125,
+ 0,0,1026,1027,5,76,0,0,1027,1028,5,125,0,0,1028,1030,5,82,0,0,1029,
+ 1019,1,0,0,0,1029,1023,1,0,0,0,1030,123,1,0,0,0,1031,1050,3,126,
+ 63,0,1032,1034,5,125,0,0,1033,1032,1,0,0,0,1033,1034,1,0,0,0,1034,
+ 1035,1,0,0,0,1035,1037,5,18,0,0,1036,1038,5,125,0,0,1037,1036,1,
+ 0,0,0,1037,1038,1,0,0,0,1038,1039,1,0,0,0,1039,1049,3,126,63,0,1040,
+ 1042,5,125,0,0,1041,1040,1,0,0,0,1041,1042,1,0,0,0,1042,1043,1,0,
+ 0,0,1043,1045,5,19,0,0,1044,1046,5,125,0,0,1045,1044,1,0,0,0,1045,
+ 1046,1,0,0,0,1046,1047,1,0,0,0,1047,1049,3,126,63,0,1048,1033,1,
+ 0,0,0,1048,1041,1,0,0,0,1049,1052,1,0,0,0,1050,1048,1,0,0,0,1050,
+ 1051,1,0,0,0,1051,125,1,0,0,0,1052,1050,1,0,0,0,1053,1080,3,128,
+ 64,0,1054,1056,5,125,0,0,1055,1054,1,0,0,0,1055,1056,1,0,0,0,1056,
+ 1057,1,0,0,0,1057,1059,5,5,0,0,1058,1060,5,125,0,0,1059,1058,1,0,
+ 0,0,1059,1060,1,0,0,0,1060,1061,1,0,0,0,1061,1079,3,128,64,0,1062,
+ 1064,5,125,0,0,1063,1062,1,0,0,0,1063,1064,1,0,0,0,1064,1065,1,0,
+ 0,0,1065,1067,5,20,0,0,1066,1068,5,125,0,0,1067,1066,1,0,0,0,1067,
+ 1068,1,0,0,0,1068,1069,1,0,0,0,1069,1079,3,128,64,0,1070,1072,5,
+ 125,0,0,1071,1070,1,0,0,0,1071,1072,1,0,0,0,1072,1073,1,0,0,0,1073,
+ 1075,5,21,0,0,1074,1076,5,125,0,0,1075,1074,1,0,0,0,1075,1076,1,
+ 0,0,0,1076,1077,1,0,0,0,1077,1079,3,128,64,0,1078,1055,1,0,0,0,1078,
+ 1063,1,0,0,0,1078,1071,1,0,0,0,1079,1082,1,0,0,0,1080,1078,1,0,0,
+ 0,1080,1081,1,0,0,0,1081,127,1,0,0,0,1082,1080,1,0,0,0,1083,1094,
+ 3,130,65,0,1084,1086,5,125,0,0,1085,1084,1,0,0,0,1085,1086,1,0,0,
+ 0,1086,1087,1,0,0,0,1087,1089,5,22,0,0,1088,1090,5,125,0,0,1089,
+ 1088,1,0,0,0,1089,1090,1,0,0,0,1090,1091,1,0,0,0,1091,1093,3,130,
+ 65,0,1092,1085,1,0,0,0,1093,1096,1,0,0,0,1094,1092,1,0,0,0,1094,
+ 1095,1,0,0,0,1095,129,1,0,0,0,1096,1094,1,0,0,0,1097,1104,3,132,
+ 66,0,1098,1100,7,1,0,0,1099,1101,5,125,0,0,1100,1099,1,0,0,0,1100,
+ 1101,1,0,0,0,1101,1102,1,0,0,0,1102,1104,3,132,66,0,1103,1097,1,
+ 0,0,0,1103,1098,1,0,0,0,1104,131,1,0,0,0,1105,1116,3,138,69,0,1106,
+ 1108,5,125,0,0,1107,1106,1,0,0,0,1107,1108,1,0,0,0,1108,1109,1,0,
+ 0,0,1109,1115,3,134,67,0,1110,1112,5,125,0,0,1111,1110,1,0,0,0,1111,
+ 1112,1,0,0,0,1112,1113,1,0,0,0,1113,1115,3,136,68,0,1114,1107,1,
+ 0,0,0,1114,1111,1,0,0,0,1115,1118,1,0,0,0,1116,1114,1,0,0,0,1116,
+ 1117,1,0,0,0,1117,1123,1,0,0,0,1118,1116,1,0,0,0,1119,1121,5,125,
+ 0,0,1120,1119,1,0,0,0,1120,1121,1,0,0,0,1121,1122,1,0,0,0,1122,1124,
+ 3,90,45,0,1123,1120,1,0,0,0,1123,1124,1,0,0,0,1124,133,1,0,0,0,1125,
+ 1126,5,8,0,0,1126,1127,3,102,51,0,1127,1128,5,9,0,0,1128,1139,1,
+ 0,0,0,1129,1131,5,8,0,0,1130,1132,3,102,51,0,1131,1130,1,0,0,0,1131,
+ 1132,1,0,0,0,1132,1133,1,0,0,0,1133,1135,5,12,0,0,1134,1136,3,102,
+ 51,0,1135,1134,1,0,0,0,1135,1136,1,0,0,0,1136,1137,1,0,0,0,1137,
+ 1139,5,9,0,0,1138,1125,1,0,0,0,1138,1129,1,0,0,0,1139,135,1,0,0,
+ 0,1140,1142,5,23,0,0,1141,1143,5,125,0,0,1142,1141,1,0,0,0,1142,
+ 1143,1,0,0,0,1143,1144,1,0,0,0,1144,1145,3,190,95,0,1145,137,1,0,
+ 0,0,1146,1171,3,176,88,0,1147,1171,3,192,96,0,1148,1171,3,140,70,
+ 0,1149,1151,5,83,0,0,1150,1152,5,125,0,0,1151,1150,1,0,0,0,1151,
+ 1152,1,0,0,0,1152,1153,1,0,0,0,1153,1155,5,6,0,0,1154,1156,5,125,
+ 0,0,1155,1154,1,0,0,0,1155,1156,1,0,0,0,1156,1157,1,0,0,0,1157,1159,
+ 5,5,0,0,1158,1160,5,125,0,0,1159,1158,1,0,0,0,1159,1160,1,0,0,0,
+ 1160,1161,1,0,0,0,1161,1171,5,7,0,0,1162,1171,3,144,72,0,1163,1171,
+ 3,146,73,0,1164,1171,3,148,74,0,1165,1171,3,152,76,0,1166,1171,3,
+ 154,77,0,1167,1171,3,158,79,0,1168,1171,3,162,81,0,1169,1171,3,174,
+ 87,0,1170,1146,1,0,0,0,1170,1147,1,0,0,0,1170,1148,1,0,0,0,1170,
+ 1149,1,0,0,0,1170,1162,1,0,0,0,1170,1163,1,0,0,0,1170,1164,1,0,0,
+ 0,1170,1165,1,0,0,0,1170,1166,1,0,0,0,1170,1167,1,0,0,0,1170,1168,
+ 1,0,0,0,1170,1169,1,0,0,0,1171,139,1,0,0,0,1172,1177,5,84,0,0,1173,
+ 1175,5,125,0,0,1174,1173,1,0,0,0,1174,1175,1,0,0,0,1175,1176,1,0,
+ 0,0,1176,1178,3,142,71,0,1177,1174,1,0,0,0,1178,1179,1,0,0,0,1179,
+ 1177,1,0,0,0,1179,1180,1,0,0,0,1180,1195,1,0,0,0,1181,1183,5,84,
+ 0,0,1182,1184,5,125,0,0,1183,1182,1,0,0,0,1183,1184,1,0,0,0,1184,
+ 1185,1,0,0,0,1185,1190,3,102,51,0,1186,1188,5,125,0,0,1187,1186,
+ 1,0,0,0,1187,1188,1,0,0,0,1188,1189,1,0,0,0,1189,1191,3,142,71,0,
+ 1190,1187,1,0,0,0,1191,1192,1,0,0,0,1192,1190,1,0,0,0,1192,1193,
+ 1,0,0,0,1193,1195,1,0,0,0,1194,1172,1,0,0,0,1194,1181,1,0,0,0,1195,
+ 1204,1,0,0,0,1196,1198,5,125,0,0,1197,1196,1,0,0,0,1197,1198,1,0,
+ 0,0,1198,1199,1,0,0,0,1199,1201,5,85,0,0,1200,1202,5,125,0,0,1201,
+ 1200,1,0,0,0,1201,1202,1,0,0,0,1202,1203,1,0,0,0,1203,1205,3,102,
+ 51,0,1204,1197,1,0,0,0,1204,1205,1,0,0,0,1205,1207,1,0,0,0,1206,
+ 1208,5,125,0,0,1207,1206,1,0,0,0,1207,1208,1,0,0,0,1208,1209,1,0,
+ 0,0,1209,1210,5,86,0,0,1210,141,1,0,0,0,1211,1213,5,87,0,0,1212,
+ 1214,5,125,0,0,1213,1212,1,0,0,0,1213,1214,1,0,0,0,1214,1215,1,0,
+ 0,0,1215,1217,3,102,51,0,1216,1218,5,125,0,0,1217,1216,1,0,0,0,1217,
+ 1218,1,0,0,0,1218,1219,1,0,0,0,1219,1221,5,88,0,0,1220,1222,5,125,
+ 0,0,1221,1220,1,0,0,0,1221,1222,1,0,0,0,1222,1223,1,0,0,0,1223,1224,
+ 3,102,51,0,1224,143,1,0,0,0,1225,1227,5,8,0,0,1226,1228,5,125,0,
+ 0,1227,1226,1,0,0,0,1227,1228,1,0,0,0,1228,1229,1,0,0,0,1229,1238,
+ 3,150,75,0,1230,1232,5,125,0,0,1231,1230,1,0,0,0,1231,1232,1,0,0,
+ 0,1232,1233,1,0,0,0,1233,1235,5,11,0,0,1234,1236,5,125,0,0,1235,
+ 1234,1,0,0,0,1235,1236,1,0,0,0,1236,1237,1,0,0,0,1237,1239,3,102,
+ 51,0,1238,1231,1,0,0,0,1238,1239,1,0,0,0,1239,1241,1,0,0,0,1240,
+ 1242,5,125,0,0,1241,1240,1,0,0,0,1241,1242,1,0,0,0,1242,1243,1,0,
+ 0,0,1243,1244,5,9,0,0,1244,145,1,0,0,0,1245,1247,5,8,0,0,1246,1248,
+ 5,125,0,0,1247,1246,1,0,0,0,1247,1248,1,0,0,0,1248,1257,1,0,0,0,
+ 1249,1251,3,174,87,0,1250,1252,5,125,0,0,1251,1250,1,0,0,0,1251,
+ 1252,1,0,0,0,1252,1253,1,0,0,0,1253,1255,5,3,0,0,1254,1256,5,125,
+ 0,0,1255,1254,1,0,0,0,1255,1256,1,0,0,0,1256,1258,1,0,0,0,1257,1249,
+ 1,0,0,0,1257,1258,1,0,0,0,1258,1259,1,0,0,0,1259,1261,3,76,38,0,
+ 1260,1262,5,125,0,0,1261,1260,1,0,0,0,1261,1262,1,0,0,0,1262,1267,
+ 1,0,0,0,1263,1265,3,66,33,0,1264,1266,5,125,0,0,1265,1264,1,0,0,
+ 0,1265,1266,1,0,0,0,1266,1268,1,0,0,0,1267,1263,1,0,0,0,1267,1268,
+ 1,0,0,0,1268,1269,1,0,0,0,1269,1271,5,11,0,0,1270,1272,5,125,0,0,
+ 1271,1270,1,0,0,0,1271,1272,1,0,0,0,1272,1273,1,0,0,0,1273,1275,
+ 3,102,51,0,1274,1276,5,125,0,0,1275,1274,1,0,0,0,1275,1276,1,0,0,
+ 0,1276,1277,1,0,0,0,1277,1278,5,9,0,0,1278,147,1,0,0,0,1279,1281,
+ 5,47,0,0,1280,1282,5,125,0,0,1281,1280,1,0,0,0,1281,1282,1,0,0,0,
+ 1282,1283,1,0,0,0,1283,1285,5,6,0,0,1284,1286,5,125,0,0,1285,1284,
+ 1,0,0,0,1285,1286,1,0,0,0,1286,1287,1,0,0,0,1287,1289,3,150,75,0,
+ 1288,1290,5,125,0,0,1289,1288,1,0,0,0,1289,1290,1,0,0,0,1290,1291,
+ 1,0,0,0,1291,1292,5,7,0,0,1292,1336,1,0,0,0,1293,1295,5,89,0,0,1294,
+ 1296,5,125,0,0,1295,1294,1,0,0,0,1295,1296,1,0,0,0,1296,1297,1,0,
+ 0,0,1297,1299,5,6,0,0,1298,1300,5,125,0,0,1299,1298,1,0,0,0,1299,
+ 1300,1,0,0,0,1300,1301,1,0,0,0,1301,1303,3,150,75,0,1302,1304,5,
+ 125,0,0,1303,1302,1,0,0,0,1303,1304,1,0,0,0,1304,1305,1,0,0,0,1305,
+ 1306,5,7,0,0,1306,1336,1,0,0,0,1307,1309,5,90,0,0,1308,1310,5,125,
+ 0,0,1309,1308,1,0,0,0,1309,1310,1,0,0,0,1310,1311,1,0,0,0,1311,1313,
+ 5,6,0,0,1312,1314,5,125,0,0,1313,1312,1,0,0,0,1313,1314,1,0,0,0,
+ 1314,1315,1,0,0,0,1315,1317,3,150,75,0,1316,1318,5,125,0,0,1317,
+ 1316,1,0,0,0,1317,1318,1,0,0,0,1318,1319,1,0,0,0,1319,1320,5,7,0,
+ 0,1320,1336,1,0,0,0,1321,1323,5,91,0,0,1322,1324,5,125,0,0,1323,
+ 1322,1,0,0,0,1323,1324,1,0,0,0,1324,1325,1,0,0,0,1325,1327,5,6,0,
+ 0,1326,1328,5,125,0,0,1327,1326,1,0,0,0,1327,1328,1,0,0,0,1328,1329,
+ 1,0,0,0,1329,1331,3,150,75,0,1330,1332,5,125,0,0,1331,1330,1,0,0,
+ 0,1331,1332,1,0,0,0,1332,1333,1,0,0,0,1333,1334,5,7,0,0,1334,1336,
+ 1,0,0,0,1335,1279,1,0,0,0,1335,1293,1,0,0,0,1335,1307,1,0,0,0,1335,
+ 1321,1,0,0,0,1336,149,1,0,0,0,1337,1342,3,156,78,0,1338,1340,5,125,
+ 0,0,1339,1338,1,0,0,0,1339,1340,1,0,0,0,1340,1341,1,0,0,0,1341,1343,
+ 3,66,33,0,1342,1339,1,0,0,0,1342,1343,1,0,0,0,1343,151,1,0,0,0,1344,
+ 1345,3,76,38,0,1345,153,1,0,0,0,1346,1348,5,6,0,0,1347,1349,5,125,
+ 0,0,1348,1347,1,0,0,0,1348,1349,1,0,0,0,1349,1350,1,0,0,0,1350,1352,
+ 3,102,51,0,1351,1353,5,125,0,0,1352,1351,1,0,0,0,1352,1353,1,0,0,
+ 0,1353,1354,1,0,0,0,1354,1355,5,7,0,0,1355,155,1,0,0,0,1356,1357,
+ 3,174,87,0,1357,1358,5,125,0,0,1358,1359,5,80,0,0,1359,1360,5,125,
+ 0,0,1360,1361,3,102,51,0,1361,157,1,0,0,0,1362,1364,3,160,80,0,1363,
+ 1365,5,125,0,0,1364,1363,1,0,0,0,1364,1365,1,0,0,0,1365,1366,1,0,
+ 0,0,1366,1368,5,6,0,0,1367,1369,5,125,0,0,1368,1367,1,0,0,0,1368,
+ 1369,1,0,0,0,1369,1374,1,0,0,0,1370,1372,5,63,0,0,1371,1373,5,125,
+ 0,0,1372,1371,1,0,0,0,1372,1373,1,0,0,0,1373,1375,1,0,0,0,1374,1370,
+ 1,0,0,0,1374,1375,1,0,0,0,1375,1393,1,0,0,0,1376,1378,3,102,51,0,
+ 1377,1379,5,125,0,0,1378,1377,1,0,0,0,1378,1379,1,0,0,0,1379,1390,
+ 1,0,0,0,1380,1382,5,2,0,0,1381,1383,5,125,0,0,1382,1381,1,0,0,0,
+ 1382,1383,1,0,0,0,1383,1384,1,0,0,0,1384,1386,3,102,51,0,1385,1387,
+ 5,125,0,0,1386,1385,1,0,0,0,1386,1387,1,0,0,0,1387,1389,1,0,0,0,
+ 1388,1380,1,0,0,0,1389,1392,1,0,0,0,1390,1388,1,0,0,0,1390,1391,
+ 1,0,0,0,1391,1394,1,0,0,0,1392,1390,1,0,0,0,1393,1376,1,0,0,0,1393,
+ 1394,1,0,0,0,1394,1395,1,0,0,0,1395,1396,5,7,0,0,1396,159,1,0,0,
+ 0,1397,1398,3,172,86,0,1398,1399,3,198,99,0,1399,161,1,0,0,0,1400,
+ 1402,5,92,0,0,1401,1403,5,125,0,0,1402,1401,1,0,0,0,1402,1403,1,
+ 0,0,0,1403,1404,1,0,0,0,1404,1406,5,24,0,0,1405,1407,5,125,0,0,1406,
+ 1405,1,0,0,0,1406,1407,1,0,0,0,1407,1416,1,0,0,0,1408,1417,3,6,3,
+ 0,1409,1414,3,68,34,0,1410,1412,5,125,0,0,1411,1410,1,0,0,0,1411,
+ 1412,1,0,0,0,1412,1413,1,0,0,0,1413,1415,3,66,33,0,1414,1411,1,0,
+ 0,0,1414,1415,1,0,0,0,1415,1417,1,0,0,0,1416,1408,1,0,0,0,1416,1409,
+ 1,0,0,0,1417,1419,1,0,0,0,1418,1420,5,125,0,0,1419,1418,1,0,0,0,
+ 1419,1420,1,0,0,0,1420,1421,1,0,0,0,1421,1422,5,25,0,0,1422,163,
+ 1,0,0,0,1423,1425,3,170,85,0,1424,1426,5,125,0,0,1425,1424,1,0,0,
+ 0,1425,1426,1,0,0,0,1426,1427,1,0,0,0,1427,1429,5,6,0,0,1428,1430,
+ 5,125,0,0,1429,1428,1,0,0,0,1429,1430,1,0,0,0,1430,1448,1,0,0,0,
+ 1431,1433,3,102,51,0,1432,1434,5,125,0,0,1433,1432,1,0,0,0,1433,
+ 1434,1,0,0,0,1434,1445,1,0,0,0,1435,1437,5,2,0,0,1436,1438,5,125,
+ 0,0,1437,1436,1,0,0,0,1437,1438,1,0,0,0,1438,1439,1,0,0,0,1439,1441,
+ 3,102,51,0,1440,1442,5,125,0,0,1441,1440,1,0,0,0,1441,1442,1,0,0,
+ 0,1442,1444,1,0,0,0,1443,1435,1,0,0,0,1444,1447,1,0,0,0,1445,1443,
+ 1,0,0,0,1445,1446,1,0,0,0,1446,1449,1,0,0,0,1447,1445,1,0,0,0,1448,
+ 1431,1,0,0,0,1448,1449,1,0,0,0,1449,1450,1,0,0,0,1450,1451,5,7,0,
+ 0,1451,165,1,0,0,0,1452,1453,3,170,85,0,1453,167,1,0,0,0,1454,1455,
+ 3,198,99,0,1455,169,1,0,0,0,1456,1457,3,172,86,0,1457,1458,3,198,
+ 99,0,1458,171,1,0,0,0,1459,1460,3,198,99,0,1460,1461,5,23,0,0,1461,
+ 1463,1,0,0,0,1462,1459,1,0,0,0,1463,1466,1,0,0,0,1464,1462,1,0,0,
+ 0,1464,1465,1,0,0,0,1465,173,1,0,0,0,1466,1464,1,0,0,0,1467,1468,
+ 3,198,99,0,1468,175,1,0,0,0,1469,1476,3,178,89,0,1470,1476,5,82,
+ 0,0,1471,1476,3,180,90,0,1472,1476,5,107,0,0,1473,1476,3,186,93,
+ 0,1474,1476,3,188,94,0,1475,1469,1,0,0,0,1475,1470,1,0,0,0,1475,
+ 1471,1,0,0,0,1475,1472,1,0,0,0,1475,1473,1,0,0,0,1475,1474,1,0,0,
+ 0,1476,177,1,0,0,0,1477,1478,7,2,0,0,1478,179,1,0,0,0,1479,1482,
+ 3,184,92,0,1480,1482,3,182,91,0,1481,1479,1,0,0,0,1481,1480,1,0,
+ 0,0,1482,181,1,0,0,0,1483,1484,7,3,0,0,1484,183,1,0,0,0,1485,1486,
+ 7,4,0,0,1486,185,1,0,0,0,1487,1489,5,8,0,0,1488,1490,5,125,0,0,1489,
+ 1488,1,0,0,0,1489,1490,1,0,0,0,1490,1508,1,0,0,0,1491,1493,3,102,
+ 51,0,1492,1494,5,125,0,0,1493,1492,1,0,0,0,1493,1494,1,0,0,0,1494,
+ 1505,1,0,0,0,1495,1497,5,2,0,0,1496,1498,5,125,0,0,1497,1496,1,0,
+ 0,0,1497,1498,1,0,0,0,1498,1499,1,0,0,0,1499,1501,3,102,51,0,1500,
+ 1502,5,125,0,0,1501,1500,1,0,0,0,1501,1502,1,0,0,0,1502,1504,1,0,
+ 0,0,1503,1495,1,0,0,0,1504,1507,1,0,0,0,1505,1503,1,0,0,0,1505,1506,
+ 1,0,0,0,1506,1509,1,0,0,0,1507,1505,1,0,0,0,1508,1491,1,0,0,0,1508,
+ 1509,1,0,0,0,1509,1510,1,0,0,0,1510,1511,5,9,0,0,1511,187,1,0,0,
+ 0,1512,1514,5,24,0,0,1513,1515,5,125,0,0,1514,1513,1,0,0,0,1514,
+ 1515,1,0,0,0,1515,1549,1,0,0,0,1516,1518,3,190,95,0,1517,1519,5,
+ 125,0,0,1518,1517,1,0,0,0,1518,1519,1,0,0,0,1519,1520,1,0,0,0,1520,
+ 1522,5,10,0,0,1521,1523,5,125,0,0,1522,1521,1,0,0,0,1522,1523,1,
+ 0,0,0,1523,1524,1,0,0,0,1524,1526,3,102,51,0,1525,1527,5,125,0,0,
+ 1526,1525,1,0,0,0,1526,1527,1,0,0,0,1527,1546,1,0,0,0,1528,1530,
+ 5,2,0,0,1529,1531,5,125,0,0,1530,1529,1,0,0,0,1530,1531,1,0,0,0,
+ 1531,1532,1,0,0,0,1532,1534,3,190,95,0,1533,1535,5,125,0,0,1534,
+ 1533,1,0,0,0,1534,1535,1,0,0,0,1535,1536,1,0,0,0,1536,1538,5,10,
+ 0,0,1537,1539,5,125,0,0,1538,1537,1,0,0,0,1538,1539,1,0,0,0,1539,
+ 1540,1,0,0,0,1540,1542,3,102,51,0,1541,1543,5,125,0,0,1542,1541,
+ 1,0,0,0,1542,1543,1,0,0,0,1543,1545,1,0,0,0,1544,1528,1,0,0,0,1545,
+ 1548,1,0,0,0,1546,1544,1,0,0,0,1546,1547,1,0,0,0,1547,1550,1,0,0,
+ 0,1548,1546,1,0,0,0,1549,1516,1,0,0,0,1549,1550,1,0,0,0,1550,1551,
+ 1,0,0,0,1551,1552,5,25,0,0,1552,189,1,0,0,0,1553,1554,3,194,97,0,
+ 1554,191,1,0,0,0,1555,1558,5,26,0,0,1556,1559,3,198,99,0,1557,1559,
+ 5,96,0,0,1558,1556,1,0,0,0,1558,1557,1,0,0,0,1559,193,1,0,0,0,1560,
+ 1563,3,198,99,0,1561,1563,3,196,98,0,1562,1560,1,0,0,0,1562,1561,
+ 1,0,0,0,1563,195,1,0,0,0,1564,1565,7,5,0,0,1565,197,1,0,0,0,1566,
+ 1567,7,6,0,0,1567,199,1,0,0,0,1568,1569,7,7,0,0,1569,201,1,0,0,0,
+ 1570,1571,7,8,0,0,1571,203,1,0,0,0,1572,1573,7,9,0,0,1573,205,1,
+ 0,0,0,291,207,211,214,217,225,229,234,241,246,249,253,257,261,267,
+ 271,276,281,285,288,290,294,298,303,307,312,316,325,330,334,338,
+ 342,345,349,359,366,379,383,389,393,397,402,407,411,417,421,427,
+ 431,437,441,445,449,453,457,462,469,473,478,485,491,496,502,505,
+ 511,513,517,521,526,530,533,540,547,550,556,559,565,569,573,577,
+ 581,586,591,595,600,603,612,621,626,639,642,650,654,659,664,668,
+ 673,679,684,691,695,700,704,708,710,714,716,720,722,728,734,738,
+ 741,744,748,754,758,761,764,770,773,776,780,786,789,792,796,800,
+ 804,806,810,812,815,819,821,827,831,835,839,842,847,852,857,862,
+ 868,872,874,878,882,884,886,894,899,910,920,930,935,939,946,951,
+ 956,961,966,971,976,981,984,990,992,1005,1008,1015,1029,1033,1037,
+ 1041,1045,1048,1050,1055,1059,1063,1067,1071,1075,1078,1080,1085,
+ 1089,1094,1100,1103,1107,1111,1114,1116,1120,1123,1131,1135,1138,
+ 1142,1151,1155,1159,1170,1174,1179,1183,1187,1192,1194,1197,1201,
+ 1204,1207,1213,1217,1221,1227,1231,1235,1238,1241,1247,1251,1255,
+ 1257,1261,1265,1267,1271,1275,1281,1285,1289,1295,1299,1303,1309,
+ 1313,1317,1323,1327,1331,1335,1339,1342,1348,1352,1364,1368,1372,
+ 1374,1378,1382,1386,1390,1393,1402,1406,1411,1414,1416,1419,1425,
+ 1429,1433,1437,1441,1445,1448,1464,1475,1481,1489,1493,1497,1501,
+ 1505,1508,1514,1518,1522,1526,1530,1534,1538,1542,1546,1549,1558,
+ 1562
+ ]
+
+class CypherParser ( Parser ):
+
+ grammarFileName = "Cypher.g4"
+
+ atn = ATNDeserializer().deserialize(serializedATN())
+
+ decisionsToDFA = [ DFA(ds, i) for i, ds in enumerate(atn.decisionToState) ]
+
+ sharedContextCache = PredictionContextCache()
+
+ literalNames = [ "", "';'", "','", "'='", "'+='", "'*'", "'('",
+ "')'", "'['", "']'", "':'", "'|'", "'..'", "'<>'",
+ "'<'", "'>'", "'<='", "'>='", "'+'", "'-'", "'/'",
+ "'%'", "'^'", "'.'", "'{'", "'}'", "'$'", "'\\u27E8'",
+ "'\\u3008'", "'\\uFE64'", "'\\uFF1C'", "'\\u27E9'",
+ "'\\u3009'", "'\\uFE65'", "'\\uFF1E'", "'\\u00AD'",
+ "'\\u2010'", "'\\u2011'", "'\\u2012'", "'\\u2013'",
+ "'\\u2014'", "'\\u2015'", "'\\u2212'", "'\\uFE58'",
+ "'\\uFE63'", "'\\uFF0D'", "", "",
+ "", "", "", "",
+ "", "", "", "",
+ "", "", "", "",
+ "", "", "", "",
+ "", "", "", "",
+ "", "", "", "",
+ "", "", "", "",
+ "", "", "", "",
+ "", "", "", "",
+ "", "", "", "",
+ "", "", "", "",
+ "", "", "", "",
+ "", "", "", "",
+ "", "", "", "",
+ "'0'" ]
+
+ symbolicNames = [ "", "", "", "",
+ "", "", "", "",
+ "", "", "", "",
+ "", "", "", "",
+ "", "", "", "",
+ "", "", "", "",
+ "", "", "", "",
+ "", "", "", "",
+ "", "", "", "",
+ "", "", "", "",
+ "", "", "", "",
+ "", "", "UNION", "ALL", "OPTIONAL",
+ "MATCH", "UNWIND", "AS", "MERGE", "ON", "CREATE",
+ "SET", "DETACH", "DELETE", "REMOVE", "CALL", "YIELD",
+ "WITH", "RETURN", "DISTINCT", "ORDER", "BY", "L_SKIP",
+ "LIMIT", "ASCENDING", "ASC", "DESCENDING", "DESC",
+ "WHERE", "OR", "XOR", "AND", "NOT", "STARTS", "ENDS",
+ "CONTAINS", "IN", "IS", "NULL", "COUNT", "CASE", "ELSE",
+ "END", "WHEN", "THEN", "ANY", "NONE", "SINGLE", "EXISTS",
+ "TRUE", "FALSE", "HexInteger", "DecimalInteger", "OctalInteger",
+ "HexLetter", "HexDigit", "Digit", "NonZeroDigit",
+ "NonZeroOctDigit", "OctDigit", "ZeroDigit", "ExponentDecimalReal",
+ "RegularDecimalReal", "StringLiteral", "EscapedChar",
+ "CONSTRAINT", "DO", "FOR", "REQUIRE", "UNIQUE", "MANDATORY",
+ "SCALAR", "OF", "ADD", "DROP", "FILTER", "EXTRACT",
+ "UnescapedSymbolicName", "IdentifierStart", "IdentifierPart",
+ "EscapedSymbolicName", "SP", "WHITESPACE", "Comment" ]
+
+ RULE_oC_Cypher = 0
+ RULE_oC_Statement = 1
+ RULE_oC_Query = 2
+ RULE_oC_RegularQuery = 3
+ RULE_oC_Union = 4
+ RULE_oC_SingleQuery = 5
+ RULE_oC_SinglePartQuery = 6
+ RULE_oC_MultiPartQuery = 7
+ RULE_oC_UpdatingClause = 8
+ RULE_oC_ReadingClause = 9
+ RULE_oC_Match = 10
+ RULE_oC_Unwind = 11
+ RULE_oC_Merge = 12
+ RULE_oC_MergeAction = 13
+ RULE_oC_Create = 14
+ RULE_oC_Set = 15
+ RULE_oC_SetItem = 16
+ RULE_oC_Delete = 17
+ RULE_oC_Remove = 18
+ RULE_oC_RemoveItem = 19
+ RULE_oC_InQueryCall = 20
+ RULE_oC_StandaloneCall = 21
+ RULE_oC_YieldItems = 22
+ RULE_oC_YieldItem = 23
+ RULE_oC_With = 24
+ RULE_oC_Return = 25
+ RULE_oC_ProjectionBody = 26
+ RULE_oC_ProjectionItems = 27
+ RULE_oC_ProjectionItem = 28
+ RULE_oC_Order = 29
+ RULE_oC_Skip = 30
+ RULE_oC_Limit = 31
+ RULE_oC_SortItem = 32
+ RULE_oC_Where = 33
+ RULE_oC_Pattern = 34
+ RULE_oC_PatternPart = 35
+ RULE_oC_AnonymousPatternPart = 36
+ RULE_oC_PatternElement = 37
+ RULE_oC_RelationshipsPattern = 38
+ RULE_oC_NodePattern = 39
+ RULE_oC_PatternElementChain = 40
+ RULE_oC_RelationshipPattern = 41
+ RULE_oC_RelationshipDetail = 42
+ RULE_oC_Properties = 43
+ RULE_oC_RelationshipTypes = 44
+ RULE_oC_NodeLabels = 45
+ RULE_oC_NodeLabel = 46
+ RULE_oC_RangeLiteral = 47
+ RULE_oC_LabelName = 48
+ RULE_oC_RelTypeName = 49
+ RULE_oC_PropertyExpression = 50
+ RULE_oC_Expression = 51
+ RULE_oC_OrExpression = 52
+ RULE_oC_XorExpression = 53
+ RULE_oC_AndExpression = 54
+ RULE_oC_NotExpression = 55
+ RULE_oC_ComparisonExpression = 56
+ RULE_oC_PartialComparisonExpression = 57
+ RULE_oC_StringListNullPredicateExpression = 58
+ RULE_oC_StringPredicateExpression = 59
+ RULE_oC_ListPredicateExpression = 60
+ RULE_oC_NullPredicateExpression = 61
+ RULE_oC_AddOrSubtractExpression = 62
+ RULE_oC_MultiplyDivideModuloExpression = 63
+ RULE_oC_PowerOfExpression = 64
+ RULE_oC_UnaryAddOrSubtractExpression = 65
+ RULE_oC_NonArithmeticOperatorExpression = 66
+ RULE_oC_ListOperatorExpression = 67
+ RULE_oC_PropertyLookup = 68
+ RULE_oC_Atom = 69
+ RULE_oC_CaseExpression = 70
+ RULE_oC_CaseAlternative = 71
+ RULE_oC_ListComprehension = 72
+ RULE_oC_PatternComprehension = 73
+ RULE_oC_Quantifier = 74
+ RULE_oC_FilterExpression = 75
+ RULE_oC_PatternPredicate = 76
+ RULE_oC_ParenthesizedExpression = 77
+ RULE_oC_IdInColl = 78
+ RULE_oC_FunctionInvocation = 79
+ RULE_oC_FunctionName = 80
+ RULE_oC_ExistentialSubquery = 81
+ RULE_oC_ExplicitProcedureInvocation = 82
+ RULE_oC_ImplicitProcedureInvocation = 83
+ RULE_oC_ProcedureResultField = 84
+ RULE_oC_ProcedureName = 85
+ RULE_oC_Namespace = 86
+ RULE_oC_Variable = 87
+ RULE_oC_Literal = 88
+ RULE_oC_BooleanLiteral = 89
+ RULE_oC_NumberLiteral = 90
+ RULE_oC_IntegerLiteral = 91
+ RULE_oC_DoubleLiteral = 92
+ RULE_oC_ListLiteral = 93
+ RULE_oC_MapLiteral = 94
+ RULE_oC_PropertyKeyName = 95
+ RULE_oC_Parameter = 96
+ RULE_oC_SchemaName = 97
+ RULE_oC_ReservedWord = 98
+ RULE_oC_SymbolicName = 99
+ RULE_oC_LeftArrowHead = 100
+ RULE_oC_RightArrowHead = 101
+ RULE_oC_Dash = 102
+
+ ruleNames = [ "oC_Cypher", "oC_Statement", "oC_Query", "oC_RegularQuery",
+ "oC_Union", "oC_SingleQuery", "oC_SinglePartQuery", "oC_MultiPartQuery",
+ "oC_UpdatingClause", "oC_ReadingClause", "oC_Match",
+ "oC_Unwind", "oC_Merge", "oC_MergeAction", "oC_Create",
+ "oC_Set", "oC_SetItem", "oC_Delete", "oC_Remove", "oC_RemoveItem",
+ "oC_InQueryCall", "oC_StandaloneCall", "oC_YieldItems",
+ "oC_YieldItem", "oC_With", "oC_Return", "oC_ProjectionBody",
+ "oC_ProjectionItems", "oC_ProjectionItem", "oC_Order",
+ "oC_Skip", "oC_Limit", "oC_SortItem", "oC_Where", "oC_Pattern",
+ "oC_PatternPart", "oC_AnonymousPatternPart", "oC_PatternElement",
+ "oC_RelationshipsPattern", "oC_NodePattern", "oC_PatternElementChain",
+ "oC_RelationshipPattern", "oC_RelationshipDetail", "oC_Properties",
+ "oC_RelationshipTypes", "oC_NodeLabels", "oC_NodeLabel",
+ "oC_RangeLiteral", "oC_LabelName", "oC_RelTypeName",
+ "oC_PropertyExpression", "oC_Expression", "oC_OrExpression",
+ "oC_XorExpression", "oC_AndExpression", "oC_NotExpression",
+ "oC_ComparisonExpression", "oC_PartialComparisonExpression",
+ "oC_StringListNullPredicateExpression", "oC_StringPredicateExpression",
+ "oC_ListPredicateExpression", "oC_NullPredicateExpression",
+ "oC_AddOrSubtractExpression", "oC_MultiplyDivideModuloExpression",
+ "oC_PowerOfExpression", "oC_UnaryAddOrSubtractExpression",
+ "oC_NonArithmeticOperatorExpression", "oC_ListOperatorExpression",
+ "oC_PropertyLookup", "oC_Atom", "oC_CaseExpression",
+ "oC_CaseAlternative", "oC_ListComprehension", "oC_PatternComprehension",
+ "oC_Quantifier", "oC_FilterExpression", "oC_PatternPredicate",
+ "oC_ParenthesizedExpression", "oC_IdInColl", "oC_FunctionInvocation",
+ "oC_FunctionName", "oC_ExistentialSubquery", "oC_ExplicitProcedureInvocation",
+ "oC_ImplicitProcedureInvocation", "oC_ProcedureResultField",
+ "oC_ProcedureName", "oC_Namespace", "oC_Variable", "oC_Literal",
+ "oC_BooleanLiteral", "oC_NumberLiteral", "oC_IntegerLiteral",
+ "oC_DoubleLiteral", "oC_ListLiteral", "oC_MapLiteral",
+ "oC_PropertyKeyName", "oC_Parameter", "oC_SchemaName",
+ "oC_ReservedWord", "oC_SymbolicName", "oC_LeftArrowHead",
+ "oC_RightArrowHead", "oC_Dash" ]
+
+ EOF = Token.EOF
+ T__0=1
+ T__1=2
+ T__2=3
+ T__3=4
+ T__4=5
+ T__5=6
+ T__6=7
+ T__7=8
+ T__8=9
+ T__9=10
+ T__10=11
+ T__11=12
+ T__12=13
+ T__13=14
+ T__14=15
+ T__15=16
+ T__16=17
+ T__17=18
+ T__18=19
+ T__19=20
+ T__20=21
+ T__21=22
+ T__22=23
+ T__23=24
+ T__24=25
+ T__25=26
+ T__26=27
+ T__27=28
+ T__28=29
+ T__29=30
+ T__30=31
+ T__31=32
+ T__32=33
+ T__33=34
+ T__34=35
+ T__35=36
+ T__36=37
+ T__37=38
+ T__38=39
+ T__39=40
+ T__40=41
+ T__41=42
+ T__42=43
+ T__43=44
+ T__44=45
+ UNION=46
+ ALL=47
+ OPTIONAL=48
+ MATCH=49
+ UNWIND=50
+ AS=51
+ MERGE=52
+ ON=53
+ CREATE=54
+ SET=55
+ DETACH=56
+ DELETE=57
+ REMOVE=58
+ CALL=59
+ YIELD=60
+ WITH=61
+ RETURN=62
+ DISTINCT=63
+ ORDER=64
+ BY=65
+ L_SKIP=66
+ LIMIT=67
+ ASCENDING=68
+ ASC=69
+ DESCENDING=70
+ DESC=71
+ WHERE=72
+ OR=73
+ XOR=74
+ AND=75
+ NOT=76
+ STARTS=77
+ ENDS=78
+ CONTAINS=79
+ IN=80
+ IS=81
+ NULL=82
+ COUNT=83
+ CASE=84
+ ELSE=85
+ END=86
+ WHEN=87
+ THEN=88
+ ANY=89
+ NONE=90
+ SINGLE=91
+ EXISTS=92
+ TRUE=93
+ FALSE=94
+ HexInteger=95
+ DecimalInteger=96
+ OctalInteger=97
+ HexLetter=98
+ HexDigit=99
+ Digit=100
+ NonZeroDigit=101
+ NonZeroOctDigit=102
+ OctDigit=103
+ ZeroDigit=104
+ ExponentDecimalReal=105
+ RegularDecimalReal=106
+ StringLiteral=107
+ EscapedChar=108
+ CONSTRAINT=109
+ DO=110
+ FOR=111
+ REQUIRE=112
+ UNIQUE=113
+ MANDATORY=114
+ SCALAR=115
+ OF=116
+ ADD=117
+ DROP=118
+ FILTER=119
+ EXTRACT=120
+ UnescapedSymbolicName=121
+ IdentifierStart=122
+ IdentifierPart=123
+ EscapedSymbolicName=124
+ SP=125
+ WHITESPACE=126
+ Comment=127
+
+ def __init__(self, input:TokenStream, output:TextIO = sys.stdout):
+ super().__init__(input, output)
+ self.checkVersion("4.13.2")
+ self._interp = ParserATNSimulator(self, self.atn, self.decisionsToDFA, self.sharedContextCache)
+ self._predicates = None
+
+
+
+
+ class OC_CypherContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_Statement(self):
+ return self.getTypedRuleContext(CypherParser.OC_StatementContext,0)
+
+
+ def EOF(self):
+ return self.getToken(CypherParser.EOF, 0)
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_Cypher
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_Cypher" ):
+ listener.enterOC_Cypher(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_Cypher" ):
+ listener.exitOC_Cypher(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_Cypher" ):
+ return visitor.visitOC_Cypher(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_Cypher(self):
+
+ localctx = CypherParser.OC_CypherContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 0, self.RULE_oC_Cypher)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 207
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 206
+ self.match(CypherParser.SP)
+
+
+ self.state = 209
+ self.oC_Statement()
+ self.state = 214
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,2,self._ctx)
+ if la_ == 1:
+ self.state = 211
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 210
+ self.match(CypherParser.SP)
+
+
+ self.state = 213
+ self.match(CypherParser.T__0)
+
+
+ self.state = 217
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 216
+ self.match(CypherParser.SP)
+
+
+ self.state = 219
+ self.match(CypherParser.EOF)
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_StatementContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_Query(self):
+ return self.getTypedRuleContext(CypherParser.OC_QueryContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_Statement
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_Statement" ):
+ listener.enterOC_Statement(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_Statement" ):
+ listener.exitOC_Statement(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_Statement" ):
+ return visitor.visitOC_Statement(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_Statement(self):
+
+ localctx = CypherParser.OC_StatementContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 2, self.RULE_oC_Statement)
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 221
+ self.oC_Query()
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_QueryContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_RegularQuery(self):
+ return self.getTypedRuleContext(CypherParser.OC_RegularQueryContext,0)
+
+
+ def oC_StandaloneCall(self):
+ return self.getTypedRuleContext(CypherParser.OC_StandaloneCallContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_Query
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_Query" ):
+ listener.enterOC_Query(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_Query" ):
+ listener.exitOC_Query(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_Query" ):
+ return visitor.visitOC_Query(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_Query(self):
+
+ localctx = CypherParser.OC_QueryContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 4, self.RULE_oC_Query)
+ try:
+ self.state = 225
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,4,self._ctx)
+ if la_ == 1:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 223
+ self.oC_RegularQuery()
+ pass
+
+ elif la_ == 2:
+ self.enterOuterAlt(localctx, 2)
+ self.state = 224
+ self.oC_StandaloneCall()
+ pass
+
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_RegularQueryContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_SingleQuery(self):
+ return self.getTypedRuleContext(CypherParser.OC_SingleQueryContext,0)
+
+
+ def oC_Union(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_UnionContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_UnionContext,i)
+
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_RegularQuery
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_RegularQuery" ):
+ listener.enterOC_RegularQuery(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_RegularQuery" ):
+ listener.exitOC_RegularQuery(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_RegularQuery" ):
+ return visitor.visitOC_RegularQuery(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_RegularQuery(self):
+
+ localctx = CypherParser.OC_RegularQueryContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 6, self.RULE_oC_RegularQuery)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 227
+ self.oC_SingleQuery()
+ self.state = 234
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,6,self._ctx)
+ while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER:
+ if _alt==1:
+ self.state = 229
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 228
+ self.match(CypherParser.SP)
+
+
+ self.state = 231
+ self.oC_Union()
+ self.state = 236
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,6,self._ctx)
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_UnionContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def UNION(self):
+ return self.getToken(CypherParser.UNION, 0)
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def ALL(self):
+ return self.getToken(CypherParser.ALL, 0)
+
+ def oC_SingleQuery(self):
+ return self.getTypedRuleContext(CypherParser.OC_SingleQueryContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_Union
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_Union" ):
+ listener.enterOC_Union(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_Union" ):
+ listener.exitOC_Union(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_Union" ):
+ return visitor.visitOC_Union(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_Union(self):
+
+ localctx = CypherParser.OC_UnionContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 8, self.RULE_oC_Union)
+ self._la = 0 # Token type
+ try:
+ self.state = 249
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,9,self._ctx)
+ if la_ == 1:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 237
+ self.match(CypherParser.UNION)
+ self.state = 238
+ self.match(CypherParser.SP)
+ self.state = 239
+ self.match(CypherParser.ALL)
+ self.state = 241
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 240
+ self.match(CypherParser.SP)
+
+
+ self.state = 243
+ self.oC_SingleQuery()
+ pass
+
+ elif la_ == 2:
+ self.enterOuterAlt(localctx, 2)
+ self.state = 244
+ self.match(CypherParser.UNION)
+ self.state = 246
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 245
+ self.match(CypherParser.SP)
+
+
+ self.state = 248
+ self.oC_SingleQuery()
+ pass
+
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_SingleQueryContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_SinglePartQuery(self):
+ return self.getTypedRuleContext(CypherParser.OC_SinglePartQueryContext,0)
+
+
+ def oC_MultiPartQuery(self):
+ return self.getTypedRuleContext(CypherParser.OC_MultiPartQueryContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_SingleQuery
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_SingleQuery" ):
+ listener.enterOC_SingleQuery(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_SingleQuery" ):
+ listener.exitOC_SingleQuery(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_SingleQuery" ):
+ return visitor.visitOC_SingleQuery(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_SingleQuery(self):
+
+ localctx = CypherParser.OC_SingleQueryContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 10, self.RULE_oC_SingleQuery)
+ try:
+ self.state = 253
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,10,self._ctx)
+ if la_ == 1:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 251
+ self.oC_SinglePartQuery()
+ pass
+
+ elif la_ == 2:
+ self.enterOuterAlt(localctx, 2)
+ self.state = 252
+ self.oC_MultiPartQuery()
+ pass
+
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_SinglePartQueryContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_Return(self):
+ return self.getTypedRuleContext(CypherParser.OC_ReturnContext,0)
+
+
+ def oC_ReadingClause(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_ReadingClauseContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_ReadingClauseContext,i)
+
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def oC_UpdatingClause(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_UpdatingClauseContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_UpdatingClauseContext,i)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_SinglePartQuery
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_SinglePartQuery" ):
+ listener.enterOC_SinglePartQuery(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_SinglePartQuery" ):
+ listener.exitOC_SinglePartQuery(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_SinglePartQuery" ):
+ return visitor.visitOC_SinglePartQuery(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_SinglePartQuery(self):
+
+ localctx = CypherParser.OC_SinglePartQueryContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 12, self.RULE_oC_SinglePartQuery)
+ self._la = 0 # Token type
+ try:
+ self.state = 290
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,19,self._ctx)
+ if la_ == 1:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 261
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ while (((_la) & ~0x3f) == 0 and ((1 << _la) & 578431077140398080) != 0):
+ self.state = 255
+ self.oC_ReadingClause()
+ self.state = 257
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 256
+ self.match(CypherParser.SP)
+
+
+ self.state = 263
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+
+ self.state = 264
+ self.oC_Return()
+ pass
+
+ elif la_ == 2:
+ self.enterOuterAlt(localctx, 2)
+ self.state = 271
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ while (((_la) & ~0x3f) == 0 and ((1 << _la) & 578431077140398080) != 0):
+ self.state = 265
+ self.oC_ReadingClause()
+ self.state = 267
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 266
+ self.match(CypherParser.SP)
+
+
+ self.state = 273
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+
+ self.state = 274
+ self.oC_UpdatingClause()
+ self.state = 281
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,16,self._ctx)
+ while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER:
+ if _alt==1:
+ self.state = 276
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 275
+ self.match(CypherParser.SP)
+
+
+ self.state = 278
+ self.oC_UpdatingClause()
+ self.state = 283
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,16,self._ctx)
+
+ self.state = 288
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,18,self._ctx)
+ if la_ == 1:
+ self.state = 285
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 284
+ self.match(CypherParser.SP)
+
+
+ self.state = 287
+ self.oC_Return()
+
+
+ pass
+
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_MultiPartQueryContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_SinglePartQuery(self):
+ return self.getTypedRuleContext(CypherParser.OC_SinglePartQueryContext,0)
+
+
+ def oC_With(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_WithContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_WithContext,i)
+
+
+ def oC_ReadingClause(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_ReadingClauseContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_ReadingClauseContext,i)
+
+
+ def oC_UpdatingClause(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_UpdatingClauseContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_UpdatingClauseContext,i)
+
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_MultiPartQuery
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_MultiPartQuery" ):
+ listener.enterOC_MultiPartQuery(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_MultiPartQuery" ):
+ listener.exitOC_MultiPartQuery(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_MultiPartQuery" ):
+ return visitor.visitOC_MultiPartQuery(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_MultiPartQuery(self):
+
+ localctx = CypherParser.OC_MultiPartQueryContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 14, self.RULE_oC_MultiPartQuery)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 314
+ self._errHandler.sync(self)
+ _alt = 1
+ while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER:
+ if _alt == 1:
+ self.state = 298
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ while (((_la) & ~0x3f) == 0 and ((1 << _la) & 578431077140398080) != 0):
+ self.state = 292
+ self.oC_ReadingClause()
+ self.state = 294
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 293
+ self.match(CypherParser.SP)
+
+
+ self.state = 300
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+
+ self.state = 307
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ while (((_la) & ~0x3f) == 0 and ((1 << _la) & 562949953421312000) != 0):
+ self.state = 301
+ self.oC_UpdatingClause()
+ self.state = 303
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 302
+ self.match(CypherParser.SP)
+
+
+ self.state = 309
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+
+ self.state = 310
+ self.oC_With()
+ self.state = 312
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 311
+ self.match(CypherParser.SP)
+
+
+
+ else:
+ raise NoViableAltException(self)
+ self.state = 316
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,25,self._ctx)
+
+ self.state = 318
+ self.oC_SinglePartQuery()
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_UpdatingClauseContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_Create(self):
+ return self.getTypedRuleContext(CypherParser.OC_CreateContext,0)
+
+
+ def oC_Merge(self):
+ return self.getTypedRuleContext(CypherParser.OC_MergeContext,0)
+
+
+ def oC_Delete(self):
+ return self.getTypedRuleContext(CypherParser.OC_DeleteContext,0)
+
+
+ def oC_Set(self):
+ return self.getTypedRuleContext(CypherParser.OC_SetContext,0)
+
+
+ def oC_Remove(self):
+ return self.getTypedRuleContext(CypherParser.OC_RemoveContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_UpdatingClause
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_UpdatingClause" ):
+ listener.enterOC_UpdatingClause(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_UpdatingClause" ):
+ listener.exitOC_UpdatingClause(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_UpdatingClause" ):
+ return visitor.visitOC_UpdatingClause(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_UpdatingClause(self):
+
+ localctx = CypherParser.OC_UpdatingClauseContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 16, self.RULE_oC_UpdatingClause)
+ try:
+ self.state = 325
+ self._errHandler.sync(self)
+ token = self._input.LA(1)
+ if token in [54]:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 320
+ self.oC_Create()
+ pass
+ elif token in [52]:
+ self.enterOuterAlt(localctx, 2)
+ self.state = 321
+ self.oC_Merge()
+ pass
+ elif token in [56, 57]:
+ self.enterOuterAlt(localctx, 3)
+ self.state = 322
+ self.oC_Delete()
+ pass
+ elif token in [55]:
+ self.enterOuterAlt(localctx, 4)
+ self.state = 323
+ self.oC_Set()
+ pass
+ elif token in [58]:
+ self.enterOuterAlt(localctx, 5)
+ self.state = 324
+ self.oC_Remove()
+ pass
+ else:
+ raise NoViableAltException(self)
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_ReadingClauseContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_Match(self):
+ return self.getTypedRuleContext(CypherParser.OC_MatchContext,0)
+
+
+ def oC_Unwind(self):
+ return self.getTypedRuleContext(CypherParser.OC_UnwindContext,0)
+
+
+ def oC_InQueryCall(self):
+ return self.getTypedRuleContext(CypherParser.OC_InQueryCallContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_ReadingClause
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_ReadingClause" ):
+ listener.enterOC_ReadingClause(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_ReadingClause" ):
+ listener.exitOC_ReadingClause(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_ReadingClause" ):
+ return visitor.visitOC_ReadingClause(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_ReadingClause(self):
+
+ localctx = CypherParser.OC_ReadingClauseContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 18, self.RULE_oC_ReadingClause)
+ try:
+ self.state = 330
+ self._errHandler.sync(self)
+ token = self._input.LA(1)
+ if token in [48, 49]:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 327
+ self.oC_Match()
+ pass
+ elif token in [50]:
+ self.enterOuterAlt(localctx, 2)
+ self.state = 328
+ self.oC_Unwind()
+ pass
+ elif token in [59]:
+ self.enterOuterAlt(localctx, 3)
+ self.state = 329
+ self.oC_InQueryCall()
+ pass
+ else:
+ raise NoViableAltException(self)
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_MatchContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def MATCH(self):
+ return self.getToken(CypherParser.MATCH, 0)
+
+ def oC_Pattern(self):
+ return self.getTypedRuleContext(CypherParser.OC_PatternContext,0)
+
+
+ def OPTIONAL(self):
+ return self.getToken(CypherParser.OPTIONAL, 0)
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def oC_Where(self):
+ return self.getTypedRuleContext(CypherParser.OC_WhereContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_Match
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_Match" ):
+ listener.enterOC_Match(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_Match" ):
+ listener.exitOC_Match(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_Match" ):
+ return visitor.visitOC_Match(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_Match(self):
+
+ localctx = CypherParser.OC_MatchContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 20, self.RULE_oC_Match)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 334
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==48:
+ self.state = 332
+ self.match(CypherParser.OPTIONAL)
+ self.state = 333
+ self.match(CypherParser.SP)
+
+
+ self.state = 336
+ self.match(CypherParser.MATCH)
+ self.state = 338
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 337
+ self.match(CypherParser.SP)
+
+
+ self.state = 340
+ self.oC_Pattern()
+ self.state = 345
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,31,self._ctx)
+ if la_ == 1:
+ self.state = 342
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 341
+ self.match(CypherParser.SP)
+
+
+ self.state = 344
+ self.oC_Where()
+
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_UnwindContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def UNWIND(self):
+ return self.getToken(CypherParser.UNWIND, 0)
+
+ def oC_Expression(self):
+ return self.getTypedRuleContext(CypherParser.OC_ExpressionContext,0)
+
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def AS(self):
+ return self.getToken(CypherParser.AS, 0)
+
+ def oC_Variable(self):
+ return self.getTypedRuleContext(CypherParser.OC_VariableContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_Unwind
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_Unwind" ):
+ listener.enterOC_Unwind(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_Unwind" ):
+ listener.exitOC_Unwind(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_Unwind" ):
+ return visitor.visitOC_Unwind(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_Unwind(self):
+
+ localctx = CypherParser.OC_UnwindContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 22, self.RULE_oC_Unwind)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 347
+ self.match(CypherParser.UNWIND)
+ self.state = 349
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 348
+ self.match(CypherParser.SP)
+
+
+ self.state = 351
+ self.oC_Expression()
+ self.state = 352
+ self.match(CypherParser.SP)
+ self.state = 353
+ self.match(CypherParser.AS)
+ self.state = 354
+ self.match(CypherParser.SP)
+ self.state = 355
+ self.oC_Variable()
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_MergeContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def MERGE(self):
+ return self.getToken(CypherParser.MERGE, 0)
+
+ def oC_PatternPart(self):
+ return self.getTypedRuleContext(CypherParser.OC_PatternPartContext,0)
+
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def oC_MergeAction(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_MergeActionContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_MergeActionContext,i)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_Merge
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_Merge" ):
+ listener.enterOC_Merge(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_Merge" ):
+ listener.exitOC_Merge(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_Merge" ):
+ return visitor.visitOC_Merge(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_Merge(self):
+
+ localctx = CypherParser.OC_MergeContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 24, self.RULE_oC_Merge)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 357
+ self.match(CypherParser.MERGE)
+ self.state = 359
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 358
+ self.match(CypherParser.SP)
+
+
+ self.state = 361
+ self.oC_PatternPart()
+ self.state = 366
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,34,self._ctx)
+ while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER:
+ if _alt==1:
+ self.state = 362
+ self.match(CypherParser.SP)
+ self.state = 363
+ self.oC_MergeAction()
+ self.state = 368
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,34,self._ctx)
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_MergeActionContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def ON(self):
+ return self.getToken(CypherParser.ON, 0)
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def MATCH(self):
+ return self.getToken(CypherParser.MATCH, 0)
+
+ def oC_Set(self):
+ return self.getTypedRuleContext(CypherParser.OC_SetContext,0)
+
+
+ def CREATE(self):
+ return self.getToken(CypherParser.CREATE, 0)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_MergeAction
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_MergeAction" ):
+ listener.enterOC_MergeAction(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_MergeAction" ):
+ listener.exitOC_MergeAction(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_MergeAction" ):
+ return visitor.visitOC_MergeAction(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_MergeAction(self):
+
+ localctx = CypherParser.OC_MergeActionContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 26, self.RULE_oC_MergeAction)
+ try:
+ self.state = 379
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,35,self._ctx)
+ if la_ == 1:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 369
+ self.match(CypherParser.ON)
+ self.state = 370
+ self.match(CypherParser.SP)
+ self.state = 371
+ self.match(CypherParser.MATCH)
+ self.state = 372
+ self.match(CypherParser.SP)
+ self.state = 373
+ self.oC_Set()
+ pass
+
+ elif la_ == 2:
+ self.enterOuterAlt(localctx, 2)
+ self.state = 374
+ self.match(CypherParser.ON)
+ self.state = 375
+ self.match(CypherParser.SP)
+ self.state = 376
+ self.match(CypherParser.CREATE)
+ self.state = 377
+ self.match(CypherParser.SP)
+ self.state = 378
+ self.oC_Set()
+ pass
+
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_CreateContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def CREATE(self):
+ return self.getToken(CypherParser.CREATE, 0)
+
+ def oC_Pattern(self):
+ return self.getTypedRuleContext(CypherParser.OC_PatternContext,0)
+
+
+ def SP(self):
+ return self.getToken(CypherParser.SP, 0)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_Create
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_Create" ):
+ listener.enterOC_Create(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_Create" ):
+ listener.exitOC_Create(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_Create" ):
+ return visitor.visitOC_Create(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_Create(self):
+
+ localctx = CypherParser.OC_CreateContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 28, self.RULE_oC_Create)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 381
+ self.match(CypherParser.CREATE)
+ self.state = 383
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 382
+ self.match(CypherParser.SP)
+
+
+ self.state = 385
+ self.oC_Pattern()
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_SetContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def SET(self):
+ return self.getToken(CypherParser.SET, 0)
+
+ def oC_SetItem(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_SetItemContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_SetItemContext,i)
+
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_Set
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_Set" ):
+ listener.enterOC_Set(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_Set" ):
+ listener.exitOC_Set(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_Set" ):
+ return visitor.visitOC_Set(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_Set(self):
+
+ localctx = CypherParser.OC_SetContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 30, self.RULE_oC_Set)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 387
+ self.match(CypherParser.SET)
+ self.state = 389
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 388
+ self.match(CypherParser.SP)
+
+
+ self.state = 391
+ self.oC_SetItem()
+ self.state = 402
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,40,self._ctx)
+ while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER:
+ if _alt==1:
+ self.state = 393
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 392
+ self.match(CypherParser.SP)
+
+
+ self.state = 395
+ self.match(CypherParser.T__1)
+ self.state = 397
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 396
+ self.match(CypherParser.SP)
+
+
+ self.state = 399
+ self.oC_SetItem()
+ self.state = 404
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,40,self._ctx)
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_SetItemContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_PropertyExpression(self):
+ return self.getTypedRuleContext(CypherParser.OC_PropertyExpressionContext,0)
+
+
+ def oC_Expression(self):
+ return self.getTypedRuleContext(CypherParser.OC_ExpressionContext,0)
+
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def oC_Variable(self):
+ return self.getTypedRuleContext(CypherParser.OC_VariableContext,0)
+
+
+ def oC_NodeLabels(self):
+ return self.getTypedRuleContext(CypherParser.OC_NodeLabelsContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_SetItem
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_SetItem" ):
+ listener.enterOC_SetItem(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_SetItem" ):
+ listener.exitOC_SetItem(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_SetItem" ):
+ return visitor.visitOC_SetItem(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_SetItem(self):
+
+ localctx = CypherParser.OC_SetItemContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 32, self.RULE_oC_SetItem)
+ self._la = 0 # Token type
+ try:
+ self.state = 441
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,48,self._ctx)
+ if la_ == 1:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 405
+ self.oC_PropertyExpression()
+ self.state = 407
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 406
+ self.match(CypherParser.SP)
+
+
+ self.state = 409
+ self.match(CypherParser.T__2)
+ self.state = 411
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 410
+ self.match(CypherParser.SP)
+
+
+ self.state = 413
+ self.oC_Expression()
+ pass
+
+ elif la_ == 2:
+ self.enterOuterAlt(localctx, 2)
+ self.state = 415
+ self.oC_Variable()
+ self.state = 417
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 416
+ self.match(CypherParser.SP)
+
+
+ self.state = 419
+ self.match(CypherParser.T__2)
+ self.state = 421
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 420
+ self.match(CypherParser.SP)
+
+
+ self.state = 423
+ self.oC_Expression()
+ pass
+
+ elif la_ == 3:
+ self.enterOuterAlt(localctx, 3)
+ self.state = 425
+ self.oC_Variable()
+ self.state = 427
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 426
+ self.match(CypherParser.SP)
+
+
+ self.state = 429
+ self.match(CypherParser.T__3)
+ self.state = 431
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 430
+ self.match(CypherParser.SP)
+
+
+ self.state = 433
+ self.oC_Expression()
+ pass
+
+ elif la_ == 4:
+ self.enterOuterAlt(localctx, 4)
+ self.state = 435
+ self.oC_Variable()
+ self.state = 437
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 436
+ self.match(CypherParser.SP)
+
+
+ self.state = 439
+ self.oC_NodeLabels()
+ pass
+
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_DeleteContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def DELETE(self):
+ return self.getToken(CypherParser.DELETE, 0)
+
+ def oC_Expression(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_ExpressionContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_ExpressionContext,i)
+
+
+ def DETACH(self):
+ return self.getToken(CypherParser.DETACH, 0)
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_Delete
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_Delete" ):
+ listener.enterOC_Delete(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_Delete" ):
+ listener.exitOC_Delete(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_Delete" ):
+ return visitor.visitOC_Delete(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_Delete(self):
+
+ localctx = CypherParser.OC_DeleteContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 34, self.RULE_oC_Delete)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 445
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==56:
+ self.state = 443
+ self.match(CypherParser.DETACH)
+ self.state = 444
+ self.match(CypherParser.SP)
+
+
+ self.state = 447
+ self.match(CypherParser.DELETE)
+ self.state = 449
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 448
+ self.match(CypherParser.SP)
+
+
+ self.state = 451
+ self.oC_Expression()
+ self.state = 462
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,53,self._ctx)
+ while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER:
+ if _alt==1:
+ self.state = 453
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 452
+ self.match(CypherParser.SP)
+
+
+ self.state = 455
+ self.match(CypherParser.T__1)
+ self.state = 457
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 456
+ self.match(CypherParser.SP)
+
+
+ self.state = 459
+ self.oC_Expression()
+ self.state = 464
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,53,self._ctx)
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_RemoveContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def REMOVE(self):
+ return self.getToken(CypherParser.REMOVE, 0)
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def oC_RemoveItem(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_RemoveItemContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_RemoveItemContext,i)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_Remove
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_Remove" ):
+ listener.enterOC_Remove(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_Remove" ):
+ listener.exitOC_Remove(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_Remove" ):
+ return visitor.visitOC_Remove(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_Remove(self):
+
+ localctx = CypherParser.OC_RemoveContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 36, self.RULE_oC_Remove)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 465
+ self.match(CypherParser.REMOVE)
+ self.state = 466
+ self.match(CypherParser.SP)
+ self.state = 467
+ self.oC_RemoveItem()
+ self.state = 478
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,56,self._ctx)
+ while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER:
+ if _alt==1:
+ self.state = 469
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 468
+ self.match(CypherParser.SP)
+
+
+ self.state = 471
+ self.match(CypherParser.T__1)
+ self.state = 473
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 472
+ self.match(CypherParser.SP)
+
+
+ self.state = 475
+ self.oC_RemoveItem()
+ self.state = 480
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,56,self._ctx)
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_RemoveItemContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_Variable(self):
+ return self.getTypedRuleContext(CypherParser.OC_VariableContext,0)
+
+
+ def oC_NodeLabels(self):
+ return self.getTypedRuleContext(CypherParser.OC_NodeLabelsContext,0)
+
+
+ def oC_PropertyExpression(self):
+ return self.getTypedRuleContext(CypherParser.OC_PropertyExpressionContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_RemoveItem
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_RemoveItem" ):
+ listener.enterOC_RemoveItem(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_RemoveItem" ):
+ listener.exitOC_RemoveItem(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_RemoveItem" ):
+ return visitor.visitOC_RemoveItem(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_RemoveItem(self):
+
+ localctx = CypherParser.OC_RemoveItemContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 38, self.RULE_oC_RemoveItem)
+ try:
+ self.state = 485
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,57,self._ctx)
+ if la_ == 1:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 481
+ self.oC_Variable()
+ self.state = 482
+ self.oC_NodeLabels()
+ pass
+
+ elif la_ == 2:
+ self.enterOuterAlt(localctx, 2)
+ self.state = 484
+ self.oC_PropertyExpression()
+ pass
+
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_InQueryCallContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def CALL(self):
+ return self.getToken(CypherParser.CALL, 0)
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def oC_ExplicitProcedureInvocation(self):
+ return self.getTypedRuleContext(CypherParser.OC_ExplicitProcedureInvocationContext,0)
+
+
+ def YIELD(self):
+ return self.getToken(CypherParser.YIELD, 0)
+
+ def oC_YieldItems(self):
+ return self.getTypedRuleContext(CypherParser.OC_YieldItemsContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_InQueryCall
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_InQueryCall" ):
+ listener.enterOC_InQueryCall(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_InQueryCall" ):
+ listener.exitOC_InQueryCall(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_InQueryCall" ):
+ return visitor.visitOC_InQueryCall(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_InQueryCall(self):
+
+ localctx = CypherParser.OC_InQueryCallContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 40, self.RULE_oC_InQueryCall)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 487
+ self.match(CypherParser.CALL)
+ self.state = 488
+ self.match(CypherParser.SP)
+ self.state = 489
+ self.oC_ExplicitProcedureInvocation()
+ self.state = 496
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,59,self._ctx)
+ if la_ == 1:
+ self.state = 491
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 490
+ self.match(CypherParser.SP)
+
+
+ self.state = 493
+ self.match(CypherParser.YIELD)
+ self.state = 494
+ self.match(CypherParser.SP)
+ self.state = 495
+ self.oC_YieldItems()
+
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_StandaloneCallContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def CALL(self):
+ return self.getToken(CypherParser.CALL, 0)
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def oC_ExplicitProcedureInvocation(self):
+ return self.getTypedRuleContext(CypherParser.OC_ExplicitProcedureInvocationContext,0)
+
+
+ def oC_ImplicitProcedureInvocation(self):
+ return self.getTypedRuleContext(CypherParser.OC_ImplicitProcedureInvocationContext,0)
+
+
+ def YIELD(self):
+ return self.getToken(CypherParser.YIELD, 0)
+
+ def oC_YieldItems(self):
+ return self.getTypedRuleContext(CypherParser.OC_YieldItemsContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_StandaloneCall
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_StandaloneCall" ):
+ listener.enterOC_StandaloneCall(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_StandaloneCall" ):
+ listener.exitOC_StandaloneCall(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_StandaloneCall" ):
+ return visitor.visitOC_StandaloneCall(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_StandaloneCall(self):
+
+ localctx = CypherParser.OC_StandaloneCallContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 42, self.RULE_oC_StandaloneCall)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 498
+ self.match(CypherParser.CALL)
+ self.state = 499
+ self.match(CypherParser.SP)
+ self.state = 502
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,60,self._ctx)
+ if la_ == 1:
+ self.state = 500
+ self.oC_ExplicitProcedureInvocation()
+ pass
+
+ elif la_ == 2:
+ self.state = 501
+ self.oC_ImplicitProcedureInvocation()
+ pass
+
+
+ self.state = 513
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,63,self._ctx)
+ if la_ == 1:
+ self.state = 505
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 504
+ self.match(CypherParser.SP)
+
+
+ self.state = 507
+ self.match(CypherParser.YIELD)
+ self.state = 508
+ self.match(CypherParser.SP)
+ self.state = 511
+ self._errHandler.sync(self)
+ token = self._input.LA(1)
+ if token in [5]:
+ self.state = 509
+ self.match(CypherParser.T__4)
+ pass
+ elif token in [83, 89, 90, 91, 98, 119, 120, 121, 124]:
+ self.state = 510
+ self.oC_YieldItems()
+ pass
+ else:
+ raise NoViableAltException(self)
+
+
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_YieldItemsContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_YieldItem(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_YieldItemContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_YieldItemContext,i)
+
+
+ def oC_Where(self):
+ return self.getTypedRuleContext(CypherParser.OC_WhereContext,0)
+
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_YieldItems
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_YieldItems" ):
+ listener.enterOC_YieldItems(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_YieldItems" ):
+ listener.exitOC_YieldItems(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_YieldItems" ):
+ return visitor.visitOC_YieldItems(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_YieldItems(self):
+
+ localctx = CypherParser.OC_YieldItemsContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 44, self.RULE_oC_YieldItems)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 515
+ self.oC_YieldItem()
+ self.state = 526
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,66,self._ctx)
+ while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER:
+ if _alt==1:
+ self.state = 517
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 516
+ self.match(CypherParser.SP)
+
+
+ self.state = 519
+ self.match(CypherParser.T__1)
+ self.state = 521
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 520
+ self.match(CypherParser.SP)
+
+
+ self.state = 523
+ self.oC_YieldItem()
+ self.state = 528
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,66,self._ctx)
+
+ self.state = 533
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,68,self._ctx)
+ if la_ == 1:
+ self.state = 530
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 529
+ self.match(CypherParser.SP)
+
+
+ self.state = 532
+ self.oC_Where()
+
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_YieldItemContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_Variable(self):
+ return self.getTypedRuleContext(CypherParser.OC_VariableContext,0)
+
+
+ def oC_ProcedureResultField(self):
+ return self.getTypedRuleContext(CypherParser.OC_ProcedureResultFieldContext,0)
+
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def AS(self):
+ return self.getToken(CypherParser.AS, 0)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_YieldItem
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_YieldItem" ):
+ listener.enterOC_YieldItem(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_YieldItem" ):
+ listener.exitOC_YieldItem(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_YieldItem" ):
+ return visitor.visitOC_YieldItem(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_YieldItem(self):
+
+ localctx = CypherParser.OC_YieldItemContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 46, self.RULE_oC_YieldItem)
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 540
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,69,self._ctx)
+ if la_ == 1:
+ self.state = 535
+ self.oC_ProcedureResultField()
+ self.state = 536
+ self.match(CypherParser.SP)
+ self.state = 537
+ self.match(CypherParser.AS)
+ self.state = 538
+ self.match(CypherParser.SP)
+
+
+ self.state = 542
+ self.oC_Variable()
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_WithContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def WITH(self):
+ return self.getToken(CypherParser.WITH, 0)
+
+ def oC_ProjectionBody(self):
+ return self.getTypedRuleContext(CypherParser.OC_ProjectionBodyContext,0)
+
+
+ def oC_Where(self):
+ return self.getTypedRuleContext(CypherParser.OC_WhereContext,0)
+
+
+ def SP(self):
+ return self.getToken(CypherParser.SP, 0)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_With
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_With" ):
+ listener.enterOC_With(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_With" ):
+ listener.exitOC_With(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_With" ):
+ return visitor.visitOC_With(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_With(self):
+
+ localctx = CypherParser.OC_WithContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 48, self.RULE_oC_With)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 544
+ self.match(CypherParser.WITH)
+ self.state = 545
+ self.oC_ProjectionBody()
+ self.state = 550
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,71,self._ctx)
+ if la_ == 1:
+ self.state = 547
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 546
+ self.match(CypherParser.SP)
+
+
+ self.state = 549
+ self.oC_Where()
+
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_ReturnContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def RETURN(self):
+ return self.getToken(CypherParser.RETURN, 0)
+
+ def oC_ProjectionBody(self):
+ return self.getTypedRuleContext(CypherParser.OC_ProjectionBodyContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_Return
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_Return" ):
+ listener.enterOC_Return(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_Return" ):
+ listener.exitOC_Return(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_Return" ):
+ return visitor.visitOC_Return(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_Return(self):
+
+ localctx = CypherParser.OC_ReturnContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 50, self.RULE_oC_Return)
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 552
+ self.match(CypherParser.RETURN)
+ self.state = 553
+ self.oC_ProjectionBody()
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_ProjectionBodyContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def oC_ProjectionItems(self):
+ return self.getTypedRuleContext(CypherParser.OC_ProjectionItemsContext,0)
+
+
+ def DISTINCT(self):
+ return self.getToken(CypherParser.DISTINCT, 0)
+
+ def oC_Order(self):
+ return self.getTypedRuleContext(CypherParser.OC_OrderContext,0)
+
+
+ def oC_Skip(self):
+ return self.getTypedRuleContext(CypherParser.OC_SkipContext,0)
+
+
+ def oC_Limit(self):
+ return self.getTypedRuleContext(CypherParser.OC_LimitContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_ProjectionBody
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_ProjectionBody" ):
+ listener.enterOC_ProjectionBody(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_ProjectionBody" ):
+ listener.exitOC_ProjectionBody(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_ProjectionBody" ):
+ return visitor.visitOC_ProjectionBody(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_ProjectionBody(self):
+
+ localctx = CypherParser.OC_ProjectionBodyContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 52, self.RULE_oC_ProjectionBody)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 559
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,73,self._ctx)
+ if la_ == 1:
+ self.state = 556
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 555
+ self.match(CypherParser.SP)
+
+
+ self.state = 558
+ self.match(CypherParser.DISTINCT)
+
+
+ self.state = 561
+ self.match(CypherParser.SP)
+ self.state = 562
+ self.oC_ProjectionItems()
+ self.state = 565
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,74,self._ctx)
+ if la_ == 1:
+ self.state = 563
+ self.match(CypherParser.SP)
+ self.state = 564
+ self.oC_Order()
+
+
+ self.state = 569
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,75,self._ctx)
+ if la_ == 1:
+ self.state = 567
+ self.match(CypherParser.SP)
+ self.state = 568
+ self.oC_Skip()
+
+
+ self.state = 573
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,76,self._ctx)
+ if la_ == 1:
+ self.state = 571
+ self.match(CypherParser.SP)
+ self.state = 572
+ self.oC_Limit()
+
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_ProjectionItemsContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_ProjectionItem(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_ProjectionItemContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_ProjectionItemContext,i)
+
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_ProjectionItems
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_ProjectionItems" ):
+ listener.enterOC_ProjectionItems(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_ProjectionItems" ):
+ listener.exitOC_ProjectionItems(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_ProjectionItems" ):
+ return visitor.visitOC_ProjectionItems(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_ProjectionItems(self):
+
+ localctx = CypherParser.OC_ProjectionItemsContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 54, self.RULE_oC_ProjectionItems)
+ self._la = 0 # Token type
+ try:
+ self.state = 603
+ self._errHandler.sync(self)
+ token = self._input.LA(1)
+ if token in [5]:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 575
+ self.match(CypherParser.T__4)
+ self.state = 586
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,79,self._ctx)
+ while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER:
+ if _alt==1:
+ self.state = 577
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 576
+ self.match(CypherParser.SP)
+
+
+ self.state = 579
+ self.match(CypherParser.T__1)
+ self.state = 581
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 580
+ self.match(CypherParser.SP)
+
+
+ self.state = 583
+ self.oC_ProjectionItem()
+ self.state = 588
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,79,self._ctx)
+
+ pass
+ elif token in [6, 8, 18, 19, 24, 26, 47, 76, 82, 83, 84, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 105, 106, 107, 119, 120, 121, 124]:
+ self.enterOuterAlt(localctx, 2)
+ self.state = 589
+ self.oC_ProjectionItem()
+ self.state = 600
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,82,self._ctx)
+ while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER:
+ if _alt==1:
+ self.state = 591
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 590
+ self.match(CypherParser.SP)
+
+
+ self.state = 593
+ self.match(CypherParser.T__1)
+ self.state = 595
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 594
+ self.match(CypherParser.SP)
+
+
+ self.state = 597
+ self.oC_ProjectionItem()
+ self.state = 602
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,82,self._ctx)
+
+ pass
+ else:
+ raise NoViableAltException(self)
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_ProjectionItemContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_Expression(self):
+ return self.getTypedRuleContext(CypherParser.OC_ExpressionContext,0)
+
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def AS(self):
+ return self.getToken(CypherParser.AS, 0)
+
+ def oC_Variable(self):
+ return self.getTypedRuleContext(CypherParser.OC_VariableContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_ProjectionItem
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_ProjectionItem" ):
+ listener.enterOC_ProjectionItem(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_ProjectionItem" ):
+ listener.exitOC_ProjectionItem(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_ProjectionItem" ):
+ return visitor.visitOC_ProjectionItem(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_ProjectionItem(self):
+
+ localctx = CypherParser.OC_ProjectionItemContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 56, self.RULE_oC_ProjectionItem)
+ try:
+ self.state = 612
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,84,self._ctx)
+ if la_ == 1:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 605
+ self.oC_Expression()
+ self.state = 606
+ self.match(CypherParser.SP)
+ self.state = 607
+ self.match(CypherParser.AS)
+ self.state = 608
+ self.match(CypherParser.SP)
+ self.state = 609
+ self.oC_Variable()
+ pass
+
+ elif la_ == 2:
+ self.enterOuterAlt(localctx, 2)
+ self.state = 611
+ self.oC_Expression()
+ pass
+
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_OrderContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def ORDER(self):
+ return self.getToken(CypherParser.ORDER, 0)
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def BY(self):
+ return self.getToken(CypherParser.BY, 0)
+
+ def oC_SortItem(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_SortItemContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_SortItemContext,i)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_Order
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_Order" ):
+ listener.enterOC_Order(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_Order" ):
+ listener.exitOC_Order(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_Order" ):
+ return visitor.visitOC_Order(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_Order(self):
+
+ localctx = CypherParser.OC_OrderContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 58, self.RULE_oC_Order)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 614
+ self.match(CypherParser.ORDER)
+ self.state = 615
+ self.match(CypherParser.SP)
+ self.state = 616
+ self.match(CypherParser.BY)
+ self.state = 617
+ self.match(CypherParser.SP)
+ self.state = 618
+ self.oC_SortItem()
+ self.state = 626
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ while _la==2:
+ self.state = 619
+ self.match(CypherParser.T__1)
+ self.state = 621
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 620
+ self.match(CypherParser.SP)
+
+
+ self.state = 623
+ self.oC_SortItem()
+ self.state = 628
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_SkipContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def L_SKIP(self):
+ return self.getToken(CypherParser.L_SKIP, 0)
+
+ def SP(self):
+ return self.getToken(CypherParser.SP, 0)
+
+ def oC_Expression(self):
+ return self.getTypedRuleContext(CypherParser.OC_ExpressionContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_Skip
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_Skip" ):
+ listener.enterOC_Skip(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_Skip" ):
+ listener.exitOC_Skip(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_Skip" ):
+ return visitor.visitOC_Skip(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_Skip(self):
+
+ localctx = CypherParser.OC_SkipContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 60, self.RULE_oC_Skip)
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 629
+ self.match(CypherParser.L_SKIP)
+ self.state = 630
+ self.match(CypherParser.SP)
+ self.state = 631
+ self.oC_Expression()
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_LimitContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def LIMIT(self):
+ return self.getToken(CypherParser.LIMIT, 0)
+
+ def SP(self):
+ return self.getToken(CypherParser.SP, 0)
+
+ def oC_Expression(self):
+ return self.getTypedRuleContext(CypherParser.OC_ExpressionContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_Limit
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_Limit" ):
+ listener.enterOC_Limit(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_Limit" ):
+ listener.exitOC_Limit(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_Limit" ):
+ return visitor.visitOC_Limit(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_Limit(self):
+
+ localctx = CypherParser.OC_LimitContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 62, self.RULE_oC_Limit)
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 633
+ self.match(CypherParser.LIMIT)
+ self.state = 634
+ self.match(CypherParser.SP)
+ self.state = 635
+ self.oC_Expression()
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_SortItemContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_Expression(self):
+ return self.getTypedRuleContext(CypherParser.OC_ExpressionContext,0)
+
+
+ def ASCENDING(self):
+ return self.getToken(CypherParser.ASCENDING, 0)
+
+ def ASC(self):
+ return self.getToken(CypherParser.ASC, 0)
+
+ def DESCENDING(self):
+ return self.getToken(CypherParser.DESCENDING, 0)
+
+ def DESC(self):
+ return self.getToken(CypherParser.DESC, 0)
+
+ def SP(self):
+ return self.getToken(CypherParser.SP, 0)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_SortItem
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_SortItem" ):
+ listener.enterOC_SortItem(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_SortItem" ):
+ listener.exitOC_SortItem(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_SortItem" ):
+ return visitor.visitOC_SortItem(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_SortItem(self):
+
+ localctx = CypherParser.OC_SortItemContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 64, self.RULE_oC_SortItem)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 637
+ self.oC_Expression()
+ self.state = 642
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,88,self._ctx)
+ if la_ == 1:
+ self.state = 639
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 638
+ self.match(CypherParser.SP)
+
+
+ self.state = 641
+ _la = self._input.LA(1)
+ if not(((((_la - 68)) & ~0x3f) == 0 and ((1 << (_la - 68)) & 15) != 0)):
+ self._errHandler.recoverInline(self)
+ else:
+ self._errHandler.reportMatch(self)
+ self.consume()
+
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_WhereContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def WHERE(self):
+ return self.getToken(CypherParser.WHERE, 0)
+
+ def SP(self):
+ return self.getToken(CypherParser.SP, 0)
+
+ def oC_Expression(self):
+ return self.getTypedRuleContext(CypherParser.OC_ExpressionContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_Where
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_Where" ):
+ listener.enterOC_Where(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_Where" ):
+ listener.exitOC_Where(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_Where" ):
+ return visitor.visitOC_Where(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_Where(self):
+
+ localctx = CypherParser.OC_WhereContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 66, self.RULE_oC_Where)
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 644
+ self.match(CypherParser.WHERE)
+ self.state = 645
+ self.match(CypherParser.SP)
+ self.state = 646
+ self.oC_Expression()
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_PatternContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_PatternPart(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_PatternPartContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_PatternPartContext,i)
+
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_Pattern
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_Pattern" ):
+ listener.enterOC_Pattern(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_Pattern" ):
+ listener.exitOC_Pattern(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_Pattern" ):
+ return visitor.visitOC_Pattern(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_Pattern(self):
+
+ localctx = CypherParser.OC_PatternContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 68, self.RULE_oC_Pattern)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 648
+ self.oC_PatternPart()
+ self.state = 659
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,91,self._ctx)
+ while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER:
+ if _alt==1:
+ self.state = 650
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 649
+ self.match(CypherParser.SP)
+
+
+ self.state = 652
+ self.match(CypherParser.T__1)
+ self.state = 654
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 653
+ self.match(CypherParser.SP)
+
+
+ self.state = 656
+ self.oC_PatternPart()
+ self.state = 661
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,91,self._ctx)
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_PatternPartContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_Variable(self):
+ return self.getTypedRuleContext(CypherParser.OC_VariableContext,0)
+
+
+ def oC_AnonymousPatternPart(self):
+ return self.getTypedRuleContext(CypherParser.OC_AnonymousPatternPartContext,0)
+
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_PatternPart
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_PatternPart" ):
+ listener.enterOC_PatternPart(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_PatternPart" ):
+ listener.exitOC_PatternPart(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_PatternPart" ):
+ return visitor.visitOC_PatternPart(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_PatternPart(self):
+
+ localctx = CypherParser.OC_PatternPartContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 70, self.RULE_oC_PatternPart)
+ self._la = 0 # Token type
+ try:
+ self.state = 673
+ self._errHandler.sync(self)
+ token = self._input.LA(1)
+ if token in [83, 89, 90, 91, 98, 119, 120, 121, 124]:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 662
+ self.oC_Variable()
+ self.state = 664
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 663
+ self.match(CypherParser.SP)
+
+
+ self.state = 666
+ self.match(CypherParser.T__2)
+ self.state = 668
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 667
+ self.match(CypherParser.SP)
+
+
+ self.state = 670
+ self.oC_AnonymousPatternPart()
+ pass
+ elif token in [6]:
+ self.enterOuterAlt(localctx, 2)
+ self.state = 672
+ self.oC_AnonymousPatternPart()
+ pass
+ else:
+ raise NoViableAltException(self)
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_AnonymousPatternPartContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_PatternElement(self):
+ return self.getTypedRuleContext(CypherParser.OC_PatternElementContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_AnonymousPatternPart
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_AnonymousPatternPart" ):
+ listener.enterOC_AnonymousPatternPart(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_AnonymousPatternPart" ):
+ listener.exitOC_AnonymousPatternPart(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_AnonymousPatternPart" ):
+ return visitor.visitOC_AnonymousPatternPart(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_AnonymousPatternPart(self):
+
+ localctx = CypherParser.OC_AnonymousPatternPartContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 72, self.RULE_oC_AnonymousPatternPart)
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 675
+ self.oC_PatternElement()
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_PatternElementContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_NodePattern(self):
+ return self.getTypedRuleContext(CypherParser.OC_NodePatternContext,0)
+
+
+ def oC_PatternElementChain(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_PatternElementChainContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_PatternElementChainContext,i)
+
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def oC_PatternElement(self):
+ return self.getTypedRuleContext(CypherParser.OC_PatternElementContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_PatternElement
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_PatternElement" ):
+ listener.enterOC_PatternElement(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_PatternElement" ):
+ listener.exitOC_PatternElement(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_PatternElement" ):
+ return visitor.visitOC_PatternElement(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_PatternElement(self):
+
+ localctx = CypherParser.OC_PatternElementContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 74, self.RULE_oC_PatternElement)
+ self._la = 0 # Token type
+ try:
+ self.state = 691
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,97,self._ctx)
+ if la_ == 1:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 677
+ self.oC_NodePattern()
+ self.state = 684
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,96,self._ctx)
+ while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER:
+ if _alt==1:
+ self.state = 679
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 678
+ self.match(CypherParser.SP)
+
+
+ self.state = 681
+ self.oC_PatternElementChain()
+ self.state = 686
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,96,self._ctx)
+
+ pass
+
+ elif la_ == 2:
+ self.enterOuterAlt(localctx, 2)
+ self.state = 687
+ self.match(CypherParser.T__5)
+ self.state = 688
+ self.oC_PatternElement()
+ self.state = 689
+ self.match(CypherParser.T__6)
+ pass
+
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_RelationshipsPatternContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_NodePattern(self):
+ return self.getTypedRuleContext(CypherParser.OC_NodePatternContext,0)
+
+
+ def oC_PatternElementChain(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_PatternElementChainContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_PatternElementChainContext,i)
+
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_RelationshipsPattern
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_RelationshipsPattern" ):
+ listener.enterOC_RelationshipsPattern(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_RelationshipsPattern" ):
+ listener.exitOC_RelationshipsPattern(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_RelationshipsPattern" ):
+ return visitor.visitOC_RelationshipsPattern(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_RelationshipsPattern(self):
+
+ localctx = CypherParser.OC_RelationshipsPatternContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 76, self.RULE_oC_RelationshipsPattern)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 693
+ self.oC_NodePattern()
+ self.state = 698
+ self._errHandler.sync(self)
+ _alt = 1
+ while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER:
+ if _alt == 1:
+ self.state = 695
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 694
+ self.match(CypherParser.SP)
+
+
+ self.state = 697
+ self.oC_PatternElementChain()
+
+ else:
+ raise NoViableAltException(self)
+ self.state = 700
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,99,self._ctx)
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_NodePatternContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def oC_Variable(self):
+ return self.getTypedRuleContext(CypherParser.OC_VariableContext,0)
+
+
+ def oC_NodeLabels(self):
+ return self.getTypedRuleContext(CypherParser.OC_NodeLabelsContext,0)
+
+
+ def oC_Properties(self):
+ return self.getTypedRuleContext(CypherParser.OC_PropertiesContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_NodePattern
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_NodePattern" ):
+ listener.enterOC_NodePattern(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_NodePattern" ):
+ listener.exitOC_NodePattern(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_NodePattern" ):
+ return visitor.visitOC_NodePattern(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_NodePattern(self):
+
+ localctx = CypherParser.OC_NodePatternContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 78, self.RULE_oC_NodePattern)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 702
+ self.match(CypherParser.T__5)
+ self.state = 704
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 703
+ self.match(CypherParser.SP)
+
+
+ self.state = 710
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if ((((_la - 83)) & ~0x3f) == 0 and ((1 << (_la - 83)) & 2680059625921) != 0):
+ self.state = 706
+ self.oC_Variable()
+ self.state = 708
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 707
+ self.match(CypherParser.SP)
+
+
+
+
+ self.state = 716
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==10:
+ self.state = 712
+ self.oC_NodeLabels()
+ self.state = 714
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 713
+ self.match(CypherParser.SP)
+
+
+
+
+ self.state = 722
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==24 or _la==26:
+ self.state = 718
+ self.oC_Properties()
+ self.state = 720
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 719
+ self.match(CypherParser.SP)
+
+
+
+
+ self.state = 724
+ self.match(CypherParser.T__6)
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_PatternElementChainContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_RelationshipPattern(self):
+ return self.getTypedRuleContext(CypherParser.OC_RelationshipPatternContext,0)
+
+
+ def oC_NodePattern(self):
+ return self.getTypedRuleContext(CypherParser.OC_NodePatternContext,0)
+
+
+ def SP(self):
+ return self.getToken(CypherParser.SP, 0)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_PatternElementChain
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_PatternElementChain" ):
+ listener.enterOC_PatternElementChain(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_PatternElementChain" ):
+ listener.exitOC_PatternElementChain(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_PatternElementChain" ):
+ return visitor.visitOC_PatternElementChain(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_PatternElementChain(self):
+
+ localctx = CypherParser.OC_PatternElementChainContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 80, self.RULE_oC_PatternElementChain)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 726
+ self.oC_RelationshipPattern()
+ self.state = 728
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 727
+ self.match(CypherParser.SP)
+
+
+ self.state = 730
+ self.oC_NodePattern()
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_RelationshipPatternContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_LeftArrowHead(self):
+ return self.getTypedRuleContext(CypherParser.OC_LeftArrowHeadContext,0)
+
+
+ def oC_Dash(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_DashContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_DashContext,i)
+
+
+ def oC_RightArrowHead(self):
+ return self.getTypedRuleContext(CypherParser.OC_RightArrowHeadContext,0)
+
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def oC_RelationshipDetail(self):
+ return self.getTypedRuleContext(CypherParser.OC_RelationshipDetailContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_RelationshipPattern
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_RelationshipPattern" ):
+ listener.enterOC_RelationshipPattern(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_RelationshipPattern" ):
+ listener.exitOC_RelationshipPattern(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_RelationshipPattern" ):
+ return visitor.visitOC_RelationshipPattern(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_RelationshipPattern(self):
+
+ localctx = CypherParser.OC_RelationshipPatternContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 82, self.RULE_oC_RelationshipPattern)
+ self._la = 0 # Token type
+ try:
+ self.state = 796
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,124,self._ctx)
+ if la_ == 1:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 732
+ self.oC_LeftArrowHead()
+ self.state = 734
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 733
+ self.match(CypherParser.SP)
+
+
+ self.state = 736
+ self.oC_Dash()
+ self.state = 738
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,109,self._ctx)
+ if la_ == 1:
+ self.state = 737
+ self.match(CypherParser.SP)
+
+
+ self.state = 741
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==8:
+ self.state = 740
+ self.oC_RelationshipDetail()
+
+
+ self.state = 744
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 743
+ self.match(CypherParser.SP)
+
+
+ self.state = 746
+ self.oC_Dash()
+ self.state = 748
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 747
+ self.match(CypherParser.SP)
+
+
+ self.state = 750
+ self.oC_RightArrowHead()
+ pass
+
+ elif la_ == 2:
+ self.enterOuterAlt(localctx, 2)
+ self.state = 752
+ self.oC_LeftArrowHead()
+ self.state = 754
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 753
+ self.match(CypherParser.SP)
+
+
+ self.state = 756
+ self.oC_Dash()
+ self.state = 758
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,114,self._ctx)
+ if la_ == 1:
+ self.state = 757
+ self.match(CypherParser.SP)
+
+
+ self.state = 761
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==8:
+ self.state = 760
+ self.oC_RelationshipDetail()
+
+
+ self.state = 764
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 763
+ self.match(CypherParser.SP)
+
+
+ self.state = 766
+ self.oC_Dash()
+ pass
+
+ elif la_ == 3:
+ self.enterOuterAlt(localctx, 3)
+ self.state = 768
+ self.oC_Dash()
+ self.state = 770
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,117,self._ctx)
+ if la_ == 1:
+ self.state = 769
+ self.match(CypherParser.SP)
+
+
+ self.state = 773
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==8:
+ self.state = 772
+ self.oC_RelationshipDetail()
+
+
+ self.state = 776
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 775
+ self.match(CypherParser.SP)
+
+
+ self.state = 778
+ self.oC_Dash()
+ self.state = 780
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 779
+ self.match(CypherParser.SP)
+
+
+ self.state = 782
+ self.oC_RightArrowHead()
+ pass
+
+ elif la_ == 4:
+ self.enterOuterAlt(localctx, 4)
+ self.state = 784
+ self.oC_Dash()
+ self.state = 786
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,121,self._ctx)
+ if la_ == 1:
+ self.state = 785
+ self.match(CypherParser.SP)
+
+
+ self.state = 789
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==8:
+ self.state = 788
+ self.oC_RelationshipDetail()
+
+
+ self.state = 792
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 791
+ self.match(CypherParser.SP)
+
+
+ self.state = 794
+ self.oC_Dash()
+ pass
+
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_RelationshipDetailContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def oC_Variable(self):
+ return self.getTypedRuleContext(CypherParser.OC_VariableContext,0)
+
+
+ def oC_RelationshipTypes(self):
+ return self.getTypedRuleContext(CypherParser.OC_RelationshipTypesContext,0)
+
+
+ def oC_RangeLiteral(self):
+ return self.getTypedRuleContext(CypherParser.OC_RangeLiteralContext,0)
+
+
+ def oC_Properties(self):
+ return self.getTypedRuleContext(CypherParser.OC_PropertiesContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_RelationshipDetail
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_RelationshipDetail" ):
+ listener.enterOC_RelationshipDetail(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_RelationshipDetail" ):
+ listener.exitOC_RelationshipDetail(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_RelationshipDetail" ):
+ return visitor.visitOC_RelationshipDetail(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_RelationshipDetail(self):
+
+ localctx = CypherParser.OC_RelationshipDetailContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 84, self.RULE_oC_RelationshipDetail)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 798
+ self.match(CypherParser.T__7)
+ self.state = 800
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 799
+ self.match(CypherParser.SP)
+
+
+ self.state = 806
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if ((((_la - 83)) & ~0x3f) == 0 and ((1 << (_la - 83)) & 2680059625921) != 0):
+ self.state = 802
+ self.oC_Variable()
+ self.state = 804
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 803
+ self.match(CypherParser.SP)
+
+
+
+
+ self.state = 812
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==10:
+ self.state = 808
+ self.oC_RelationshipTypes()
+ self.state = 810
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 809
+ self.match(CypherParser.SP)
+
+
+
+
+ self.state = 815
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==5:
+ self.state = 814
+ self.oC_RangeLiteral()
+
+
+ self.state = 821
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==24 or _la==26:
+ self.state = 817
+ self.oC_Properties()
+ self.state = 819
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 818
+ self.match(CypherParser.SP)
+
+
+
+
+ self.state = 823
+ self.match(CypherParser.T__8)
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_PropertiesContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_MapLiteral(self):
+ return self.getTypedRuleContext(CypherParser.OC_MapLiteralContext,0)
+
+
+ def oC_Parameter(self):
+ return self.getTypedRuleContext(CypherParser.OC_ParameterContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_Properties
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_Properties" ):
+ listener.enterOC_Properties(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_Properties" ):
+ listener.exitOC_Properties(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_Properties" ):
+ return visitor.visitOC_Properties(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_Properties(self):
+
+ localctx = CypherParser.OC_PropertiesContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 86, self.RULE_oC_Properties)
+ try:
+ self.state = 827
+ self._errHandler.sync(self)
+ token = self._input.LA(1)
+ if token in [24]:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 825
+ self.oC_MapLiteral()
+ pass
+ elif token in [26]:
+ self.enterOuterAlt(localctx, 2)
+ self.state = 826
+ self.oC_Parameter()
+ pass
+ else:
+ raise NoViableAltException(self)
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_RelationshipTypesContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_RelTypeName(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_RelTypeNameContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_RelTypeNameContext,i)
+
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_RelationshipTypes
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_RelationshipTypes" ):
+ listener.enterOC_RelationshipTypes(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_RelationshipTypes" ):
+ listener.exitOC_RelationshipTypes(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_RelationshipTypes" ):
+ return visitor.visitOC_RelationshipTypes(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_RelationshipTypes(self):
+
+ localctx = CypherParser.OC_RelationshipTypesContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 88, self.RULE_oC_RelationshipTypes)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 829
+ self.match(CypherParser.T__9)
+ self.state = 831
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 830
+ self.match(CypherParser.SP)
+
+
+ self.state = 833
+ self.oC_RelTypeName()
+ self.state = 847
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,138,self._ctx)
+ while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER:
+ if _alt==1:
+ self.state = 835
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 834
+ self.match(CypherParser.SP)
+
+
+ self.state = 837
+ self.match(CypherParser.T__10)
+ self.state = 839
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==10:
+ self.state = 838
+ self.match(CypherParser.T__9)
+
+
+ self.state = 842
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 841
+ self.match(CypherParser.SP)
+
+
+ self.state = 844
+ self.oC_RelTypeName()
+ self.state = 849
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,138,self._ctx)
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_NodeLabelsContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_NodeLabel(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_NodeLabelContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_NodeLabelContext,i)
+
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_NodeLabels
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_NodeLabels" ):
+ listener.enterOC_NodeLabels(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_NodeLabels" ):
+ listener.exitOC_NodeLabels(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_NodeLabels" ):
+ return visitor.visitOC_NodeLabels(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_NodeLabels(self):
+
+ localctx = CypherParser.OC_NodeLabelsContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 90, self.RULE_oC_NodeLabels)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 850
+ self.oC_NodeLabel()
+ self.state = 857
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,140,self._ctx)
+ while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER:
+ if _alt==1:
+ self.state = 852
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 851
+ self.match(CypherParser.SP)
+
+
+ self.state = 854
+ self.oC_NodeLabel()
+ self.state = 859
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,140,self._ctx)
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_NodeLabelContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_LabelName(self):
+ return self.getTypedRuleContext(CypherParser.OC_LabelNameContext,0)
+
+
+ def SP(self):
+ return self.getToken(CypherParser.SP, 0)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_NodeLabel
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_NodeLabel" ):
+ listener.enterOC_NodeLabel(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_NodeLabel" ):
+ listener.exitOC_NodeLabel(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_NodeLabel" ):
+ return visitor.visitOC_NodeLabel(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_NodeLabel(self):
+
+ localctx = CypherParser.OC_NodeLabelContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 92, self.RULE_oC_NodeLabel)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 860
+ self.match(CypherParser.T__9)
+ self.state = 862
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 861
+ self.match(CypherParser.SP)
+
+
+ self.state = 864
+ self.oC_LabelName()
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_RangeLiteralContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def oC_IntegerLiteral(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_IntegerLiteralContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_IntegerLiteralContext,i)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_RangeLiteral
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_RangeLiteral" ):
+ listener.enterOC_RangeLiteral(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_RangeLiteral" ):
+ listener.exitOC_RangeLiteral(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_RangeLiteral" ):
+ return visitor.visitOC_RangeLiteral(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_RangeLiteral(self):
+
+ localctx = CypherParser.OC_RangeLiteralContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 94, self.RULE_oC_RangeLiteral)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 866
+ self.match(CypherParser.T__4)
+ self.state = 868
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 867
+ self.match(CypherParser.SP)
+
+
+ self.state = 874
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if ((((_la - 95)) & ~0x3f) == 0 and ((1 << (_la - 95)) & 7) != 0):
+ self.state = 870
+ self.oC_IntegerLiteral()
+ self.state = 872
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 871
+ self.match(CypherParser.SP)
+
+
+
+
+ self.state = 886
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==12:
+ self.state = 876
+ self.match(CypherParser.T__11)
+ self.state = 878
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 877
+ self.match(CypherParser.SP)
+
+
+ self.state = 884
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if ((((_la - 95)) & ~0x3f) == 0 and ((1 << (_la - 95)) & 7) != 0):
+ self.state = 880
+ self.oC_IntegerLiteral()
+ self.state = 882
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 881
+ self.match(CypherParser.SP)
+
+
+
+
+
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_LabelNameContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_SchemaName(self):
+ return self.getTypedRuleContext(CypherParser.OC_SchemaNameContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_LabelName
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_LabelName" ):
+ listener.enterOC_LabelName(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_LabelName" ):
+ listener.exitOC_LabelName(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_LabelName" ):
+ return visitor.visitOC_LabelName(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_LabelName(self):
+
+ localctx = CypherParser.OC_LabelNameContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 96, self.RULE_oC_LabelName)
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 888
+ self.oC_SchemaName()
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_RelTypeNameContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_SchemaName(self):
+ return self.getTypedRuleContext(CypherParser.OC_SchemaNameContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_RelTypeName
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_RelTypeName" ):
+ listener.enterOC_RelTypeName(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_RelTypeName" ):
+ listener.exitOC_RelTypeName(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_RelTypeName" ):
+ return visitor.visitOC_RelTypeName(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_RelTypeName(self):
+
+ localctx = CypherParser.OC_RelTypeNameContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 98, self.RULE_oC_RelTypeName)
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 890
+ self.oC_SchemaName()
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_PropertyExpressionContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_Atom(self):
+ return self.getTypedRuleContext(CypherParser.OC_AtomContext,0)
+
+
+ def oC_PropertyLookup(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_PropertyLookupContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_PropertyLookupContext,i)
+
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_PropertyExpression
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_PropertyExpression" ):
+ listener.enterOC_PropertyExpression(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_PropertyExpression" ):
+ listener.exitOC_PropertyExpression(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_PropertyExpression" ):
+ return visitor.visitOC_PropertyExpression(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_PropertyExpression(self):
+
+ localctx = CypherParser.OC_PropertyExpressionContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 100, self.RULE_oC_PropertyExpression)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 892
+ self.oC_Atom()
+ self.state = 897
+ self._errHandler.sync(self)
+ _alt = 1
+ while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER:
+ if _alt == 1:
+ self.state = 894
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 893
+ self.match(CypherParser.SP)
+
+
+ self.state = 896
+ self.oC_PropertyLookup()
+
+ else:
+ raise NoViableAltException(self)
+ self.state = 899
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,150,self._ctx)
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_ExpressionContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_OrExpression(self):
+ return self.getTypedRuleContext(CypherParser.OC_OrExpressionContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_Expression
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_Expression" ):
+ listener.enterOC_Expression(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_Expression" ):
+ listener.exitOC_Expression(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_Expression" ):
+ return visitor.visitOC_Expression(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_Expression(self):
+
+ localctx = CypherParser.OC_ExpressionContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 102, self.RULE_oC_Expression)
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 901
+ self.oC_OrExpression()
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_OrExpressionContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_XorExpression(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_XorExpressionContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_XorExpressionContext,i)
+
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def OR(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.OR)
+ else:
+ return self.getToken(CypherParser.OR, i)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_OrExpression
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_OrExpression" ):
+ listener.enterOC_OrExpression(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_OrExpression" ):
+ listener.exitOC_OrExpression(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_OrExpression" ):
+ return visitor.visitOC_OrExpression(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_OrExpression(self):
+
+ localctx = CypherParser.OC_OrExpressionContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 104, self.RULE_oC_OrExpression)
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 903
+ self.oC_XorExpression()
+ self.state = 910
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,151,self._ctx)
+ while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER:
+ if _alt==1:
+ self.state = 904
+ self.match(CypherParser.SP)
+ self.state = 905
+ self.match(CypherParser.OR)
+ self.state = 906
+ self.match(CypherParser.SP)
+ self.state = 907
+ self.oC_XorExpression()
+ self.state = 912
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,151,self._ctx)
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_XorExpressionContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_AndExpression(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_AndExpressionContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_AndExpressionContext,i)
+
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def XOR(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.XOR)
+ else:
+ return self.getToken(CypherParser.XOR, i)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_XorExpression
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_XorExpression" ):
+ listener.enterOC_XorExpression(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_XorExpression" ):
+ listener.exitOC_XorExpression(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_XorExpression" ):
+ return visitor.visitOC_XorExpression(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_XorExpression(self):
+
+ localctx = CypherParser.OC_XorExpressionContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 106, self.RULE_oC_XorExpression)
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 913
+ self.oC_AndExpression()
+ self.state = 920
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,152,self._ctx)
+ while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER:
+ if _alt==1:
+ self.state = 914
+ self.match(CypherParser.SP)
+ self.state = 915
+ self.match(CypherParser.XOR)
+ self.state = 916
+ self.match(CypherParser.SP)
+ self.state = 917
+ self.oC_AndExpression()
+ self.state = 922
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,152,self._ctx)
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_AndExpressionContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_NotExpression(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_NotExpressionContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_NotExpressionContext,i)
+
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def AND(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.AND)
+ else:
+ return self.getToken(CypherParser.AND, i)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_AndExpression
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_AndExpression" ):
+ listener.enterOC_AndExpression(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_AndExpression" ):
+ listener.exitOC_AndExpression(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_AndExpression" ):
+ return visitor.visitOC_AndExpression(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_AndExpression(self):
+
+ localctx = CypherParser.OC_AndExpressionContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 108, self.RULE_oC_AndExpression)
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 923
+ self.oC_NotExpression()
+ self.state = 930
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,153,self._ctx)
+ while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER:
+ if _alt==1:
+ self.state = 924
+ self.match(CypherParser.SP)
+ self.state = 925
+ self.match(CypherParser.AND)
+ self.state = 926
+ self.match(CypherParser.SP)
+ self.state = 927
+ self.oC_NotExpression()
+ self.state = 932
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,153,self._ctx)
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_NotExpressionContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_ComparisonExpression(self):
+ return self.getTypedRuleContext(CypherParser.OC_ComparisonExpressionContext,0)
+
+
+ def NOT(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.NOT)
+ else:
+ return self.getToken(CypherParser.NOT, i)
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_NotExpression
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_NotExpression" ):
+ listener.enterOC_NotExpression(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_NotExpression" ):
+ listener.exitOC_NotExpression(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_NotExpression" ):
+ return visitor.visitOC_NotExpression(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_NotExpression(self):
+
+ localctx = CypherParser.OC_NotExpressionContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 110, self.RULE_oC_NotExpression)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 939
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ while _la==76:
+ self.state = 933
+ self.match(CypherParser.NOT)
+ self.state = 935
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 934
+ self.match(CypherParser.SP)
+
+
+ self.state = 941
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+
+ self.state = 942
+ self.oC_ComparisonExpression()
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_ComparisonExpressionContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_StringListNullPredicateExpression(self):
+ return self.getTypedRuleContext(CypherParser.OC_StringListNullPredicateExpressionContext,0)
+
+
+ def oC_PartialComparisonExpression(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_PartialComparisonExpressionContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_PartialComparisonExpressionContext,i)
+
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_ComparisonExpression
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_ComparisonExpression" ):
+ listener.enterOC_ComparisonExpression(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_ComparisonExpression" ):
+ listener.exitOC_ComparisonExpression(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_ComparisonExpression" ):
+ return visitor.visitOC_ComparisonExpression(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_ComparisonExpression(self):
+
+ localctx = CypherParser.OC_ComparisonExpressionContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 112, self.RULE_oC_ComparisonExpression)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 944
+ self.oC_StringListNullPredicateExpression()
+ self.state = 951
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,157,self._ctx)
+ while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER:
+ if _alt==1:
+ self.state = 946
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 945
+ self.match(CypherParser.SP)
+
+
+ self.state = 948
+ self.oC_PartialComparisonExpression()
+ self.state = 953
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,157,self._ctx)
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_PartialComparisonExpressionContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_StringListNullPredicateExpression(self):
+ return self.getTypedRuleContext(CypherParser.OC_StringListNullPredicateExpressionContext,0)
+
+
+ def SP(self):
+ return self.getToken(CypherParser.SP, 0)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_PartialComparisonExpression
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_PartialComparisonExpression" ):
+ listener.enterOC_PartialComparisonExpression(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_PartialComparisonExpression" ):
+ listener.exitOC_PartialComparisonExpression(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_PartialComparisonExpression" ):
+ return visitor.visitOC_PartialComparisonExpression(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_PartialComparisonExpression(self):
+
+ localctx = CypherParser.OC_PartialComparisonExpressionContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 114, self.RULE_oC_PartialComparisonExpression)
+ self._la = 0 # Token type
+ try:
+ self.state = 984
+ self._errHandler.sync(self)
+ token = self._input.LA(1)
+ if token in [3]:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 954
+ self.match(CypherParser.T__2)
+ self.state = 956
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 955
+ self.match(CypherParser.SP)
+
+
+ self.state = 958
+ self.oC_StringListNullPredicateExpression()
+ pass
+ elif token in [13]:
+ self.enterOuterAlt(localctx, 2)
+ self.state = 959
+ self.match(CypherParser.T__12)
+ self.state = 961
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 960
+ self.match(CypherParser.SP)
+
+
+ self.state = 963
+ self.oC_StringListNullPredicateExpression()
+ pass
+ elif token in [14]:
+ self.enterOuterAlt(localctx, 3)
+ self.state = 964
+ self.match(CypherParser.T__13)
+ self.state = 966
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 965
+ self.match(CypherParser.SP)
+
+
+ self.state = 968
+ self.oC_StringListNullPredicateExpression()
+ pass
+ elif token in [15]:
+ self.enterOuterAlt(localctx, 4)
+ self.state = 969
+ self.match(CypherParser.T__14)
+ self.state = 971
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 970
+ self.match(CypherParser.SP)
+
+
+ self.state = 973
+ self.oC_StringListNullPredicateExpression()
+ pass
+ elif token in [16]:
+ self.enterOuterAlt(localctx, 5)
+ self.state = 974
+ self.match(CypherParser.T__15)
+ self.state = 976
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 975
+ self.match(CypherParser.SP)
+
+
+ self.state = 978
+ self.oC_StringListNullPredicateExpression()
+ pass
+ elif token in [17]:
+ self.enterOuterAlt(localctx, 6)
+ self.state = 979
+ self.match(CypherParser.T__16)
+ self.state = 981
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 980
+ self.match(CypherParser.SP)
+
+
+ self.state = 983
+ self.oC_StringListNullPredicateExpression()
+ pass
+ else:
+ raise NoViableAltException(self)
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_StringListNullPredicateExpressionContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_AddOrSubtractExpression(self):
+ return self.getTypedRuleContext(CypherParser.OC_AddOrSubtractExpressionContext,0)
+
+
+ def oC_StringPredicateExpression(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_StringPredicateExpressionContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_StringPredicateExpressionContext,i)
+
+
+ def oC_ListPredicateExpression(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_ListPredicateExpressionContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_ListPredicateExpressionContext,i)
+
+
+ def oC_NullPredicateExpression(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_NullPredicateExpressionContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_NullPredicateExpressionContext,i)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_StringListNullPredicateExpression
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_StringListNullPredicateExpression" ):
+ listener.enterOC_StringListNullPredicateExpression(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_StringListNullPredicateExpression" ):
+ listener.exitOC_StringListNullPredicateExpression(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_StringListNullPredicateExpression" ):
+ return visitor.visitOC_StringListNullPredicateExpression(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_StringListNullPredicateExpression(self):
+
+ localctx = CypherParser.OC_StringListNullPredicateExpressionContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 116, self.RULE_oC_StringListNullPredicateExpression)
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 986
+ self.oC_AddOrSubtractExpression()
+ self.state = 992
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,166,self._ctx)
+ while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER:
+ if _alt==1:
+ self.state = 990
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,165,self._ctx)
+ if la_ == 1:
+ self.state = 987
+ self.oC_StringPredicateExpression()
+ pass
+
+ elif la_ == 2:
+ self.state = 988
+ self.oC_ListPredicateExpression()
+ pass
+
+ elif la_ == 3:
+ self.state = 989
+ self.oC_NullPredicateExpression()
+ pass
+
+
+ self.state = 994
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,166,self._ctx)
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_StringPredicateExpressionContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_AddOrSubtractExpression(self):
+ return self.getTypedRuleContext(CypherParser.OC_AddOrSubtractExpressionContext,0)
+
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def STARTS(self):
+ return self.getToken(CypherParser.STARTS, 0)
+
+ def WITH(self):
+ return self.getToken(CypherParser.WITH, 0)
+
+ def ENDS(self):
+ return self.getToken(CypherParser.ENDS, 0)
+
+ def CONTAINS(self):
+ return self.getToken(CypherParser.CONTAINS, 0)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_StringPredicateExpression
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_StringPredicateExpression" ):
+ listener.enterOC_StringPredicateExpression(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_StringPredicateExpression" ):
+ listener.exitOC_StringPredicateExpression(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_StringPredicateExpression" ):
+ return visitor.visitOC_StringPredicateExpression(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_StringPredicateExpression(self):
+
+ localctx = CypherParser.OC_StringPredicateExpressionContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 118, self.RULE_oC_StringPredicateExpression)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1005
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,167,self._ctx)
+ if la_ == 1:
+ self.state = 995
+ self.match(CypherParser.SP)
+ self.state = 996
+ self.match(CypherParser.STARTS)
+ self.state = 997
+ self.match(CypherParser.SP)
+ self.state = 998
+ self.match(CypherParser.WITH)
+ pass
+
+ elif la_ == 2:
+ self.state = 999
+ self.match(CypherParser.SP)
+ self.state = 1000
+ self.match(CypherParser.ENDS)
+ self.state = 1001
+ self.match(CypherParser.SP)
+ self.state = 1002
+ self.match(CypherParser.WITH)
+ pass
+
+ elif la_ == 3:
+ self.state = 1003
+ self.match(CypherParser.SP)
+ self.state = 1004
+ self.match(CypherParser.CONTAINS)
+ pass
+
+
+ self.state = 1008
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1007
+ self.match(CypherParser.SP)
+
+
+ self.state = 1010
+ self.oC_AddOrSubtractExpression()
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_ListPredicateExpressionContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def IN(self):
+ return self.getToken(CypherParser.IN, 0)
+
+ def oC_AddOrSubtractExpression(self):
+ return self.getTypedRuleContext(CypherParser.OC_AddOrSubtractExpressionContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_ListPredicateExpression
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_ListPredicateExpression" ):
+ listener.enterOC_ListPredicateExpression(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_ListPredicateExpression" ):
+ listener.exitOC_ListPredicateExpression(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_ListPredicateExpression" ):
+ return visitor.visitOC_ListPredicateExpression(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_ListPredicateExpression(self):
+
+ localctx = CypherParser.OC_ListPredicateExpressionContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 120, self.RULE_oC_ListPredicateExpression)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1012
+ self.match(CypherParser.SP)
+ self.state = 1013
+ self.match(CypherParser.IN)
+ self.state = 1015
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1014
+ self.match(CypherParser.SP)
+
+
+ self.state = 1017
+ self.oC_AddOrSubtractExpression()
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_NullPredicateExpressionContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def IS(self):
+ return self.getToken(CypherParser.IS, 0)
+
+ def NULL(self):
+ return self.getToken(CypherParser.NULL, 0)
+
+ def NOT(self):
+ return self.getToken(CypherParser.NOT, 0)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_NullPredicateExpression
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_NullPredicateExpression" ):
+ listener.enterOC_NullPredicateExpression(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_NullPredicateExpression" ):
+ listener.exitOC_NullPredicateExpression(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_NullPredicateExpression" ):
+ return visitor.visitOC_NullPredicateExpression(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_NullPredicateExpression(self):
+
+ localctx = CypherParser.OC_NullPredicateExpressionContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 122, self.RULE_oC_NullPredicateExpression)
+ try:
+ self.state = 1029
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,170,self._ctx)
+ if la_ == 1:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1019
+ self.match(CypherParser.SP)
+ self.state = 1020
+ self.match(CypherParser.IS)
+ self.state = 1021
+ self.match(CypherParser.SP)
+ self.state = 1022
+ self.match(CypherParser.NULL)
+ pass
+
+ elif la_ == 2:
+ self.enterOuterAlt(localctx, 2)
+ self.state = 1023
+ self.match(CypherParser.SP)
+ self.state = 1024
+ self.match(CypherParser.IS)
+ self.state = 1025
+ self.match(CypherParser.SP)
+ self.state = 1026
+ self.match(CypherParser.NOT)
+ self.state = 1027
+ self.match(CypherParser.SP)
+ self.state = 1028
+ self.match(CypherParser.NULL)
+ pass
+
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_AddOrSubtractExpressionContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_MultiplyDivideModuloExpression(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_MultiplyDivideModuloExpressionContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_MultiplyDivideModuloExpressionContext,i)
+
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_AddOrSubtractExpression
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_AddOrSubtractExpression" ):
+ listener.enterOC_AddOrSubtractExpression(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_AddOrSubtractExpression" ):
+ listener.exitOC_AddOrSubtractExpression(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_AddOrSubtractExpression" ):
+ return visitor.visitOC_AddOrSubtractExpression(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_AddOrSubtractExpression(self):
+
+ localctx = CypherParser.OC_AddOrSubtractExpressionContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 124, self.RULE_oC_AddOrSubtractExpression)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1031
+ self.oC_MultiplyDivideModuloExpression()
+ self.state = 1050
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,176,self._ctx)
+ while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER:
+ if _alt==1:
+ self.state = 1048
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,175,self._ctx)
+ if la_ == 1:
+ self.state = 1033
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1032
+ self.match(CypherParser.SP)
+
+
+ self.state = 1035
+ self.match(CypherParser.T__17)
+ self.state = 1037
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1036
+ self.match(CypherParser.SP)
+
+
+ self.state = 1039
+ self.oC_MultiplyDivideModuloExpression()
+ pass
+
+ elif la_ == 2:
+ self.state = 1041
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1040
+ self.match(CypherParser.SP)
+
+
+ self.state = 1043
+ self.match(CypherParser.T__18)
+ self.state = 1045
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1044
+ self.match(CypherParser.SP)
+
+
+ self.state = 1047
+ self.oC_MultiplyDivideModuloExpression()
+ pass
+
+
+ self.state = 1052
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,176,self._ctx)
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_MultiplyDivideModuloExpressionContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_PowerOfExpression(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_PowerOfExpressionContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_PowerOfExpressionContext,i)
+
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_MultiplyDivideModuloExpression
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_MultiplyDivideModuloExpression" ):
+ listener.enterOC_MultiplyDivideModuloExpression(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_MultiplyDivideModuloExpression" ):
+ listener.exitOC_MultiplyDivideModuloExpression(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_MultiplyDivideModuloExpression" ):
+ return visitor.visitOC_MultiplyDivideModuloExpression(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_MultiplyDivideModuloExpression(self):
+
+ localctx = CypherParser.OC_MultiplyDivideModuloExpressionContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 126, self.RULE_oC_MultiplyDivideModuloExpression)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1053
+ self.oC_PowerOfExpression()
+ self.state = 1080
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,184,self._ctx)
+ while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER:
+ if _alt==1:
+ self.state = 1078
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,183,self._ctx)
+ if la_ == 1:
+ self.state = 1055
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1054
+ self.match(CypherParser.SP)
+
+
+ self.state = 1057
+ self.match(CypherParser.T__4)
+ self.state = 1059
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1058
+ self.match(CypherParser.SP)
+
+
+ self.state = 1061
+ self.oC_PowerOfExpression()
+ pass
+
+ elif la_ == 2:
+ self.state = 1063
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1062
+ self.match(CypherParser.SP)
+
+
+ self.state = 1065
+ self.match(CypherParser.T__19)
+ self.state = 1067
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1066
+ self.match(CypherParser.SP)
+
+
+ self.state = 1069
+ self.oC_PowerOfExpression()
+ pass
+
+ elif la_ == 3:
+ self.state = 1071
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1070
+ self.match(CypherParser.SP)
+
+
+ self.state = 1073
+ self.match(CypherParser.T__20)
+ self.state = 1075
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1074
+ self.match(CypherParser.SP)
+
+
+ self.state = 1077
+ self.oC_PowerOfExpression()
+ pass
+
+
+ self.state = 1082
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,184,self._ctx)
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_PowerOfExpressionContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_UnaryAddOrSubtractExpression(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_UnaryAddOrSubtractExpressionContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_UnaryAddOrSubtractExpressionContext,i)
+
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_PowerOfExpression
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_PowerOfExpression" ):
+ listener.enterOC_PowerOfExpression(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_PowerOfExpression" ):
+ listener.exitOC_PowerOfExpression(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_PowerOfExpression" ):
+ return visitor.visitOC_PowerOfExpression(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_PowerOfExpression(self):
+
+ localctx = CypherParser.OC_PowerOfExpressionContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 128, self.RULE_oC_PowerOfExpression)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1083
+ self.oC_UnaryAddOrSubtractExpression()
+ self.state = 1094
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,187,self._ctx)
+ while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER:
+ if _alt==1:
+ self.state = 1085
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1084
+ self.match(CypherParser.SP)
+
+
+ self.state = 1087
+ self.match(CypherParser.T__21)
+ self.state = 1089
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1088
+ self.match(CypherParser.SP)
+
+
+ self.state = 1091
+ self.oC_UnaryAddOrSubtractExpression()
+ self.state = 1096
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,187,self._ctx)
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_UnaryAddOrSubtractExpressionContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_NonArithmeticOperatorExpression(self):
+ return self.getTypedRuleContext(CypherParser.OC_NonArithmeticOperatorExpressionContext,0)
+
+
+ def SP(self):
+ return self.getToken(CypherParser.SP, 0)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_UnaryAddOrSubtractExpression
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_UnaryAddOrSubtractExpression" ):
+ listener.enterOC_UnaryAddOrSubtractExpression(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_UnaryAddOrSubtractExpression" ):
+ listener.exitOC_UnaryAddOrSubtractExpression(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_UnaryAddOrSubtractExpression" ):
+ return visitor.visitOC_UnaryAddOrSubtractExpression(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_UnaryAddOrSubtractExpression(self):
+
+ localctx = CypherParser.OC_UnaryAddOrSubtractExpressionContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 130, self.RULE_oC_UnaryAddOrSubtractExpression)
+ self._la = 0 # Token type
+ try:
+ self.state = 1103
+ self._errHandler.sync(self)
+ token = self._input.LA(1)
+ if token in [6, 8, 24, 26, 47, 82, 83, 84, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 105, 106, 107, 119, 120, 121, 124]:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1097
+ self.oC_NonArithmeticOperatorExpression()
+ pass
+ elif token in [18, 19]:
+ self.enterOuterAlt(localctx, 2)
+ self.state = 1098
+ _la = self._input.LA(1)
+ if not(_la==18 or _la==19):
+ self._errHandler.recoverInline(self)
+ else:
+ self._errHandler.reportMatch(self)
+ self.consume()
+ self.state = 1100
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1099
+ self.match(CypherParser.SP)
+
+
+ self.state = 1102
+ self.oC_NonArithmeticOperatorExpression()
+ pass
+ else:
+ raise NoViableAltException(self)
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_NonArithmeticOperatorExpressionContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_Atom(self):
+ return self.getTypedRuleContext(CypherParser.OC_AtomContext,0)
+
+
+ def oC_NodeLabels(self):
+ return self.getTypedRuleContext(CypherParser.OC_NodeLabelsContext,0)
+
+
+ def oC_ListOperatorExpression(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_ListOperatorExpressionContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_ListOperatorExpressionContext,i)
+
+
+ def oC_PropertyLookup(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_PropertyLookupContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_PropertyLookupContext,i)
+
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_NonArithmeticOperatorExpression
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_NonArithmeticOperatorExpression" ):
+ listener.enterOC_NonArithmeticOperatorExpression(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_NonArithmeticOperatorExpression" ):
+ listener.exitOC_NonArithmeticOperatorExpression(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_NonArithmeticOperatorExpression" ):
+ return visitor.visitOC_NonArithmeticOperatorExpression(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_NonArithmeticOperatorExpression(self):
+
+ localctx = CypherParser.OC_NonArithmeticOperatorExpressionContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 132, self.RULE_oC_NonArithmeticOperatorExpression)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1105
+ self.oC_Atom()
+ self.state = 1116
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,193,self._ctx)
+ while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER:
+ if _alt==1:
+ self.state = 1114
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,192,self._ctx)
+ if la_ == 1:
+ self.state = 1107
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1106
+ self.match(CypherParser.SP)
+
+
+ self.state = 1109
+ self.oC_ListOperatorExpression()
+ pass
+
+ elif la_ == 2:
+ self.state = 1111
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1110
+ self.match(CypherParser.SP)
+
+
+ self.state = 1113
+ self.oC_PropertyLookup()
+ pass
+
+
+ self.state = 1118
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,193,self._ctx)
+
+ self.state = 1123
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,195,self._ctx)
+ if la_ == 1:
+ self.state = 1120
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1119
+ self.match(CypherParser.SP)
+
+
+ self.state = 1122
+ self.oC_NodeLabels()
+
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_ListOperatorExpressionContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_Expression(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_ExpressionContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_ExpressionContext,i)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_ListOperatorExpression
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_ListOperatorExpression" ):
+ listener.enterOC_ListOperatorExpression(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_ListOperatorExpression" ):
+ listener.exitOC_ListOperatorExpression(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_ListOperatorExpression" ):
+ return visitor.visitOC_ListOperatorExpression(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_ListOperatorExpression(self):
+
+ localctx = CypherParser.OC_ListOperatorExpressionContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 134, self.RULE_oC_ListOperatorExpression)
+ self._la = 0 # Token type
+ try:
+ self.state = 1138
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,198,self._ctx)
+ if la_ == 1:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1125
+ self.match(CypherParser.T__7)
+ self.state = 1126
+ self.oC_Expression()
+ self.state = 1127
+ self.match(CypherParser.T__8)
+ pass
+
+ elif la_ == 2:
+ self.enterOuterAlt(localctx, 2)
+ self.state = 1129
+ self.match(CypherParser.T__7)
+ self.state = 1131
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if (((_la) & ~0x3f) == 0 and ((1 << _la) & 140737573028160) != 0) or ((((_la - 76)) & ~0x3f) == 0 and ((1 << (_la - 76)) & 343051394343361) != 0):
+ self.state = 1130
+ self.oC_Expression()
+
+
+ self.state = 1133
+ self.match(CypherParser.T__11)
+ self.state = 1135
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if (((_la) & ~0x3f) == 0 and ((1 << _la) & 140737573028160) != 0) or ((((_la - 76)) & ~0x3f) == 0 and ((1 << (_la - 76)) & 343051394343361) != 0):
+ self.state = 1134
+ self.oC_Expression()
+
+
+ self.state = 1137
+ self.match(CypherParser.T__8)
+ pass
+
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_PropertyLookupContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_PropertyKeyName(self):
+ return self.getTypedRuleContext(CypherParser.OC_PropertyKeyNameContext,0)
+
+
+ def SP(self):
+ return self.getToken(CypherParser.SP, 0)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_PropertyLookup
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_PropertyLookup" ):
+ listener.enterOC_PropertyLookup(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_PropertyLookup" ):
+ listener.exitOC_PropertyLookup(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_PropertyLookup" ):
+ return visitor.visitOC_PropertyLookup(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_PropertyLookup(self):
+
+ localctx = CypherParser.OC_PropertyLookupContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 136, self.RULE_oC_PropertyLookup)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1140
+ self.match(CypherParser.T__22)
+ self.state = 1142
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1141
+ self.match(CypherParser.SP)
+
+
+ self.state = 1144
+ self.oC_PropertyKeyName()
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_AtomContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_Literal(self):
+ return self.getTypedRuleContext(CypherParser.OC_LiteralContext,0)
+
+
+ def oC_Parameter(self):
+ return self.getTypedRuleContext(CypherParser.OC_ParameterContext,0)
+
+
+ def oC_CaseExpression(self):
+ return self.getTypedRuleContext(CypherParser.OC_CaseExpressionContext,0)
+
+
+ def COUNT(self):
+ return self.getToken(CypherParser.COUNT, 0)
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def oC_ListComprehension(self):
+ return self.getTypedRuleContext(CypherParser.OC_ListComprehensionContext,0)
+
+
+ def oC_PatternComprehension(self):
+ return self.getTypedRuleContext(CypherParser.OC_PatternComprehensionContext,0)
+
+
+ def oC_Quantifier(self):
+ return self.getTypedRuleContext(CypherParser.OC_QuantifierContext,0)
+
+
+ def oC_PatternPredicate(self):
+ return self.getTypedRuleContext(CypherParser.OC_PatternPredicateContext,0)
+
+
+ def oC_ParenthesizedExpression(self):
+ return self.getTypedRuleContext(CypherParser.OC_ParenthesizedExpressionContext,0)
+
+
+ def oC_FunctionInvocation(self):
+ return self.getTypedRuleContext(CypherParser.OC_FunctionInvocationContext,0)
+
+
+ def oC_ExistentialSubquery(self):
+ return self.getTypedRuleContext(CypherParser.OC_ExistentialSubqueryContext,0)
+
+
+ def oC_Variable(self):
+ return self.getTypedRuleContext(CypherParser.OC_VariableContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_Atom
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_Atom" ):
+ listener.enterOC_Atom(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_Atom" ):
+ listener.exitOC_Atom(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_Atom" ):
+ return visitor.visitOC_Atom(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_Atom(self):
+
+ localctx = CypherParser.OC_AtomContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 138, self.RULE_oC_Atom)
+ self._la = 0 # Token type
+ try:
+ self.state = 1170
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,203,self._ctx)
+ if la_ == 1:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1146
+ self.oC_Literal()
+ pass
+
+ elif la_ == 2:
+ self.enterOuterAlt(localctx, 2)
+ self.state = 1147
+ self.oC_Parameter()
+ pass
+
+ elif la_ == 3:
+ self.enterOuterAlt(localctx, 3)
+ self.state = 1148
+ self.oC_CaseExpression()
+ pass
+
+ elif la_ == 4:
+ self.enterOuterAlt(localctx, 4)
+ self.state = 1149
+ self.match(CypherParser.COUNT)
+ self.state = 1151
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1150
+ self.match(CypherParser.SP)
+
+
+ self.state = 1153
+ self.match(CypherParser.T__5)
+ self.state = 1155
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1154
+ self.match(CypherParser.SP)
+
+
+ self.state = 1157
+ self.match(CypherParser.T__4)
+ self.state = 1159
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1158
+ self.match(CypherParser.SP)
+
+
+ self.state = 1161
+ self.match(CypherParser.T__6)
+ pass
+
+ elif la_ == 5:
+ self.enterOuterAlt(localctx, 5)
+ self.state = 1162
+ self.oC_ListComprehension()
+ pass
+
+ elif la_ == 6:
+ self.enterOuterAlt(localctx, 6)
+ self.state = 1163
+ self.oC_PatternComprehension()
+ pass
+
+ elif la_ == 7:
+ self.enterOuterAlt(localctx, 7)
+ self.state = 1164
+ self.oC_Quantifier()
+ pass
+
+ elif la_ == 8:
+ self.enterOuterAlt(localctx, 8)
+ self.state = 1165
+ self.oC_PatternPredicate()
+ pass
+
+ elif la_ == 9:
+ self.enterOuterAlt(localctx, 9)
+ self.state = 1166
+ self.oC_ParenthesizedExpression()
+ pass
+
+ elif la_ == 10:
+ self.enterOuterAlt(localctx, 10)
+ self.state = 1167
+ self.oC_FunctionInvocation()
+ pass
+
+ elif la_ == 11:
+ self.enterOuterAlt(localctx, 11)
+ self.state = 1168
+ self.oC_ExistentialSubquery()
+ pass
+
+ elif la_ == 12:
+ self.enterOuterAlt(localctx, 12)
+ self.state = 1169
+ self.oC_Variable()
+ pass
+
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_CaseExpressionContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def END(self):
+ return self.getToken(CypherParser.END, 0)
+
+ def ELSE(self):
+ return self.getToken(CypherParser.ELSE, 0)
+
+ def oC_Expression(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_ExpressionContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_ExpressionContext,i)
+
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def CASE(self):
+ return self.getToken(CypherParser.CASE, 0)
+
+ def oC_CaseAlternative(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_CaseAlternativeContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_CaseAlternativeContext,i)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_CaseExpression
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_CaseExpression" ):
+ listener.enterOC_CaseExpression(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_CaseExpression" ):
+ listener.exitOC_CaseExpression(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_CaseExpression" ):
+ return visitor.visitOC_CaseExpression(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_CaseExpression(self):
+
+ localctx = CypherParser.OC_CaseExpressionContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 140, self.RULE_oC_CaseExpression)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1194
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,209,self._ctx)
+ if la_ == 1:
+ self.state = 1172
+ self.match(CypherParser.CASE)
+ self.state = 1177
+ self._errHandler.sync(self)
+ _alt = 1
+ while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER:
+ if _alt == 1:
+ self.state = 1174
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1173
+ self.match(CypherParser.SP)
+
+
+ self.state = 1176
+ self.oC_CaseAlternative()
+
+ else:
+ raise NoViableAltException(self)
+ self.state = 1179
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,205,self._ctx)
+
+ pass
+
+ elif la_ == 2:
+ self.state = 1181
+ self.match(CypherParser.CASE)
+ self.state = 1183
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1182
+ self.match(CypherParser.SP)
+
+
+ self.state = 1185
+ self.oC_Expression()
+ self.state = 1190
+ self._errHandler.sync(self)
+ _alt = 1
+ while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER:
+ if _alt == 1:
+ self.state = 1187
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1186
+ self.match(CypherParser.SP)
+
+
+ self.state = 1189
+ self.oC_CaseAlternative()
+
+ else:
+ raise NoViableAltException(self)
+ self.state = 1192
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,208,self._ctx)
+
+ pass
+
+
+ self.state = 1204
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,212,self._ctx)
+ if la_ == 1:
+ self.state = 1197
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1196
+ self.match(CypherParser.SP)
+
+
+ self.state = 1199
+ self.match(CypherParser.ELSE)
+ self.state = 1201
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1200
+ self.match(CypherParser.SP)
+
+
+ self.state = 1203
+ self.oC_Expression()
+
+
+ self.state = 1207
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1206
+ self.match(CypherParser.SP)
+
+
+ self.state = 1209
+ self.match(CypherParser.END)
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_CaseAlternativeContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def WHEN(self):
+ return self.getToken(CypherParser.WHEN, 0)
+
+ def oC_Expression(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_ExpressionContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_ExpressionContext,i)
+
+
+ def THEN(self):
+ return self.getToken(CypherParser.THEN, 0)
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_CaseAlternative
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_CaseAlternative" ):
+ listener.enterOC_CaseAlternative(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_CaseAlternative" ):
+ listener.exitOC_CaseAlternative(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_CaseAlternative" ):
+ return visitor.visitOC_CaseAlternative(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_CaseAlternative(self):
+
+ localctx = CypherParser.OC_CaseAlternativeContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 142, self.RULE_oC_CaseAlternative)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1211
+ self.match(CypherParser.WHEN)
+ self.state = 1213
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1212
+ self.match(CypherParser.SP)
+
+
+ self.state = 1215
+ self.oC_Expression()
+ self.state = 1217
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1216
+ self.match(CypherParser.SP)
+
+
+ self.state = 1219
+ self.match(CypherParser.THEN)
+ self.state = 1221
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1220
+ self.match(CypherParser.SP)
+
+
+ self.state = 1223
+ self.oC_Expression()
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_ListComprehensionContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_FilterExpression(self):
+ return self.getTypedRuleContext(CypherParser.OC_FilterExpressionContext,0)
+
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def oC_Expression(self):
+ return self.getTypedRuleContext(CypherParser.OC_ExpressionContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_ListComprehension
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_ListComprehension" ):
+ listener.enterOC_ListComprehension(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_ListComprehension" ):
+ listener.exitOC_ListComprehension(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_ListComprehension" ):
+ return visitor.visitOC_ListComprehension(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_ListComprehension(self):
+
+ localctx = CypherParser.OC_ListComprehensionContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 144, self.RULE_oC_ListComprehension)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1225
+ self.match(CypherParser.T__7)
+ self.state = 1227
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1226
+ self.match(CypherParser.SP)
+
+
+ self.state = 1229
+ self.oC_FilterExpression()
+ self.state = 1238
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,220,self._ctx)
+ if la_ == 1:
+ self.state = 1231
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1230
+ self.match(CypherParser.SP)
+
+
+ self.state = 1233
+ self.match(CypherParser.T__10)
+ self.state = 1235
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1234
+ self.match(CypherParser.SP)
+
+
+ self.state = 1237
+ self.oC_Expression()
+
+
+ self.state = 1241
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1240
+ self.match(CypherParser.SP)
+
+
+ self.state = 1243
+ self.match(CypherParser.T__8)
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_PatternComprehensionContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_RelationshipsPattern(self):
+ return self.getTypedRuleContext(CypherParser.OC_RelationshipsPatternContext,0)
+
+
+ def oC_Expression(self):
+ return self.getTypedRuleContext(CypherParser.OC_ExpressionContext,0)
+
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def oC_Variable(self):
+ return self.getTypedRuleContext(CypherParser.OC_VariableContext,0)
+
+
+ def oC_Where(self):
+ return self.getTypedRuleContext(CypherParser.OC_WhereContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_PatternComprehension
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_PatternComprehension" ):
+ listener.enterOC_PatternComprehension(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_PatternComprehension" ):
+ listener.exitOC_PatternComprehension(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_PatternComprehension" ):
+ return visitor.visitOC_PatternComprehension(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_PatternComprehension(self):
+
+ localctx = CypherParser.OC_PatternComprehensionContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 146, self.RULE_oC_PatternComprehension)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1245
+ self.match(CypherParser.T__7)
+ self.state = 1247
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1246
+ self.match(CypherParser.SP)
+
+
+ self.state = 1257
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if ((((_la - 83)) & ~0x3f) == 0 and ((1 << (_la - 83)) & 2680059625921) != 0):
+ self.state = 1249
+ self.oC_Variable()
+ self.state = 1251
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1250
+ self.match(CypherParser.SP)
+
+
+ self.state = 1253
+ self.match(CypherParser.T__2)
+ self.state = 1255
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1254
+ self.match(CypherParser.SP)
+
+
+
+
+ self.state = 1259
+ self.oC_RelationshipsPattern()
+ self.state = 1261
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1260
+ self.match(CypherParser.SP)
+
+
+ self.state = 1267
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==72:
+ self.state = 1263
+ self.oC_Where()
+ self.state = 1265
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1264
+ self.match(CypherParser.SP)
+
+
+
+
+ self.state = 1269
+ self.match(CypherParser.T__10)
+ self.state = 1271
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1270
+ self.match(CypherParser.SP)
+
+
+ self.state = 1273
+ self.oC_Expression()
+ self.state = 1275
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1274
+ self.match(CypherParser.SP)
+
+
+ self.state = 1277
+ self.match(CypherParser.T__8)
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_QuantifierContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def ALL(self):
+ return self.getToken(CypherParser.ALL, 0)
+
+ def oC_FilterExpression(self):
+ return self.getTypedRuleContext(CypherParser.OC_FilterExpressionContext,0)
+
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def ANY(self):
+ return self.getToken(CypherParser.ANY, 0)
+
+ def NONE(self):
+ return self.getToken(CypherParser.NONE, 0)
+
+ def SINGLE(self):
+ return self.getToken(CypherParser.SINGLE, 0)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_Quantifier
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_Quantifier" ):
+ listener.enterOC_Quantifier(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_Quantifier" ):
+ listener.exitOC_Quantifier(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_Quantifier" ):
+ return visitor.visitOC_Quantifier(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_Quantifier(self):
+
+ localctx = CypherParser.OC_QuantifierContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 148, self.RULE_oC_Quantifier)
+ self._la = 0 # Token type
+ try:
+ self.state = 1335
+ self._errHandler.sync(self)
+ token = self._input.LA(1)
+ if token in [47]:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1279
+ self.match(CypherParser.ALL)
+ self.state = 1281
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1280
+ self.match(CypherParser.SP)
+
+
+ self.state = 1283
+ self.match(CypherParser.T__5)
+ self.state = 1285
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1284
+ self.match(CypherParser.SP)
+
+
+ self.state = 1287
+ self.oC_FilterExpression()
+ self.state = 1289
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1288
+ self.match(CypherParser.SP)
+
+
+ self.state = 1291
+ self.match(CypherParser.T__6)
+ pass
+ elif token in [89]:
+ self.enterOuterAlt(localctx, 2)
+ self.state = 1293
+ self.match(CypherParser.ANY)
+ self.state = 1295
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1294
+ self.match(CypherParser.SP)
+
+
+ self.state = 1297
+ self.match(CypherParser.T__5)
+ self.state = 1299
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1298
+ self.match(CypherParser.SP)
+
+
+ self.state = 1301
+ self.oC_FilterExpression()
+ self.state = 1303
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1302
+ self.match(CypherParser.SP)
+
+
+ self.state = 1305
+ self.match(CypherParser.T__6)
+ pass
+ elif token in [90]:
+ self.enterOuterAlt(localctx, 3)
+ self.state = 1307
+ self.match(CypherParser.NONE)
+ self.state = 1309
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1308
+ self.match(CypherParser.SP)
+
+
+ self.state = 1311
+ self.match(CypherParser.T__5)
+ self.state = 1313
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1312
+ self.match(CypherParser.SP)
+
+
+ self.state = 1315
+ self.oC_FilterExpression()
+ self.state = 1317
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1316
+ self.match(CypherParser.SP)
+
+
+ self.state = 1319
+ self.match(CypherParser.T__6)
+ pass
+ elif token in [91]:
+ self.enterOuterAlt(localctx, 4)
+ self.state = 1321
+ self.match(CypherParser.SINGLE)
+ self.state = 1323
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1322
+ self.match(CypherParser.SP)
+
+
+ self.state = 1325
+ self.match(CypherParser.T__5)
+ self.state = 1327
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1326
+ self.match(CypherParser.SP)
+
+
+ self.state = 1329
+ self.oC_FilterExpression()
+ self.state = 1331
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1330
+ self.match(CypherParser.SP)
+
+
+ self.state = 1333
+ self.match(CypherParser.T__6)
+ pass
+ else:
+ raise NoViableAltException(self)
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_FilterExpressionContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_IdInColl(self):
+ return self.getTypedRuleContext(CypherParser.OC_IdInCollContext,0)
+
+
+ def oC_Where(self):
+ return self.getTypedRuleContext(CypherParser.OC_WhereContext,0)
+
+
+ def SP(self):
+ return self.getToken(CypherParser.SP, 0)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_FilterExpression
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_FilterExpression" ):
+ listener.enterOC_FilterExpression(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_FilterExpression" ):
+ listener.exitOC_FilterExpression(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_FilterExpression" ):
+ return visitor.visitOC_FilterExpression(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_FilterExpression(self):
+
+ localctx = CypherParser.OC_FilterExpressionContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 150, self.RULE_oC_FilterExpression)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1337
+ self.oC_IdInColl()
+ self.state = 1342
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,245,self._ctx)
+ if la_ == 1:
+ self.state = 1339
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1338
+ self.match(CypherParser.SP)
+
+
+ self.state = 1341
+ self.oC_Where()
+
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_PatternPredicateContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_RelationshipsPattern(self):
+ return self.getTypedRuleContext(CypherParser.OC_RelationshipsPatternContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_PatternPredicate
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_PatternPredicate" ):
+ listener.enterOC_PatternPredicate(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_PatternPredicate" ):
+ listener.exitOC_PatternPredicate(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_PatternPredicate" ):
+ return visitor.visitOC_PatternPredicate(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_PatternPredicate(self):
+
+ localctx = CypherParser.OC_PatternPredicateContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 152, self.RULE_oC_PatternPredicate)
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1344
+ self.oC_RelationshipsPattern()
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_ParenthesizedExpressionContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_Expression(self):
+ return self.getTypedRuleContext(CypherParser.OC_ExpressionContext,0)
+
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_ParenthesizedExpression
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_ParenthesizedExpression" ):
+ listener.enterOC_ParenthesizedExpression(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_ParenthesizedExpression" ):
+ listener.exitOC_ParenthesizedExpression(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_ParenthesizedExpression" ):
+ return visitor.visitOC_ParenthesizedExpression(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_ParenthesizedExpression(self):
+
+ localctx = CypherParser.OC_ParenthesizedExpressionContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 154, self.RULE_oC_ParenthesizedExpression)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1346
+ self.match(CypherParser.T__5)
+ self.state = 1348
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1347
+ self.match(CypherParser.SP)
+
+
+ self.state = 1350
+ self.oC_Expression()
+ self.state = 1352
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1351
+ self.match(CypherParser.SP)
+
+
+ self.state = 1354
+ self.match(CypherParser.T__6)
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_IdInCollContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_Variable(self):
+ return self.getTypedRuleContext(CypherParser.OC_VariableContext,0)
+
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def IN(self):
+ return self.getToken(CypherParser.IN, 0)
+
+ def oC_Expression(self):
+ return self.getTypedRuleContext(CypherParser.OC_ExpressionContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_IdInColl
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_IdInColl" ):
+ listener.enterOC_IdInColl(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_IdInColl" ):
+ listener.exitOC_IdInColl(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_IdInColl" ):
+ return visitor.visitOC_IdInColl(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_IdInColl(self):
+
+ localctx = CypherParser.OC_IdInCollContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 156, self.RULE_oC_IdInColl)
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1356
+ self.oC_Variable()
+ self.state = 1357
+ self.match(CypherParser.SP)
+ self.state = 1358
+ self.match(CypherParser.IN)
+ self.state = 1359
+ self.match(CypherParser.SP)
+ self.state = 1360
+ self.oC_Expression()
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_FunctionInvocationContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_FunctionName(self):
+ return self.getTypedRuleContext(CypherParser.OC_FunctionNameContext,0)
+
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def DISTINCT(self):
+ return self.getToken(CypherParser.DISTINCT, 0)
+
+ def oC_Expression(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_ExpressionContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_ExpressionContext,i)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_FunctionInvocation
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_FunctionInvocation" ):
+ listener.enterOC_FunctionInvocation(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_FunctionInvocation" ):
+ listener.exitOC_FunctionInvocation(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_FunctionInvocation" ):
+ return visitor.visitOC_FunctionInvocation(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_FunctionInvocation(self):
+
+ localctx = CypherParser.OC_FunctionInvocationContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 158, self.RULE_oC_FunctionInvocation)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1362
+ self.oC_FunctionName()
+ self.state = 1364
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1363
+ self.match(CypherParser.SP)
+
+
+ self.state = 1366
+ self.match(CypherParser.T__5)
+ self.state = 1368
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1367
+ self.match(CypherParser.SP)
+
+
+ self.state = 1374
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==63:
+ self.state = 1370
+ self.match(CypherParser.DISTINCT)
+ self.state = 1372
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1371
+ self.match(CypherParser.SP)
+
+
+
+
+ self.state = 1393
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if (((_la) & ~0x3f) == 0 and ((1 << _la) & 140737573028160) != 0) or ((((_la - 76)) & ~0x3f) == 0 and ((1 << (_la - 76)) & 343051394343361) != 0):
+ self.state = 1376
+ self.oC_Expression()
+ self.state = 1378
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1377
+ self.match(CypherParser.SP)
+
+
+ self.state = 1390
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ while _la==2:
+ self.state = 1380
+ self.match(CypherParser.T__1)
+ self.state = 1382
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1381
+ self.match(CypherParser.SP)
+
+
+ self.state = 1384
+ self.oC_Expression()
+ self.state = 1386
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1385
+ self.match(CypherParser.SP)
+
+
+ self.state = 1392
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+
+
+
+ self.state = 1395
+ self.match(CypherParser.T__6)
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_FunctionNameContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_Namespace(self):
+ return self.getTypedRuleContext(CypherParser.OC_NamespaceContext,0)
+
+
+ def oC_SymbolicName(self):
+ return self.getTypedRuleContext(CypherParser.OC_SymbolicNameContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_FunctionName
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_FunctionName" ):
+ listener.enterOC_FunctionName(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_FunctionName" ):
+ listener.exitOC_FunctionName(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_FunctionName" ):
+ return visitor.visitOC_FunctionName(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_FunctionName(self):
+
+ localctx = CypherParser.OC_FunctionNameContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 160, self.RULE_oC_FunctionName)
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1397
+ self.oC_Namespace()
+ self.state = 1398
+ self.oC_SymbolicName()
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_ExistentialSubqueryContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def EXISTS(self):
+ return self.getToken(CypherParser.EXISTS, 0)
+
+ def oC_RegularQuery(self):
+ return self.getTypedRuleContext(CypherParser.OC_RegularQueryContext,0)
+
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def oC_Pattern(self):
+ return self.getTypedRuleContext(CypherParser.OC_PatternContext,0)
+
+
+ def oC_Where(self):
+ return self.getTypedRuleContext(CypherParser.OC_WhereContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_ExistentialSubquery
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_ExistentialSubquery" ):
+ listener.enterOC_ExistentialSubquery(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_ExistentialSubquery" ):
+ listener.exitOC_ExistentialSubquery(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_ExistentialSubquery" ):
+ return visitor.visitOC_ExistentialSubquery(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_ExistentialSubquery(self):
+
+ localctx = CypherParser.OC_ExistentialSubqueryContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 162, self.RULE_oC_ExistentialSubquery)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1400
+ self.match(CypherParser.EXISTS)
+ self.state = 1402
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1401
+ self.match(CypherParser.SP)
+
+
+ self.state = 1404
+ self.match(CypherParser.T__23)
+ self.state = 1406
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1405
+ self.match(CypherParser.SP)
+
+
+ self.state = 1416
+ self._errHandler.sync(self)
+ token = self._input.LA(1)
+ if token in [48, 49, 50, 52, 54, 55, 56, 57, 58, 59, 61, 62]:
+ self.state = 1408
+ self.oC_RegularQuery()
+ pass
+ elif token in [6, 83, 89, 90, 91, 98, 119, 120, 121, 124]:
+ self.state = 1409
+ self.oC_Pattern()
+ self.state = 1414
+ self._errHandler.sync(self)
+ la_ = self._interp.adaptivePredict(self._input,260,self._ctx)
+ if la_ == 1:
+ self.state = 1411
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1410
+ self.match(CypherParser.SP)
+
+
+ self.state = 1413
+ self.oC_Where()
+
+
+ pass
+ else:
+ raise NoViableAltException(self)
+
+ self.state = 1419
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1418
+ self.match(CypherParser.SP)
+
+
+ self.state = 1421
+ self.match(CypherParser.T__24)
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_ExplicitProcedureInvocationContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_ProcedureName(self):
+ return self.getTypedRuleContext(CypherParser.OC_ProcedureNameContext,0)
+
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def oC_Expression(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_ExpressionContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_ExpressionContext,i)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_ExplicitProcedureInvocation
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_ExplicitProcedureInvocation" ):
+ listener.enterOC_ExplicitProcedureInvocation(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_ExplicitProcedureInvocation" ):
+ listener.exitOC_ExplicitProcedureInvocation(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_ExplicitProcedureInvocation" ):
+ return visitor.visitOC_ExplicitProcedureInvocation(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_ExplicitProcedureInvocation(self):
+
+ localctx = CypherParser.OC_ExplicitProcedureInvocationContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 164, self.RULE_oC_ExplicitProcedureInvocation)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1423
+ self.oC_ProcedureName()
+ self.state = 1425
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1424
+ self.match(CypherParser.SP)
+
+
+ self.state = 1427
+ self.match(CypherParser.T__5)
+ self.state = 1429
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1428
+ self.match(CypherParser.SP)
+
+
+ self.state = 1448
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if (((_la) & ~0x3f) == 0 and ((1 << _la) & 140737573028160) != 0) or ((((_la - 76)) & ~0x3f) == 0 and ((1 << (_la - 76)) & 343051394343361) != 0):
+ self.state = 1431
+ self.oC_Expression()
+ self.state = 1433
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1432
+ self.match(CypherParser.SP)
+
+
+ self.state = 1445
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ while _la==2:
+ self.state = 1435
+ self.match(CypherParser.T__1)
+ self.state = 1437
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1436
+ self.match(CypherParser.SP)
+
+
+ self.state = 1439
+ self.oC_Expression()
+ self.state = 1441
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1440
+ self.match(CypherParser.SP)
+
+
+ self.state = 1447
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+
+
+
+ self.state = 1450
+ self.match(CypherParser.T__6)
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_ImplicitProcedureInvocationContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_ProcedureName(self):
+ return self.getTypedRuleContext(CypherParser.OC_ProcedureNameContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_ImplicitProcedureInvocation
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_ImplicitProcedureInvocation" ):
+ listener.enterOC_ImplicitProcedureInvocation(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_ImplicitProcedureInvocation" ):
+ listener.exitOC_ImplicitProcedureInvocation(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_ImplicitProcedureInvocation" ):
+ return visitor.visitOC_ImplicitProcedureInvocation(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_ImplicitProcedureInvocation(self):
+
+ localctx = CypherParser.OC_ImplicitProcedureInvocationContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 166, self.RULE_oC_ImplicitProcedureInvocation)
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1452
+ self.oC_ProcedureName()
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_ProcedureResultFieldContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_SymbolicName(self):
+ return self.getTypedRuleContext(CypherParser.OC_SymbolicNameContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_ProcedureResultField
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_ProcedureResultField" ):
+ listener.enterOC_ProcedureResultField(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_ProcedureResultField" ):
+ listener.exitOC_ProcedureResultField(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_ProcedureResultField" ):
+ return visitor.visitOC_ProcedureResultField(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_ProcedureResultField(self):
+
+ localctx = CypherParser.OC_ProcedureResultFieldContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 168, self.RULE_oC_ProcedureResultField)
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1454
+ self.oC_SymbolicName()
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_ProcedureNameContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_Namespace(self):
+ return self.getTypedRuleContext(CypherParser.OC_NamespaceContext,0)
+
+
+ def oC_SymbolicName(self):
+ return self.getTypedRuleContext(CypherParser.OC_SymbolicNameContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_ProcedureName
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_ProcedureName" ):
+ listener.enterOC_ProcedureName(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_ProcedureName" ):
+ listener.exitOC_ProcedureName(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_ProcedureName" ):
+ return visitor.visitOC_ProcedureName(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_ProcedureName(self):
+
+ localctx = CypherParser.OC_ProcedureNameContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 170, self.RULE_oC_ProcedureName)
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1456
+ self.oC_Namespace()
+ self.state = 1457
+ self.oC_SymbolicName()
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_NamespaceContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_SymbolicName(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_SymbolicNameContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_SymbolicNameContext,i)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_Namespace
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_Namespace" ):
+ listener.enterOC_Namespace(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_Namespace" ):
+ listener.exitOC_Namespace(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_Namespace" ):
+ return visitor.visitOC_Namespace(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_Namespace(self):
+
+ localctx = CypherParser.OC_NamespaceContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 172, self.RULE_oC_Namespace)
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1464
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,270,self._ctx)
+ while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER:
+ if _alt==1:
+ self.state = 1459
+ self.oC_SymbolicName()
+ self.state = 1460
+ self.match(CypherParser.T__22)
+ self.state = 1466
+ self._errHandler.sync(self)
+ _alt = self._interp.adaptivePredict(self._input,270,self._ctx)
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_VariableContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_SymbolicName(self):
+ return self.getTypedRuleContext(CypherParser.OC_SymbolicNameContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_Variable
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_Variable" ):
+ listener.enterOC_Variable(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_Variable" ):
+ listener.exitOC_Variable(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_Variable" ):
+ return visitor.visitOC_Variable(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_Variable(self):
+
+ localctx = CypherParser.OC_VariableContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 174, self.RULE_oC_Variable)
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1467
+ self.oC_SymbolicName()
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_LiteralContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_BooleanLiteral(self):
+ return self.getTypedRuleContext(CypherParser.OC_BooleanLiteralContext,0)
+
+
+ def NULL(self):
+ return self.getToken(CypherParser.NULL, 0)
+
+ def oC_NumberLiteral(self):
+ return self.getTypedRuleContext(CypherParser.OC_NumberLiteralContext,0)
+
+
+ def StringLiteral(self):
+ return self.getToken(CypherParser.StringLiteral, 0)
+
+ def oC_ListLiteral(self):
+ return self.getTypedRuleContext(CypherParser.OC_ListLiteralContext,0)
+
+
+ def oC_MapLiteral(self):
+ return self.getTypedRuleContext(CypherParser.OC_MapLiteralContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_Literal
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_Literal" ):
+ listener.enterOC_Literal(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_Literal" ):
+ listener.exitOC_Literal(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_Literal" ):
+ return visitor.visitOC_Literal(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_Literal(self):
+
+ localctx = CypherParser.OC_LiteralContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 176, self.RULE_oC_Literal)
+ try:
+ self.state = 1475
+ self._errHandler.sync(self)
+ token = self._input.LA(1)
+ if token in [93, 94]:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1469
+ self.oC_BooleanLiteral()
+ pass
+ elif token in [82]:
+ self.enterOuterAlt(localctx, 2)
+ self.state = 1470
+ self.match(CypherParser.NULL)
+ pass
+ elif token in [95, 96, 97, 105, 106]:
+ self.enterOuterAlt(localctx, 3)
+ self.state = 1471
+ self.oC_NumberLiteral()
+ pass
+ elif token in [107]:
+ self.enterOuterAlt(localctx, 4)
+ self.state = 1472
+ self.match(CypherParser.StringLiteral)
+ pass
+ elif token in [8]:
+ self.enterOuterAlt(localctx, 5)
+ self.state = 1473
+ self.oC_ListLiteral()
+ pass
+ elif token in [24]:
+ self.enterOuterAlt(localctx, 6)
+ self.state = 1474
+ self.oC_MapLiteral()
+ pass
+ else:
+ raise NoViableAltException(self)
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_BooleanLiteralContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def TRUE(self):
+ return self.getToken(CypherParser.TRUE, 0)
+
+ def FALSE(self):
+ return self.getToken(CypherParser.FALSE, 0)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_BooleanLiteral
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_BooleanLiteral" ):
+ listener.enterOC_BooleanLiteral(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_BooleanLiteral" ):
+ listener.exitOC_BooleanLiteral(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_BooleanLiteral" ):
+ return visitor.visitOC_BooleanLiteral(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_BooleanLiteral(self):
+
+ localctx = CypherParser.OC_BooleanLiteralContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 178, self.RULE_oC_BooleanLiteral)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1477
+ _la = self._input.LA(1)
+ if not(_la==93 or _la==94):
+ self._errHandler.recoverInline(self)
+ else:
+ self._errHandler.reportMatch(self)
+ self.consume()
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_NumberLiteralContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_DoubleLiteral(self):
+ return self.getTypedRuleContext(CypherParser.OC_DoubleLiteralContext,0)
+
+
+ def oC_IntegerLiteral(self):
+ return self.getTypedRuleContext(CypherParser.OC_IntegerLiteralContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_NumberLiteral
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_NumberLiteral" ):
+ listener.enterOC_NumberLiteral(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_NumberLiteral" ):
+ listener.exitOC_NumberLiteral(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_NumberLiteral" ):
+ return visitor.visitOC_NumberLiteral(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_NumberLiteral(self):
+
+ localctx = CypherParser.OC_NumberLiteralContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 180, self.RULE_oC_NumberLiteral)
+ try:
+ self.state = 1481
+ self._errHandler.sync(self)
+ token = self._input.LA(1)
+ if token in [105, 106]:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1479
+ self.oC_DoubleLiteral()
+ pass
+ elif token in [95, 96, 97]:
+ self.enterOuterAlt(localctx, 2)
+ self.state = 1480
+ self.oC_IntegerLiteral()
+ pass
+ else:
+ raise NoViableAltException(self)
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_IntegerLiteralContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def HexInteger(self):
+ return self.getToken(CypherParser.HexInteger, 0)
+
+ def OctalInteger(self):
+ return self.getToken(CypherParser.OctalInteger, 0)
+
+ def DecimalInteger(self):
+ return self.getToken(CypherParser.DecimalInteger, 0)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_IntegerLiteral
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_IntegerLiteral" ):
+ listener.enterOC_IntegerLiteral(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_IntegerLiteral" ):
+ listener.exitOC_IntegerLiteral(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_IntegerLiteral" ):
+ return visitor.visitOC_IntegerLiteral(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_IntegerLiteral(self):
+
+ localctx = CypherParser.OC_IntegerLiteralContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 182, self.RULE_oC_IntegerLiteral)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1483
+ _la = self._input.LA(1)
+ if not(((((_la - 95)) & ~0x3f) == 0 and ((1 << (_la - 95)) & 7) != 0)):
+ self._errHandler.recoverInline(self)
+ else:
+ self._errHandler.reportMatch(self)
+ self.consume()
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_DoubleLiteralContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def ExponentDecimalReal(self):
+ return self.getToken(CypherParser.ExponentDecimalReal, 0)
+
+ def RegularDecimalReal(self):
+ return self.getToken(CypherParser.RegularDecimalReal, 0)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_DoubleLiteral
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_DoubleLiteral" ):
+ listener.enterOC_DoubleLiteral(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_DoubleLiteral" ):
+ listener.exitOC_DoubleLiteral(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_DoubleLiteral" ):
+ return visitor.visitOC_DoubleLiteral(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_DoubleLiteral(self):
+
+ localctx = CypherParser.OC_DoubleLiteralContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 184, self.RULE_oC_DoubleLiteral)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1485
+ _la = self._input.LA(1)
+ if not(_la==105 or _la==106):
+ self._errHandler.recoverInline(self)
+ else:
+ self._errHandler.reportMatch(self)
+ self.consume()
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_ListLiteralContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def oC_Expression(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_ExpressionContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_ExpressionContext,i)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_ListLiteral
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_ListLiteral" ):
+ listener.enterOC_ListLiteral(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_ListLiteral" ):
+ listener.exitOC_ListLiteral(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_ListLiteral" ):
+ return visitor.visitOC_ListLiteral(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_ListLiteral(self):
+
+ localctx = CypherParser.OC_ListLiteralContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 186, self.RULE_oC_ListLiteral)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1487
+ self.match(CypherParser.T__7)
+ self.state = 1489
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1488
+ self.match(CypherParser.SP)
+
+
+ self.state = 1508
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if (((_la) & ~0x3f) == 0 and ((1 << _la) & 140737573028160) != 0) or ((((_la - 76)) & ~0x3f) == 0 and ((1 << (_la - 76)) & 343051394343361) != 0):
+ self.state = 1491
+ self.oC_Expression()
+ self.state = 1493
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1492
+ self.match(CypherParser.SP)
+
+
+ self.state = 1505
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ while _la==2:
+ self.state = 1495
+ self.match(CypherParser.T__1)
+ self.state = 1497
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1496
+ self.match(CypherParser.SP)
+
+
+ self.state = 1499
+ self.oC_Expression()
+ self.state = 1501
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1500
+ self.match(CypherParser.SP)
+
+
+ self.state = 1507
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+
+
+
+ self.state = 1510
+ self.match(CypherParser.T__8)
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_MapLiteralContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def SP(self, i:int=None):
+ if i is None:
+ return self.getTokens(CypherParser.SP)
+ else:
+ return self.getToken(CypherParser.SP, i)
+
+ def oC_PropertyKeyName(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_PropertyKeyNameContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_PropertyKeyNameContext,i)
+
+
+ def oC_Expression(self, i:int=None):
+ if i is None:
+ return self.getTypedRuleContexts(CypherParser.OC_ExpressionContext)
+ else:
+ return self.getTypedRuleContext(CypherParser.OC_ExpressionContext,i)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_MapLiteral
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_MapLiteral" ):
+ listener.enterOC_MapLiteral(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_MapLiteral" ):
+ listener.exitOC_MapLiteral(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_MapLiteral" ):
+ return visitor.visitOC_MapLiteral(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_MapLiteral(self):
+
+ localctx = CypherParser.OC_MapLiteralContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 188, self.RULE_oC_MapLiteral)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1512
+ self.match(CypherParser.T__23)
+ self.state = 1514
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1513
+ self.match(CypherParser.SP)
+
+
+ self.state = 1549
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if (((_la) & ~0x3f) == 0 and ((1 << _la) & -1729452625654448128) != 0) or ((((_la - 64)) & ~0x3f) == 0 and ((1 << (_la - 64)) & 1441116715713822719) != 0):
+ self.state = 1516
+ self.oC_PropertyKeyName()
+ self.state = 1518
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1517
+ self.match(CypherParser.SP)
+
+
+ self.state = 1520
+ self.match(CypherParser.T__9)
+ self.state = 1522
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1521
+ self.match(CypherParser.SP)
+
+
+ self.state = 1524
+ self.oC_Expression()
+ self.state = 1526
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1525
+ self.match(CypherParser.SP)
+
+
+ self.state = 1546
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ while _la==2:
+ self.state = 1528
+ self.match(CypherParser.T__1)
+ self.state = 1530
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1529
+ self.match(CypherParser.SP)
+
+
+ self.state = 1532
+ self.oC_PropertyKeyName()
+ self.state = 1534
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1533
+ self.match(CypherParser.SP)
+
+
+ self.state = 1536
+ self.match(CypherParser.T__9)
+ self.state = 1538
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1537
+ self.match(CypherParser.SP)
+
+
+ self.state = 1540
+ self.oC_Expression()
+ self.state = 1542
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+ if _la==125:
+ self.state = 1541
+ self.match(CypherParser.SP)
+
+
+ self.state = 1548
+ self._errHandler.sync(self)
+ _la = self._input.LA(1)
+
+
+
+ self.state = 1551
+ self.match(CypherParser.T__24)
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_PropertyKeyNameContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_SchemaName(self):
+ return self.getTypedRuleContext(CypherParser.OC_SchemaNameContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_PropertyKeyName
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_PropertyKeyName" ):
+ listener.enterOC_PropertyKeyName(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_PropertyKeyName" ):
+ listener.exitOC_PropertyKeyName(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_PropertyKeyName" ):
+ return visitor.visitOC_PropertyKeyName(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_PropertyKeyName(self):
+
+ localctx = CypherParser.OC_PropertyKeyNameContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 190, self.RULE_oC_PropertyKeyName)
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1553
+ self.oC_SchemaName()
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_ParameterContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_SymbolicName(self):
+ return self.getTypedRuleContext(CypherParser.OC_SymbolicNameContext,0)
+
+
+ def DecimalInteger(self):
+ return self.getToken(CypherParser.DecimalInteger, 0)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_Parameter
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_Parameter" ):
+ listener.enterOC_Parameter(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_Parameter" ):
+ listener.exitOC_Parameter(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_Parameter" ):
+ return visitor.visitOC_Parameter(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_Parameter(self):
+
+ localctx = CypherParser.OC_ParameterContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 192, self.RULE_oC_Parameter)
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1555
+ self.match(CypherParser.T__25)
+ self.state = 1558
+ self._errHandler.sync(self)
+ token = self._input.LA(1)
+ if token in [83, 89, 90, 91, 98, 119, 120, 121, 124]:
+ self.state = 1556
+ self.oC_SymbolicName()
+ pass
+ elif token in [96]:
+ self.state = 1557
+ self.match(CypherParser.DecimalInteger)
+ pass
+ else:
+ raise NoViableAltException(self)
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_SchemaNameContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def oC_SymbolicName(self):
+ return self.getTypedRuleContext(CypherParser.OC_SymbolicNameContext,0)
+
+
+ def oC_ReservedWord(self):
+ return self.getTypedRuleContext(CypherParser.OC_ReservedWordContext,0)
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_SchemaName
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_SchemaName" ):
+ listener.enterOC_SchemaName(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_SchemaName" ):
+ listener.exitOC_SchemaName(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_SchemaName" ):
+ return visitor.visitOC_SchemaName(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_SchemaName(self):
+
+ localctx = CypherParser.OC_SchemaNameContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 194, self.RULE_oC_SchemaName)
+ try:
+ self.state = 1562
+ self._errHandler.sync(self)
+ token = self._input.LA(1)
+ if token in [83, 89, 90, 91, 98, 119, 120, 121, 124]:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1560
+ self.oC_SymbolicName()
+ pass
+ elif token in [46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 84, 85, 86, 87, 88, 92, 93, 94, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118]:
+ self.enterOuterAlt(localctx, 2)
+ self.state = 1561
+ self.oC_ReservedWord()
+ pass
+ else:
+ raise NoViableAltException(self)
+
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_ReservedWordContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def ALL(self):
+ return self.getToken(CypherParser.ALL, 0)
+
+ def ASC(self):
+ return self.getToken(CypherParser.ASC, 0)
+
+ def ASCENDING(self):
+ return self.getToken(CypherParser.ASCENDING, 0)
+
+ def BY(self):
+ return self.getToken(CypherParser.BY, 0)
+
+ def CREATE(self):
+ return self.getToken(CypherParser.CREATE, 0)
+
+ def DELETE(self):
+ return self.getToken(CypherParser.DELETE, 0)
+
+ def DESC(self):
+ return self.getToken(CypherParser.DESC, 0)
+
+ def DESCENDING(self):
+ return self.getToken(CypherParser.DESCENDING, 0)
+
+ def DETACH(self):
+ return self.getToken(CypherParser.DETACH, 0)
+
+ def EXISTS(self):
+ return self.getToken(CypherParser.EXISTS, 0)
+
+ def LIMIT(self):
+ return self.getToken(CypherParser.LIMIT, 0)
+
+ def MATCH(self):
+ return self.getToken(CypherParser.MATCH, 0)
+
+ def MERGE(self):
+ return self.getToken(CypherParser.MERGE, 0)
+
+ def ON(self):
+ return self.getToken(CypherParser.ON, 0)
+
+ def OPTIONAL(self):
+ return self.getToken(CypherParser.OPTIONAL, 0)
+
+ def ORDER(self):
+ return self.getToken(CypherParser.ORDER, 0)
+
+ def REMOVE(self):
+ return self.getToken(CypherParser.REMOVE, 0)
+
+ def RETURN(self):
+ return self.getToken(CypherParser.RETURN, 0)
+
+ def SET(self):
+ return self.getToken(CypherParser.SET, 0)
+
+ def L_SKIP(self):
+ return self.getToken(CypherParser.L_SKIP, 0)
+
+ def WHERE(self):
+ return self.getToken(CypherParser.WHERE, 0)
+
+ def WITH(self):
+ return self.getToken(CypherParser.WITH, 0)
+
+ def UNION(self):
+ return self.getToken(CypherParser.UNION, 0)
+
+ def UNWIND(self):
+ return self.getToken(CypherParser.UNWIND, 0)
+
+ def AND(self):
+ return self.getToken(CypherParser.AND, 0)
+
+ def AS(self):
+ return self.getToken(CypherParser.AS, 0)
+
+ def CONTAINS(self):
+ return self.getToken(CypherParser.CONTAINS, 0)
+
+ def DISTINCT(self):
+ return self.getToken(CypherParser.DISTINCT, 0)
+
+ def ENDS(self):
+ return self.getToken(CypherParser.ENDS, 0)
+
+ def IN(self):
+ return self.getToken(CypherParser.IN, 0)
+
+ def IS(self):
+ return self.getToken(CypherParser.IS, 0)
+
+ def NOT(self):
+ return self.getToken(CypherParser.NOT, 0)
+
+ def OR(self):
+ return self.getToken(CypherParser.OR, 0)
+
+ def STARTS(self):
+ return self.getToken(CypherParser.STARTS, 0)
+
+ def XOR(self):
+ return self.getToken(CypherParser.XOR, 0)
+
+ def FALSE(self):
+ return self.getToken(CypherParser.FALSE, 0)
+
+ def TRUE(self):
+ return self.getToken(CypherParser.TRUE, 0)
+
+ def NULL(self):
+ return self.getToken(CypherParser.NULL, 0)
+
+ def CONSTRAINT(self):
+ return self.getToken(CypherParser.CONSTRAINT, 0)
+
+ def DO(self):
+ return self.getToken(CypherParser.DO, 0)
+
+ def FOR(self):
+ return self.getToken(CypherParser.FOR, 0)
+
+ def REQUIRE(self):
+ return self.getToken(CypherParser.REQUIRE, 0)
+
+ def UNIQUE(self):
+ return self.getToken(CypherParser.UNIQUE, 0)
+
+ def CASE(self):
+ return self.getToken(CypherParser.CASE, 0)
+
+ def WHEN(self):
+ return self.getToken(CypherParser.WHEN, 0)
+
+ def THEN(self):
+ return self.getToken(CypherParser.THEN, 0)
+
+ def ELSE(self):
+ return self.getToken(CypherParser.ELSE, 0)
+
+ def END(self):
+ return self.getToken(CypherParser.END, 0)
+
+ def MANDATORY(self):
+ return self.getToken(CypherParser.MANDATORY, 0)
+
+ def SCALAR(self):
+ return self.getToken(CypherParser.SCALAR, 0)
+
+ def OF(self):
+ return self.getToken(CypherParser.OF, 0)
+
+ def ADD(self):
+ return self.getToken(CypherParser.ADD, 0)
+
+ def DROP(self):
+ return self.getToken(CypherParser.DROP, 0)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_ReservedWord
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_ReservedWord" ):
+ listener.enterOC_ReservedWord(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_ReservedWord" ):
+ listener.exitOC_ReservedWord(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_ReservedWord" ):
+ return visitor.visitOC_ReservedWord(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_ReservedWord(self):
+
+ localctx = CypherParser.OC_ReservedWordContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 196, self.RULE_oC_ReservedWord)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1564
+ _la = self._input.LA(1)
+ if not((((_la) & ~0x3f) == 0 and ((1 << _la) & -1729452625654448128) != 0) or ((((_la - 64)) & ~0x3f) == 0 and ((1 << (_la - 64)) & 35993614558953471) != 0)):
+ self._errHandler.recoverInline(self)
+ else:
+ self._errHandler.reportMatch(self)
+ self.consume()
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_SymbolicNameContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+ def UnescapedSymbolicName(self):
+ return self.getToken(CypherParser.UnescapedSymbolicName, 0)
+
+ def EscapedSymbolicName(self):
+ return self.getToken(CypherParser.EscapedSymbolicName, 0)
+
+ def HexLetter(self):
+ return self.getToken(CypherParser.HexLetter, 0)
+
+ def COUNT(self):
+ return self.getToken(CypherParser.COUNT, 0)
+
+ def FILTER(self):
+ return self.getToken(CypherParser.FILTER, 0)
+
+ def EXTRACT(self):
+ return self.getToken(CypherParser.EXTRACT, 0)
+
+ def ANY(self):
+ return self.getToken(CypherParser.ANY, 0)
+
+ def NONE(self):
+ return self.getToken(CypherParser.NONE, 0)
+
+ def SINGLE(self):
+ return self.getToken(CypherParser.SINGLE, 0)
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_SymbolicName
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_SymbolicName" ):
+ listener.enterOC_SymbolicName(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_SymbolicName" ):
+ listener.exitOC_SymbolicName(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_SymbolicName" ):
+ return visitor.visitOC_SymbolicName(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_SymbolicName(self):
+
+ localctx = CypherParser.OC_SymbolicNameContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 198, self.RULE_oC_SymbolicName)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1566
+ _la = self._input.LA(1)
+ if not(((((_la - 83)) & ~0x3f) == 0 and ((1 << (_la - 83)) & 2680059625921) != 0)):
+ self._errHandler.recoverInline(self)
+ else:
+ self._errHandler.reportMatch(self)
+ self.consume()
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_LeftArrowHeadContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_LeftArrowHead
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_LeftArrowHead" ):
+ listener.enterOC_LeftArrowHead(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_LeftArrowHead" ):
+ listener.exitOC_LeftArrowHead(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_LeftArrowHead" ):
+ return visitor.visitOC_LeftArrowHead(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_LeftArrowHead(self):
+
+ localctx = CypherParser.OC_LeftArrowHeadContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 200, self.RULE_oC_LeftArrowHead)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1568
+ _la = self._input.LA(1)
+ if not((((_la) & ~0x3f) == 0 and ((1 << _la) & 2013282304) != 0)):
+ self._errHandler.recoverInline(self)
+ else:
+ self._errHandler.reportMatch(self)
+ self.consume()
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_RightArrowHeadContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_RightArrowHead
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_RightArrowHead" ):
+ listener.enterOC_RightArrowHead(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_RightArrowHead" ):
+ listener.exitOC_RightArrowHead(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_RightArrowHead" ):
+ return visitor.visitOC_RightArrowHead(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_RightArrowHead(self):
+
+ localctx = CypherParser.OC_RightArrowHeadContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 202, self.RULE_oC_RightArrowHead)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1570
+ _la = self._input.LA(1)
+ if not((((_la) & ~0x3f) == 0 and ((1 << _la) & 32212287488) != 0)):
+ self._errHandler.recoverInline(self)
+ else:
+ self._errHandler.reportMatch(self)
+ self.consume()
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+ class OC_DashContext(ParserRuleContext):
+ __slots__ = 'parser'
+
+ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1):
+ super().__init__(parent, invokingState)
+ self.parser = parser
+
+
+ def getRuleIndex(self):
+ return CypherParser.RULE_oC_Dash
+
+ def enterRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "enterOC_Dash" ):
+ listener.enterOC_Dash(self)
+
+ def exitRule(self, listener:ParseTreeListener):
+ if hasattr( listener, "exitOC_Dash" ):
+ listener.exitOC_Dash(self)
+
+ def accept(self, visitor:ParseTreeVisitor):
+ if hasattr( visitor, "visitOC_Dash" ):
+ return visitor.visitOC_Dash(self)
+ else:
+ return visitor.visitChildren(self)
+
+
+
+
+ def oC_Dash(self):
+
+ localctx = CypherParser.OC_DashContext(self, self._ctx, self.state)
+ self.enterRule(localctx, 204, self.RULE_oC_Dash)
+ self._la = 0 # Token type
+ try:
+ self.enterOuterAlt(localctx, 1)
+ self.state = 1572
+ _la = self._input.LA(1)
+ if not((((_la) & ~0x3f) == 0 and ((1 << _la) & 70334384963584) != 0)):
+ self._errHandler.recoverInline(self)
+ else:
+ self._errHandler.reportMatch(self)
+ self.consume()
+ except RecognitionException as re:
+ localctx.exception = re
+ self._errHandler.reportError(self, re)
+ self._errHandler.recover(self, re)
+ finally:
+ self.exitRule()
+ return localctx
+
+
+
+
+
diff --git a/packages/ai/src/ai/common/graph/age/_cypher/gen/CypherVisitor.py b/packages/ai/src/ai/common/graph/age/_cypher/gen/CypherVisitor.py
new file mode 100644
index 000000000..27a7239dc
--- /dev/null
+++ b/packages/ai/src/ai/common/graph/age/_cypher/gen/CypherVisitor.py
@@ -0,0 +1,528 @@
+# Generated from Cypher.g4 by ANTLR 4.13.2
+from antlr4 import *
+if "." in __name__:
+ from .CypherParser import CypherParser
+else:
+ from CypherParser import CypherParser
+
+# This class defines a complete generic visitor for a parse tree produced by CypherParser.
+
+class CypherVisitor(ParseTreeVisitor):
+
+ # Visit a parse tree produced by CypherParser#oC_Cypher.
+ def visitOC_Cypher(self, ctx:CypherParser.OC_CypherContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_Statement.
+ def visitOC_Statement(self, ctx:CypherParser.OC_StatementContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_Query.
+ def visitOC_Query(self, ctx:CypherParser.OC_QueryContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_RegularQuery.
+ def visitOC_RegularQuery(self, ctx:CypherParser.OC_RegularQueryContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_Union.
+ def visitOC_Union(self, ctx:CypherParser.OC_UnionContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_SingleQuery.
+ def visitOC_SingleQuery(self, ctx:CypherParser.OC_SingleQueryContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_SinglePartQuery.
+ def visitOC_SinglePartQuery(self, ctx:CypherParser.OC_SinglePartQueryContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_MultiPartQuery.
+ def visitOC_MultiPartQuery(self, ctx:CypherParser.OC_MultiPartQueryContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_UpdatingClause.
+ def visitOC_UpdatingClause(self, ctx:CypherParser.OC_UpdatingClauseContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_ReadingClause.
+ def visitOC_ReadingClause(self, ctx:CypherParser.OC_ReadingClauseContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_Match.
+ def visitOC_Match(self, ctx:CypherParser.OC_MatchContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_Unwind.
+ def visitOC_Unwind(self, ctx:CypherParser.OC_UnwindContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_Merge.
+ def visitOC_Merge(self, ctx:CypherParser.OC_MergeContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_MergeAction.
+ def visitOC_MergeAction(self, ctx:CypherParser.OC_MergeActionContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_Create.
+ def visitOC_Create(self, ctx:CypherParser.OC_CreateContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_Set.
+ def visitOC_Set(self, ctx:CypherParser.OC_SetContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_SetItem.
+ def visitOC_SetItem(self, ctx:CypherParser.OC_SetItemContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_Delete.
+ def visitOC_Delete(self, ctx:CypherParser.OC_DeleteContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_Remove.
+ def visitOC_Remove(self, ctx:CypherParser.OC_RemoveContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_RemoveItem.
+ def visitOC_RemoveItem(self, ctx:CypherParser.OC_RemoveItemContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_InQueryCall.
+ def visitOC_InQueryCall(self, ctx:CypherParser.OC_InQueryCallContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_StandaloneCall.
+ def visitOC_StandaloneCall(self, ctx:CypherParser.OC_StandaloneCallContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_YieldItems.
+ def visitOC_YieldItems(self, ctx:CypherParser.OC_YieldItemsContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_YieldItem.
+ def visitOC_YieldItem(self, ctx:CypherParser.OC_YieldItemContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_With.
+ def visitOC_With(self, ctx:CypherParser.OC_WithContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_Return.
+ def visitOC_Return(self, ctx:CypherParser.OC_ReturnContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_ProjectionBody.
+ def visitOC_ProjectionBody(self, ctx:CypherParser.OC_ProjectionBodyContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_ProjectionItems.
+ def visitOC_ProjectionItems(self, ctx:CypherParser.OC_ProjectionItemsContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_ProjectionItem.
+ def visitOC_ProjectionItem(self, ctx:CypherParser.OC_ProjectionItemContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_Order.
+ def visitOC_Order(self, ctx:CypherParser.OC_OrderContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_Skip.
+ def visitOC_Skip(self, ctx:CypherParser.OC_SkipContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_Limit.
+ def visitOC_Limit(self, ctx:CypherParser.OC_LimitContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_SortItem.
+ def visitOC_SortItem(self, ctx:CypherParser.OC_SortItemContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_Where.
+ def visitOC_Where(self, ctx:CypherParser.OC_WhereContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_Pattern.
+ def visitOC_Pattern(self, ctx:CypherParser.OC_PatternContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_PatternPart.
+ def visitOC_PatternPart(self, ctx:CypherParser.OC_PatternPartContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_AnonymousPatternPart.
+ def visitOC_AnonymousPatternPart(self, ctx:CypherParser.OC_AnonymousPatternPartContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_PatternElement.
+ def visitOC_PatternElement(self, ctx:CypherParser.OC_PatternElementContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_RelationshipsPattern.
+ def visitOC_RelationshipsPattern(self, ctx:CypherParser.OC_RelationshipsPatternContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_NodePattern.
+ def visitOC_NodePattern(self, ctx:CypherParser.OC_NodePatternContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_PatternElementChain.
+ def visitOC_PatternElementChain(self, ctx:CypherParser.OC_PatternElementChainContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_RelationshipPattern.
+ def visitOC_RelationshipPattern(self, ctx:CypherParser.OC_RelationshipPatternContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_RelationshipDetail.
+ def visitOC_RelationshipDetail(self, ctx:CypherParser.OC_RelationshipDetailContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_Properties.
+ def visitOC_Properties(self, ctx:CypherParser.OC_PropertiesContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_RelationshipTypes.
+ def visitOC_RelationshipTypes(self, ctx:CypherParser.OC_RelationshipTypesContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_NodeLabels.
+ def visitOC_NodeLabels(self, ctx:CypherParser.OC_NodeLabelsContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_NodeLabel.
+ def visitOC_NodeLabel(self, ctx:CypherParser.OC_NodeLabelContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_RangeLiteral.
+ def visitOC_RangeLiteral(self, ctx:CypherParser.OC_RangeLiteralContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_LabelName.
+ def visitOC_LabelName(self, ctx:CypherParser.OC_LabelNameContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_RelTypeName.
+ def visitOC_RelTypeName(self, ctx:CypherParser.OC_RelTypeNameContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_PropertyExpression.
+ def visitOC_PropertyExpression(self, ctx:CypherParser.OC_PropertyExpressionContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_Expression.
+ def visitOC_Expression(self, ctx:CypherParser.OC_ExpressionContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_OrExpression.
+ def visitOC_OrExpression(self, ctx:CypherParser.OC_OrExpressionContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_XorExpression.
+ def visitOC_XorExpression(self, ctx:CypherParser.OC_XorExpressionContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_AndExpression.
+ def visitOC_AndExpression(self, ctx:CypherParser.OC_AndExpressionContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_NotExpression.
+ def visitOC_NotExpression(self, ctx:CypherParser.OC_NotExpressionContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_ComparisonExpression.
+ def visitOC_ComparisonExpression(self, ctx:CypherParser.OC_ComparisonExpressionContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_PartialComparisonExpression.
+ def visitOC_PartialComparisonExpression(self, ctx:CypherParser.OC_PartialComparisonExpressionContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_StringListNullPredicateExpression.
+ def visitOC_StringListNullPredicateExpression(self, ctx:CypherParser.OC_StringListNullPredicateExpressionContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_StringPredicateExpression.
+ def visitOC_StringPredicateExpression(self, ctx:CypherParser.OC_StringPredicateExpressionContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_ListPredicateExpression.
+ def visitOC_ListPredicateExpression(self, ctx:CypherParser.OC_ListPredicateExpressionContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_NullPredicateExpression.
+ def visitOC_NullPredicateExpression(self, ctx:CypherParser.OC_NullPredicateExpressionContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_AddOrSubtractExpression.
+ def visitOC_AddOrSubtractExpression(self, ctx:CypherParser.OC_AddOrSubtractExpressionContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_MultiplyDivideModuloExpression.
+ def visitOC_MultiplyDivideModuloExpression(self, ctx:CypherParser.OC_MultiplyDivideModuloExpressionContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_PowerOfExpression.
+ def visitOC_PowerOfExpression(self, ctx:CypherParser.OC_PowerOfExpressionContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_UnaryAddOrSubtractExpression.
+ def visitOC_UnaryAddOrSubtractExpression(self, ctx:CypherParser.OC_UnaryAddOrSubtractExpressionContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_NonArithmeticOperatorExpression.
+ def visitOC_NonArithmeticOperatorExpression(self, ctx:CypherParser.OC_NonArithmeticOperatorExpressionContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_ListOperatorExpression.
+ def visitOC_ListOperatorExpression(self, ctx:CypherParser.OC_ListOperatorExpressionContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_PropertyLookup.
+ def visitOC_PropertyLookup(self, ctx:CypherParser.OC_PropertyLookupContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_Atom.
+ def visitOC_Atom(self, ctx:CypherParser.OC_AtomContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_CaseExpression.
+ def visitOC_CaseExpression(self, ctx:CypherParser.OC_CaseExpressionContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_CaseAlternative.
+ def visitOC_CaseAlternative(self, ctx:CypherParser.OC_CaseAlternativeContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_ListComprehension.
+ def visitOC_ListComprehension(self, ctx:CypherParser.OC_ListComprehensionContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_PatternComprehension.
+ def visitOC_PatternComprehension(self, ctx:CypherParser.OC_PatternComprehensionContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_Quantifier.
+ def visitOC_Quantifier(self, ctx:CypherParser.OC_QuantifierContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_FilterExpression.
+ def visitOC_FilterExpression(self, ctx:CypherParser.OC_FilterExpressionContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_PatternPredicate.
+ def visitOC_PatternPredicate(self, ctx:CypherParser.OC_PatternPredicateContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_ParenthesizedExpression.
+ def visitOC_ParenthesizedExpression(self, ctx:CypherParser.OC_ParenthesizedExpressionContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_IdInColl.
+ def visitOC_IdInColl(self, ctx:CypherParser.OC_IdInCollContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_FunctionInvocation.
+ def visitOC_FunctionInvocation(self, ctx:CypherParser.OC_FunctionInvocationContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_FunctionName.
+ def visitOC_FunctionName(self, ctx:CypherParser.OC_FunctionNameContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_ExistentialSubquery.
+ def visitOC_ExistentialSubquery(self, ctx:CypherParser.OC_ExistentialSubqueryContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_ExplicitProcedureInvocation.
+ def visitOC_ExplicitProcedureInvocation(self, ctx:CypherParser.OC_ExplicitProcedureInvocationContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_ImplicitProcedureInvocation.
+ def visitOC_ImplicitProcedureInvocation(self, ctx:CypherParser.OC_ImplicitProcedureInvocationContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_ProcedureResultField.
+ def visitOC_ProcedureResultField(self, ctx:CypherParser.OC_ProcedureResultFieldContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_ProcedureName.
+ def visitOC_ProcedureName(self, ctx:CypherParser.OC_ProcedureNameContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_Namespace.
+ def visitOC_Namespace(self, ctx:CypherParser.OC_NamespaceContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_Variable.
+ def visitOC_Variable(self, ctx:CypherParser.OC_VariableContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_Literal.
+ def visitOC_Literal(self, ctx:CypherParser.OC_LiteralContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_BooleanLiteral.
+ def visitOC_BooleanLiteral(self, ctx:CypherParser.OC_BooleanLiteralContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_NumberLiteral.
+ def visitOC_NumberLiteral(self, ctx:CypherParser.OC_NumberLiteralContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_IntegerLiteral.
+ def visitOC_IntegerLiteral(self, ctx:CypherParser.OC_IntegerLiteralContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_DoubleLiteral.
+ def visitOC_DoubleLiteral(self, ctx:CypherParser.OC_DoubleLiteralContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_ListLiteral.
+ def visitOC_ListLiteral(self, ctx:CypherParser.OC_ListLiteralContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_MapLiteral.
+ def visitOC_MapLiteral(self, ctx:CypherParser.OC_MapLiteralContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_PropertyKeyName.
+ def visitOC_PropertyKeyName(self, ctx:CypherParser.OC_PropertyKeyNameContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_Parameter.
+ def visitOC_Parameter(self, ctx:CypherParser.OC_ParameterContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_SchemaName.
+ def visitOC_SchemaName(self, ctx:CypherParser.OC_SchemaNameContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_ReservedWord.
+ def visitOC_ReservedWord(self, ctx:CypherParser.OC_ReservedWordContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_SymbolicName.
+ def visitOC_SymbolicName(self, ctx:CypherParser.OC_SymbolicNameContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_LeftArrowHead.
+ def visitOC_LeftArrowHead(self, ctx:CypherParser.OC_LeftArrowHeadContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_RightArrowHead.
+ def visitOC_RightArrowHead(self, ctx:CypherParser.OC_RightArrowHeadContext):
+ return self.visitChildren(ctx)
+
+
+ # Visit a parse tree produced by CypherParser#oC_Dash.
+ def visitOC_Dash(self, ctx:CypherParser.OC_DashContext):
+ return self.visitChildren(ctx)
+
+
+
+del CypherParser
\ No newline at end of file
diff --git a/packages/ai/src/ai/common/graph/age/_cypher/gen/__init__.py b/packages/ai/src/ai/common/graph/age/_cypher/gen/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/ai/src/ai/common/graph/age/analysis.py b/packages/ai/src/ai/common/graph/age/analysis.py
new file mode 100644
index 000000000..eb73e605c
--- /dev/null
+++ b/packages/ai/src/ai/common/graph/age/analysis.py
@@ -0,0 +1,265 @@
+# =============================================================================
+# MIT License
+# Copyright (c) 2026 Aparavi Software AG
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# =============================================================================
+
+"""Cypher analysis: openCypher ANTLR parse -> structured facts.
+
+This is the parse stage of the translation pipeline. It runs the vendored
+openCypher M23 parser (see ``_cypher/``) over the query text and extracts the
+facts every later stage needs:
+
+- the RETURN projection (column display names, or ``RETURN *`` / no RETURN),
+- which write clauses appear (typed contexts, not regex),
+- variable-length relationship ranges (for the firewall's depth cap),
+- ``$param`` names, and invoked function names (for the capability table).
+
+Parsing failures raise :class:`~.errors.AgeTranslationError` with the
+collected syntax messages — the same text the LLM repair loop feeds back.
+"""
+
+from __future__ import annotations
+
+import re
+from dataclasses import dataclass, field
+from typing import List, Optional, Set, Tuple
+
+from antlr4 import CommonTokenStream, InputStream, ParserRuleContext
+from antlr4.error.ErrorListener import ErrorListener
+
+from ._cypher.gen.CypherLexer import CypherLexer
+from ._cypher.gen.CypherParser import CypherParser
+from .errors import AgeTranslationError
+
+
+@dataclass
+class ReturnColumn:
+ """One projection item of the final RETURN clause."""
+
+ # Name used to key decoded result rows: the AS alias when present, the
+ # bare variable/property text when simple, else a generated placeholder.
+ display_name: str
+ # Raw expression text (diagnostics only).
+ expression: str
+ # True when display_name came from an explicit AS alias.
+ is_alias: bool = False
+
+
+@dataclass
+class CypherFacts:
+ """Everything later pipeline stages need to know about a Cypher query."""
+
+ query: str
+ # None => the statement has no RETURN clause (pure write); the emitter
+ # synthesizes a single throwaway column in that case.
+ return_columns: Optional[List[ReturnColumn]] = None
+ returns_star: bool = False
+ write_clauses: Set[str] = field(default_factory=set)
+ has_call: bool = False
+ # (lower_bound, upper_bound) per variable-length pattern; None = unbounded.
+ var_length_ranges: List[Tuple[Optional[int], Optional[int]]] = field(default_factory=list)
+ param_names: Set[str] = field(default_factory=set)
+ function_names: Set[str] = field(default_factory=set)
+ # MERGE ... ON CREATE/ON MATCH actions (capability: merge_on_set).
+ has_merge_action: bool = False
+ # A label check used as an expression (e.g. WHERE (n:Label)) rather than in
+ # a pattern (capability: where_label_check).
+ has_where_label_check: bool = False
+ # A pattern node carrying more than one label (capability: multi_label).
+ has_multi_label: bool = False
+ # ORDER BY references a bare projection alias rather than an expression
+ # (capability: order_by_alias — AGE 1.5.0: 'could not find rte for ').
+ has_order_by_alias: bool = False
+
+ @property
+ def is_write(self) -> bool:
+ return bool(self.write_clauses)
+
+
+class _CollectingErrorListener(ErrorListener):
+ """Collect syntax errors instead of ANTLR's default stderr printing."""
+
+ def __init__(self) -> None:
+ self.errors: List[str] = []
+
+ def syntaxError(self, recognizer, offendingSymbol, line, column, msg, e): # noqa: N802 (ANTLR API)
+ self.errors.append(f'line {line}:{column} {msg}')
+
+
+def _strip_backticks(name: str) -> str:
+ if len(name) >= 2 and name.startswith('`') and name.endswith('`'):
+ return name[1:-1].replace('``', '`')
+ return name
+
+
+def _source_text(ctx: ParserRuleContext) -> str:
+ """Original source slice for a context (getText() drops whitespace)."""
+ stream = ctx.start.getInputStream()
+ return stream.getText(ctx.start.start, ctx.stop.stop)
+
+
+def _walk(ctx, visit) -> None:
+ visit(ctx)
+ for i in range(ctx.getChildCount()):
+ child = ctx.getChild(i)
+ if child.getChildCount() or isinstance(child, ParserRuleContext):
+ _walk(child, visit)
+ else:
+ visit(child)
+
+
+def _depth(ctx) -> int:
+ d = 0
+ node = ctx
+ while node.parentCtx is not None:
+ node = node.parentCtx
+ d += 1
+ return d
+
+
+def _parse_range_literal(text: str) -> Tuple[Optional[int], Optional[int]]:
+ """``*``/``*3``/``*1..3``/``*..5``/``*2..`` -> (lower, upper), None=absent."""
+ body = text.lstrip('*').replace(' ', '')
+ if not body:
+ return (None, None)
+ if '..' not in body:
+ n = int(body)
+ return (n, n)
+ low_s, _, high_s = body.partition('..')
+ return (int(low_s) if low_s else None, int(high_s) if high_s else None)
+
+
+def _projection_column(item_ctx) -> ReturnColumn:
+ """Build a ReturnColumn from an oC_ProjectionItem context."""
+ expression = _source_text(item_ctx)
+ # Grammar: oC_ProjectionItem : ( oC_Expression SP AS SP oC_Variable ) | oC_Expression ;
+ variable = item_ctx.oC_Variable() if hasattr(item_ctx, 'oC_Variable') else None
+ if variable is not None:
+ return ReturnColumn(display_name=_strip_backticks(variable.getText()), expression=expression, is_alias=True)
+ expr_text = item_ctx.oC_Expression().getText()
+ return ReturnColumn(display_name=_strip_backticks(expr_text), expression=expression)
+
+
+def analyze(query: str) -> CypherFacts:
+ """Parse ``query`` with the openCypher grammar and extract translation facts.
+
+ Raises:
+ AgeTranslationError: when the text is not syntactically valid Cypher.
+ """
+ if not query or not query.strip():
+ raise AgeTranslationError('Empty Cypher query')
+
+ listener = _CollectingErrorListener()
+ lexer = CypherLexer(InputStream(query))
+ lexer.removeErrorListeners()
+ lexer.addErrorListener(listener)
+ parser = CypherParser(CommonTokenStream(lexer))
+ parser.removeErrorListeners()
+ parser.addErrorListener(listener)
+
+ # The recursive-descent parse (and the walk below) burn stack per nesting
+ # level. The firewall's pre-parse nesting cap keeps normal input far from
+ # the limit; this backstop keeps the layer's contract — every failure is
+ # an AgeTranslationError — even for input that slips past the scan.
+ try:
+ tree = parser.oC_Cypher()
+ except RecursionError:
+ raise AgeTranslationError(
+ 'Cypher expression is nested too deeply to parse (reduce parenthesis/expression nesting)'
+ ) from None
+ if listener.errors:
+ raise AgeTranslationError('Cypher syntax error: ' + '; '.join(listener.errors[:5]))
+
+ facts = CypherFacts(query=query)
+
+ # Write clauses / CALLs / ranges / params / functions — a single walk.
+ write_map = {
+ CypherParser.OC_CreateContext: 'CREATE',
+ CypherParser.OC_MergeContext: 'MERGE',
+ CypherParser.OC_DeleteContext: 'DELETE',
+ CypherParser.OC_SetContext: 'SET',
+ CypherParser.OC_RemoveContext: 'REMOVE',
+ }
+ returns: List = []
+
+ def visit(node) -> None:
+ for ctx_cls, clause in write_map.items():
+ if isinstance(node, ctx_cls):
+ facts.write_clauses.add(clause)
+ if isinstance(node, (CypherParser.OC_StandaloneCallContext, CypherParser.OC_InQueryCallContext)):
+ facts.has_call = True
+ if isinstance(node, CypherParser.OC_RangeLiteralContext):
+ facts.var_length_ranges.append(_parse_range_literal(node.getText()))
+ if isinstance(node, CypherParser.OC_ParameterContext):
+ facts.param_names.add(node.getText().lstrip('$'))
+ if isinstance(node, CypherParser.OC_FunctionInvocationContext):
+ name_ctx = node.oC_FunctionName()
+ if name_ctx is not None:
+ facts.function_names.add(name_ctx.getText().lower())
+ if isinstance(node, CypherParser.OC_MergeActionContext):
+ facts.has_merge_action = True
+ if isinstance(node, CypherParser.OC_NodeLabelsContext):
+ if len(node.oC_NodeLabel()) > 1:
+ facts.has_multi_label = True
+ # A NodeLabels context under a WHERE (not inside a pattern) is a
+ # label check used as an expression: WHERE (n:Label).
+ parent = node.parentCtx
+ while parent is not None:
+ if isinstance(parent, CypherParser.OC_WhereContext):
+ facts.has_where_label_check = True
+ break
+ if isinstance(parent, CypherParser.OC_PatternContext):
+ break
+ parent = parent.parentCtx
+ if isinstance(node, CypherParser.OC_ReturnContext):
+ returns.append(node)
+ if isinstance(node, CypherParser.OC_SortItemContext):
+ sort_items.append(node.oC_Expression().getText())
+
+ sort_items: List[str] = []
+ try:
+ _walk(tree, visit)
+ except RecursionError:
+ raise AgeTranslationError(
+ 'Cypher expression is nested too deeply to analyze (reduce parenthesis/expression nesting)'
+ ) from None
+
+ if returns:
+ # Multiple RETURNs at the same (shallowest) depth = UNION branches; all
+ # branches must project the same column count, so the first shallowest
+ # RETURN defines the projection. Deeper RETURNs (subqueries) are not
+ # the statement's result shape.
+ top = min(returns, key=_depth)
+ items_ctx = top.oC_ProjectionBody().oC_ProjectionItems()
+ # Grammar: oC_ProjectionItems : ( '*' ... ) | ( oC_ProjectionItem ... ) ;
+ if items_ctx.getChild(0).getText() == '*':
+ facts.returns_star = True
+ else:
+ facts.return_columns = [_projection_column(item) for item in items_ctx.oC_ProjectionItem()]
+
+ # ORDER BY a bare AS-alias (not an expression) — an AGE 1.5.0 wrinkle:
+ # verified to fail with 'could not find rte for '.
+ if facts.return_columns:
+ aliases = {c.display_name for c in facts.return_columns if c.is_alias}
+ bare = re.compile(r'^[A-Za-z_][A-Za-z0-9_]*$')
+ facts.has_order_by_alias = any(bare.fullmatch(item) and item in aliases for item in sort_items)
+
+ return facts
diff --git a/packages/ai/src/ai/common/graph/age/capabilities.py b/packages/ai/src/ai/common/graph/age/capabilities.py
new file mode 100644
index 000000000..842cc78c7
--- /dev/null
+++ b/packages/ai/src/ai/common/graph/age/capabilities.py
@@ -0,0 +1,187 @@
+# =============================================================================
+# MIT License
+# Copyright (c) 2026 Aparavi Software AG
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# =============================================================================
+
+"""Dialect capability table — the rewrite framework, keyed by AGE version.
+
+Apache AGE speaks a Cypher subset with version-specific gaps. Each capability
+cell records how a given AGE version handles one feature:
+
+- ``SUPPORTED``: passes through untouched.
+- ``EMULATE``: a rewrite hook transforms the query into something AGE runs
+ (framework only in v1 — no emulations are implemented yet).
+- ``REJECT``: raise :class:`~.errors.AgeUnsupportedFeature` before touching
+ the database, with an actionable message.
+- ``TBD``: not yet verified against the live version. TBD cells pass through
+ — if AGE cannot run the construct, its own error surfaces via EXPLAIN or
+ execution (which the LLM repair loop consumes). Verify each TBD cell
+ against the live instance and promote it to a real status.
+
+Cells marked verified below were confirmed empirically against a container on
+the live pin (PG16 + AGE 1.5.0); see the layer README.
+"""
+
+from __future__ import annotations
+
+import enum
+from dataclasses import dataclass
+from typing import Callable, Dict, Optional
+
+from .analysis import CypherFacts
+from .errors import AgeUnsupportedFeature
+
+
+class CellStatus(enum.Enum):
+ SUPPORTED = 'supported'
+ EMULATE = 'emulate'
+ REJECT = 'reject'
+ TBD = 'tbd'
+
+
+@dataclass(frozen=True)
+class Capability:
+ """One feature cell for one AGE version."""
+
+ feature: str
+ status: CellStatus
+ # Predicate: does this query use the feature?
+ detect: Callable[[CypherFacts], bool]
+ # Message detail for REJECT; verification note otherwise.
+ detail: str = ''
+ # EMULATE hook: CypherFacts -> rewritten Cypher text. v1: none implemented.
+ rewrite: Optional[Callable[[CypherFacts], str]] = None
+
+
+def _uses_function(name: str) -> Callable[[CypherFacts], bool]:
+ return lambda facts: name in facts.function_names
+
+
+# ---------------------------------------------------------------------------
+# AGE 1.5.0 — what the RocketRide cloud runs
+# ---------------------------------------------------------------------------
+
+AGE_1_5_0: Dict[str, Capability] = {
+ cap.feature: cap
+ for cap in (
+ # --- verified against the live pin (see README / age-mechanics probes) ---
+ Capability(
+ feature='datetime_function',
+ status=CellStatus.REJECT,
+ detect=_uses_function('datetime'),
+ detail=(
+ 'AGE 1.5.0 has no datetime() (function ag_catalog.age_datetime does not exist); '
+ 'store timestamps as ISO-8601 strings or epoch numbers instead'
+ ),
+ ),
+ Capability(
+ feature='return_star',
+ status=CellStatus.REJECT,
+ detect=lambda facts: facts.returns_star,
+ detail=(
+ "AGE requires an explicit result-column list and 'RETURN *' needs full scope "
+ 'analysis to synthesize one; list the columns explicitly (e.g. RETURN a, b)'
+ ),
+ ),
+ Capability(
+ feature='order_by_alias',
+ status=CellStatus.REJECT,
+ detect=lambda facts: facts.has_order_by_alias,
+ detail=(
+ "AGE 1.5.0 cannot ORDER BY a projection alias ('could not find rte for '); "
+ 'order by the expression itself (e.g. ORDER BY r.since, not ORDER BY since)'
+ ),
+ ),
+ # --- verified 2026-07-28 against the exact pin container (PG 16.14 +
+ # AGE 1.5.0): all four are syntax-level rejections ('syntax error at or
+ # near ...'), while plain MERGE on the same graph succeeds — so these
+ # are real 1.5.0 grammar gaps, not harness artifacts. ---
+ Capability(
+ feature='merge_on_set',
+ status=CellStatus.REJECT,
+ detect=lambda facts: facts.has_merge_action,
+ detail=(
+ "AGE 1.5.0 does not parse MERGE ... ON CREATE/ON MATCH SET (syntax error at 'ON'); "
+ 'use plain MERGE, then a separate SET clause (MERGE (n) ... SET n.prop = ...)'
+ ),
+ ),
+ Capability(
+ feature='where_label_check',
+ status=CellStatus.REJECT,
+ detect=lambda facts: facts.has_where_label_check,
+ detail=(
+ "AGE 1.5.0 does not parse label predicates in WHERE — 'WHERE n:Label' and "
+ "'WHERE (n:Label)' both fail (syntax error at ':'); put the label in the "
+ 'pattern instead (MATCH (n:Label))'
+ ),
+ ),
+ Capability(
+ feature='multi_label',
+ status=CellStatus.REJECT,
+ detect=lambda facts: facts.has_multi_label,
+ detail=(
+ 'AGE 1.5.0 does not parse multiple labels on one node — (n:A:B) fails in both '
+ "CREATE and MATCH (syntax error at ':'); model the second label as a property "
+ 'or an edge to a category node'
+ ),
+ ),
+ Capability(
+ feature='shortest_path',
+ status=CellStatus.REJECT,
+ detect=_uses_function('shortestpath'),
+ detail=(
+ 'AGE 1.5.0 has no shortestPath() (syntax error); use a bounded variable-length '
+ 'match (e.g. (a)-[*..3]-(b)) and rank by path length in the query'
+ ),
+ ),
+ )
+}
+
+#: Capability tables keyed by AGE version string.
+CAPABILITY_TABLES: Dict[str, Dict[str, Capability]] = {
+ '1.5.0': AGE_1_5_0,
+}
+
+DEFAULT_AGE_VERSION = '1.5.0'
+
+
+def apply_capabilities(facts: CypherFacts, age_version: str = DEFAULT_AGE_VERSION) -> CypherFacts:
+ """Run the dialect gate: REJECT cells raise; EMULATE cells rewrite.
+
+ Unknown versions fall back to the newest known table (closest behaviour
+ beats no gate at all). TBD and SUPPORTED cells pass through unchanged.
+ """
+ table = CAPABILITY_TABLES.get(age_version)
+ if table is None:
+ table = CAPABILITY_TABLES[sorted(CAPABILITY_TABLES)[-1]]
+
+ for cap in table.values():
+ if not cap.detect(facts):
+ continue
+ if cap.status is CellStatus.REJECT:
+ raise AgeUnsupportedFeature(cap.feature, cap.detail)
+ if cap.status is CellStatus.EMULATE and cap.rewrite is not None:
+ # v1 ships the framework only; when emulations land they re-analyze
+ # the rewritten text so later stages see consistent facts.
+ from .analysis import analyze
+
+ facts = analyze(cap.rewrite(facts))
+ return facts
diff --git a/packages/ai/src/ai/common/graph/age/decode.py b/packages/ai/src/ai/common/graph/age/decode.py
new file mode 100644
index 000000000..0d0277877
--- /dev/null
+++ b/packages/ai/src/ai/common/graph/age/decode.py
@@ -0,0 +1,120 @@
+# =============================================================================
+# MIT License
+# Copyright (c) 2026 Aparavi Software AG
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# =============================================================================
+
+"""agtype decoding: AGE result text -> plain Python values.
+
+Wraps the vendored Apache AGE driver decoder (``_agtype/``, ANTLR-based —
+handles the full agtype superset incl. ``::vertex``/``::edge``/``::path``
+annotations, nested containers, and numeric edge cases that ``json.loads``
+cannot). Graph entities are flattened to plain dicts so rows survive the node
+layer's JSON sanitisation:
+
+- vertex -> ``{'id', 'label', 'properties'}``
+- edge -> ``{'id', 'label', 'start_id', 'end_id', 'properties'}``
+- path -> list of those, in traversal order
+"""
+
+from __future__ import annotations
+
+from decimal import Decimal
+from typing import Any
+
+from antlr4 import CommonTokenStream, InputStream
+from antlr4.error.ErrorListener import ErrorListener
+
+from ._agtype import Edge, Path, Vertex
+from ._agtype.builder import ResultVisitor
+from ._agtype.gen.AgtypeLexer import AgtypeLexer
+from ._agtype.gen.AgtypeParser import AgtypeParser
+from .errors import AgeTranslationError
+
+
+class _StrictErrorListener(ErrorListener):
+ """Fail loud on malformed agtype (the vendored default error-recovers silently)."""
+
+ def __init__(self) -> None:
+ self.errors: list[str] = []
+
+ def syntaxError(self, recognizer, offendingSymbol, line, column, msg, e): # noqa: N802 (ANTLR API)
+ self.errors.append(f'line {line}:{column} {msg}')
+
+
+def _parse_agtype_strict(text: str) -> Any:
+ """Parse agtype text with the vendored grammar, raising on syntax errors."""
+ listener = _StrictErrorListener()
+ lexer = AgtypeLexer(InputStream(text))
+ lexer.removeErrorListeners()
+ lexer.addErrorListener(listener)
+ parser = AgtypeParser(CommonTokenStream(lexer))
+ parser.removeErrorListeners()
+ parser.addErrorListener(listener)
+ tree = parser.agType()
+ if listener.errors:
+ raise AgeTranslationError('agtype syntax error: ' + '; '.join(listener.errors[:3]))
+ return tree.accept(ResultVisitor(None))
+
+
+def _to_plain(value: Any) -> Any:
+ if isinstance(value, Vertex):
+ return {
+ 'id': value.id,
+ 'label': value.label,
+ 'properties': _to_plain(value.properties or {}),
+ }
+ if isinstance(value, Edge):
+ return {
+ 'id': value.id,
+ 'label': value.label,
+ 'start_id': getattr(value, 'start_id', None),
+ 'end_id': getattr(value, 'end_id', None),
+ 'properties': _to_plain(value.properties or {}),
+ }
+ if isinstance(value, Path):
+ return [_to_plain(entity) for entity in value]
+ if isinstance(value, Decimal):
+ # agtype numerics parse to Decimal; JSON layers downstream want floats.
+ return float(value)
+ if isinstance(value, dict):
+ return {k: _to_plain(v) for k, v in value.items()}
+ if isinstance(value, list):
+ return [_to_plain(v) for v in value]
+ return value
+
+
+def decode_agtype(text: Any) -> Any:
+ """Decode one agtype column value into a plain Python value.
+
+ psycopg2 returns agtype columns as ``str`` (no registered adapter); SQL
+ NULL arrives as ``None`` and passes through.
+ """
+ if text is None:
+ return None
+ if not isinstance(text, str):
+ # Defensive: an adapter already produced a Python value.
+ return _to_plain(text)
+ try:
+ return _to_plain(_parse_agtype_strict(text))
+ except AgeTranslationError:
+ raise
+ except Exception as e:
+ raise AgeTranslationError(f'Failed to decode agtype value: {e}') from e
diff --git a/packages/ai/src/ai/common/graph/age/emit.py b/packages/ai/src/ai/common/graph/age/emit.py
new file mode 100644
index 000000000..6c5c50e3b
--- /dev/null
+++ b/packages/ai/src/ai/common/graph/age/emit.py
@@ -0,0 +1,169 @@
+# =============================================================================
+# MIT License
+# Copyright (c) 2026 Aparavi Software AG
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# =============================================================================
+
+"""SQL emission: the ``cypher()`` envelope, column list, and bind plan.
+
+Facts verified against the live AGE 1.5.0 pin (see the layer README):
+
+- The envelope is ``SELECT * FROM cypher('', $tag$$tag$ [, $1])
+ AS ()``. The AS-list column *count* must equal the RETURN
+ column count exactly; the *names* are arbitrary — we emit generated safe
+ identifiers (``c0..cN``) and key decoded rows by the Cypher display names.
+- Statements without RETURN take a single synthesized column and yield 0 rows.
+- The cypher() params argument MUST be a real prepared-statement parameter —
+ an inline ``'...'::agtype`` literal is rejected by AGE. With params we emit
+ ``PREPARE (agtype) AS ...`` / ``EXECUTE (%s::agtype)`` /
+ ``DEALLOCATE `` for one transaction (transaction-pooler safe;
+ a rolled-back transaction discards its prepared statements).
+- The Cypher body is embedded via dollar-quoting; the tag is chosen to not
+ collide with the body so user text can never escape the envelope.
+
+The emitter is DB-driver-agnostic: it produces SQL strings with psycopg2
+(``%s``) placeholders plus the bind values; the node owns the connection.
+"""
+
+from __future__ import annotations
+
+import json
+import re
+import uuid
+from dataclasses import dataclass, field
+from typing import Any, Dict, List, Optional, Tuple
+
+from .analysis import CypherFacts
+from .errors import AgeTranslationError
+
+# AGE graph names: unquoted-identifier discipline, same as table names.
+VALID_GRAPH_NAME = re.compile(r'^[A-Za-z_][A-Za-z0-9_]{0,62}$')
+
+
+@dataclass
+class TranslatedQuery:
+ """The executable plan for one Cypher query.
+
+ ``statements`` run in order inside ONE transaction; the statement at
+ ``result_index`` produces the result rows (one agtype column per entry in
+ ``columns``). ``binds`` pair positionally with ``statements``.
+ """
+
+ columns: List[str]
+ statements: List[str] = field(default_factory=list)
+ binds: List[Tuple[Any, ...]] = field(default_factory=list)
+ result_index: int = 0
+ # True when the statement set carries no RETURN (pure write): zero rows
+ # are expected and 'affected' semantics apply.
+ has_return: bool = True
+ # The caller must open the transaction READ ONLY (safe path).
+ read_only: bool = False
+
+
+def _dollar_quote(body: str) -> str:
+ """Wrap ``body`` in a dollar-quote tag guaranteed absent from it."""
+ tag = '$rr_cypher$'
+ counter = 0
+ while tag in body:
+ counter += 1
+ tag = f'$rr_cypher{counter}$'
+ return f'{tag}{body}{tag}'
+
+
+def _params_to_agtype_json(params: Dict[str, Any]) -> str:
+ """Serialize the params map to the JSON text cast to agtype at bind time."""
+ try:
+ return json.dumps(params)
+ except (TypeError, ValueError) as e:
+ raise AgeTranslationError(f'Cypher parameters are not JSON-serialisable: {e}') from e
+
+
+def emit(
+ facts: CypherFacts,
+ graph_name: str,
+ params: Optional[Dict[str, Any]] = None,
+ limit: Optional[int] = None,
+ statement_timeout_ms: int = 30_000,
+ read_only: bool = False,
+) -> TranslatedQuery:
+ """Build the transaction's statement list for one translated query.
+
+ The caller wraps the statements in BEGIN/COMMIT (READ ONLY when
+ ``read_only``); AGE is preloaded server-side, so no ``LOAD`` is emitted.
+ """
+ if not VALID_GRAPH_NAME.fullmatch(graph_name or ''):
+ raise AgeTranslationError(
+ f'Invalid graph name {graph_name!r}: must be a valid identifier '
+ '(letters, digits, underscores; max 63 chars)'
+ )
+ if params and not facts.param_names:
+ raise AgeTranslationError('Parameters were supplied but the Cypher query references no $parameters')
+ if facts.param_names:
+ missing = sorted(facts.param_names - set(params or {}))
+ if missing:
+ raise AgeTranslationError(
+ 'Cypher query references parameters with no supplied values: '
+ + ', '.join(f'${name}' for name in missing)
+ )
+
+ columns = [c.display_name for c in facts.return_columns] if facts.return_columns else []
+ has_return = facts.return_columns is not None
+ # Generated safe identifiers; count is what AGE checks, names are ours.
+ as_list = ', '.join(f'c{i} agtype' for i in range(len(columns))) if has_return else 'v agtype'
+
+ body = _dollar_quote(facts.query)
+ tq = TranslatedQuery(columns=columns, has_return=has_return)
+
+ # Session discipline for the transaction-pooled cloud endpoint: LOCAL only.
+ tq.statements.append('SET LOCAL search_path = ag_catalog,"$user",public')
+ tq.binds.append(())
+ timeout = max(1, int(statement_timeout_ms))
+ tq.statements.append(f"SET LOCAL statement_timeout = '{timeout}ms'")
+ tq.binds.append(())
+
+ outer_limit = ''
+ if limit is not None:
+ outer_limit = f' LIMIT {max(0, int(limit))}'
+
+ # Key the branch on the query's $parameters, not on bool(params): the
+ # missing-value check above guarantees params covers param_names here.
+ if facts.param_names:
+ # cypher()'s params argument must be a prepared-statement parameter.
+ stmt_name = f'_rr_age_{uuid.uuid4().hex[:12]}'
+ select = (
+ f'PREPARE {stmt_name}(agtype) AS '
+ f"SELECT * FROM cypher('{graph_name}', {body}, $1) AS ({as_list}){outer_limit}"
+ )
+ tq.statements.append(select)
+ tq.binds.append(())
+ tq.statements.append(f'EXECUTE {stmt_name}(%s::agtype)')
+ tq.binds.append((_params_to_agtype_json(params),))
+ tq.result_index = len(tq.statements) - 1
+ tq.statements.append(f'DEALLOCATE {stmt_name}')
+ tq.binds.append(())
+ else:
+ select = f"SELECT * FROM cypher('{graph_name}', {body}) AS ({as_list}){outer_limit}"
+ tq.statements.append(select)
+ tq.binds.append(())
+ tq.result_index = len(tq.statements) - 1
+
+ # read_only is enforced by the caller's BEGIN READ ONLY; carried for tests.
+ tq.read_only = read_only
+ return tq
diff --git a/packages/ai/src/ai/common/graph/age/errors.py b/packages/ai/src/ai/common/graph/age/errors.py
new file mode 100644
index 000000000..9cc82510c
--- /dev/null
+++ b/packages/ai/src/ai/common/graph/age/errors.py
@@ -0,0 +1,54 @@
+# =============================================================================
+# MIT License
+# Copyright (c) 2026 Aparavi Software AG
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# =============================================================================
+
+"""Error taxonomy for the Cypher -> Apache AGE translation layer.
+
+All three fail loud (per the design): callers surface the message to the
+LLM-repair loop or to the user rather than degrading silently.
+"""
+
+
+class AgeTranslationError(Exception):
+ """The Cypher text could not be translated (e.g. it does not parse)."""
+
+
+class AgeUnsupportedFeature(AgeTranslationError):
+ """The Cypher uses a feature this AGE version cannot run (capability REJECT)."""
+
+ def __init__(self, feature: str, detail: str = ''):
+ self.feature = feature
+ message = f'Unsupported on Apache AGE: {feature}'
+ if detail:
+ message += f' — {detail}'
+ super().__init__(message)
+
+
+class AgeFirewallRejected(AgeTranslationError):
+ """The query violates a firewall rule (resource cap or write on the safe path)."""
+
+ def __init__(self, rule: str, detail: str = ''):
+ self.rule = rule
+ message = f'Query rejected by firewall ({rule})'
+ if detail:
+ message += f': {detail}'
+ super().__init__(message)
diff --git a/packages/ai/src/ai/common/graph/age/firewall.py b/packages/ai/src/ai/common/graph/age/firewall.py
new file mode 100644
index 000000000..bb567ccc5
--- /dev/null
+++ b/packages/ai/src/ai/common/graph/age/firewall.py
@@ -0,0 +1,135 @@
+# =============================================================================
+# MIT License
+# Copyright (c) 2026 Aparavi Software AG
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# =============================================================================
+
+"""Query firewall for the Cypher -> AGE pipeline.
+
+Two rule families, applied per the design:
+
+- **Resource caps** — enforced on BOTH paths (safe and raw EXECUTE). Raw
+ execute skips *semantic* checks, never resource limits: a runaway traversal
+ is just as expensive when a trusted app submits it. The database-side
+ backstop is a ``statement_timeout`` the emitter sets per transaction.
+- **Semantic rules** — safe path only. Writes and procedure CALLs are
+ rejected here before translation; the true guard is the read-only
+ transaction the node runs safe queries in (server-side, like FalkorDB's
+ RO_QUERY), with this check giving precise, pre-flight errors.
+
+Caps are constructor arguments; the node currently wires only
+``statement_timeout_ms`` through its config (``query_timeout_ms`` in
+services.json) and leaves the rest at these defaults, which were
+sanity-checked against the pinned AGE 1.5.0 container.
+"""
+
+from __future__ import annotations
+
+from dataclasses import dataclass
+
+from .analysis import CypherFacts
+from .errors import AgeFirewallRejected
+
+# Defaults tuned against the pinned container (see the layer README):
+# unbounded/deep variable-length traversals are the main resource hazard —
+# AGE expands them recursively; depth 10 on a connected graph is already huge.
+DEFAULT_MAX_QUERY_LENGTH = 10_000
+DEFAULT_MAX_VAR_LENGTH_DEPTH = 10
+DEFAULT_STATEMENT_TIMEOUT_MS = 30_000
+# The ANTLR parser burns ~14 stack frames per expression nesting level, so
+# Python's default recursion limit trips around 70 nested parens. 50 keeps a
+# deterministic, well-messaged rejection comfortably below that cliff.
+DEFAULT_MAX_NESTING_DEPTH = 50
+
+
+@dataclass(frozen=True)
+class FirewallConfig:
+ max_query_length: int = DEFAULT_MAX_QUERY_LENGTH
+ max_var_length_depth: int = DEFAULT_MAX_VAR_LENGTH_DEPTH
+ max_nesting_depth: int = DEFAULT_MAX_NESTING_DEPTH
+ # Applied by the emitter as SET LOCAL statement_timeout in the query's
+ # transaction — the database-side resource backstop for both paths.
+ statement_timeout_ms: int = DEFAULT_STATEMENT_TIMEOUT_MS
+
+
+def check_pre_parse(cypher: str, config: FirewallConfig) -> None:
+ """Caps that must run BEFORE the parse — both paths.
+
+ The ANTLR parse is the expensive step these caps exist to bound (linear
+ CPU in query length; stack depth in expression nesting), so checking them
+ after ``analyze()`` would defeat their purpose. Character scans only —
+ no parser involvement. Raises AgeFirewallRejected on violation.
+
+ The nesting scan counts brackets inside string literals too; a legitimate
+ string containing 50+ consecutive open brackets is contrived enough that
+ the deterministic pre-parse rejection is the better trade.
+ """
+ if len(cypher) > config.max_query_length:
+ raise AgeFirewallRejected(
+ 'max_query_length',
+ f'query is {len(cypher)} chars (limit {config.max_query_length})',
+ )
+ depth = 0
+ for ch in cypher:
+ if ch in '([{':
+ depth += 1
+ if depth > config.max_nesting_depth:
+ raise AgeFirewallRejected(
+ 'max_nesting_depth',
+ f'expression nesting exceeds {config.max_nesting_depth} levels '
+ '(reduce parenthesis/bracket nesting)',
+ )
+ elif ch in ')]}':
+ depth = max(0, depth - 1)
+
+
+def check_resource_caps(facts: CypherFacts, config: FirewallConfig) -> None:
+ """Resource caps — both paths. Raises AgeFirewallRejected on violation."""
+ if len(facts.query) > config.max_query_length:
+ raise AgeFirewallRejected(
+ 'max_query_length',
+ f'query is {len(facts.query)} chars (limit {config.max_query_length})',
+ )
+
+ for lower, upper in facts.var_length_ranges:
+ if upper is None:
+ raise AgeFirewallRejected(
+ 'unbounded_var_length',
+ f'variable-length pattern without an upper bound (use e.g. *1..{config.max_var_length_depth})',
+ )
+ if upper > config.max_var_length_depth:
+ raise AgeFirewallRejected(
+ 'max_var_length_depth',
+ f'variable-length upper bound {upper} exceeds limit {config.max_var_length_depth}',
+ )
+ if lower is not None and lower > upper:
+ raise AgeFirewallRejected('invalid_var_length_range', f'lower bound {lower} exceeds upper bound {upper}')
+
+
+def check_semantics_readonly(facts: CypherFacts) -> None:
+ """Semantic rules — safe path only. Rejects writes and procedure CALLs."""
+ if facts.write_clauses:
+ clauses = ', '.join(sorted(facts.write_clauses))
+ raise AgeFirewallRejected(
+ 'write_clause',
+ f'{clauses} not allowed on the read-only path (use the execute path for writes)',
+ )
+ if facts.has_call:
+ raise AgeFirewallRejected('procedure_call', 'CALL is not allowed on the read-only path')
diff --git a/packages/ai/src/ai/common/graph/age/translate.py b/packages/ai/src/ai/common/graph/age/translate.py
new file mode 100644
index 000000000..58116de09
--- /dev/null
+++ b/packages/ai/src/ai/common/graph/age/translate.py
@@ -0,0 +1,107 @@
+# =============================================================================
+# MIT License
+# Copyright (c) 2026 Aparavi Software AG
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# =============================================================================
+
+"""The translation pipeline: Cypher text in, executable SQL plan out.
+
+Stage order (per the design):
+
+1. **Parse** — openCypher ANTLR grammar (:mod:`.analysis`).
+2. **Firewall** — resource caps on both paths; semantic read-only rules on
+ the safe path only (:mod:`.firewall`).
+3. **Dialect** — capability table keyed by AGE version (:mod:`.capabilities`).
+4. **Emit** — ``cypher()`` envelope, synthesized column list, bind plan
+ (:mod:`.emit`).
+
+Decoding results back to Python is :func:`~.decode.decode_agtype`; row
+assembly from the emitted plan is :func:`decode_row` here. The layer is a
+pure transform — no database access — so every stage unit-tests offline.
+"""
+
+from __future__ import annotations
+
+import enum
+from typing import Any, Dict, List, Optional, Sequence
+
+from .analysis import analyze
+from .capabilities import DEFAULT_AGE_VERSION, apply_capabilities
+from .decode import decode_agtype
+from .emit import TranslatedQuery, emit
+from .firewall import FirewallConfig, check_pre_parse, check_resource_caps, check_semantics_readonly
+
+
+class TranslateMode(enum.Enum):
+ """Which firewall posture the caller runs under."""
+
+ # The LLM/tool path: semantic read-only rules + resource caps; the node
+ # runs the plan in a READ ONLY transaction.
+ SAFE = 'safe'
+ # The gated EXECUTE path: resource caps still apply, semantics don't.
+ RAW = 'raw'
+
+
+def translate(
+ cypher: str,
+ params: Optional[Dict[str, Any]] = None,
+ limit: Optional[int] = None,
+ mode: TranslateMode = TranslateMode.SAFE,
+ graph_name: str = 'rocketride',
+ age_version: str = DEFAULT_AGE_VERSION,
+ firewall: Optional[FirewallConfig] = None,
+) -> TranslatedQuery:
+ """Translate one Cypher query into an executable AGE SQL plan.
+
+ Raises:
+ AgeTranslationError: the text does not parse (or cannot be emitted).
+ AgeUnsupportedFeature: a capability cell rejects the construct.
+ AgeFirewallRejected: a resource cap or (safe path) semantic rule fires.
+ """
+ config = firewall or FirewallConfig()
+
+ # Length/nesting caps run BEFORE the parse — the parse is the expensive
+ # step they bound, so an oversize query is rejected in O(n) scan time
+ # instead of after seconds of ANTLR work.
+ check_pre_parse(cypher, config)
+
+ facts = analyze(cypher)
+
+ # Firewall: resource caps guard BOTH paths; only semantics are mode-gated.
+ check_resource_caps(facts, config)
+ if mode is TranslateMode.SAFE:
+ check_semantics_readonly(facts)
+
+ facts = apply_capabilities(facts, age_version)
+
+ return emit(
+ facts,
+ graph_name=graph_name,
+ params=params,
+ limit=limit,
+ statement_timeout_ms=config.statement_timeout_ms,
+ read_only=mode is TranslateMode.SAFE,
+ )
+
+
+def decode_row(plan: TranslatedQuery, raw_row: Sequence[Any]) -> Dict[str, Any]:
+ """Decode one raw result row into a dict keyed by Cypher display names."""
+ names: List[str] = plan.columns or []
+ return {(names[i] if i < len(names) else f'col{i}'): decode_agtype(value) for i, value in enumerate(raw_row)}
diff --git a/packages/ai/src/ai/common/requirements.txt b/packages/ai/src/ai/common/requirements.txt
index 0b30d2b15..ed4679617 100644
--- a/packages/ai/src/ai/common/requirements.txt
+++ b/packages/ai/src/ai/common/requirements.txt
@@ -6,3 +6,7 @@ json5
RestrictedPython
SQLAlchemy>=2.0,<2.1
yappi
+# graph/age translation layer: runtime for the vendored ANTLR-generated
+# openCypher + agtype parsers (range, not ==: depends() compiles all
+# requirement files into one constraint set)
+antlr4-python3-runtime>=4.13.2,<4.14
diff --git a/packages/ai/src/ai/common/rocketride_db.py b/packages/ai/src/ai/common/rocketride_db.py
new file mode 100644
index 000000000..7ee80c8eb
--- /dev/null
+++ b/packages/ai/src/ai/common/rocketride_db.py
@@ -0,0 +1,194 @@
+# =============================================================================
+# MIT License
+# Copyright (c) 2026 Aparavi Software AG
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# =============================================================================
+
+"""
+Shared connection resolution for the RocketRide cloud database nodes.
+
+``rocketride_sql``, ``rocketride_vector`` and ``rocketride_graph`` all connect
+to the same per-tenant cloud database and take **no** connection config. Rather
+than reading host/user/password, they resolve a ready DSN from the account layer
+keyed by the authenticated ``client_id``. Centralising that resolution here
+gives the three nodes one identical seam — and one place for tests to inject a
+fake resolver — so node code is byte-for-byte independent of which
+``Account.resolve_db_dsn`` implementation (OSS stub, real SaaS, or fake) is live.
+
+In the hosted path the task engine resolves the DSN server-side and injects it
+as ``ROCKETRIDE_DB_DSN``; nodes never resolve identity themselves (identity
+rides the task file since #1686 — the task engine does NOT set
+``ROCKETRIDE_CLIENT_ID``). The env-based ``current_client_id`` accessor
+remains for the account fallback used by server-process callers and tests.
+The account call is async while node lifecycle code (``beginGlobal`` /
+``Store.__init__``) is synchronous, so it is bridged with ``asyncio.run`` —
+safe only from a thread with no running loop, which is exactly how nodes are
+constructed.
+"""
+
+import asyncio
+import os
+import urllib.parse
+
+# Identity override consumed only by the account fallback (server-process
+# callers and tests). The task engine does NOT set this in node subprocesses:
+# identity rides the task file (#1686) and the ROCKETRIDE_* env namespace is
+# caller-influenced by design.
+CLIENT_ID_ENV = 'ROCKETRIDE_CLIENT_ID'
+
+# The task engine resolves the per-tenant DSN server-side at task start (only
+# the server process has SaaS account context) and injects it here for
+# pipelines that contain RocketRide cloud DB nodes.
+DB_DSN_ENV = 'ROCKETRIDE_DB_DSN'
+
+# When server-side resolution fails (e.g. a broker outage), the task engine
+# passes the reason down here instead of a DSN, so the node can report the
+# real cause rather than the misleading "sign into RocketRide cloud" error.
+DB_RESOLVE_ERROR_ENV = 'ROCKETRIDE_DB_RESOLVE_ERROR'
+
+
+def _run_async(coro):
+ """Run a coroutine from synchronous node lifecycle code.
+
+ Only safe to call from a thread with no running event loop — node
+ ``beginGlobal`` / ``Store.__init__`` are invoked synchronously by the
+ engine, which is the supported caller. Pre-check so a misuse surfaces with
+ a clear message instead of ``asyncio.run``'s generic RuntimeError.
+ """
+ try:
+ asyncio.get_running_loop()
+ except RuntimeError:
+ pass
+ else:
+ raise RuntimeError(
+ 'resolve_rocketride_dsn must not be called from a thread with a running event loop; '
+ 'RocketRide DB node lifecycle methods are constructed synchronously by the engine.'
+ )
+ return asyncio.run(coro)
+
+
+def current_client_id() -> str:
+ """Return the authenticated ``client_id`` for the running task.
+
+ Raises:
+ ValueError: if the identity env var is missing — which normally means
+ the node is running outside the task engine, or the connection is
+ not signed into RocketRide cloud.
+ """
+ client_id = os.environ.get(CLIENT_ID_ENV, '').strip()
+ if not client_id:
+ raise ValueError(
+ f'{CLIENT_ID_ENV} is not set; RocketRide cloud DB nodes require a signed-in RocketRide cloud identity'
+ )
+ return client_id
+
+
+def to_sqlalchemy_url(dsn: str) -> str:
+ """Normalise a libpq URL DSN to a SQLAlchemy psycopg2 URL.
+
+ ``psycopg2.connect`` accepts a bare ``postgresql://`` / ``postgres://`` URL,
+ but SQLAlchemy's ``create_engine`` needs an explicit driver in the scheme.
+ A DSN that already names a driver (``postgresql+psycopg2://``) or is a
+ key/value libpq string (``host=... dbname=...``) is passed through
+ unchanged.
+ """
+ if '+' in dsn.split(':', 1)[0]:
+ # Scheme already carries a driver (e.g. postgresql+psycopg2://).
+ return dsn
+ for prefix in ('postgresql://', 'postgres://'):
+ if dsn.startswith(prefix):
+ return 'postgresql+psycopg2://' + dsn[len(prefix) :]
+ return dsn
+
+
+def resolve_rocketride_dsn() -> str:
+ """Resolve the per-tenant libpq DSN for the current client.
+
+ Delivery order:
+
+ 1. ``ROCKETRIDE_DB_DSN`` env — the production path. The task engine
+ resolves the DSN server-side at task start (the SaaS account context
+ exists only in the server process; node subprocesses always see the
+ OSS account) and injects it here at task start.
+ 2. ``Account.resolve_db_dsn(client_id)`` — fallback for callers running
+ inside the server process (or tests injecting a fake account). On an
+ unconfigured open-source build this raises the cloud-sign-in error.
+
+ Returns the raw DSN (URL form, directly usable by ``psycopg2.connect``).
+ Callers that need a SQLAlchemy engine URL should wrap the result in
+ :func:`to_sqlalchemy_url`.
+ """
+ injected = os.environ.get(DB_DSN_ENV, '').strip()
+ if injected:
+ return injected
+
+ # Server-side resolution was attempted and failed: report that failure,
+ # not the sign-in error the account fallback below would produce (the
+ # task engine scrubs the broker env from node subprocesses, so the
+ # fallback cannot succeed here anyway).
+ resolve_error = os.environ.get(DB_RESOLVE_ERROR_ENV, '').strip()
+ if resolve_error:
+ raise ValueError(f'RocketRide cloud database resolution failed at task start: {resolve_error}')
+
+ # Lazy import: ai.common is imported very early, and ai.account pulls in the
+ # OSS/SaaS overlay — importing at module load risks a cycle.
+ from ai.account import account
+
+ dsn = _run_async(account.resolve_db_dsn(current_client_id()))
+ if not dsn or not isinstance(dsn, str):
+ raise ValueError('Account.resolve_db_dsn returned an empty DSN')
+ return dsn
+
+
+def parse_dsn_fields(dsn: str) -> dict:
+ """Best-effort parse of a URL-form DSN into host/user/password/database.
+
+ Used only to populate cosmetic / bookkeeping fields (e.g. the base class's
+ ``database`` used in log messages and the vector node's connection subKey).
+ The DSN itself remains the source of truth for the actual connection.
+ Non-URL DSNs yield empty fields.
+ """
+ empty = {'host': '', 'port': None, 'user': '', 'password': '', 'database': ''}
+ try:
+ parsed = urllib.parse.urlparse(dsn)
+ except ValueError:
+ # e.g. mismatched IPv6 brackets — urlparse itself rejects the URL.
+ return empty
+ if not parsed.scheme:
+ return empty
+ # hostname/port validate lazily and raise ValueError on malformed
+ # authorities (bad bracket host, non-numeric or out-of-range port); these
+ # fields are cosmetic, so degrade per-field rather than fail node
+ # construction.
+ try:
+ host = parsed.hostname or ''
+ except ValueError:
+ host = ''
+ try:
+ port = parsed.port
+ except ValueError:
+ port = None
+ return {
+ 'host': host,
+ 'port': port,
+ 'user': urllib.parse.unquote(parsed.username) if parsed.username else '',
+ 'password': urllib.parse.unquote(parsed.password) if parsed.password else '',
+ 'database': (parsed.path or '').lstrip('/'),
+ }
diff --git a/packages/ai/src/ai/modules/task/task_engine.py b/packages/ai/src/ai/modules/task/task_engine.py
index 85384ddfd..70d570aec 100644
--- a/packages/ai/src/ai/modules/task/task_engine.py
+++ b/packages/ai/src/ai/modules/task/task_engine.py
@@ -380,6 +380,65 @@ def _resolve_pipeline(self, pipeline: Dict[str, Any]) -> Dict[str, Any]:
"""
return resolve_pipeline_env(pipeline, self._env)
+ # Providers that connect to the per-tenant RocketRide cloud database and
+ # therefore need ROCKETRIDE_DB_DSN injected into the task subprocess.
+ _ROCKETRIDE_DB_PROVIDERS = frozenset({'rocketride_sql', 'rocketride_vector', 'rocketride_graph'})
+
+ def _pipeline_uses_rocketride_db(self) -> bool:
+ """True when any pipeline component is a RocketRide cloud DB node."""
+ components = self._pipeline.get('components') or []
+ return any(
+ isinstance(component, dict) and component.get('provider') in self._ROCKETRIDE_DB_PROVIDERS
+ for component in components
+ )
+
+ async def _build_subprocess_env(self) -> Dict[str, str]:
+ """Build the environment for the task subprocess.
+
+ Credential hygiene for the RocketRide cloud DB path:
+
+ - The broker credential can resolve ANY tenant's DSN — it must never
+ reach node subprocesses, which run user pipeline code.
+ - A DSN inherited from the parent environment must not leak into
+ pipelines that didn't resolve one (and must not survive a broker
+ failure as a stale value pointing at who-knows-which tenant).
+
+ Children only ever get the single DSN resolved here, for pipelines
+ that actually contain one of the DB nodes. Identity is NOT delivered
+ through the environment (it rides the task file's 'identity' block —
+ the ROCKETRIDE_* env namespace is caller-influenced by design).
+ """
+ subprocess_env = os.environ.copy()
+
+ subprocess_env.pop('ROCKETRIDE_DB_BROKER_URL', None)
+ subprocess_env.pop('ROCKETRIDE_DB_BROKER_TOKEN', None)
+ subprocess_env.pop('ROCKETRIDE_DB_DSN', None)
+ subprocess_env.pop('ROCKETRIDE_DB_RESOLVE_ERROR', None)
+
+ # Resolve the per-tenant DSN server-side (the SaaS account context
+ # exists only in this process) and hand it to the node subprocess via
+ # env — the same delivery mechanism as ROCKETRIDE_CLIENT_ID. Scoped to
+ # pipelines that actually contain one of the DB nodes so unrelated
+ # tasks never trigger provisioning. Resolution failure is non-fatal
+ # here: the node surfaces its own clear error, with the failure reason
+ # passed down so it isn't misreported as a sign-in problem.
+ if self._pipeline_uses_rocketride_db():
+ try:
+ from ai.account import account
+
+ dsn = await account.resolve_db_dsn(self.client_id)
+ subprocess_env['ROCKETRIDE_DB_DSN'] = dsn
+ except NotImplementedError:
+ # Broker env not configured (open-source default) — the
+ # node raises the sign-in message itself.
+ pass
+ except Exception as e:
+ self.debug_message(f'RocketRide DB DSN resolution failed: {e}')
+ reason = (str(e).strip().splitlines() or [repr(e)])[0]
+ subprocess_env['ROCKETRIDE_DB_RESOLVE_ERROR'] = reason
+
+ return subprocess_env
+
def _check_pipeline(self, pipeline: Dict[str, Any]) -> None:
"""
Validate pipeline configuration and extract metadata.
@@ -1979,7 +2038,9 @@ async def start_task(self) -> None:
# Launch subprocess. Identity travels in the TASK FILE (see
# _build_task's 'identity' block), never the environment — the
# ROCKETRIDE_* env namespace is caller-influenced by design.
- subprocess_env = os.environ.copy()
+ # _build_subprocess_env additionally scrubs the RocketRide DB
+ # broker credentials and injects the resolved per-tenant DSN.
+ subprocess_env = await self._build_subprocess_env()
# avoidMocks: strip ROCKETRIDE_MOCK so node.py loads real libraries
if self._pipeline.get('avoidMocks'):
diff --git a/packages/ai/tests/ai/account/test_resolve_db_dsn.py b/packages/ai/tests/ai/account/test_resolve_db_dsn.py
new file mode 100644
index 000000000..17106014d
--- /dev/null
+++ b/packages/ai/tests/ai/account/test_resolve_db_dsn.py
@@ -0,0 +1,205 @@
+# =============================================================================
+# MIT License
+# Copyright (c) 2026 Aparavi Software AG
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# =============================================================================
+
+"""Tests for ``AccountBase.resolve_db_dsn`` — the env-gated DB broker call.
+
+The broker contract (locked 2026-07-23, shipped as saas #381):
+``POST {ROCKETRIDE_DB_BROKER_URL} {"tenant_id": ...}`` with a Bearer token ->
+``{"database", "role", "dsn", "created"}`` (only ``dsn`` is read), idempotent.
+A real localhost HTTP server plays the broker so the actual urllib code path
+is exercised.
+"""
+
+from __future__ import annotations
+
+import asyncio
+import json
+import threading
+from http.server import BaseHTTPRequestHandler, HTTPServer
+
+import pytest
+
+from ai.account.base import AccountBase
+
+TEST_DSN = 'postgresql://r_t1_ab:secret@pooler.internal:5432/t_t1_ab?sslmode=require'
+TEST_TOKEN = 'broker-token-123'
+
+
+class _Minimal(AccountBase):
+ async def authenticate(self, credential): # pragma: no cover - unused
+ return None
+
+
+class _FakeBroker(BaseHTTPRequestHandler):
+ """Stands in for the data-core provisioner."""
+
+ requests: list = []
+
+ def do_POST(self): # noqa: N802 (http.server API)
+ length = int(self.headers.get('Content-Length', 0))
+ body = json.loads(self.rfile.read(length) or b'{}')
+ type(self).requests.append({'auth': self.headers.get('Authorization'), 'body': body})
+
+ tenant = body.get('tenant_id', '')
+ if self.headers.get('Authorization') != f'Bearer {TEST_TOKEN}':
+ self.send_response(401)
+ self.end_headers()
+ return
+ if tenant == 'unknown-tenant':
+ self.send_response(404)
+ self.end_headers()
+ return
+ if tenant == 'no-dsn-tenant':
+ payload = {'database': 't_broken', 'role': 't_broken_rw', 'created': False}
+ elif tenant == 'no-sslmode-tenant':
+ bare = 'postgresql://r_t1_ab:secret@pooler.internal:5432/t_t1_ab'
+ payload = {'database': 't_t1_ab', 'role': 't_t1_ab_rw', 'dsn': bare, 'created': False}
+ elif tenant == 'bad-scheme-tenant':
+ payload = {'database': 't_t1_ab', 'role': 't_t1_ab_rw', 'dsn': 'mysql://u:p@h/db', 'created': False}
+ else:
+ payload = {'database': 't_t1_ab', 'role': 't_t1_ab_rw', 'dsn': TEST_DSN, 'created': False}
+ raw = json.dumps(payload).encode('utf-8')
+ self.send_response(200)
+ self.send_header('Content-Type', 'application/json')
+ self.send_header('Content-Length', str(len(raw)))
+ self.end_headers()
+ self.wfile.write(raw)
+
+ def log_message(self, *args): # silence request logging
+ pass
+
+
+@pytest.fixture()
+def broker(monkeypatch):
+ _FakeBroker.requests = []
+ server = HTTPServer(('127.0.0.1', 0), _FakeBroker)
+ thread = threading.Thread(target=server.serve_forever, daemon=True)
+ thread.start()
+ url = f'http://127.0.0.1:{server.server_port}/provision'
+ monkeypatch.setenv('ROCKETRIDE_DB_BROKER_URL', url)
+ monkeypatch.setenv('ROCKETRIDE_DB_BROKER_TOKEN', TEST_TOKEN)
+ yield _FakeBroker
+ server.shutdown()
+ server.server_close()
+
+
+def _resolve(client_id: str) -> str:
+ return asyncio.run(_Minimal().resolve_db_dsn(client_id))
+
+
+def test_unconfigured_env_raises_signin_error(monkeypatch):
+ monkeypatch.delenv('ROCKETRIDE_DB_BROKER_URL', raising=False)
+ monkeypatch.delenv('ROCKETRIDE_DB_BROKER_TOKEN', raising=False)
+ with pytest.raises(NotImplementedError, match='require signing into RocketRide cloud'):
+ _resolve('tenant-1')
+
+
+def test_partial_env_still_raises(monkeypatch, broker):
+ monkeypatch.delenv('ROCKETRIDE_DB_BROKER_TOKEN', raising=False)
+ with pytest.raises(NotImplementedError):
+ _resolve('tenant-1')
+
+
+def test_resolves_dsn_via_broker(broker):
+ assert _resolve('tenant-1') == TEST_DSN
+
+
+def test_client_id_passed_verbatim_as_tenant_id_with_bearer(broker):
+ _resolve('Client-42 ')
+ assert broker.requests == [{'auth': f'Bearer {TEST_TOKEN}', 'body': {'tenant_id': 'Client-42'}}]
+
+
+def test_idempotent_recall_returns_same_dsn(broker):
+ assert _resolve('tenant-1') == _resolve('tenant-1') == TEST_DSN
+ assert len(broker.requests) == 2
+
+
+def test_broker_4xx_raises_runtime_error(broker):
+ with pytest.raises(RuntimeError, match='rejected provision.*404'):
+ _resolve('unknown-tenant')
+
+
+def test_bad_token_raises_runtime_error(broker, monkeypatch):
+ monkeypatch.setenv('ROCKETRIDE_DB_BROKER_TOKEN', 'wrong')
+ with pytest.raises(RuntimeError, match='rejected provision.*401'):
+ _resolve('tenant-1')
+
+
+def test_missing_dsn_in_response_raises(broker):
+ with pytest.raises(RuntimeError, match='did not include a DSN'):
+ _resolve('no-dsn-tenant')
+
+
+def test_unreachable_broker_raises(monkeypatch):
+ monkeypatch.setenv('ROCKETRIDE_DB_BROKER_URL', 'http://127.0.0.1:9/provision')
+ monkeypatch.setenv('ROCKETRIDE_DB_BROKER_TOKEN', TEST_TOKEN)
+ with pytest.raises(RuntimeError, match='unreachable'):
+ _resolve('tenant-1')
+
+
+def test_empty_client_id_rejected(broker):
+ with pytest.raises(ValueError, match='non-empty client_id'):
+ _resolve(' ')
+
+
+# ---------------------------------------------------------------------------
+# Transport security guards
+# ---------------------------------------------------------------------------
+
+
+def test_http_broker_url_rejected_for_non_localhost(monkeypatch):
+ """The broker token can resolve any tenant's DSN — never send it over
+ plain http to a remote host (a deployment typo must fail loudly).
+ """
+ monkeypatch.setenv('ROCKETRIDE_DB_BROKER_URL', 'http://broker.internal/provision')
+ monkeypatch.setenv('ROCKETRIDE_DB_BROKER_TOKEN', TEST_TOKEN)
+ with pytest.raises(RuntimeError, match='must use https'):
+ _resolve('tenant-1')
+
+
+def test_https_and_localhost_http_broker_urls_accepted():
+ # https anywhere; plain http only on the loopback (local rig / this suite).
+ AccountBase._check_broker_url('https://api.rocketride.ai/db/provision')
+ AccountBase._check_broker_url('http://localhost:8090/provision')
+ AccountBase._check_broker_url('http://127.0.0.1:8090/provision')
+ with pytest.raises(RuntimeError, match='must use https'):
+ AccountBase._check_broker_url('ftp://broker.internal/provision')
+ with pytest.raises(RuntimeError, match='must use https'):
+ AccountBase._check_broker_url('file:///etc/passwd')
+
+
+def test_dsn_without_sslmode_gets_require_pinned(broker):
+ """A broker regression that drops sslmode must not silently downgrade
+ every tenant connection to cleartext.
+ """
+ dsn = _resolve('no-sslmode-tenant')
+ assert dsn == 'postgresql://r_t1_ab:secret@pooler.internal:5432/t_t1_ab?sslmode=require'
+
+
+def test_dsn_with_existing_sslmode_passes_through_unchanged(broker):
+ assert _resolve('tenant-1') == TEST_DSN
+
+
+def test_non_postgres_dsn_rejected(broker):
+ with pytest.raises(RuntimeError, match='non-PostgreSQL DSN'):
+ _resolve('bad-scheme-tenant')
diff --git a/packages/ai/tests/ai/modules/task/test_task_engine.py b/packages/ai/tests/ai/modules/task/test_task_engine.py
index ddea26881..ddfaf042c 100644
--- a/packages/ai/tests/ai/modules/task/test_task_engine.py
+++ b/packages/ai/tests/ai/modules/task/test_task_engine.py
@@ -736,6 +736,111 @@ async def test_forward_task_event_debugger_swallows_send_failure():
await Task._forward_task_event(t, EVENT_TYPE.DEBUGGER, {'event': 'output'})
+# ---------------------------------------------------------------------------
+# _pipeline_uses_rocketride_db
+# ---------------------------------------------------------------------------
+
+
+def test_pipeline_uses_rocketride_db_detects_each_provider():
+ """Any of the three RocketRide cloud DB providers triggers DSN injection."""
+ for provider in ('rocketride_sql', 'rocketride_vector', 'rocketride_graph'):
+ t = _task(pipeline={'components': [{'id': 'a', 'provider': 'chat'}, {'id': 'b', 'provider': provider}]})
+ assert Task._pipeline_uses_rocketride_db(t), provider
+
+
+def test_pipeline_uses_rocketride_db_false_without_db_nodes():
+ """Ordinary pipelines never trigger provisioning."""
+ t = _task(pipeline={'components': [{'id': 'a', 'provider': 'chat'}, {'id': 'b', 'provider': 'db_postgres'}]})
+ assert not Task._pipeline_uses_rocketride_db(t)
+
+
+def test_pipeline_uses_rocketride_db_tolerates_malformed_components():
+ """Missing components / non-dict entries must not raise at task start."""
+ assert not Task._pipeline_uses_rocketride_db(_task(pipeline={}))
+ t = _task(pipeline={'components': ['not-a-dict', {'no-provider': True}]})
+ assert not Task._pipeline_uses_rocketride_db(t)
+
+
+# ---------------------------------------------------------------------------
+# _build_subprocess_env — RocketRide DB credential hygiene
+# ---------------------------------------------------------------------------
+
+_DB_PIPELINE = {'components': [{'id': 'db', 'provider': 'rocketride_sql'}]}
+
+
+def _env_task(pipeline=None):
+ t = _task(pipeline=pipeline if pipeline is not None else {})
+ t.client_id = 'client-env-test'
+ return t
+
+
+def _patch_resolve(monkeypatch, fake):
+ import ai.account
+
+ monkeypatch.setattr(ai.account.account, 'resolve_db_dsn', fake)
+
+
+@pytest.mark.asyncio
+async def test_subprocess_env_scrubs_broker_credentials(monkeypatch):
+ """The broker credential can mint ANY tenant's DSN — it must never reach
+ node subprocesses, and neither may a parent-level DSN or stale error.
+ """
+ monkeypatch.setenv('ROCKETRIDE_DB_BROKER_URL', 'https://broker.example')
+ monkeypatch.setenv('ROCKETRIDE_DB_BROKER_TOKEN', 'super-secret')
+ monkeypatch.setenv('ROCKETRIDE_DB_DSN', 'postgresql://stale@parent/db')
+ monkeypatch.setenv('ROCKETRIDE_DB_RESOLVE_ERROR', 'stale reason')
+
+ env = await Task._build_subprocess_env(_env_task()) # no DB nodes
+
+ assert 'ROCKETRIDE_DB_BROKER_URL' not in env
+ assert 'ROCKETRIDE_DB_BROKER_TOKEN' not in env
+ assert 'ROCKETRIDE_DB_DSN' not in env
+ assert 'ROCKETRIDE_DB_RESOLVE_ERROR' not in env
+ # Identity rides the task file (#1686), never the environment.
+ assert 'ROCKETRIDE_CLIENT_ID' not in env
+
+
+@pytest.mark.asyncio
+async def test_subprocess_env_injects_resolved_dsn(monkeypatch):
+ async def fake_resolve(client_id):
+ assert client_id == 'client-env-test'
+ return 'postgresql://tenant@pooler/db?sslmode=require'
+
+ _patch_resolve(monkeypatch, fake_resolve)
+ env = await Task._build_subprocess_env(_env_task(pipeline=_DB_PIPELINE))
+ assert env['ROCKETRIDE_DB_DSN'] == 'postgresql://tenant@pooler/db?sslmode=require'
+
+
+@pytest.mark.asyncio
+async def test_subprocess_env_stale_dsn_does_not_survive_broker_failure(monkeypatch):
+ """A parent-env DSN must not become the node's DSN when resolution fails —
+ it could point at another tenant. The failure reason is passed down instead.
+ """
+ monkeypatch.setenv('ROCKETRIDE_DB_DSN', 'postgresql://stale@parent/other-tenant')
+
+ async def fake_resolve(client_id):
+ raise RuntimeError('DB broker request failed: HTTP 503')
+
+ _patch_resolve(monkeypatch, fake_resolve)
+ t = _env_task(pipeline=_DB_PIPELINE)
+ env = await Task._build_subprocess_env(t)
+
+ assert 'ROCKETRIDE_DB_DSN' not in env
+ assert env['ROCKETRIDE_DB_RESOLVE_ERROR'] == 'DB broker request failed: HTTP 503'
+ t.debug_message.assert_called_once()
+
+
+@pytest.mark.asyncio
+async def test_subprocess_env_unconfigured_account_is_nonfatal(monkeypatch):
+ async def fake_resolve(client_id):
+ raise NotImplementedError('sign in')
+
+ _patch_resolve(monkeypatch, fake_resolve)
+ env = await Task._build_subprocess_env(_env_task(pipeline=_DB_PIPELINE))
+ assert 'ROCKETRIDE_DB_DSN' not in env
+ assert 'ROCKETRIDE_DB_RESOLVE_ERROR' not in env
+
+
# ---------------------------------------------------------------------------
# _accumulate_analytics — run analytics in the status body
# ---------------------------------------------------------------------------
diff --git a/packages/docs/content-static/integrations/neo4j.md b/packages/docs/content-static/integrations/neo4j.md
index ac0ba9ff2..cabde47e8 100644
--- a/packages/docs/content-static/integrations/neo4j.md
+++ b/packages/docs/content-static/integrations/neo4j.md
@@ -5,7 +5,7 @@ sidebar_position: 4
# Neo4j
-The `db_neo4j` node answers natural-language questions against a Neo4j graph
+The `graph_neo4j` node answers natural-language questions against a Neo4j graph
database by translating them to Cypher with a connected LLM. It plays two roles:
a **pipeline node** that takes questions on the `questions` lane and emits
results downstream, and an **agent tool** that exposes graph retrieval directly
@@ -51,7 +51,7 @@ The node connects over the Bolt protocol with the official neo4j Python driver.
```json
{
"id": "graph_1",
- "provider": "neo4jdb",
+ "provider": "graph_neo4j",
"config": {
"uri": "neo4j+s://your-instance.databases.neo4j.io",
"auth_method": "userpass",
@@ -101,7 +101,8 @@ prompt so Cypher is generated against the real structure.
## Agent tools
When connected to an agent, the node exposes three functions under the node's
-prefix (e.g. `neo4j.get_data`):
+ID as a prefix (`.get_data` — e.g. `graph_1.get_data` for the sample
+config above):
| Tool | What it does |
| --- | --- |
@@ -122,6 +123,6 @@ fast rather than mid-pipeline.
## Related
-- [`db_neo4j` node reference](/nodes/db_neo4j)
+- [`graph_neo4j` node reference](/nodes/graph_neo4j)
- [Qdrant integration](/integrations/qdrant) — vector retrieval for RAG
- [Concepts: Agents & Tools](/concepts/agents-tools-skills)
diff --git a/pyproject.toml b/pyproject.toml
index d26bef27f..c3302e0f9 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -13,7 +13,11 @@ line-length = 120
# Target Python version
target-version = "py310"
-# Directories to exclude from linting
+# Directories to exclude from linting.
+# force-exclude makes the list apply even when paths are passed explicitly
+# (lefthook's `ruff check {staged_files}`), which is what keeps the vendored
+# ANTLR-generated parsers out of lint.
+force-exclude = true
exclude = [
".git",
".venv",
@@ -24,6 +28,10 @@ exclude = [
"node_modules",
".eggs",
"*.egg-info",
+ # Vendored third-party code (Apache-2.0) incl. ANTLR-generated parsers —
+ # not held to local style; provenance in ai/common/graph/age/README.md
+ "packages/ai/src/ai/common/graph/age/_agtype",
+ "packages/ai/src/ai/common/graph/age/_cypher",
]
[tool.ruff.format]