frogql 0.2.2__tar.gz → 0.2.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- frogql-0.2.4/.github/workflows/pages.yml +39 -0
- frogql-0.2.4/.github/workflows/release-wasm.yml +78 -0
- {frogql-0.2.2 → frogql-0.2.4}/CLAUDE.md +110 -17
- {frogql-0.2.2 → frogql-0.2.4}/Cargo.lock +134 -2
- {frogql-0.2.2 → frogql-0.2.4}/PKG-INFO +1 -1
- {frogql-0.2.2 → frogql-0.2.4}/README.md +58 -2
- frogql-0.2.4/assets/frogql-logo-512.png +0 -0
- frogql-0.2.4/assets/frogql-logo.png +0 -0
- frogql-0.2.4/bench/REPEAT_RANGE_REPORT.md +166 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/cross-system/README.md +65 -3
- frogql-0.2.4/bench/cross-system/_lib/memrun.py +297 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/cross-system/compare_results.py +42 -1
- frogql-0.2.4/bench/cross-system/grafeo/DIVERGENCES.md +98 -0
- frogql-0.2.4/bench/cross-system/grafeo/README.md +80 -0
- frogql-0.2.4/bench/cross-system/grafeo/ic1.gql +46 -0
- frogql-0.2.4/bench/cross-system/grafeo/ic11.gql +19 -0
- frogql-0.2.4/bench/cross-system/grafeo/ic12.gql +26 -0
- frogql-0.2.4/bench/cross-system/grafeo/ic13.gql +13 -0
- frogql-0.2.4/bench/cross-system/grafeo/ic2.gql +24 -0
- frogql-0.2.4/bench/cross-system/grafeo/ic3.gql +42 -0
- frogql-0.2.4/bench/cross-system/grafeo/ic4.gql +24 -0
- frogql-0.2.4/bench/cross-system/grafeo/ic5.gql +19 -0
- frogql-0.2.4/bench/cross-system/grafeo/ic6.gql +15 -0
- frogql-0.2.4/bench/cross-system/grafeo/ic7.gql +41 -0
- frogql-0.2.4/bench/cross-system/grafeo/ic8.gql +15 -0
- frogql-0.2.4/bench/cross-system/grafeo/ic9.gql +16 -0
- frogql-0.2.4/bench/cross-system/grafeo/requirements.txt +5 -0
- frogql-0.2.4/bench/cross-system/grafeo/run.py +321 -0
- frogql-0.2.4/bench/cross-system/grafeo/setup.py +277 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/cross-system/kuzu/DIVERGENCES.md +36 -0
- frogql-0.2.4/bench/cross-system/kuzu/ic1.cypher +49 -0
- frogql-0.2.4/bench/cross-system/kuzu/ic12.cypher +30 -0
- frogql-0.2.4/bench/cross-system/kuzu/ic13.cypher +19 -0
- frogql-0.2.4/bench/cross-system/kuzu/ic3.cypher +43 -0
- frogql-0.2.4/bench/cross-system/kuzu/ic4.cypher +24 -0
- frogql-0.2.4/bench/cross-system/kuzu/ic7.cypher +47 -0
- frogql-0.2.4/bench/cross-system/plot_cpu_mem.py +198 -0
- frogql-0.2.4/bench/cross-system/plot_results.py +136 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/cross-system/run_all.sh +127 -19
- frogql-0.2.4/bench/cross-system/run_server.sh +166 -0
- frogql-0.2.4/bench/grafeo-vs-frogql/README.md +88 -0
- frogql-0.2.4/bench/grafeo-vs-frogql/bench.py +246 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/ldbc-queries/ic1.toml +9 -26
- frogql-0.2.4/bench/ldbc-queries/ic10.toml +35 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/ldbc-queries/ic11.toml +1 -3
- frogql-0.2.4/bench/ldbc-queries/ic12.toml +33 -0
- frogql-0.2.4/bench/ldbc-queries/ic13.toml +20 -0
- frogql-0.2.4/bench/ldbc-queries/ic14.toml +50 -0
- frogql-0.2.4/bench/ldbc-queries/ic3.toml +42 -0
- frogql-0.2.4/bench/ldbc-queries/ic4.toml +29 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/ldbc-queries/ic5.toml +1 -2
- frogql-0.2.4/bench/ldbc-queries/ic7.toml +50 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/ldbc-queries/ic9.toml +1 -2
- {frogql-0.2.2 → frogql-0.2.4}/docs/internals/implemented-optimizations.md +42 -0
- frogql-0.2.4/docs/internals/iso-gql-gaps.md +176 -0
- frogql-0.2.4/docs/internals/shortest-path-fix-plan.md +184 -0
- frogql-0.2.4/docs/internals/wasm-browser-plan.md +177 -0
- {frogql-0.2.2 → frogql-0.2.4}/pyproject.toml +1 -1
- {frogql-0.2.2 → frogql-0.2.4}/python/Cargo.toml +1 -1
- {frogql-0.2.2 → frogql-0.2.4}/python/src/lib.rs +13 -3
- frogql-0.2.4/site/.nojekyll +0 -0
- frogql-0.2.4/site/assets/favicon.png +0 -0
- frogql-0.2.4/site/assets/frogql-logo.png +0 -0
- frogql-0.2.4/site/assets/movies.json +1 -0
- frogql-0.2.4/site/assets/og-image.png +0 -0
- frogql-0.2.4/site/index.html +1058 -0
- frogql-0.2.4/src/bin/bench_repeat.rs +267 -0
- {frogql-0.2.2 → frogql-0.2.4}/src/bin/frogql.rs +27 -12
- {frogql-0.2.2 → frogql-0.2.4}/src/bin/ldbc_bench.rs +3 -2
- {frogql-0.2.2 → frogql-0.2.4}/src/bin/orderby_bench.rs +24 -11
- {frogql-0.2.2 → frogql-0.2.4}/src/elaborate/mod.rs +161 -6
- {frogql-0.2.2 → frogql-0.2.4}/src/lib.rs +3 -15
- {frogql-0.2.2 → frogql-0.2.4}/src/model/csv_loader.rs +8 -8
- frogql-0.2.4/src/model/graph.rs +962 -0
- {frogql-0.2.2 → frogql-0.2.4}/src/model/value.rs +21 -1
- {frogql-0.2.2 → frogql-0.2.4}/src/optimizer/existential.rs +58 -4
- {frogql-0.2.2 → frogql-0.2.4}/src/optimizer/order_by_alias.rs +12 -1
- {frogql-0.2.2 → frogql-0.2.4}/src/optimizer/pushdown.rs +144 -4
- {frogql-0.2.2 → frogql-0.2.4}/src/optimizer/unroll_repeat.rs +10 -0
- {frogql-0.2.2 → frogql-0.2.4}/src/parser/grammar.rs +703 -128
- {frogql-0.2.2 → frogql-0.2.4}/src/parser/lexer.rs +49 -1
- {frogql-0.2.2 → frogql-0.2.4}/src/runtime/engine.rs +2096 -202
- {frogql-0.2.2 → frogql-0.2.4}/src/runtime/ltj/pattern_extract.rs +19 -4
- {frogql-0.2.2 → frogql-0.2.4}/src/runtime/ltj/triple_index.rs +2 -2
- {frogql-0.2.2 → frogql-0.2.4}/src/runtime/mod.rs +1 -0
- frogql-0.2.4/src/runtime/path_select.rs +265 -0
- {frogql-0.2.2 → frogql-0.2.4}/src/store/disk.rs +2 -2
- {frogql-0.2.2 → frogql-0.2.4}/src/store/dump.rs +12 -12
- {frogql-0.2.2 → frogql-0.2.4}/src/store/io.rs +20 -19
- {frogql-0.2.2 → frogql-0.2.4}/src/store/lazy.rs +15 -57
- {frogql-0.2.2 → frogql-0.2.4}/src/store/overlay.rs +49 -1
- {frogql-0.2.2 → frogql-0.2.4}/src/store/secondary_index.rs +6 -6
- {frogql-0.2.2 → frogql-0.2.4}/src/syntax/dm.rs +8 -0
- frogql-0.2.4/src/syntax/expr.rs +527 -0
- {frogql-0.2.2 → frogql-0.2.4}/src/syntax/mod.rs +1 -0
- frogql-0.2.4/src/syntax/path_pattern.rs +205 -0
- frogql-0.2.4/src/syntax/path_prefix.rs +151 -0
- {frogql-0.2.2 → frogql-0.2.4}/src/syntax/query.rs +43 -5
- {frogql-0.2.2 → frogql-0.2.4}/src/typing/checker.rs +532 -29
- {frogql-0.2.2 → frogql-0.2.4}/src/typing/format.rs +2 -0
- {frogql-0.2.2 → frogql-0.2.4}/src/typing/inference.rs +13 -13
- {frogql-0.2.2 → frogql-0.2.4}/src/typing/path_type.rs +4 -1
- {frogql-0.2.2 → frogql-0.2.4}/src/typing/simple_type.rs +7 -0
- {frogql-0.2.2 → frogql-0.2.4}/src/typing/validate.rs +7 -7
- {frogql-0.2.2 → frogql-0.2.4}/src/typing/variable_type.rs +16 -0
- {frogql-0.2.2 → frogql-0.2.4}/tests/aggregates_proptest.rs +5 -5
- {frogql-0.2.2 → frogql-0.2.4}/tests/bench_test.rs +15 -15
- frogql-0.2.4/tests/builtin_floor_cast_test.rs +81 -0
- {frogql-0.2.2 → frogql-0.2.4}/tests/coalesce_test.rs +5 -5
- frogql-0.2.4/tests/collect_list_test.rs +163 -0
- frogql-0.2.4/tests/correlated_subquery_test.rs +172 -0
- {frogql-0.2.2 → frogql-0.2.4}/tests/count_test.rs +123 -11
- frogql-0.2.4/tests/division_test.rs +63 -0
- {frogql-0.2.2 → frogql-0.2.4}/tests/dm_default_test.rs +3 -3
- {frogql-0.2.2 → frogql-0.2.4}/tests/dm_delete_expr_test.rs +2 -2
- {frogql-0.2.2 → frogql-0.2.4}/tests/dm_label_test.rs +4 -4
- {frogql-0.2.2 → frogql-0.2.4}/tests/dm_persistence_test.rs +2 -2
- {frogql-0.2.2 → frogql-0.2.4}/tests/dm_remove_test.rs +2 -2
- {frogql-0.2.2 → frogql-0.2.4}/tests/dm_runtime_test.rs +2 -2
- {frogql-0.2.2 → frogql-0.2.4}/tests/dm_schema_test.rs +2 -2
- {frogql-0.2.2 → frogql-0.2.4}/tests/dm_set_test.rs +2 -2
- {frogql-0.2.2 → frogql-0.2.4}/tests/dump_test.rs +6 -6
- {frogql-0.2.2 → frogql-0.2.4}/tests/elaborate_test.rs +3 -3
- {frogql-0.2.2 → frogql-0.2.4}/tests/exists_fold_test.rs +9 -3
- {frogql-0.2.2 → frogql-0.2.4}/tests/exists_runtime_test.rs +5 -5
- {frogql-0.2.2 → frogql-0.2.4}/tests/first_class_node_edge_values_test.rs +16 -17
- {frogql-0.2.2 → frogql-0.2.4}/tests/float_test.rs +86 -4
- {frogql-0.2.2 → frogql-0.2.4}/tests/graph_type_test.rs +3 -3
- frogql-0.2.4/tests/group_by_alias_test.rs +117 -0
- frogql-0.2.4/tests/group_by_var_test.rs +96 -0
- frogql-0.2.4/tests/ic7_test.rs +113 -0
- frogql-0.2.4/tests/iso_expr_test.rs +121 -0
- {frogql-0.2.2 → frogql-0.2.4}/tests/lattice_proptest.rs +1 -0
- {frogql-0.2.2 → frogql-0.2.4}/tests/lazy_mut_test.rs +2 -2
- frogql-0.2.4/tests/list_comprehension_test.rs +114 -0
- {frogql-0.2.2 → frogql-0.2.4}/tests/list_test.rs +5 -5
- {frogql-0.2.2 → frogql-0.2.4}/tests/ltj_label_disjunction_test.rs +3 -3
- frogql-0.2.4/tests/ltj_reverse_path_test.rs +123 -0
- frogql-0.2.4/tests/memory_mut_test.rs +375 -0
- {frogql-0.2.2 → frogql-0.2.4}/tests/multi_match_proptest.rs +4 -4
- {frogql-0.2.2 → frogql-0.2.4}/tests/multi_match_test.rs +4 -4
- frogql-0.2.4/tests/named_path_test.rs +176 -0
- {frogql-0.2.2 → frogql-0.2.4}/tests/null_test.rs +4 -4
- {frogql-0.2.2 → frogql-0.2.4}/tests/optional_match_test.rs +7 -7
- {frogql-0.2.2 → frogql-0.2.4}/tests/order_by_optimization_test.rs +10 -10
- {frogql-0.2.2 → frogql-0.2.4}/tests/order_by_test.rs +6 -6
- {frogql-0.2.2 → frogql-0.2.4}/tests/parallel_edge_test.rs +5 -5
- {frogql-0.2.2 → frogql-0.2.4}/tests/parse_and_run_test.rs +30 -3
- {frogql-0.2.2 → frogql-0.2.4}/tests/parser_test.rs +47 -2
- frogql-0.2.4/tests/path_prefix_test.rs +606 -0
- frogql-0.2.4/tests/record_expr_test.rs +94 -0
- {frogql-0.2.2 → frogql-0.2.4}/tests/record_test.rs +6 -6
- {frogql-0.2.2 → frogql-0.2.4}/tests/runtime_test.rs +6 -6
- frogql-0.2.4/tests/shortest_bfs_test.rs +221 -0
- {frogql-0.2.2 → frogql-0.2.4}/tests/store_runtime_test.rs +7 -7
- {frogql-0.2.2 → frogql-0.2.4}/tests/text2gql_test.rs +7 -7
- {frogql-0.2.2 → frogql-0.2.4}/tests/typecheck_gaps_order_by_test.rs +7 -7
- frogql-0.2.4/tests/value_subquery_test.rs +99 -0
- frogql-0.2.2/bench/ldbc-queries/ic10.toml +0 -48
- frogql-0.2.2/bench/ldbc-queries/ic12.toml +0 -41
- frogql-0.2.2/bench/ldbc-queries/ic13.toml +0 -31
- frogql-0.2.2/bench/ldbc-queries/ic14.toml +0 -61
- frogql-0.2.2/bench/ldbc-queries/ic3.toml +0 -48
- frogql-0.2.2/bench/ldbc-queries/ic4.toml +0 -38
- frogql-0.2.2/bench/ldbc-queries/ic7.toml +0 -70
- frogql-0.2.2/docs/internals/iso-gql-gaps.md +0 -99
- frogql-0.2.2/src/model/graph.rs +0 -598
- frogql-0.2.2/src/syntax/expr.rs +0 -263
- frogql-0.2.2/src/syntax/path_pattern.rs +0 -102
- {frogql-0.2.2 → frogql-0.2.4}/.github/workflows/ci.yml +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/.github/workflows/release-npm.yml +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/.github/workflows/release.yml +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/.gitignore +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/ARCHITECTURE.md +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/Cargo.toml +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/LICENSE +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/MANUAL.md +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/.gitignore +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/BENCHMARK_PLAN.md +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/INTERNAL_BENCHMARK.md +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/LDBC_BENCHMARK.md +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/LDBC_BENCH_PLAN.md +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/cross-system/QUERIES.md +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/cross-system/SURVEY.md +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/cross-system/_lib/row_hash.py +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/cross-system/gqlite/run.sh +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/cross-system/graphqlite/DIVERGENCES.md +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/cross-system/graphqlite/ic11.cypher +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/cross-system/graphqlite/ic2.cypher +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/cross-system/graphqlite/ic5.cypher +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/cross-system/graphqlite/ic6.cypher +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/cross-system/graphqlite/ic8.cypher +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/cross-system/graphqlite/ic9.cypher +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/cross-system/graphqlite/requirements.txt +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/cross-system/graphqlite/run.py +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/cross-system/graphqlite/setup.py +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/cross-system/install_python_deps.sh +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/cross-system/kuzu/README.md +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/cross-system/kuzu/ic11.cypher +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/cross-system/kuzu/ic2.cypher +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/cross-system/kuzu/ic5.cypher +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/cross-system/kuzu/ic6.cypher +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/cross-system/kuzu/ic8.cypher +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/cross-system/kuzu/ic9.cypher +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/cross-system/kuzu/requirements.txt +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/cross-system/kuzu/run.py +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/cross-system/kuzu/setup.py +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/ldbc-queries/ic2.toml +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/ldbc-queries/ic6.toml +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/ldbc-queries/ic8.toml +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/queries/1-tree.gql +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/queries/2-3-lollipop.gql +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/queries/2-comb.gql +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/queries/2-tree.gql +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/queries/3-4-lollipop.gql +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/queries/3-clique.gql +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/queries/3-cycle.gql +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/queries/3-path.gql +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/queries/4-clique.gql +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/queries/4-cycle.gql +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/queries/4-path.gql +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/scripts/csv_to_json.py +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/scripts/download_livejournal.sh +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/scripts/generate_queries.py +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/bench/scripts/run_bench.sh +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/docs/data-import.md +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/docs/data-modification.md +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/docs/graph-types.md +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/docs/internals/JOIN_STRATEGY_NOTES.md +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/docs/internals/graph-type-catalog-plan.md +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/docs/internals/possible-optimizations.md +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/docs/internals/rules.md +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/docs/internals/storage-architecture.md +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/docs/internals/typechecker_migration.md +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/docs/query-language.md +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/docs/secondary-indexes.md +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/address_queries.json +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/bom.gdb +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/books_queries.json +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/disney.gdb +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/disney_queries.json +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/financial_financial_management.gdb +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/financial_financial_management_queries.json +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/financial_fraud_detection.gdb +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/financial_fraud_detection_queries.json +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/financial_payment.gdb +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/financial_payment_queries.json +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/fraud_detection.gdb +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/gameofthrones.gdb +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/grandstack.gdb +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/hockey.gdb +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/hockey_queries.json +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/information_data_lineage.gdb +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/information_data_lineage_queries.json +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/information_technology_identity_and_access_management.gdb +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/information_technology_identity_and_access_management_queries.json +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/information_technology_iot.gdb +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/information_technology_iot_queries.json +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/information_technology_it_asset_management.gdb +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/information_technology_it_asset_management_queries.json +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/knowledge_general_knowledge.gdb +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/knowledge_general_knowledge_queries.json +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/knowledge_graph_geography.gdb +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/knowledge_graph_geography_queries.json +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/manufacturing_bombill_of_materials.gdb +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/manufacturing_bombill_of_materials_queries.json +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/manufacturing_production_process.gdb +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/manufacturing_production_process_queries.json +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/moivelens.gdb +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/moivelens_queries.json +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/movies.gdb +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/northwind.gdb +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/olympics_queries.json +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/soccer_2016.gdb +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/soccer_2016_queries.json +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/social_network_recommendation.gdb +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/social_network_recommendation_queries.json +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/social_network_twitter.gdb +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/social_network_twitter_queries.json +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/stackoverflow2.gdb +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/student_loan.gdb +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/student_loan_queries.json +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/typecheck_demo.rs +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/typecheck_repl_smoke.rs +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/video_games_queries.json +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/world.gdb +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/examples/world_queries.json +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/python/LICENSE +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/python/README.md +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/scripts/convert_dev_datasets.py +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/src/bin/bench_queries.rs +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/src/bin/bench_setup.rs +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/src/bin/convert_edgelist.rs +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/src/bin/internal_bench.rs +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/src/model/graph_access.rs +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/src/model/mod.rs +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/src/optimizer/mod.rs +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/src/pager/header.rs +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/src/pager/mod.rs +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/src/pager/page.rs +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/src/pager/pager.rs +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/src/parser/mod.rs +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/src/runtime/assignment.rs +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/src/runtime/catalog.rs +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/src/runtime/dm.rs +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/src/runtime/ltj/algorithm.rs +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/src/runtime/ltj/iterator.rs +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/src/runtime/ltj/mod.rs +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/src/runtime/ltj/veo.rs +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/src/runtime/result.rs +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/src/store/catalog_io.rs +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/src/store/disk_index.rs +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/src/store/mod.rs +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/src/store/record.rs +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/src/store/secondary_index_io.rs +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/src/store/string_table.rs +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/src/syntax/descriptor.rs +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/src/syntax/statement.rs +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/src/typing/descriptor_type.rs +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/src/typing/label_type.rs +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/src/typing/mod.rs +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/src/typing/property_type.rs +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/src/typing/type_environment.rs +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/test_data/fraud.json +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/test_data/ltj_label_disjunction.json +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/test_data/movies.json +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/test_data/social-network.json +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/tests/aggregates_proptest.proptest-regressions +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/tests/compile_diagnostics.rs +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/tests/parser_dm_test.rs +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/tests/typecheck_smoke.rs +0 -0
- {frogql-0.2.2 → frogql-0.2.4}/tests/typecheck_test.rs +0 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
name: Deploy website (GitHub Pages)
|
|
2
|
+
|
|
3
|
+
# Publishes the static landing page in site/ to GitHub Pages.
|
|
4
|
+
# Served at https://pleiad.github.io/frogql/
|
|
5
|
+
#
|
|
6
|
+
# One-time setup: repo Settings → Pages → "Build and deployment" →
|
|
7
|
+
# Source: "GitHub Actions".
|
|
8
|
+
|
|
9
|
+
on:
|
|
10
|
+
push:
|
|
11
|
+
branches: [ main ]
|
|
12
|
+
paths:
|
|
13
|
+
- "site/**"
|
|
14
|
+
- ".github/workflows/pages.yml"
|
|
15
|
+
workflow_dispatch:
|
|
16
|
+
|
|
17
|
+
permissions:
|
|
18
|
+
contents: read
|
|
19
|
+
pages: write
|
|
20
|
+
id-token: write
|
|
21
|
+
|
|
22
|
+
concurrency:
|
|
23
|
+
group: pages
|
|
24
|
+
cancel-in-progress: true
|
|
25
|
+
|
|
26
|
+
jobs:
|
|
27
|
+
deploy:
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
environment:
|
|
30
|
+
name: github-pages
|
|
31
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
32
|
+
steps:
|
|
33
|
+
- uses: actions/checkout@v4
|
|
34
|
+
- uses: actions/configure-pages@v5
|
|
35
|
+
- uses: actions/upload-pages-artifact@v3
|
|
36
|
+
with:
|
|
37
|
+
path: site
|
|
38
|
+
- id: deployment
|
|
39
|
+
uses: actions/deploy-pages@v4
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
name: Release wasm
|
|
2
|
+
|
|
3
|
+
# Build the browser WASM package with wasm-pack and publish it to npm as
|
|
4
|
+
# `frogql-wasm`. Fires on a `v*` tag (alongside `release.yml` for PyPI and
|
|
5
|
+
# `release-npm.yml` for native Node) AND on manual `workflow_dispatch`.
|
|
6
|
+
#
|
|
7
|
+
# The manual trigger matters: the version is read from `wasm/Cargo.toml`
|
|
8
|
+
# (via the built package.json), NOT from the tag ref, so you can ship
|
|
9
|
+
# `frogql-wasm` on its own — e.g. catch it up to a version the other
|
|
10
|
+
# registries already published — without re-tagging the whole suite. The
|
|
11
|
+
# publish is idempotent (skips if the version already exists on npm).
|
|
12
|
+
#
|
|
13
|
+
# Unlike the napi package there are no per-platform binaries: WebAssembly
|
|
14
|
+
# is portable, so this is one build + one publish. Reuses the `npm`
|
|
15
|
+
# GitHub Environment and the `NPM_TOKEN` secret.
|
|
16
|
+
|
|
17
|
+
on:
|
|
18
|
+
push:
|
|
19
|
+
tags:
|
|
20
|
+
- "v*"
|
|
21
|
+
workflow_dispatch:
|
|
22
|
+
|
|
23
|
+
permissions:
|
|
24
|
+
contents: read
|
|
25
|
+
id-token: write # npm provenance
|
|
26
|
+
|
|
27
|
+
jobs:
|
|
28
|
+
publish:
|
|
29
|
+
name: Build + publish frogql-wasm
|
|
30
|
+
runs-on: ubuntu-22.04
|
|
31
|
+
environment: npm
|
|
32
|
+
steps:
|
|
33
|
+
- uses: actions/checkout@v4
|
|
34
|
+
|
|
35
|
+
- name: Setup Rust
|
|
36
|
+
uses: dtolnay/rust-toolchain@stable
|
|
37
|
+
with:
|
|
38
|
+
targets: wasm32-unknown-unknown
|
|
39
|
+
|
|
40
|
+
- name: Install wasm-pack
|
|
41
|
+
uses: jetli/wasm-pack-action@v0.4.0
|
|
42
|
+
with:
|
|
43
|
+
version: latest
|
|
44
|
+
|
|
45
|
+
- name: Setup Node
|
|
46
|
+
uses: actions/setup-node@v4
|
|
47
|
+
with:
|
|
48
|
+
node-version: 20
|
|
49
|
+
check-latest: true
|
|
50
|
+
registry-url: https://registry.npmjs.org
|
|
51
|
+
|
|
52
|
+
- name: Build package
|
|
53
|
+
# `web` target (not `bundler`): it fetches the .wasm via
|
|
54
|
+
# `import.meta.url`, which Vite/Rollup/esbuild handle natively with
|
|
55
|
+
# no extra plugin. The `bundler` target requires consumers to add
|
|
56
|
+
# `vite-plugin-wasm`, which is friction for the most common setup.
|
|
57
|
+
# Consumers call `await init()` once, then use the API.
|
|
58
|
+
run: wasm-pack build wasm --target web --out-dir pkg
|
|
59
|
+
|
|
60
|
+
- name: Publish to npm (idempotent)
|
|
61
|
+
working-directory: wasm/pkg
|
|
62
|
+
env:
|
|
63
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
64
|
+
NPM_CONFIG_PROVENANCE: "true"
|
|
65
|
+
run: |
|
|
66
|
+
npm config set "//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}"
|
|
67
|
+
name=$(node -p "require('./package.json').name")
|
|
68
|
+
version=$(node -p "require('./package.json').version")
|
|
69
|
+
# dist-tag from the version itself (not the git ref), so manual
|
|
70
|
+
# dispatch and tag pushes behave identically: a pre-release
|
|
71
|
+
# (contains '-') lands on `next`, a clean version on `latest`.
|
|
72
|
+
if [[ "$version" == *-* ]]; then DIST_TAG=next; else DIST_TAG=latest; fi
|
|
73
|
+
echo "Publishing ${name}@${version} → dist-tag=${DIST_TAG}"
|
|
74
|
+
if npm view "${name}@${version}" version >/dev/null 2>&1; then
|
|
75
|
+
echo "::notice::${name}@${version} already on npm — skipping"
|
|
76
|
+
else
|
|
77
|
+
npm publish --tag "${DIST_TAG}" --access public --provenance
|
|
78
|
+
fi
|
|
@@ -24,7 +24,8 @@ cargo test --test parser_test --test runtime_test --test store_runtime_test \
|
|
|
24
24
|
--test parser_dm_test --test lazy_mut_test --test dm_runtime_test \
|
|
25
25
|
--test dm_persistence_test --test dm_schema_test --test dm_default_test \
|
|
26
26
|
--test dump_test --test dm_set_test --test dm_remove_test \
|
|
27
|
-
--test dm_label_test --test dm_delete_expr_test
|
|
27
|
+
--test dm_label_test --test dm_delete_expr_test --test memory_mut_test \
|
|
28
|
+
--test path_prefix_test --test shortest_bfs_test
|
|
28
29
|
|
|
29
30
|
# Single test
|
|
30
31
|
cargo test --test runtime_test test_join_star_any_label -- --exact
|
|
@@ -44,6 +45,13 @@ cargo build --release
|
|
|
44
45
|
cd python && source <your-venv>/bin/activate && pip install maturin && maturin develop --release
|
|
45
46
|
# For wheels to ship to other machines:
|
|
46
47
|
cd python && maturin build --release # output in target/wheels/
|
|
48
|
+
|
|
49
|
+
# Browser WASM bindings (frogql-wasm)
|
|
50
|
+
rustup target add wasm32-unknown-unknown # one-time
|
|
51
|
+
cargo test -p frogql-wasm # host tests of the engine core (query_json/dm_json)
|
|
52
|
+
cargo build -p frogql-wasm --target wasm32-unknown-unknown
|
|
53
|
+
# Generate the publishable npm package (web target); release-wasm.yml runs the same:
|
|
54
|
+
cargo install wasm-pack && wasm-pack build wasm --target web --out-dir pkg
|
|
47
55
|
```
|
|
48
56
|
|
|
49
57
|
### Pre-commit checklist for Rust changes (non-negotiable)
|
|
@@ -55,7 +63,7 @@ cd python && maturin build --release # output in target/wheels/
|
|
|
55
63
|
|
|
56
64
|
## Workspace layout
|
|
57
65
|
|
|
58
|
-
Cargo workspace with
|
|
66
|
+
Cargo workspace with four members and `resolver = "2"`:
|
|
59
67
|
- `.` (root) — the `gqlrust` library crate + CLI binaries under `src/bin/`:
|
|
60
68
|
- `frogql` — interactive REPL with line editing (rustyline). **Requires `repl` feature.**
|
|
61
69
|
- `bench_queries` — generic benchmark runner
|
|
@@ -65,6 +73,7 @@ Cargo workspace with three members and `resolver = "2"`:
|
|
|
65
73
|
- `convert_edgelist` — edge-list format converter
|
|
66
74
|
- `python/` — the `frogql-py` crate: a `cdylib` exposing a PyO3 extension module named `frogql`. Depends on `gqlrust = { path = "..", default-features = false }` so the wheel ships only the library half (no rustyline/ureq/etc.). Built and installed with maturin (`maturin develop` for local dev, `maturin build --release` for wheels). Maturin installs into whichever venv is active.
|
|
67
75
|
- `node/` — the `frogql-node` crate: a `cdylib` exposing a napi-rs extension named `frogql`. Same `default-features = false` discipline as `python/`. Built and packaged via `@napi-rs/cli` (see `node/package.json` scripts). Distributed on npm as a host package (`frogql`) plus five platform sub-packages (`frogql-darwin-x64`, `frogql-darwin-arm64`, `frogql-linux-x64-gnu`, `frogql-linux-arm64-gnu`, `frogql-win32-x64-msvc`) declared in `optionalDependencies`; npm picks the right one at install time. The host's `index.js` (platform dispatcher) and `index.d.ts` (TS types) are auto-generated by `napi build` but **checked into git** — required so the publish job can ship them without rebuilding.
|
|
76
|
+
- `wasm/` — the `frogql-wasm` crate: a `cdylib` (+ `rlib` for host tests) exposing a `wasm-bindgen` module for the **browser**. Same `default-features = false` discipline. Wraps **`MemoryGraphStore`** only (no filesystem in the browser): `open_json(json)` → `Connection` with `execute(query, limit?)`, `to_json()`, `schema()`, `node_count`, `edge_count`. Read queries return row objects; INSERT/SET/DELETE work in RAM via the overlay; DDL / `CREATE INDEX` are rejected (no catalog/index in-memory). Persistence is the JSON string from `to_json()` round-tripped through `open_json` (store it in IndexedDB). Build for the browser with `wasm-pack build --target bundler` (needs `wasm-pack` + the `wasm32-unknown-unknown` target); the engine core (`query_json`/`dm_json`) has host-target unit tests runnable via `cargo test -p frogql-wasm`. See `docs/internals/wasm-browser-plan.md`.
|
|
68
77
|
|
|
69
78
|
`resolver = "2"` is required: with v1, building the python crate `--target X` (cross-compile) unifies features globally and drags in `gqlrust`'s default `repl` + `bench` features even when `default-features = false` is set on the dep. That pulled `ureq → ring`, which fails to cross-build on the manylinux2014 aarch64 container. Resolver v2 computes features per-target and isolates the wheel build. Same trick keeps the node crate's wheels clean.
|
|
70
79
|
|
|
@@ -88,15 +97,17 @@ Dev: `proptest` (used by `aggregates_proptest`, `lattice_proptest`, `multi_match
|
|
|
88
97
|
|
|
89
98
|
## Releases (PyPI + npm in lock-step)
|
|
90
99
|
|
|
91
|
-
One git tag fires
|
|
100
|
+
One git tag fires all three registries (PyPI wheel, native npm, browser WASM npm). Pushing `v*` triggers:
|
|
92
101
|
- `.github/workflows/release.yml` → builds wheels (Linux x86_64+aarch64, macOS x86_64+arm64, Windows x86_64; manylinux2014, abi3-py38) + sdist, uploads via `MATURIN_PYPI_TOKEN`, runs in the `pypi` GitHub Environment for required-reviewers gating.
|
|
93
102
|
- `.github/workflows/release-npm.yml` → 5-target build matrix (mac arm64 native, mac x64 cross-compiled from arm64, linux x64 native, linux arm64 via zig, windows x64 native), publishes the host `frogql` package plus the 5 platform sub-packages via `NPM_TOKEN`, runs in the `npm` GitHub Environment. Pre-release versions (any with a `-` like `0.2.0-rc.3`) land on dist-tag `next`; clean `v0.2.0` lands on `latest`.
|
|
103
|
+
- `.github/workflows/release-wasm.yml` → single platform-independent build (`wasm-pack build wasm --target web`), publishes the **`frogql-wasm`** npm package (unscoped, consumed as `import init, { open_json } from "frogql-wasm"`). Reuses the `npm` Environment + `NPM_TOKEN`. WebAssembly is portable, so there's no build matrix. Same dist-tag logic and idempotent skip-if-exists as the napi job. The `web` target (not `bundler`) is deliberate: it needs no `vite-plugin-wasm` in the consumer.
|
|
94
104
|
|
|
95
|
-
Cut a release by bumping **
|
|
105
|
+
Cut a release by bumping **five files** in lock-step plus regenerating `Cargo.lock` (auto on any `cargo build`):
|
|
96
106
|
- `python/pyproject.toml` (PEP 440 form: `0.2.0rc3`)
|
|
97
107
|
- `python/Cargo.toml` (semver: `0.2.0-rc.3`)
|
|
98
108
|
- `node/Cargo.toml`
|
|
99
109
|
- `node/package.json` (host version + the 5 `optionalDependencies` versions)
|
|
110
|
+
- `wasm/Cargo.toml` (semver; the published `frogql-wasm` version is derived from it by wasm-pack)
|
|
100
111
|
|
|
101
112
|
Then `git tag vX.Y.Z && git push origin vX.Y.Z`. Both registries reject re-publishing, so always bump. The npm release also requires `node/index.js` + `node/index.d.ts` to be committed at the tagged SHA; regenerate them with `npm run build` inside `node/` whenever the API surface changes and commit the diff.
|
|
102
113
|
|
|
@@ -133,7 +144,7 @@ Local downstream dev:
|
|
|
133
144
|
- `runtime::dm::run_dm(&store, &dm, schema_for_validation)` — execute one ISO §13 data-modifying statement (INSERT / DELETE / DETACH DELETE). `schema_for_validation` is `Some(&Schema)` only when G2000 should fire (active type is neither DEFAULT nor absent)
|
|
134
145
|
- `LazyGraphStore::open_or_create(path)` — sqlite3-style; creates an empty `.gdb` if `path` doesn't exist, then opens it
|
|
135
146
|
- `LazyGraphStore::save(path)` — atomic save of the merged base+overlay view (tmp+rename); refreshes DEFAULT before persisting
|
|
136
|
-
- `LazyGraphStore::materialize_to_graph()` — decode the merged view into an in-RAM `
|
|
147
|
+
- `LazyGraphStore::materialize_to_graph()` — decode the merged view into an in-RAM `MemoryGraphStore` with compacted IDs; used by `save` and the dump utility
|
|
137
148
|
- `LazyGraphStore::refresh_default_if_dirty()` — re-run `infer_simple_schema` if the catalog's `default_dirty` flag is set; idempotent
|
|
138
149
|
|
|
139
150
|
### Graph-type catalog
|
|
@@ -164,11 +175,11 @@ Operational invariants Claude must keep in mind:
|
|
|
164
175
|
- **DEFAULT lifecycle**: `GraphTypeCatalog.default_dirty` (in-RAM only, `#[serde(skip)]`) flips after every successful DML; `refresh_default_if_dirty` re-runs `infer_simple_schema` lazily on `handle_show("DEFAULT")` and `LazyGraphStore::save`. Eager refresh would cost O(N+E) per mutation.
|
|
165
176
|
- **Cache invalidation**: every successful DML calls `Runtime::invalidate_caches()` (REPL) or clears `Connection.triple_index` (Python); next query rebuilds the six-ordering index from base+overlay (~670 ms SF0.1).
|
|
166
177
|
|
|
167
|
-
**Persistence (`.save`).** `LazyGraphStore::save(path)` materialises merged base+overlay into a temporary `
|
|
178
|
+
**Persistence (`.save`).** `LazyGraphStore::save(path)` materialises merged base+overlay into a temporary `MemoryGraphStore` and calls `save_graph_with_catalog_and_indexes_atomic` (writes graph + catalog + persisted DDL index list to `<path>.tmp`, atomic `rename`). `LazyGraphStore::open_or_create(path)` mirrors SQLite — non-existent path writes an empty `.gdb` first.
|
|
168
179
|
|
|
169
180
|
**Auto-commit is OFF by design.** Forgetting `.save` loses the overlay (DML) AND any DDL declared this session (`CREATE INDEX` / `DROP INDEX` only mutate the in-memory `RefCell<SecondaryIndex>` via `build_declared`; the persisted list at `header.secondary_index_root` rewrites only on `.save`). Same trade-off as SQLite's explicit-commit model — lets users experiment without writing.
|
|
170
181
|
|
|
171
|
-
**Dump**: `store::dump::dump_to_json_file` (round-trips through `
|
|
182
|
+
**Dump**: `store::dump::dump_to_json_file` (round-trips through `MemoryGraphStore::from_json_value`) and `dump_to_gql_file` (emits an `INSERT`-per-node + `MATCH+INSERT`-per-edge script; uses a synthetic `_dump_id` property). Available via `.dump-json` / `.dump-gql` REPL meta-commands.
|
|
172
183
|
|
|
173
184
|
Tests: `parser_dm_test.rs`, `lazy_mut_test.rs`, `dm_runtime_test.rs`, `dm_persistence_test.rs`, `dm_schema_test.rs`, `dm_default_test.rs`, `dump_test.rs`, `dm_set_test.rs`, `dm_remove_test.rs`, `dm_label_test.rs`, `dm_delete_expr_test.rs`.
|
|
174
185
|
|
|
@@ -179,22 +190,32 @@ All node/edge IDs are `u32` internally (`pub type Id = u32` in `model/value.rs`)
|
|
|
179
190
|
### GraphAccess trait
|
|
180
191
|
|
|
181
192
|
The runtime is generic over `GraphAccess`. Node and edge methods are separate: `node_labels(id)` / `edge_labels(id)`, `node_props(id)` / `edge_props(id)`. The runtime knows which to call from context (filtering nodes vs edges). Three backends:
|
|
182
|
-
- `
|
|
193
|
+
- `MemoryGraphStore` — in-memory from JSON, all data in RAM. Full read + DML backend: implements `GraphAccess` and `GraphAccessMut` via the same `RefCell<MutationOverlay>` as `LazyGraphStore` (reads merge base + overlay; mutations stage in the overlay). Parity covered by `tests/memory_mut_test.rs`.
|
|
183
194
|
- `LazyGraphStore` — topology (edge_src/tgt) + label index in RAM, labels/props read from disk via LRU page cache. No string names in memory.
|
|
184
195
|
- `DiskGraphStore` — topology in RAM, everything else from disk
|
|
185
196
|
|
|
186
197
|
### Parser grammar hierarchy
|
|
187
198
|
|
|
188
199
|
```
|
|
189
|
-
full_query
|
|
190
|
-
query
|
|
200
|
+
full_query = MATCH? query (WHERE expr)? (RETURN items)? (GROUP BY ...)? (ORDER BY ...)? (LIMIT INT)?
|
|
201
|
+
query = operand ("," operand)* ← Join (lowest precedence)
|
|
202
|
+
operand = path_prefix? path_pattern ← Selected (ISO §16.6 path-pattern prefix)
|
|
191
203
|
path_pattern = path_term ("|" path_term)* ← Union
|
|
192
|
-
path_term
|
|
193
|
-
path_factor
|
|
204
|
+
path_term = path_factor+ ← Concat (juxtaposition)
|
|
205
|
+
path_factor = path_primary quantifier? ← Repeat {n,m} / `*` / `+` / `?`
|
|
194
206
|
```
|
|
195
207
|
|
|
196
208
|
`MATCH` keyword is optional — bare path patterns like `(x)-[]->(y)` still work. `OPTIONAL MATCH` is supported as a top-level match clause. `is` and `IS` are aliases for the `typed`/`TYPED` type-predicate keyword; `IS NULL` / `IS NOT NULL` are dedicated null tests detected via lookahead before the type-predicate path. The `AS` keyword is ambiguous between type cast (in expressions) and alias (in RETURN); `return_comparison()` excludes `AS` from operators so it's available for aliases.
|
|
197
209
|
|
|
210
|
+
#### Path-pattern prefixes (ISO §16.6)
|
|
211
|
+
|
|
212
|
+
A `path_prefix` is parsed per comma operand (`parse_path_prefix` in `path_pattern_operand`), so it scopes to one `<path pattern>` and does not leak across a comma-join or union. A non-trivial prefix wraps its pattern in `PathPattern::Selected { prefix, pattern }`; the trivial `WALK ALL` is dropped (stored as a plain pattern) so the runtime skips the materialize-and-select pass. The prefix carries a `PathMode` (restrictive) and a `PathSearch` (selective), both in `src/syntax/path_prefix.rs`:
|
|
213
|
+
|
|
214
|
+
- **Path modes** (`WALK` default, `TRAIL`, `SIMPLE`, `ACYCLIC`) constrain which walks count: TRAIL forbids repeated edges, ACYCLIC forbids repeated nodes, SIMPLE forbids repeated nodes except a closing first==last cycle.
|
|
215
|
+
- **Path searches** (`ALL` default, `ANY [N]`, `SHORTEST [N] [PATHS]`, `SHORTEST N GROUPS`) pick a subset per `(first node, last node)` boundary partition. The surface forms `ANY SHORTEST` / `ALL SHORTEST` normalize to `SHORTEST 1 PATHS` / `SHORTEST 1 GROUPS` (ISO §16.6 SR 2c).
|
|
216
|
+
|
|
217
|
+
`ANY` lexes to its own `Token::Any` (so `ANY <pattern>` is distinct from a `*` type wildcard); in label position `(x:ANY)` it stays an alias for the `*` any-label wildcard (`label_primary` accepts both). `TRAIL/SIMPLE/ACYCLIC/SHORTEST/GROUPS/PATHS/WALK` are soft keywords, matched case-insensitively only in prefix position, so they remain usable as labels and variable names elsewhere. Tests in `tests/path_prefix_test.rs`.
|
|
218
|
+
|
|
198
219
|
`LIMIT N` populates `Query.limit: Option<u32>`; the runtime combines it with any caller-supplied cap via `min` (smaller wins). `LIMIT 0` short-circuits to an empty binding table per ISO/IEC 39075:2024.
|
|
199
220
|
|
|
200
221
|
#### Comments and operator aliases (ISO §3.10)
|
|
@@ -232,7 +253,7 @@ Primary strategy for joins and concatenations of directed/undirected edges. Wors
|
|
|
232
253
|
**In-loop filters** (`FilterKind`): `NodeLabel`, `NodeProperty`, `NodeAttrCmp` (`=`, `!=`, `<`, `<=`, `>`, `>=`), `NodeInSet` (btree-resolved range). Placed at the VEO level where all dependencies are bound; pushed down by the optimizer from WHERE conjuncts.
|
|
233
254
|
|
|
234
255
|
**Current limits**:
|
|
235
|
-
1. Repetitions `{n,m}`: unrolled by `optimizer::unroll_repeat` for bounded ranges with no named inner variables and single-edge inner. Other shapes (
|
|
256
|
+
1. Repetitions `{n,m}`: unrolled by `optimizer::unroll_repeat` for bounded ranges with no named inner variables and single-edge inner. Other bounded shapes (named edge/node vars, range > `MAX_UNROLL = 8`) stay on the hash-join repetition path. Unbounded repetition (`*`/`+`/`{n,}`) is not an LTJ shape; it requires a §16.6 prefix and runs through the dedicated finite searches (`run_repetition_shortest` / `run_repetition_unbounded_mode`, see *Path-pattern prefixes*).
|
|
236
257
|
2. Any-direction edges (without tilde): not modelled as triples.
|
|
237
258
|
3. WHERE: label and pushed value predicates run inside the loop; arbitrary WHERE post-filters. Var-vs-var predicates (`a <> b`) are not pushed into the LTJ filter set yet — they evaluate post-pattern via `PathPattern::Filter`.
|
|
238
259
|
4. TripleIndex not persisted: cached on `Runtime` via `RefCell<Option<Arc<TripleIndex>>>`, built once per Runtime (eagerly at REPL/Connection open via `warm_triple_index()`).
|
|
@@ -244,7 +265,27 @@ When LTJ can't decompose, the pairwise hash-join takes over: both sides evaluate
|
|
|
244
265
|
|
|
245
266
|
`-[x]->{n,m}` binds `x` to a `Group` of matched edges, not a single edge. `to_group()` wraps each value in a singleton group; `concat_group()` concatenates groups. Nested repetitions produce nested groups: `(-[x]->{1,2}){1,2}` gives `x ↦ [[e1], [e2, e3]]`. The zero-repetition base case fills variables with empty groups.
|
|
246
267
|
|
|
247
|
-
`engine.rs::run_repetition_range` evaluates `{lb,ub}` in a single pass: the inner pattern runs once, the `first → indices` hash is built once, and every level `1..=ub` grows in a single `rows` buffer reusing the previous level's slice by index. Levels below `lb` get drained at the end via one `Vec::drain`. Replaces an earlier per-length loop that scaled as `O((ub-lb+1) × ub)` with `O(ub)`.
|
|
268
|
+
`engine.rs::run_repetition_range` evaluates bounded `{lb,ub}` in a single pass: the inner pattern runs once, the `first → indices` hash is built once, and every level `1..=ub` grows in a single `rows` buffer reusing the previous level's slice by index. Levels below `lb` get drained at the end via one `Vec::drain`. Replaces an earlier per-length loop that scaled as `O((ub-lb+1) × ub)` with `O(ub)`.
|
|
269
|
+
|
|
270
|
+
**Unbounded repetition** (`*`, `+`, `{n,}` with no upper bound) is infinite under plain `WALK ALL`, so the typechecker rejects it unless a §16.6 prefix makes it finite (see *Path-pattern prefixes* below). The `Repeat` arm of `run_path_pattern` dispatches on `Runtime::unbounded_policy` (a `Cell` set while evaluating a `Selected` operand): `Shortest { count, groups }` routes to `run_repetition_shortest`, `Mode(mode)` to `run_repetition_unbounded_mode`, and `Forbidden` panics (an invariant the typechecker guarantees, so it is only reachable via `compile_query_unchecked`). **The inner pattern must contribute ≥1 edge per application**: an empty-matching inner (e.g. a bare `(x)` node) under unbounded repetition is a hard typecheck error, since a zero-length lap never advances the length-ordered search and would loop forever (the bounded case stays a warning, since it terminates regardless).
|
|
271
|
+
|
|
272
|
+
### Path-pattern prefixes (ISO §16.6): modes, search, unbounded repetition
|
|
273
|
+
|
|
274
|
+
`PathPattern::Selected { prefix, pattern }` is evaluated in `engine.rs::run_path_pattern` and `src/runtime/path_select.rs`. The inner pattern runs with no LIMIT (selection ranks the full candidate set), then `apply_path_prefix` filters by mode and reduces by search, and any caller LIMIT is applied afterward. Selection partitions rows by the `(first node id, last node id)` boundary key and acts per partition:
|
|
275
|
+
|
|
276
|
+
- **Mode filter** (`path_satisfies_mode`): drops rows whose path repeats an edge (TRAIL) or node (ACYCLIC; SIMPLE allows only a closing first==last).
|
|
277
|
+
- **`ANY N`** (`select_any`): keep up to `N` rows per partition in production order.
|
|
278
|
+
- **`SHORTEST N [PATHS]`** (`select_shortest_paths`): the `N` shortest rows per partition, stable-sorted by edge length (ties broken by production order).
|
|
279
|
+
- **`SHORTEST N GROUPS`** (`select_shortest_groups`): every row whose length is among the `N` shortest distinct lengths in its partition.
|
|
280
|
+
|
|
281
|
+
For **bounded** patterns, `apply_path_prefix` materializes all rows then selects. For **unbounded** repetition, a dedicated finite search avoids materializing the infinite walk set:
|
|
282
|
+
|
|
283
|
+
- `run_repetition_shortest` (WALK + SHORTEST) is a length-ordered k-shortest **walk** search: a `BinaryHeap` (min-heap on path length, monotone `seq` tie-break) expands paths in non-decreasing length, with a per-`(first,last)` budget that admits ≤`count` paths (PATHS) or ≤`count` distinct lengths (GROUPS) and prunes the rest. It terminates on cycles because per-pair lengths strictly grow, and pruning is sound by optimal substructure (the prefix of a k-shortest walk to a node is itself among the k-shortest walks to its predecessor — `first` is fixed along a concat chain, so the per-pair budget is the per-node k-shortest-walk budget).
|
|
284
|
+
- `run_repetition_unbounded_mode` (TRAIL / SIMPLE / ACYCLIC) enumerates with a worklist, pruning any partial path that already violates the mode; bounded by `|E|` (TRAIL) or `|V|` (SIMPLE/ACYCLIC). A restrictive mode takes precedence over a co-present search (`SHORTEST 2 TRAIL …*` enumerates TRAIL-valid paths, then `apply_path_prefix` reduces that finite set by `SHORTEST 2`).
|
|
285
|
+
|
|
286
|
+
**BFS fast-path for single-edge shortest** (`try_shortest_bfs`, runtime side, `engine.rs`). The walk-enumeration heap above grows like `b^d` and OOMs on social graphs, so the `Selected` arm first tries a node-dominated BFS that settles each node once (O(V+E) per source). It activates only for the canonical `(src) [single-edge]{lb,ub} (tgt)` shape under WALK + `SHORTEST 1 PATHS|GROUPS` (`ANY`/`ALL SHORTEST`), with `lb ≤ 1` and an unlabelled-or-single-label edge that binds no variable; any other shape returns `None` and falls back to `run_repetition_shortest`, so semantics never regress. It drives the BFS from the smaller endpoint set (reverse-walking the adjacency when driven from the target), reconstructs paths through a predecessor DAG (all minimum-length paths for `GROUPS`, one for `PATHS`), and reproduces the generic enumerator's coincident-endpoint behavior under `+`/`{1,…}` — the length-0 self path for `*`, and the trivial undirected closed walk (`n-e-m-e-n`, ub-gated) under a non-zero lower bound; a directed coincident pair defers to the generic path. `GQLITE_DISABLE_SHORTEST_BFS=1` forces it off (the `shortest_bfs_test` differential suite asserts BFS ≡ generic). Collapses LDBC IC1/IC13 from tens of seconds to ~20–30 ms and unblocks IC14's OOM (now ~0.7 s median, real results).
|
|
287
|
+
|
|
288
|
+
**Typechecker gates** (`src/typing/checker.rs`): `check_unbounded_repetition` rejects an unbounded repeat whose nearest enclosing prefix does not license it (`PathPrefix::unbounded_support`), and rejects `SHORTEST` over `{n,}` with `n ≥ 2` (only `*`/`+` are supported; use a restrictive mode for higher lower bounds). `check_selective_isolation` enforces ISO §16.6 SR 5–8: a selective pattern (any non-`ALL` search) may share only its boundary (endpoint) variables with the rest of the query, so it stays evaluable in isolation; sharing an interior variable is an error. A `Selected` wrapper does not change variable types (`check_path_pattern` recurses through it).
|
|
248
289
|
|
|
249
290
|
### EXISTS / NOT EXISTS
|
|
250
291
|
|
|
@@ -254,12 +295,44 @@ When LTJ can't decompose, the pairwise hash-join takes over: both sides evaluate
|
|
|
254
295
|
|
|
255
296
|
**Optimisation** (`src/optimizer/existential.rs`): runs after the per-pattern pushdown passes. Walks every `Expr` reachable from `Query` (WHERE filters, GROUP BY, RETURN, recursive into nested existentials), runs the typechecker on each body against the active schema, and rewrites empty bodies to literals — `false` for `Exists`, `true` for `NotExists`. Catches shape-driven emptiness (a label or property the schema rejects). The pass does not thread outer-scope correlation into the body, so refinement-aware emptiness is left for a future pass; no literal-Boolean propagation either, so an inner-only fold inside an outer body does not collapse the outer.
|
|
256
297
|
|
|
257
|
-
**Runtime** (`engine.rs::eval_exists`):
|
|
298
|
+
**Runtime** (`engine.rs::eval_exists`): three regimes share `Runtime::exists_cache: RefCell<HashMap<usize, ExistsCache>>` keyed by the body's heap address.
|
|
258
299
|
- *Uncorrelated* (no shared variable with outer μ): `run_match_chain(body, limit=1)` once, cache the bool; subsequent rows reuse it.
|
|
259
|
-
- *Correlated
|
|
300
|
+
- *Correlated — pinned* (`ExistsCache::CorrelatedPinned`, the default when the body is LTJ-pinnable): per outer row, collapse the body to one pattern and run it with the correlation variables pinned to that row's node ids via `try_ltj_with_pins` (`pinned_run_multi`, which unwraps the `Filter` carrying the body's WHERE so the predicate still runs), then memoise the non-emptiness verdict by correlation tuple. The body is evaluated once per *distinct correlation tuple*, never over the whole graph. This is the LDBC IC4 / IC10 anti-join shape (one person → a handful of friends): collapses the per-param IC4 cost from a fixed full-graph materialisation (~3 s on a slow host, the old floor) to a few targeted probes. `exists_body_pinned` returns `None` (→ fall back to materialise-once) when a correlation value is not a `Node`, the body is OPTIONAL/`Selected`/non-decomposable, **or the body contains an undirected edge** (`PathPattern::has_undirected_edge` — pinning both endpoints of a `~[...]~` does not constrain the LTJ to that specific pair, so it would yield false positives; the undirected `~[:knows]~` in IC7's `isNew` takes this fallback). Force off with `GQLITE_DISABLE_EXISTS_PIN=1`.
|
|
301
|
+
- *Correlated — materialise-once* (`ExistsCache::Correlated`, fallback): `build_correlated_set` runs `run_match_chain(body, 0)` once (full body), projects every row onto the correlation set (sorted variable names), stores a `HashSet<Vec<PathValue>>`. Per outer row, build the probe key from μ and check membership — semi-join for `EXISTS`, anti-join for `NOT EXISTS`. Wins when the outer side binds many distinct correlation tuples (amortises the one scan); the pinned regime wins when it binds few.
|
|
260
302
|
|
|
261
303
|
The four phases live in commits `4d13327` (parse + typecheck), `163ee30` (fold optimiser), `134890f` (uncorrelated runtime), `d5b4a45` (correlated runtime). Tests in `tests/parser_test.rs`, `tests/typecheck_test.rs`, `tests/exists_fold_test.rs`, `tests/exists_runtime_test.rs`. Formal type rules in `latex/extension/main.tex` (`\textsc{TExists}`, `\textsc{TNotExists}`, plus `\isEmpty(\matchseq) \equiv e \lor \mathsf{empty}(\Gamma')` and the rewrite rules `\textsc{ExistsEmpty}` / `\textsc{NotExistsEmpty}`).
|
|
262
304
|
|
|
305
|
+
### Aggregates and RETURN arithmetic
|
|
306
|
+
|
|
307
|
+
`RETURN` items are `ReturnItem::Expr { expr, alias }` or `ReturnItem::Aggregate { agg, alias }`. Aggregates also compose **inside** value expressions via `Expr::Agg(Box<Aggregator>)`, so arithmetic over aggregate results works: `COUNT(DISTINCT x) + COUNT(DISTINCT y) AS total`. The parser recognises an aggregate call in `primary_expr` (so it can be a `Binop` operand); a *bare* top-level aggregate is re-folded back into `ReturnItem::Aggregate` so the existing aggregate-projection and ORDER BY-matching paths stay unchanged. `Expr::contains_agg()` classifies a RETURN expr as reduced-over-group (not a grouping key).
|
|
308
|
+
|
|
309
|
+
Runtime (`engine.rs::run_aggregated`): an aggregate-bearing RETURN expr is evaluated per group by `fold_aggs` — each `Expr::Agg` node is reduced over the group to an `Expr::Const`, then the residual arithmetic runs against the group's representative row (so non-aggregate leaves like `otherPerson.firstName` resolve to their grouped value). The typechecker types `Expr::Agg` as the reducer's result (`COUNT`→Int, `AVG`→Float, `SUM`→numeric, `MIN`/`MAX`→element type) and exempts aggregate-bearing exprs from the GROUP BY key check. This unblocked LDBC IC3 (`COUNT(DISTINCT messageX) + COUNT(DISTINCT messageY) AS totalCount`). Tests in `tests/count_test.rs`.
|
|
310
|
+
|
|
311
|
+
### Value subqueries, RECORD, scalar builtins, GROUP BY by variable (IC7 cluster)
|
|
312
|
+
|
|
313
|
+
Six value-expression primitives added together to unblock LDBC IC7 (`bench/ldbc-queries/ic7.toml`, `tests/ic7_test.rs`):
|
|
314
|
+
|
|
315
|
+
- **Division `/`** — ISO `<solidus>`; `Token::Slash`, `BinOp::Div`, lexed past the `/* */` comment (consumed earlier in `skip_whitespace`). Runtime `eval_binop`: Int/Int truncates, mixed/Float widen, div-by-zero → `Failure` → Null (3VL).
|
|
316
|
+
- **`FLOOR` / `CAST`** — a generic `Expr::Call { name, args }` with dispatch in `engine.rs::eval_call`. `FLOOR(x)→Float`. `CAST(x AS INTEGER|FLOAT)` *converts* the value (the target rides as `Expr::Type` in `args[1]`); distinct from `BinOp::As`, which only type-*asserts*. Both are soft keywords (only before `(`). Parser parses the CAST operand with `return_comparison()` so the `AS` separator is not swallowed. This is the home for future builtins (CEIL/ABS/SIZE/…); the path functions (`ELEMENTS`/`PATH_LENGTH`/`CARDINALITY`) also live in this dispatch — see *Named paths and path functions*.
|
|
317
|
+
- **`RECORD { k: <expr>, ... }`** — `Expr::Record { fields: Vec<(String, Expr)> }` with expression values. `RECORD` keyword optional (soft, before `{`); shares the brace parser with the bare `{k: v}` literal via `parse_brace_record`, keeping the `:`-lookahead value-vs-type split and a const fast-path (all-`Const` fields fold to `Value::Record`). Types as `SimpleType::Record`; `FieldAccess` already resolves fields.
|
|
318
|
+
- **`VALUE { MATCH ... RETURN <1 item> ORDER BY ... LIMIT 1 }`** — `Expr::ValueSubquery { body: Box<Query> }`, a correlated scalar/record subquery (arg-max per group). Parser `parse_value_body` (allows RETURN/ORDER BY/LIMIT, rejects GROUP BY/DISTINCT, exactly one item). Typecheck reuses `check_subquery_body` then types the single RETURN item. Runtime `eval_value_subquery` has two regimes, same split as correlated EXISTS (`ValueSubqueryCache { map, pinned }`, keyed by body heap ptr, cleared per top-level `run_query`): **pinned** (default when pinnable, `value_subquery_pinned`) runs the body per outer row with the correlation vars pinned to that row's node ids (`pinned_run_multi`), projects + ORDER BY + LIMIT 1, and memoises the value by correlation tuple — body evaluated once per *distinct tuple*, never globally; **materialise-once** (fallback) runs the body once, buckets all rows by correlation key, projects each bucket. Pinned bails (→ materialise-once) on the same conditions as `exists_body_pinned` (non-Node correlation value, OPTIONAL/`Selected`, non-decomposable, undirected edge) plus a parameter correlation or a grouping/aggregate body (those need `run_aggregated`, not the plain arg-max projection). Force off with `GQLITE_DISABLE_VALUE_SUBQUERY_PIN=1`. This is IC7's dominant cost: its `VALUE { (person)<-[:hasCreator]-(m)<-[l:likes]-(liker) ... LIMIT 1 }` body, run uncorrelated, materialised the whole graph's like relation (109 k triples / param); pinned to `(person, liker)` it touches a handful — IC7 1.3 s → ~9 ms.
|
|
319
|
+
- **`GROUP BY <binding variable>`** — ISO `<grouping element> ::= <binding variable reference>`, so `GROUP BY liker, person` groups by node identity. `run_query` routes a GROUP-BY-without-aggregates query through `run_aggregated` (`needs_grouping = has_aggs || group_by.is_some()`). The typechecker's functional-dependency check (`check_returns_match_group_by`) accepts a non-aggregate projection when it structurally equals a key OR references only bare-`Expr::Var` grouping keys; subquery/aggregate-bearing items are exempt (evaluated on the group's representative row). **Grouping by a property** (`GROUP BY x.city`) only licenses the structural-match shape, not sibling attributes.
|
|
320
|
+
- **`ORDER BY <alias>.<field>`** — `SortKey::ColumnField { col, path }` (post-projection): walks a record-valued projected column. Needed because IC7 orders by `latestLike.likeCreationDate` where `latestLike` is a VALUE-subquery record column.
|
|
321
|
+
|
|
322
|
+
Latent fix made here: `eq_value` (the `GroupKey` equality backing grouping/DISTINCT) had no `Value::Node`/`Edge`/`Null` arms, so two equal nodes compared unequal — grouping by a node variable never collapsed. Now compares reference values by id. Elaboration recurses into subquery bodies (`elaborate_expr` over RETURN / ORDER BY / Filter exprs) so a subquery's own descriptor `value_filters` lower. Tests: `tests/{division,builtin_floor_cast,record_expr,group_by_var,value_subquery,ic7}_test.rs`. Remaining LDBC IC blockers + roadmap in `docs/internals/iso-gql-gaps.md`.
|
|
323
|
+
|
|
324
|
+
### Named paths and path functions (ISO §16.6 + §20.16)
|
|
325
|
+
|
|
326
|
+
`MATCH p = (a)-[:knows]->(b)` binds a whole comma operand's matched path to a path variable, materialized as a `Value::Path` for the §20.16 path functions. Unblocks the shared prerequisite of LDBC IC1 / IC13 / IC14 (each still has one further gap: `COLLECT_LIST`, `CASE WHEN`, list comprehension).
|
|
327
|
+
|
|
328
|
+
- **AST**: `PathPattern::Named { var, pattern }` wraps one operand, *outside* any §16.6 prefix (`Named { var, Selected { prefix, pattern } }`). Parsed in `path_pattern_operand` by an ISO `<path variable declaration>` lookahead (`Name` `=` at the operand start — a comparison `=` never begins an operand). The wrapper is transparent everywhere it is walked (elaborate, optimizer pushdown, the typechecker's isolation/unbounded/var-count passes); it never appears below a Concat, so LTJ on a single operand still fires and only a comma-joined Named operand falls back to hash-join.
|
|
329
|
+
- **Value model**: `PathValue::Path(Vec<PathValue>)` (binding-table form, distinct from `Group` so it projects to `Value::Path`, not `Value::List`) and `Value::Path(Vec<Value>)` (projected form, alternating node/edge reference values in match order). Both wired into `path_value_to_value`, `hash_value`, `eq_value`. A path is never a property value — `value_to_prop` (store) errors on it.
|
|
330
|
+
- **Runtime** (`engine.rs::run_path_pattern` `Named` arm): evaluate the inner operand, then bind `var → PathValue::Path(row.path().0.clone())` per row. The path is captured from the `ResultRow.paths` the runtime already builds (including the LTJ and SHORTEST paths), not recomputed.
|
|
331
|
+
- **Path functions** (`eval_call`): `ELEMENTS` (all elements), `PATH_LENGTH` (edge count), `CARDINALITY` (node + edge count) are ISO §20.16. `NODES` / `EDGES` (node-only / edge-only projections) are **not** ISO — they are a documented translation divergence for the LDBC queries; mark them as such in the toml. All are soft keywords resolved by `path_function_name` (only `NAME(` is special, so `nodes`/`elements`/… stay usable as variables and labels).
|
|
332
|
+
- **Types**: terminal `SimpleType::Path` + `VariableType::Path` (only meets/subtypes itself, never refines against schema, `is_empty == false` so a path binding never empties an environment). The checker binds `var → VariableType::Path` in the `TypeEnvironment` and requires each path-function argument to type as `Path` — a provably non-path argument (e.g. a node variable) is a hard type error.
|
|
333
|
+
|
|
334
|
+
Tests in `tests/named_path_test.rs`. Full ISO context + roadmap in `docs/internals/iso-gql-gaps.md` §2.9.
|
|
335
|
+
|
|
263
336
|
### Null semantics
|
|
264
337
|
|
|
265
338
|
`Value::Null` is a first-class variant. Properties that are absent from a node/edge map are treated as null at query time, and explicit nulls round-trip through the on-disk format.
|
|
@@ -313,6 +386,7 @@ Passes the runtime relies on (each one-line summary; flags + file refs are the o
|
|
|
313
386
|
- **LTJ multi-way join + concat** — see *Join strategy* above.
|
|
314
387
|
- **Type-predicate pushdown** (`is T` → descriptor `PropertyType`).
|
|
315
388
|
- **Value-predicate pushdown** (`x.attr <op> literal` for `=`, `!=`, `<`, `<=`, `>`, `>=` → `value_preds` on node descriptor; emitted as `FilterKind::NodeAttrCmp`; nodes only).
|
|
389
|
+
- **Selected-path boundary pushdown** (`pushdown.rs`, ISO §16.6) — into a `Selected` pattern, a mode-only prefix (`PathSearch::All`, e.g. `ACYCLIC`) admits *all* constraints, but a selective prefix (`ANY`/`SHORTEST`) admits **only boundary (endpoint) variable** constraints; interior-node predicates stay as a post-selection `Filter`. Sound because selection partitions per `(source, target)` pair: restricting endpoints before the search equals filtering after, whereas filtering an interior node before would change which paths are shortest. `Constraints::retain_vars` keeps only the boundary vars; `walk_kinds` and `merge_constraints` carry the split.
|
|
316
390
|
- **Index-driven constant folding** — `Eq` predicates that hit a hash index pre-bind the variable and drop it from VEO; empty hit short-circuits to zero rows. `pattern_extract::fold_indexed_constants`.
|
|
317
391
|
- **Range index folding** — `<` / `<=` / `>` / `>=` predicates that hit a btree precompute the matching set and replace `NodeAttrCmp` with `FilterKind::NodeInSet`. `pattern_extract::fold_range_filters`.
|
|
318
392
|
- **Repeat unrolling** (`unroll_repeat.rs`) — `(P){lb, ub}` → `Union(P^lb..P^ub)` for bounded ranges with single-edge inner and empty freevars; inserts anonymous `Node(None)` boundaries and distributes Union over Concat. `MAX_UNROLL = 8` (covers `{1,8}` and tighter). Fixed-length `lb == ub` short-circuits to a single flat concat with no Union envelope. `GQLITE_DISABLE_REPEAT_UNROLL=1`.
|
|
@@ -327,7 +401,7 @@ Passes the runtime relies on (each one-line summary; flags + file refs are the o
|
|
|
327
401
|
|
|
328
402
|
DDL: `CREATE [HASH | BTREE] INDEX [<name>] ON :Label(prop) [USING HASH | BTREE]`, `DROP INDEX <name>`, `SHOW INDEXES` (or `.indexes`). Both prefix and suffix syntaxes work; HASH is the default kind. Re-declaring the same kind on the same `(label, prop)` is the only conflict.
|
|
329
403
|
|
|
330
|
-
GraphAccess trait methods: `lookup_node_eq(label, prop, value) -> Option<Vec<Id>>`, `lookup_node_range(label, prop, lo, hi) -> Option<Vec<Id>>`, `lookup_node_ordered(label, prop, asc) -> Option<Vec<Id>>`. `
|
|
404
|
+
GraphAccess trait methods: `lookup_node_eq(label, prop, value) -> Option<Vec<Id>>`, `lookup_node_range(label, prop, lo, hi) -> Option<Vec<Id>>`, `lookup_node_ordered(label, prop, asc) -> Option<Vec<Id>>`. `MemoryGraphStore` (in-RAM JSON backend) returns `None` from all three and falls back to scan — it has no secondary index, so queries stay correct but unaccelerated.
|
|
331
405
|
|
|
332
406
|
**Persistence (commit `2153319`).** Auto entries are memory-only (rebuilt every open, deterministic). DDL entries (`auto = false`) ARE persisted in the `.gdb` via `header.secondary_index_root` → chained `PageType::SecondaryIndex` pages → JSON-encoded `Vec<PersistedSpec>`. Save side: `save_graph_with_catalog_and_indexes_atomic` (`store/io.rs`). Load side: `LazyGraphStore::open` reads the list and replays each entry via `build_declared` after the auto-build. See `store/secondary_index_io.rs` and `docs/secondary-indexes.md`.
|
|
333
407
|
|
|
@@ -337,6 +411,25 @@ Diagnostic env vars: `GQLITE_DEBUG_INDEXES=1` (auto-built indexes + pinned varia
|
|
|
337
411
|
|
|
338
412
|
LDBC IC2 on `bench/data/ldbc-sf0.1.gdb` (15 params × 3 iters, lazy backend, `--limit 20`): 2417 ms (no indexes) → 1377 ms (auto hash+btree) → **8.7 ms** (TripleIndex cached + warmed at open, 276× total). Reference: GraphQLite (SQLite + Cypher) measures 32.8 ms median on the same query.
|
|
339
413
|
|
|
414
|
+
## Benchmarks
|
|
415
|
+
|
|
416
|
+
Two benches, deliberately split (full operational doc in `bench/cross-system/README.md`):
|
|
417
|
+
|
|
418
|
+
- **Internal bench** (`internal_bench` bin, `bench/INTERNAL_BENCHMARK.md`) — gqlite's own components in isolation (typechecker on/off, lazy/disk backend, RSS). Engine diagnostics, not cross-engine comparisons.
|
|
419
|
+
- **External / cross-system bench** (`bench/cross-system/`) — gqlite vs other graph databases on LDBC SNB Interactive Complex (IC) query latency. The headline numbers.
|
|
420
|
+
|
|
421
|
+
### Cross-system harness (`bench/cross-system/`)
|
|
422
|
+
|
|
423
|
+
`run_all.sh` orchestrates **systems on the outer loop, ICs on the inner**: per system, set up once (load full LDBC SF0.1 into its native format), run each requested IC, then exit (per-system memory reclaimed at process exit). Output lands in `results/<timestamp>/` — per-(system,IC) CSV (`query;backend;params;row;iter;result_count;elapsed_ns`), `comparison.txt`, `setup_times.txt`, `run_info.txt`.
|
|
424
|
+
|
|
425
|
+
- **IC source-of-truth** is `bench/ldbc-queries/ic<n>.toml` — the canonical GQL the gqlite path runs directly via `ldbc_bench`. Each external system translates it to its own dialect file (`<system>/ic<n>.cypher` or `.gql`); per-system deviations live in `<system>/DIVERGENCES.md`. Only ICs with `status = "implemented"` run (currently IC2,5,6,8,9,11).
|
|
426
|
+
- **Row-equivalence oracle**: every runner sha256-hashes its iter-0 result and emits a `ROW … hash=<hex>` stderr line. `_lib/row_hash.py` **must byte-mirror** the `canonicalize_*` functions in `src/bin/ldbc_bench.rs` so all runners produce identical hashes for the same logical rows; `compare_results.py` cross-checks them. A mismatch is a real per-system translation bug, not noise — with ORDER BY in every toml the iter-0 result is deterministic, so byte-equal blobs ⇒ byte-equal results. This is what makes a cross-system latency comparison legitimate.
|
|
427
|
+
- **Measurement caveat** (quote it when quoting numbers): gqlite is benched through its Rust binary (`ldbc_bench`, no Python in the path); external systems through their Python wheels (~1–2 ms FFI per call). Each is measured via its primary user-facing interface, not normalized — latency is indicative, the row-equivalence check is the apples-to-apples part.
|
|
428
|
+
|
|
429
|
+
**Adding a system**: create `bench/cross-system/<sys>/` with `setup.py` (load the full LDBC SF0.1, IC-agnostic — counts must match gqlite's 327 588 nodes / 1 477 965 edges), `run.py` (emit the CSV schema + the ROW hash via `_lib/row_hash.py`), `requirements.txt`, `ic<n>.{cypher,gql}` translations, `README.md`, `DIVERGENCES.md`; then register it in `run_all.sh` (`ALL_SYSTEMS`, `SETUP_CMD`, `SETUP_MARKER`, `RUNNER`, `REBUILD_FLAG`) and add the `backend`→system mapping in `compare_results.py`'s `_normalize`. `kuzu/` is the closest template for an embedded Python engine; `grafeo/` is the GQL-native one (and shows the dialect carve-outs: no second top-level `MATCH`, no `(:A|B)` pattern alternation, `GROUP BY` by property-expression not alias, sub-labels via `type=` filter).
|
|
430
|
+
|
|
431
|
+
Run + chart: `bench_setup` (downloads LDBC SF0.1) → `install_python_deps.sh` → `bench/cross-system/run_all.sh --only gqlite,<sys> --ics 2,5,6,8,9,11` → `python bench/cross-system/plot_results.py` (median-per-IC grouped bars, PNG + SVG). A separate synthetic micro-bench (same-data, same-query, result-verified latency on a generated social graph) lives in `bench/grafeo-vs-frogql/`. `bench/data/` and `results/` are gitignored.
|
|
432
|
+
|
|
340
433
|
## Conventions
|
|
341
434
|
|
|
342
435
|
- Labels in patterns require the `:` prefix: `-[:Transfer]->`, not `-[Transfer]->`.
|
|
@@ -59,6 +59,12 @@ dependencies = [
|
|
|
59
59
|
"generic-array",
|
|
60
60
|
]
|
|
61
61
|
|
|
62
|
+
[[package]]
|
|
63
|
+
name = "bumpalo"
|
|
64
|
+
version = "3.20.3"
|
|
65
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
66
|
+
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
|
67
|
+
|
|
62
68
|
[[package]]
|
|
63
69
|
name = "cc"
|
|
64
70
|
version = "1.2.61"
|
|
@@ -92,6 +98,16 @@ dependencies = [
|
|
|
92
98
|
"error-code",
|
|
93
99
|
]
|
|
94
100
|
|
|
101
|
+
[[package]]
|
|
102
|
+
name = "console_error_panic_hook"
|
|
103
|
+
version = "0.1.7"
|
|
104
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
105
|
+
checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
|
|
106
|
+
dependencies = [
|
|
107
|
+
"cfg-if",
|
|
108
|
+
"wasm-bindgen",
|
|
109
|
+
]
|
|
110
|
+
|
|
95
111
|
[[package]]
|
|
96
112
|
name = "convert_case"
|
|
97
113
|
version = "0.6.0"
|
|
@@ -273,7 +289,7 @@ dependencies = [
|
|
|
273
289
|
|
|
274
290
|
[[package]]
|
|
275
291
|
name = "frogql-node"
|
|
276
|
-
version = "0.2.
|
|
292
|
+
version = "0.2.4"
|
|
277
293
|
dependencies = [
|
|
278
294
|
"gqlrust",
|
|
279
295
|
"napi",
|
|
@@ -284,12 +300,48 @@ dependencies = [
|
|
|
284
300
|
|
|
285
301
|
[[package]]
|
|
286
302
|
name = "frogql-py"
|
|
287
|
-
version = "0.2.
|
|
303
|
+
version = "0.2.4"
|
|
288
304
|
dependencies = [
|
|
289
305
|
"gqlrust",
|
|
290
306
|
"pyo3",
|
|
291
307
|
]
|
|
292
308
|
|
|
309
|
+
[[package]]
|
|
310
|
+
name = "frogql-wasm"
|
|
311
|
+
version = "0.2.4"
|
|
312
|
+
dependencies = [
|
|
313
|
+
"console_error_panic_hook",
|
|
314
|
+
"gqlrust",
|
|
315
|
+
"serde",
|
|
316
|
+
"serde-wasm-bindgen",
|
|
317
|
+
"serde_json",
|
|
318
|
+
"wasm-bindgen",
|
|
319
|
+
]
|
|
320
|
+
|
|
321
|
+
[[package]]
|
|
322
|
+
name = "futures-core"
|
|
323
|
+
version = "0.3.32"
|
|
324
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
325
|
+
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
|
|
326
|
+
|
|
327
|
+
[[package]]
|
|
328
|
+
name = "futures-task"
|
|
329
|
+
version = "0.3.32"
|
|
330
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
331
|
+
checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
|
|
332
|
+
|
|
333
|
+
[[package]]
|
|
334
|
+
name = "futures-util"
|
|
335
|
+
version = "0.3.32"
|
|
336
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
337
|
+
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
|
|
338
|
+
dependencies = [
|
|
339
|
+
"futures-core",
|
|
340
|
+
"futures-task",
|
|
341
|
+
"pin-project-lite",
|
|
342
|
+
"slab",
|
|
343
|
+
]
|
|
344
|
+
|
|
293
345
|
[[package]]
|
|
294
346
|
name = "generic-array"
|
|
295
347
|
version = "0.14.7"
|
|
@@ -529,6 +581,18 @@ dependencies = [
|
|
|
529
581
|
"libc",
|
|
530
582
|
]
|
|
531
583
|
|
|
584
|
+
[[package]]
|
|
585
|
+
name = "js-sys"
|
|
586
|
+
version = "0.3.99"
|
|
587
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
588
|
+
checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11"
|
|
589
|
+
dependencies = [
|
|
590
|
+
"cfg-if",
|
|
591
|
+
"futures-util",
|
|
592
|
+
"once_cell",
|
|
593
|
+
"wasm-bindgen",
|
|
594
|
+
]
|
|
595
|
+
|
|
532
596
|
[[package]]
|
|
533
597
|
name = "leb128fmt"
|
|
534
598
|
version = "0.1.0"
|
|
@@ -706,6 +770,12 @@ version = "2.3.2"
|
|
|
706
770
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
707
771
|
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
|
708
772
|
|
|
773
|
+
[[package]]
|
|
774
|
+
name = "pin-project-lite"
|
|
775
|
+
version = "0.2.17"
|
|
776
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
777
|
+
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
|
778
|
+
|
|
709
779
|
[[package]]
|
|
710
780
|
name = "pkg-config"
|
|
711
781
|
version = "0.3.33"
|
|
@@ -1094,6 +1164,17 @@ dependencies = [
|
|
|
1094
1164
|
"serde_derive",
|
|
1095
1165
|
]
|
|
1096
1166
|
|
|
1167
|
+
[[package]]
|
|
1168
|
+
name = "serde-wasm-bindgen"
|
|
1169
|
+
version = "0.6.5"
|
|
1170
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1171
|
+
checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b"
|
|
1172
|
+
dependencies = [
|
|
1173
|
+
"js-sys",
|
|
1174
|
+
"serde",
|
|
1175
|
+
"wasm-bindgen",
|
|
1176
|
+
]
|
|
1177
|
+
|
|
1097
1178
|
[[package]]
|
|
1098
1179
|
name = "serde_core"
|
|
1099
1180
|
version = "1.0.228"
|
|
@@ -1153,6 +1234,12 @@ version = "1.3.0"
|
|
|
1153
1234
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1154
1235
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
|
1155
1236
|
|
|
1237
|
+
[[package]]
|
|
1238
|
+
name = "slab"
|
|
1239
|
+
version = "0.4.12"
|
|
1240
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1241
|
+
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
|
1242
|
+
|
|
1156
1243
|
[[package]]
|
|
1157
1244
|
name = "smallvec"
|
|
1158
1245
|
version = "1.15.1"
|
|
@@ -1435,6 +1522,51 @@ dependencies = [
|
|
|
1435
1522
|
"wit-bindgen 0.51.0",
|
|
1436
1523
|
]
|
|
1437
1524
|
|
|
1525
|
+
[[package]]
|
|
1526
|
+
name = "wasm-bindgen"
|
|
1527
|
+
version = "0.2.122"
|
|
1528
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1529
|
+
checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409"
|
|
1530
|
+
dependencies = [
|
|
1531
|
+
"cfg-if",
|
|
1532
|
+
"once_cell",
|
|
1533
|
+
"rustversion",
|
|
1534
|
+
"wasm-bindgen-macro",
|
|
1535
|
+
"wasm-bindgen-shared",
|
|
1536
|
+
]
|
|
1537
|
+
|
|
1538
|
+
[[package]]
|
|
1539
|
+
name = "wasm-bindgen-macro"
|
|
1540
|
+
version = "0.2.122"
|
|
1541
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1542
|
+
checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6"
|
|
1543
|
+
dependencies = [
|
|
1544
|
+
"quote",
|
|
1545
|
+
"wasm-bindgen-macro-support",
|
|
1546
|
+
]
|
|
1547
|
+
|
|
1548
|
+
[[package]]
|
|
1549
|
+
name = "wasm-bindgen-macro-support"
|
|
1550
|
+
version = "0.2.122"
|
|
1551
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1552
|
+
checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e"
|
|
1553
|
+
dependencies = [
|
|
1554
|
+
"bumpalo",
|
|
1555
|
+
"proc-macro2",
|
|
1556
|
+
"quote",
|
|
1557
|
+
"syn",
|
|
1558
|
+
"wasm-bindgen-shared",
|
|
1559
|
+
]
|
|
1560
|
+
|
|
1561
|
+
[[package]]
|
|
1562
|
+
name = "wasm-bindgen-shared"
|
|
1563
|
+
version = "0.2.122"
|
|
1564
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1565
|
+
checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437"
|
|
1566
|
+
dependencies = [
|
|
1567
|
+
"unicode-ident",
|
|
1568
|
+
]
|
|
1569
|
+
|
|
1438
1570
|
[[package]]
|
|
1439
1571
|
name = "wasm-encoder"
|
|
1440
1572
|
version = "0.244.0"
|