Skip to content

feat(gfql/index): index DDL travels with the query that uses it, in one gfql() call (#2119) - #2132

Merged
lmeyerov merged 2 commits into
masterfrom
feat/gfql-2119-index-ddl-in-one-call
Oct 3, 2026
Merged

lmeyerov merged 2 commits into
masterfrom
feat/gfql-2119-index-ddl-in-one-call

Conversation

@lmeyerov

@lmeyerov lmeyerov commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

Closes #2119.

What

There was no way to build an index and use it in one g.gfql(...): DDL was a whole-string fast path, CreateIndex/DropIndex were wire ops the chain and let() type gates rejected, and CREATE ...; MATCH ... was "malformed DDL". Now:

  • call('create_index', {kind, column?, name?}) / call('drop_index', {kind?, column?}) are safelisted GFQL ops over the existing Plottable methods — so DDL sits at the front of a chain (the chain already runs call() prefixes before traversals), in a let() binding, or in chain JSON.
  • CreateIndex / DropIndex objects, and their JSON, in a chain list or let binding normalize to those calls. ShowIndexes (a table, not a graph) and a drop-by-name (no method form) are rejected with the standalone form they do have.
  • A Cypher string may lead with CREATE GFQL INDEX ...; statements: they build first, the remainder runs on the indexed graph. One statement — with or without ; — keeps the one-op path; a ; inside the query body is never a boundary.

Two registry drops found on the way and fixed: gfql()'s index_policy branch copied self instead of the DDL-applied graph; the chain's all-call() branch rebuilt its result from self when it had attached its synthetic edge-index column (migrated with the existing rebind_edges idiom).

Pinned

test_index_ddl_in_one_call.py: every fused form is served by the same index seams as the two-step form (gfql_explain), on pandas and cuDF (with and without an edge-id binding); the string form works on polars too, while the polars chain engine's pre-existing "no call() before a traversal" decline is pinned for the list form. Negatives: ShowIndexes in a chain, drop-by-name in a chain, unknown/missing kind, SHOW inside a multi-statement, malformed leading DDL. Wire round-trip of a chain holding a raw CreateIndex document. create_index/drop_index join the polars chain-vs-DAG consistency matrix so the conformance ledger counts them exercised (no waiver). Docs: one-call forms in indexing.rst.

Local: ruff/guards/surface guard/mypy clean; 602 related tests pass; route-off replay clean.

Not here (said on the issue)

CREATE GFQL INDEX as a statement inside GRAPH { } / a CALL graphistry.index.* procedure — grammar and procedure-resolver work; the one-call need the docs example had is met by the forms above.

🤖 Generated with Claude Code

https://claude.ai/code/session_017ropeBMLJUuy6ViYwy15ud

lmeyerov and others added 2 commits October 3, 2026 11:56
…ne gfql() call

There was no way to build an index and use it in one call: DDL was a whole-string
fast path, CreateIndex/DropIndex were wire ops the chain and let() type gates
rejected, and `CREATE ...; MATCH ...` was "malformed DDL". Now:

* `call('create_index', {kind, column?, name?})` and `call('drop_index', {kind?,
  column?})` are safelisted GFQL ops over the existing Plottable methods, so DDL sits
  at the front of a chain, in a let() binding, or in chain JSON.
* CreateIndex/DropIndex objects (and their JSON) in a chain list or let binding
  normalize to those calls; ShowIndexes (a table) and a drop-by-name (no method
  form) are rejected with the standalone form they do have.
* A Cypher string may lead with `CREATE GFQL INDEX ...;` statements: they build
  first and the remainder runs on the indexed graph. One statement, with or without
  ';', keeps the one-op path.
* Two registry drops found on the way: gfql()'s index_policy branch copied `self`
  instead of the DDL-applied graph, and the chain's all-call branch rebuilt its result
  from `self` when it had attached its edge-index column. Both fixed; every fused
  form is pinned to be served by the same index seams as the two-step form, on
  pandas and cuDF; the polars chain engine's existing "no call() before a
  traversal" decline is pinned for the list form, and the string form works there.

Closes #2119.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_017ropeBMLJUuy6ViYwy15ud
…docs snippet runs on the page fixture

A caller's later mutation of their list must still be seen and re-validated on
execute (test_mutated_operation_list_is_not_trusted); only a list with
CreateIndex/DropIndex gets a normalized copy. The indexing page's one-call
example now uses the fixture's string ids so the doc-example audit executes it.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_017ropeBMLJUuy6ViYwy15ud
@lmeyerov
lmeyerov merged commit dd6daca into master Oct 3, 2026
90 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

GFQL: index DDL cannot be fused with a query in one gfql() call (no CREATE GFQL INDEX inside GRAPH { }, no CreateIndex chain/let stage)

1 participant