From 7938a442545539d627c7c9b121bd0cb81b944e27 Mon Sep 17 00:00:00 2001 From: Leo Meyerovich Date: Tue, 13 Oct 2026 12:00:13 -0700 Subject: [PATCH 1/4] feat(gfql): add typed node segment indexes for indexed hops --- CHANGELOG.md | 2 + bin/test-polars.sh | 1 + docs/source/gfql/indexing.rst | 33 ++ graphistry/compute/ComputeMixin.py | 19 +- graphistry/compute/gfql/index/__init__.py | 13 +- graphistry/compute/gfql/index/api.py | 121 ++++++- graphistry/compute/gfql/index/build.py | 60 +++- graphistry/compute/gfql/index/cypher_ddl.py | 2 +- graphistry/compute/gfql/index/registry.py | 49 ++- graphistry/compute/gfql/index/traverse.py | 118 ++++++- graphistry/compute/gfql/index/types.py | 6 +- graphistry/compute/gfql/index/wire.py | 9 +- .../gfql/index/test_node_type_segments.py | 318 ++++++++++++++++++ 13 files changed, 725 insertions(+), 26 deletions(-) create mode 100644 graphistry/tests/compute/gfql/index/test_node_type_segments.py diff --git a/CHANGELOG.md b/CHANGELOG.md index b0f84c6804..3cb1419f31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - **GFQL relationship-type adjacency sidecars** (#2184): `gfql_index_edges(..., type_column=..., type_values=...)` explicitly builds forward and reverse adjacency indexes for selected string or integer relationship values. Matching queries can narrow candidate edge rows while retaining the full edge predicate and canonical results; queries without a live sidecar use ordinary adjacency. Sidecars are built at graph setup, share neighbor arrays with ordinary indexes, and appear as `typed:` rows with incremental CSR bytes in `show_indexes()`. Typed options are keyword-only. cuDF builds may add overhead on dense-community shapes, and sidecars must be rebuilt after rebinding or changing edges. +- **GFQL node-type wavefront segments**: `gfql_index_node_types(..., type_column=..., type_values=...)` builds node row-position segments for selected scalar types or Boolean labels. Matching seeded wavefront hops can narrow source and destination candidates while retaining canonical node filters and results; queries without a live segment use the ordinary paths. Node segments are built at graph setup and appear as `typed_node:` `node_type_segment` rows in `show_indexes()`; selective drop supports all segments, one column, or one segment. + - **GFQL Cypher aggregates over relationship rows**: in a single required `MATCH`, aggregates such as `MATCH (a)-[r]->(b) RETURN a, count(*), sum(r.w), collect(b.id)` run on per-path binding rows, so group keys and arguments can reference any node or relationship alias, including `DISTINCT`, post-aggregate arithmetic (`count(b) + 1`) and multi-alias keys. These shapes previously raised unsupported-query errors. ### Fixed diff --git a/bin/test-polars.sh b/bin/test-polars.sh index 895d79a87c..517b3922e6 100755 --- a/bin/test-polars.sh +++ b/bin/test-polars.sh @@ -193,6 +193,7 @@ POLARS_TEST_FILES=( graphistry/tests/compute/gfql/test_unseeded_binding_limit.py graphistry/tests/compute/gfql/index/test_indexed_bindings.py graphistry/tests/compute/gfql/index/test_typed_adjacency.py + graphistry/tests/compute/gfql/index/test_node_type_segments.py graphistry/tests/compute/gfql/index/test_indexed_bindings_membership_seed.py graphistry/tests/compute/gfql/index/test_index_ddl_in_one_call.py graphistry/tests/compute/gfql/cypher/test_in_list_seeds_the_pattern.py diff --git a/docs/source/gfql/indexing.rst b/docs/source/gfql/indexing.rst index 0bfa191f7e..492667d66c 100644 --- a/docs/source/gfql/indexing.rst +++ b/docs/source/gfql/indexing.rst @@ -200,6 +200,39 @@ The ``typed:`` rows in ``show_indexes()`` report incremental CSR bytes; they share the ordinary adjacency's neighbor arrays. Build for the engine used by the query, and rebuild after binding a new edge frame or changing edges in place. +Select node types or labels +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For repeated hops that filter nodes by type or label, build row-position sidecars +for the scalar or Boolean values those queries need: + +.. code-block:: python + + labeled_nodes = pd.DataFrame({ + "id": [0, 1, 2], "type": ["PERSON", "ROBOT", "PERSON"], + "label__Active": [True, False, True], + }) + labeled_g = graphistry.nodes(labeled_nodes, "id") + labeled_g = labeled_g.gfql_index_node_types( + type_column="type", type_values=["PERSON"] + ) + labeled_g = labeled_g.gfql_index_node_types( + type_column="label__Active", type_values=[True] + ) + labeled_g.show_indexes()[["name", "nbytes", "valid", "usable"]] + +``gfql_index_node_types(type_column=..., type_values=...)`` is the node-frame dual +of relationship-type sidecars. A scalar column partitions rows; a Boolean +``label__