Selecto.Builder.Sql.Hierarchy (Selecto v0.3.8)
Hierarchical SQL pattern generation for self-referencing relationships.
Supports adjacency lists, materialized paths, and closure table patterns using recursive CTEs and specialized SQL constructs.
The module exposes concrete builders for each hierarchy strategy and a deterministic fallback join for unrecognized patterns.
Link to this section Summary
Functions
Build recursive CTE for adjacency list pattern.
Build closure table join patterns.
Build hierarchical join with appropriate CTE pattern.
Build materialized path query patterns.
Example of hierarchical join integration using Selecto-powered CTEs.
Link to this section Functions
build_adjacency_list_cte(selecto, join, config)
Build recursive CTE for adjacency list pattern.
Generates a recursive CTE that traverses parent-child relationships to build hierarchical paths and levels. Uses Selecto-powered CTE generation for safety.
examples
Examples
# Build CTE for category hierarchy
{hierarchy_cte, params} = build_adjacency_list_cte(selecto, :categories, config)
# Generates CTE like:
# WITH RECURSIVE category_hierarchy AS (
# -- Base case: root nodes
# SELECT id, name, parent_id, 0 as level, CAST(id AS TEXT) as path
# FROM categories WHERE parent_id IS NULL
# UNION ALL
# -- Recursive case: child nodes
# SELECT c.id, c.name, c.parent_id, h.level + 1, h.path || '/' || c.id
# FROM categories c JOIN category_hierarchy h ON c.parent_id = h.id
# WHERE h.level < 5
# )Returns: {cte_iodata, params}
build_closure_table_query(selecto, join, config)
Build closure table join patterns.
Generates queries for closure table pattern with ancestor-descendant relationships. Closure tables maintain all ancestor-descendant relationships explicitly.
example
Example
# For a closure table with ancestor_id, descendant_id, depth columns
config = %{
source: "categories",
closure_table: "category_closure",
ancestor_field: "ancestor_id",
descendant_field: "descendant_id",
depth_field: "depth"
}Returns: {query_iodata, params}
build_hierarchy_join_with_cte(selecto, join, config, pattern, fc, p, ctes)
Build hierarchical join with appropriate CTE pattern.
Returns: {from_clause_iodata, params, ctes}.
build_materialized_path_query(selecto, join, config)
Build materialized path query patterns.
Generates SQL for path-based hierarchy queries using LIKE patterns and depth calculations.
example
Example
# For a path like "root/electronics/computers"
config = %{
source: "categories",
path_field: "path",
path_separator: "/",
root_path: "root"
}
# Generates SQL that calculates depth and filters by path patternsReturns: {query_iodata, params}
example_selecto_hierarchy_usage()
Example of hierarchical join integration using Selecto-powered CTEs.
This demonstrates the integration pattern for teams wiring these CTE helpers into higher-level query flows.
# Instead of raw SQL generation, we'll use Selecto queries:
def build_adjacency_cte_with_selecto(selecto, join, config) do
domain = build_hierarchy_domain(selecto, join, config)
connection = selecto.postgrex_opts
# Base case using Selecto
base_case = Selecto.configure(domain, connection)
|> Selecto.select([
"id",
"name",
"parent_id",
{:literal, 0, "level"},
{:literal, "id", "path"}
])
|> Selecto.filter([{"parent_id", nil}])
# Recursive case using Selecto
recursive_case = Selecto.configure(domain, connection)
|> Selecto.select([
"c.id",
"c.name",
"c.parent_id",
"h.level + 1",
{:func, "concat", ["h.path", {:literal, "/"}, "c.id"]}
])
|> Selecto.filter([{"h.level", {:lt, 5}}])
# Special handling for CTE JOIN would be added here
# Use Selecto-powered CTE generation
Selecto.Builder.Cte.build_recursive_cte_from_selecto(
hierarchy_name(join), base_case, recursive_case
)
end