# Schema Registration The extension builds its graph from explicit registration metadata stored in extension catalog tables. Register node tables, edge relationships, and optional filter columns before calling `graph.build()`. ## Registration Flow ## Register Node Tables ```sql SELECT graph.add_table( table_name := 'public.users'::regclass, id_column := 'id', columns := ARRAY['name', 'email', 'status'], tenant_column := NULL ); ``` The `id_column` must match a primary key or a unique `NOT NULL` index. The code validates this in `catalog/validate.rs` before writing the registration catalog. For composite identifiers, use the overload with `id_columns`: ```sql SELECT graph.add_table( table_name := 'public.memberships'::regclass, id_columns := ARRAY['account_id', 'user_id'], columns := ARRAY['role', 'state'] ); ``` Composite IDs are encoded as JSON array text during build. For example, `account_id = 10, user_id = 55` becomes a primary-key string similar to `["10","55"]`. ### Arguments | Argument | Type | Required | Meaning | |---|---|---:|---| | `table_name` | `REGCLASS` | yes | Source table to index as graph nodes | | `id_column` | `TEXT` | yes for scalar overload | Primary key or unique not-null column | | `id_columns` | `TEXT[]` | yes for composite overload | Ordered primary key or unique not-null columns | | `columns` | `TEXT[]` | no | Source columns available to `graph.search()` | | `tenant_column` | `TEXT` | no | Per-row tenant value used for tenant-scoped traversal and search | Search only considers columns registered in `columns`. Traversal filter pushdown uses `graph.add_filter_column()` separately. ## Register FK-Style Edges FK-style registration reads source rows from `from_table`, takes the row primary key as the source node, and uses `from_column` as the target primary-key value. ```sql SELECT graph.add_edge( from_table := 'public.orders'::regclass, from_column := 'customer_id', to_table := 'public.customers'::regclass, to_column := 'id', label := 'placed_by', bidirectional := true ); ``` This models: ```text orders.id --placed_by--> customers.id source PK target PK from orders.customer_id ``` ## Register Edge-Table Relationships Edge-table style registration is used when one table contains relationship rows. The source and target values both come from the edge table. ```sql SELECT graph.add_edge( from_table := 'public.relationships'::regclass, from_column := 'node_id_start', to_table := 'public.entities'::regclass, to_column := 'node_id_end', label := 'related_to', bidirectional := false ); ``` The builder first tries table-specific endpoint resolution. If needed, it falls back to resolving the endpoint primary-key value across registered tables, ordered by table OID. Prefer unambiguous primary keys across graph tables. ## Dynamic Edge Labels When `label_column` is set, each edge row may provide its own label. Empty or NULL values fall back to the static `label`. ```sql SELECT graph.add_edge( from_table := 'public.weighted_edges'::regclass, from_column := 'src', to_table := 'public.nodes'::regclass, to_column := 'dst', label := 'route', bidirectional := false, label_column := 'rel_type' ); ``` Edge labels are assigned compact `u8` IDs. The current maximum is 254 distinct user-facing labels; exceeding it raises `PG004`. ## Weighted Edges Register `weight_column` to enable `graph.weighted_shortest_path()`: ```sql SELECT graph.add_edge( from_table := 'public.routes'::regclass, from_column := 'src', to_table := 'public.places'::regclass, to_column := 'dst', label := 'route', bidirectional := false, weight_column := 'cost' ); ``` The weight column must be numeric. Build clamps loaded weights into `u32`; NULL or missing weights default to `1` when a weighted store is built. ## Register Filter Columns Filter columns are separate from search columns. They are encoded into `FilterIndex` so traversal can evaluate predicates without joining back to SQL for every candidate neighbor. ```sql SELECT graph.add_filter_column( table_name := 'public.users'::regclass, column_name := 'age', column_type := 'numeric' ); SELECT graph.add_filter_column( table_name := 'public.users'::regclass, column_name := 'state', column_type := 'text' ); ``` Supported `column_type` values: | Type | Encoding | |---|---| | `numeric` | Signed integer domain | | `boolean` | Boolean domain with sparse bitmap support | | `text` | Interned text dictionary token | | `date` | Days from `DATE '2000-01-01'` | | `timestamptz` | Microseconds from Unix epoch | | `uuid` | Canonical 128-bit UUID integer | ## Inspect Registration ```sql SELECT * FROM graph.registered_tables(); SELECT * FROM graph.registered_edges(); ``` ## Remove Registration ```sql SELECT graph.remove_edge('placed_by'); SELECT graph.remove_table('public.orders'::regclass); ``` Removing a table also removes associated filter columns and edges where that table is either endpoint. Rebuild after removal: ```sql SELECT * FROM graph.build(); ``` ## Auto-Discovery `graph.auto_discover(schema_name)` scans tables and foreign keys in one schema, registers discovered tables and FK edges, then calls `graph.build()`. ```sql SELECT * FROM graph.auto_discover('public'); ``` `graph.auto_discover_tables()` limits discovery to selected tables and FK edges between only those tables: ```sql SELECT * FROM graph.auto_discover_tables( ARRAY[ 'public.users'::regclass, 'public.orders'::regclass ], tenant_column := 'account_id' ); ``` Use auto-discovery for quick starts and formal FK schemas. Use manual registration when relationship tables, dynamic labels, weights, or custom tenant behavior matter. Auto-discovery registers bounded string-like columns as searchable properties. This keeps quick-start search predictable for common names, codes, labels, and small text attributes while avoiding unexpectedly broad graph metadata from large text, numeric, JSON, or timestamp columns. Use manual registration when the searchable surface needs to include non-text properties or a narrower set of text columns: ```sql SELECT graph.add_table( table_name := 'public.users'::regclass, key := ARRAY['id'], columns := ARRAY['name', 'email', 'status'] ); ``` Traversal filters are registered separately from searchable properties: ```sql SELECT graph.add_filter_column( table_name := 'public.users'::regclass, column_name := 'created_at', column_type := 'timestamptz' ); ```