# Filters And Aggregation Traversal filters are JSONB values produced by helper functions or by equivalent JSON. Aggregation uses strict JSON traversal specs and aggregation specs. ## Register Filter Columns First ```sql SELECT graph.add_filter_column('public.users'::regclass, 'age', 'numeric'); SELECT graph.add_filter_column('public.users'::regclass, 'active', 'boolean'); SELECT graph.add_filter_column('public.users'::regclass, 'state', 'text'); SELECT * FROM graph.build(); ``` Filter columns are loaded into `FilterIndex` at build time. If you add or change filter-column registration, rebuild before using it in traversal. ## Filter Helper Functions | Function | Meaning | |---|---| | `graph.equals(column, value)` / `graph.eq(column, value)` | equality | | `graph.not_equals(column, value)` / `graph.neq(column, value)` | inequality | | `graph.greater_than(column, value)` / `graph.gt(column, value)` | greater than | | `graph.at_least(column, value)` / `graph.gte(column, value)` | greater than or equal | | `graph.less_than(column, value)` / `graph.lt(column, value)` | less than | | `graph.at_most(column, value)` / `graph.lte(column, value)` | less than or equal | | `graph.between(column, lower, upper)` | inclusive range | | `graph.all(filters)` | logical AND merge | | `graph.on_node(filter)` | wrap helper output under the traversal node scope | Examples: ```sql SELECT * FROM graph.traverse( 'public.users'::regclass, 'u1', max_depth := 2, filter := graph.all(ARRAY[ graph.gte('age', 40), graph.neq('state', '"inactive"'::jsonb) ]), hydrate := true ); ``` ```sql SELECT * FROM graph.find_related( 'name', 'Alice', where_node := graph.gt('age', 40), target_table := 'public.users'::regclass ); ``` The helper overloads accept convenient scalar text and integer values for common cases, but JSONB values are the general representation. ## Filter Semantics Filters are ANDed. A node must satisfy every predicate to be included. JSON numbers compare as numbers. Integer values are compared exactly across the signed and unsigned 64-bit range; decimal values use numeric fallback semantics. JSON strings remain strings, so `"123"` does not match the number `123`. Missing or NULL values: | Operator family | NULL behavior | |---|---| | Equality and comparison | Does not match | | `eq` with JSON `null` | Matches absent value | | `neq` with JSON `null` | Matches present value | The public helper set currently focuses on equality, inequality, comparisons, and ranges. For NULL checks, pass JSON `null` explicitly: ```sql SELECT * FROM graph.traverse( 'public.users'::regclass, 'u1', filter := graph.eq('deleted_at', 'null'::jsonb) ); ``` ## Path Formatting Helpers `graph.node_ref_string()` returns a canonical JSON array string used by strict aggregation traversal specs: ```sql SELECT graph.node_ref_string('public.users'::regclass, 'u1'); ``` `graph.format_path()` converts a traversal `path` and `edge_path` pair to text: ```sql SELECT graph.format_path( '[{"table":"users","id":"u1"},{"table":"users","id":"u2"}]'::jsonb, '["friend"]'::jsonb ); ``` Result: ```text users:u1 --friend--> users:u2 ``` ## Path Count Estimate `graph.path_count_estimate()` evaluates a strict traversal spec and returns: | Column | Meaning | |---|---| | `estimated_paths` | Count up to `graph.max_exact_path_count` | | `exact` | Whether the count completed exactly | | `capped` | Whether counting stopped at the configured cap | Example: ```sql WITH req AS ( SELECT jsonb_build_object( 'starts', jsonb_build_array(graph.node_ref_string('public.users'::regclass, 'u1')), 'direction', 'out', 'min_depth', 0, 'max_depth', 2, 'edge_types', jsonb_build_array('friend'), 'node_tables', jsonb_build_array('public.users') ) AS traversal ) SELECT * FROM graph.path_count_estimate((SELECT traversal FROM req)); ``` ## Aggregation `graph.aggregate(traversal, aggregations, scope, path_limit)` runs server-side aggregates over traversal results. ```sql WITH req AS ( SELECT jsonb_build_object( 'starts', jsonb_build_array(graph.node_ref_string('public.users'::regclass, 'u1')), 'direction', 'out', 'min_depth', 0, 'max_depth', 1, 'edge_types', jsonb_build_array('friend'), 'node_tables', jsonb_build_array('public.users') ) AS traversal ) SELECT graph.aggregate( traversal, '{ "sum":[{"table":"public.users","column":"age","as":"total_age"}], "avg":[{"table":"public.users","column":"age","as":"avg_age"}], "count":[{"table":"public.users","column":"id","as":"user_count"}] }'::jsonb ) FROM req; ``` Supported aggregation groups: | Group | Meaning | |---|---| | `sum` | Sum numeric column values | | `avg` | Average numeric column values | | `count` | Count non-null column values | Each spec object contains: | Key | Meaning | |---|---| | `table` | Source table regclass text | | `column` | Source column to read | | `as` | Output JSON key | ## Aggregation Scope | Scope | Behavior | |---|---| | `returned_nodes` | Aggregate over the returned traversal node set | | `chosen_parent_path` | Aggregate over the selected parent path to each result | | `all_possible_paths` | Enumerate all possible paths under the traversal spec until `path_limit` | Use `graph.path_count_estimate()` before `all_possible_paths` on broad graphs. `all_possible_paths` can grow exponentially. The code enforces `graph.max_exact_path_count` and the explicit `path_limit` to avoid unbounded path enumeration.