Skip to content

feat: Properly support parameter placeholders in Python/Rust #103

Description

@prrao87

Summary

$param is parsed as a field name; other syntaxes (@param, :param, {param}, ?) that users coming from other databases may be familiar with, also fail. with_parameter also doesn’t help in Python, even though the Rust docs state that it exists.

Currently in lance-graph, it's necessary do custom string substitution before the parameterized Cypher query ever reaches the parser.

For example, in query.py, execute_query() calls apply_params(), which replaces $city, $country, etc. with literal values (e.g., 'London') in the query string. So Lance Graph never sees $city; it only sees a fully inlined literal, which is supported.

So the engine itself still doesn’t support placeholders — we’re just emulating parameters upstream in Python. Can we do better?

Example

import pyarrow as pa
from lance_graph import GraphConfig, CypherQuery

people = pa.table({"id": [1, 2], "age": [30, 40]})
cfg = GraphConfig.builder().with_node_label("Person", "id").build()
datasets = {"Person": people}

query = "MATCH (p:Person) WHERE p.age > $age RETURN p.id"
CypherQuery(query).with_parameter("age", 35).with_config(cfg).execute(datasets)
Traceback (most recent call last):
  File "/Users/prrao/code/graph-benchmark/lance_graph/tt.py", line 9, in <module>
    CypherQuery(query).with_parameter("age", 35).with_config(cfg).execute(datasets)
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^
ValueError: Query planning error: Failed to build filter: Schema error: No field named "$age". Did you mean 'p__age'?.

Expected

Returns id=2, when the age value is passed as a Cypher parameter.

Actual

When we attempt the example above, we get the following:
ValueError: ... No field named "$age".

A lot of custom boilerplate was needed to be written (see below) to correctly parse the parameters into a syntax that naturally maps to other systems:

def format_cypher_value(value: Any) -> str:
    if isinstance(value, str):
        escaped = value.replace("'", "''")
        return f"'{escaped}'"
    if isinstance(value, bool):
        return "true" if value else "false"
    if value is None:
        return "null"
    return str(value)


def apply_params(query: str, params: dict[str, Any]) -> str:
    # Lance Graph currently doesn't parse $param placeholders; inline values here
    # so the query string is fully concrete before parsing.
    for key, value in params.items():
        query = query.replace(f"${key}", format_cypher_value(value))
    return query


def execute_query(
    query: str,
    cfg: GraphConfig,
    datasets: dict[str, pa.Table],
    params: dict[str, Any] | None = None,
) -> pl.DataFrame:
    if params:
        # Inline params instead of using CypherQuery.with_parameter, which isn't
        # respected by the current parser.
        query = apply_params(query, params)
    cypher = CypherQuery(query)
    result = cypher.with_config(cfg).execute(datasets)
    return to_polars(result)


# use in query
result = execute_query(query, cfg, datasets, params=params)

Proposal

Currently the user has to inline parameters manually (string substitution) to proceed. We should support parameterized query execution in Cypher more naturally, like other graph DBs, to make writing queries more natural for users coming from those systems, if possible.

Environment

The following environment was used to test this:

lance-graph 0.4.0
Python 3.13
macOS Tahoe 26.2

Activity

  1. ChunxuTang commented on Jan 29, 2026

    @ChunxuTang
    Collaborator

    Hi @leiyuou, do you want to take a look at this issue?
    I previously left some ideas to implement it at https://github.com/lance-format/lance-graph/blob/main/crates/lance-graph/src/datafusion_planner/expression.rs#L296. Hope that could be helpful.

    Note that after implementing the support of parameters on the Rust side, we also need to expose and support it on the Python side.

  2. leiyuou commented on Feb 2, 2026

    @leiyuou
    Contributor

    I will work on it

  3. prrao87 commented on Feb 2, 2026

    @prrao87
    ContributorAuthor

    Thank you! I think this will improve performance on some query workloads in the LDBC benchmark - I see that lance-graph is far slower, by around 10x, than other alternatives. Can't think of a good reason why - I can only pinpoint this to Python overheads in preparing and executing the queries via parameters passed in from Python.

  4. changed the title [-]Parameter placeholders don’t work (even though `with_parameter` exists in the codebase)[/-] [+]feat: Properly support parameter placeholders in Python/Rust[/+] on Feb 2, 2026
  5. added theissue type on Feb 2, 2026
  6. added a commit that references this issue on Mar 4, 2026
    af1e4bd
  7. prrao87 commented on Mar 16, 2026

    @prrao87
    ContributorAuthor

    Hi @ChunxuTang, it's been a while since the last release - could we get a new release with this parameterized Cypher support?

  8. ChunxuTang commented on Mar 21, 2026

    @ChunxuTang
    Collaborator

    Hi @ChunxuTang, it's been a while since the last release - could we get a new release with this parameterized Cypher support?

    Sure, sounds good to me. Will kick off a release soon today. Cc @beinan

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions