Repository navigation
feat(sql): support SQLEngine to run graph patterns on the same CSR - #115
Merged
Merged
Conversation
SQL could only reach the graph through joins written by hand, and SqlEngine read every Lance dataset into memory first. SqlEngine now runs on a CypherEngine snapshot, so SQL and Cypher share the same session, CSR, statistics and lazy Lance scans with column and filter pushdown. - GRAPH_TABLE (MATCH ... WHERE ... COLUMNS (...)), modelled on DuckPGQ, is rewritten to a graph_table() table function. The Cypher planner plans its pattern and DataFusion inlines the plan as a view, so CSR hops and the CSR optimizer rules apply inside SQL. - SqlEngine(datasets, config=None, use_csr=False) and SqlEngine.from_engine(engine), which shares a CypherEngine's snapshot. execute and explain take $name parameters, in the SQL and in patterns alike, and execute takes a timeout. - SQL plans are cached per snapshot as templates, under the Cypher cache's rules; patterns are planned as Cypher templates, so one bind fills both. - Only queries run: DDL, DML and SET are rejected. - SqlDialect moves to lance_csr.dialect to avoid an import cycle; it is still exported from lance_csr and lance_csr.sql. The Rust crate docs no longer call the API experimental, and the lance-csr-catalog docs now say what the crate is for. Tests: cargo test --workspace, cargo clippy --workspace --all-targets -- -D warnings, make test, make lint, docs snippets, zensical build --strict, typos.
Deploying lance-csr with
|
| Latest commit: |
cfbc86d
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://f0d50e16.lance-csr.pages.dev |
| Branch Preview URL: | https://feat-sql-graph-table.lance-csr.pages.dev |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Before this change, SQL could reach the graph only through joins written by hand, so it never used the CSR, and
SqlEngineread every Lance dataset into memory before running a query. This PR follows DuckPGQ's design, a graph pattern embedded in a SQLFROMclause, without implementing the SQL/PGQ dialect. The Cypher planner plans the pattern, so its hops run on the CSR and the rest of the query is ordinary DataFusion SQL over lazily scanned Lance data.What changed
GRAPH_TABLEclause.GRAPH_TABLE (MATCH ... WHERE ... COLUMNS (...))is rewritten to agraph_table('<Cypher>')table function. The function plans the Cypher and returns the plan as a view, which DataFusion inlines into the SQL plan. CSR hops therefore appear asCsrExpandnodes, and the existing CSR optimizer rules (filter pushdown, column pruning, degree counting) apply with no changes. The pattern bodies are Cypher. There is noCREATE PROPERTY GRAPH: the graph is the engine'sGraphConfig, and a graph name beforeMATCHis accepted and ignored.SqlEngineruns on aCypherEnginesnapshot. In Rust and Python,SqlEngine(datasets, config=None, use_csr=False)builds one, andSqlEngine.from_engine(engine)shares an existing engine's datasets, CSR and statistics, so both engines move together oncheckout_latest(). Without a config it is plain SQL, andGRAPH_TABLEraisesValueError.SqlQuerystill reads datasets into memory on each call. A namespace-opened Lance dataset can't be reopened by URI, so a graph-lessSqlEnginestill reads it into memory; with a config it raisesValueError, asCypherEnginedoes.executeandexplaintake oneparamsdict for$namein the SQL and in every pattern. Patterns are planned as Cypher templates, so a whole SQL plan is cached per snapshot under the Cypher cache's rules: up to 128 entries, keyed by the text plus parameter names and types, with values bound before optimization. List and map values, and patterns that can't be templates, are planned on every call. A missing value raisesValueErrorat planning.execute(..., timeout=...)uses the same cooperative deadline asCypherEngine.execute.INSERT,COPY ... TO) and statements such asSETare rejected, so SQL can't write files.SqlDialectmoves to its own module to break an import cycle. It is still exported fromlance_csrandlance_csr.sql, so imports don't change.lance-csr-catalognow explains what it is for (the table catalog the planner uses, andDirNamespace), with a doctest example. Thelance-csrcrate docs no longer say it translates Cypher into SQL.Behaviour change for existing
SqlEngineusers: in-memory tables are now split into one partition per core, and Lance datasets are no longer read eagerly. Results are unchanged.Joins written in plain SQL outside
GRAPH_TABLEstill run as ordinary joins. Lowering those to the CSR would need a separate join-to-CSR optimizer rule.Validation
cargo test --workspace: 693 passed, including new tests that check CSR and join-path results match Cypher, Lance filter pushdown, rejected writes, template reuse across values and types, uncached list parameters, and the cache reset oncheckout_latest().cargo clippy --workspace --all-targets -- -D warningsandcargo fmt --all --check: clean.make test: 1694 passed, including the newSqlEnginetests. The timeout test stops a nested-loop join at 0.2 s; without a deadline, the same query runs for more than 3 s.make lint(ruff and pyright): clean.zensical build --strictandtypospass.cargo docstill reports 6 broken intra-doc links. All are in code that was already onmain, and the count is the same before and after this PR.🤖 Generated with Claude Code