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
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:
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
);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:
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:
SELECT graph.node_ref_string('public.users'::regclass, 'u1');graph.format_path() converts a traversal path and edge_path pair to text:
SELECT graph.format_path(
'[{"table":"users","id":"u1"},{"table":"users","id":"u2"}]'::jsonb,
'["friend"]'::jsonb
);Result:
users:u1 --friend--> users:u2Path 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:
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.
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.