Skip to content

[BUG]: SEARCH clause emitted on 2026.x servers that default to Cypher 5, causing a syntax error with no fallback #560

Description

@martinohanlon

Before You Report a Bug, Please Confirm You Have Done The Following...

  • I have updated to the latest version of the packages.
  • I have searched for both existing issues and closed issues and found none that matched my issue.

neo4j-graphrag-python's version

1.18.0

Python version

3.13.12

Operating System

Windows 11 (also reproducible on any OS)

Dependencies

fsspec==2026.6.0
neo4j==6.2.0
neo4j-graphrag==1.18.0
numpy==2.5.1
openai==1.109.1
pydantic==2.13.4
pydantic_core==2.46.4
python-dotenv==1.2.2
tenacity==9.1.4

Reproducible example

Reproduces reliably against the public demo server neo4j+s://demo.neo4jlabs.com
(recommendations database), which runs a 2026.x server whose default Cypher
language version is 5
.

import neo4j
from neo4j_graphrag.embeddings import OpenAIEmbeddings
from neo4j_graphrag.retrievers import VectorRetriever

driver = neo4j.GraphDatabase.driver(
    "neo4j+s://demo.neo4jlabs.com",
    auth=("recommendations", "recommendations"),
)
embedder = OpenAIEmbeddings(model="text-embedding-ada-002", api_key="sk-...")

retriever = VectorRetriever(
    driver=driver,
    index_name="moviePlotsEmbedding",   # over Movie.plotEmbedding (1536-dim)
    embedder=embedder,
    return_properties=["title", "year", "plot", "imdbRating"],
    neo4j_database="recommendations",
)

# Raises ClientError: the generated `SEARCH ... IN (VECTOR INDEX ...)` clause
# does not parse under Cypher 5.
retriever.search(query_text="space adventure", top_k=5)

Relevant Log Output

A neo4j.exceptions.ClientError Cypher syntax error on the
SEARCH node IN (VECTOR INDEX ...) clause (Cypher 5 does not recognise it).
Because it is a syntax error rather than a PropertyNotFound, the built-in
fallback is skipped and the exception propagates.

Expected Result

The vector search succeeds (as it did before the SEARCH-clause path existed),
by using the portable CALL db.index.vector.queryNodes(...) procedure when the
server won't parse the SEARCH clause.

What happened instead?

The search raises a hard ClientError. Root cause:

supports_search_clause gates purely on the server's calendar version, not
on the effective Cypher language version:

# neo4j_graphrag/utils/version_utils.py
def supports_search_clause(driver, database=None) -> bool:
    ...
    return version_tuple >= (2026, 1, 0)

The SEARCH ... IN (VECTOR INDEX ...) clause requires the query to run under
Cypher 25. A 2026.x server whose default language is Cypher 5 reports a
version >= (2026, 1, 0) but still rejects the clause at parse time — so
version alone is not a sufficient signal.

The only in-flight fallback is scoped to PropertyNotFound, so a syntax error
re-raises:

# neo4j_graphrag/retrievers/vector.py  (get_search_results)
except neo4j.exceptions.ClientError as e:
    if use_search_clause and "PropertyNotFound" in str(e):
        # ... retry with the procedure-based query
    else:
        raise   # <-- syntax errors land here and propagate

The same pattern exists in the async variant and in hybrid.py. There is also
no public constructor argument or method to force the procedure path — the only
lever is the private _node_label attribute (set in _fetch_index_infos).

Additional Info

Current workaround (relies on a private attribute):

def _force_procedure_vector_query(retriever) -> None:
    # Clear the auto-detected node label so the retriever emits the portable
    # CALL db.index.vector.queryNodes(...) procedure instead of the Cypher-25
    # SEARCH clause.
    if hasattr(retriever, "_node_label"):
        retriever._node_label = None

retriever = VectorRetriever(...)
_force_procedure_vector_query(retriever)

Suggested fixes (any one resolves this):

  1. Widen the fallback in get_search_results to also catch Cypher syntax
    errors (not just PropertyNotFound) and retry via the procedure path.
  2. Detect the effective/default Cypher language version — not just the server
    calendar version — in supports_search_clause, so a Cypher-5-default server
    takes the procedure path.
  3. Expose a public opt-out, e.g. prefer_search_clause: bool = True on the
    vector/hybrid retrievers, so callers can force the portable procedure query.

Happy to open a PR for option 1 or 3 if useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions