Files
codebase-memory-mcp/src/foundation/constants.h
T
Martin Vogelandalexisperinger-ux 43ecc0988c feat(dbt): extract dbt model lineage from Jinja-templated SQL
A dbt model is an ordinary .sql file whose dependencies are written
{{ ref('other_model') }} or {{ source('group','table') }}, never as
literal table names. The SQL grammar cannot read those -- FROM {{ ref(
'x') }} is a parse error to it -- so the dependency structure of an
entire dbt project was invisible to the graph.

extract_dbt.c runs as a sub-extractor inside the normal indexing
pipeline. Per qualifying file it emits one Model definition (named by
the file stem, dbt's own model identity) plus one usage per ref()/
source() call; pass_usages resolves those into model -> relation
lineage edges like any other reference.

Model is a relation label alongside Table and View, which is what makes
the rest work without new plumbing: registry seeding, the central
relation veto, the incremental surface hash, search ranking and the
architecture queries all pick it up from cbm_label_is_relation. Sharing
one label class also means a model's source('raw','customers') resolves
onto a Table declared in a plain DDL migration in the same repository,
so dbt lineage and SQL lineage form one graph rather than two, while
the veto keeps model names out of every non-lineage consumer.

The pass is self-gating: SQL files only, and only those carrying a real
ref()/source() call. The dbt builtins are themselves the evidence, which
is cheaper than a dbt_project.yml lookup and more precise -- templated
SQL that is not dbt (an Airflow {{ ds }} parameter) produces no Model
node and no usages even inside a dbt repository.

{% macro %} definitions are deliberately excluded: the vendored
tree-sitter-jinja2 grammar has no node types for {% %} statements, so
they can only be recovered by a hand-written scanner that cannot see
comments or find {% endmacro %} for a correct span. Filed as follow-up
rather than approximated.

Tests cover the lineage, the last-string-argument semantics of both
builtins, the gate (against a plain-SQL control extraction), plain DDL
staying untouched, and an end-to-end pipeline case asserting model ->
model across files, model -> Table onto plain DDL, and cross-language
isolation. Disabling the pass reddens three of them; removing Model from
the relation set breaks lineage outright.

Implements the lineage half of #575.

Co-authored-by: alexisperinger-ux <alexis.peringer@iss-stoxx.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-20 10:23:48 +02:00

116 lines
5.0 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* constants.h — Project-wide named constants.
*
* Eliminates magic numbers flagged by readability-magic-numbers.
* Every literal integer/float in source should reference a named constant.
*/
#ifndef CBM_CONSTANTS_H
#define CBM_CONSTANTS_H
/* ── Allocation counts ───────────────────────────────────────── */
enum { CBM_ALLOC_ONE = 1 }; /* calloc(CBM_ALLOC_ONE, sizeof(T)) */
/* ── Byte / character constants ──────────────────────────────── */
enum {
CBM_BYTE_RANGE = 256, /* full byte range 0x00–0xFF */
CBM_QUOTE_PAIR = 2, /* two quote characters (open + close) */
CBM_QUOTE_OFFSET = 1, /* skip opening quote */
};
/* ── Size units (powers of 2) ────────────────────────────────── */
enum {
CBM_SZ_2 = 2,
CBM_SZ_3 = 3,
CBM_SZ_4 = 4,
CBM_SZ_5 = 5,
CBM_SZ_6 = 6,
CBM_SZ_7 = 7,
CBM_SZ_8 = 8,
CBM_SZ_16 = 16,
CBM_SZ_32 = 32,
CBM_SZ_64 = 64,
CBM_SZ_128 = 128,
CBM_SZ_256 = 256,
CBM_SZ_512 = 512,
CBM_SZ_1K = 1024,
CBM_SZ_2K = 2048,
CBM_SZ_4K = 4096,
CBM_SZ_8K = 8192,
CBM_SZ_16K = 16384,
CBM_SZ_32K = 32768,
CBM_SZ_64K = 65536,
};
/* ── Numeric bases and common factors ────────────────────────── */
enum {
CBM_DECIMAL_BASE = 10,
CBM_HEX_BASE = 16,
CBM_PERCENT = 100,
};
/* ── Tree-sitter field name helper ───────────────────────────── */
/* Usage: ts_node_child_by_field_name(node, TS_FIELD("callee"))
* Expands to: ts_node_child_by_field_name(node, TS_FIELD("callee"))
* The sizeof includes the NUL terminator, so subtract 1. */
#define TS_FIELD(name) (name), (uint32_t)(sizeof(name) - SKIP_ONE)
/* ── Tree-sitter line offset ─────────────────────────────────── */
/* ts_node row is 0-based; source lines are 1-based. */
enum { TS_LINE_OFFSET = 1 };
/* Common offset constants. */
/* Common offset constants. */
/* ── Sentinel values ─────────────────────────────────────────── */
enum {
CBM_NOT_FOUND = -1, /* search miss, invalid index */
CBM_INIT_DONE = 1, /* initialization flag */
};
/* ── Default pagination limits ───────────────────────────────── */
/* Default page size for search_graph and the underlying store-layer search.
* Responses land in an LLM agent's context window, so the default favors a
* cheap first page (~50 TOON rows ≈ 1.5K tokens) over raw coverage; the
* response always carries 'total' and 'has_more', and agents page via
* offset+limit or narrow with label/file_pattern when has_more is true. */
enum { CBM_DEFAULT_SEARCH_LIMIT = 50 };
/* ── Time conversion factors ─────────────────────────────────── */
#define CBM_NSEC_PER_SEC 1000000000ULL
#define CBM_USEC_PER_SEC 1000000ULL
#define CBM_MSEC_PER_SEC 1000ULL
#define CBM_NSEC_PER_USEC 1000ULL
#define CBM_NSEC_PER_MSEC 1000000ULL
/* ── Common string/buffer sizes ──────────────────────────────── */
enum {
CBM_SMALL_BUF = 3, /* small scratch buffers */
CBM_NAME_BUF = 4, /* name buffer slots */
CBM_PATH_MAX = 1024, /* path buffer size */
CBM_LINE_BUF = 512, /* line read buffer */
};
/* Common offset constants (used across many files). */
enum { SKIP_ONE = 1, PAIR_LEN = 2 };
/* ── Label allowlists for SQL ────────────────────────────────────
* SQL mirror of cbm_label_is_type_like() (internal/cbm/helpers.c). That
* function is documented as the single source of truth for type-like labels
* "instead of scattering `|| strcmp(label,\"Struct\")==0` across the tree" —
* but a SQL string literal cannot call it, so several queries hardcoded
* ('Function','Method','Class') and silently stopped matching the moment
* Struct/Interface/Enum/Type/Trait began being emitted.
*
* Use these instead of inlining a label list. tests/test_store_nodes.c pins
* them against cbm_label_is_type_like(), so adding a type-like label there
* without updating these fails CI rather than quietly shrinking query results. */
#define CBM_SQL_TYPE_LIKE_LABELS "'Class','Struct','Interface','Enum','Type','Trait'"
#define CBM_SQL_CALLABLE_LABELS "'Function','Method'"
#define CBM_SQL_CALLABLE_OR_TYPE_LABELS CBM_SQL_CALLABLE_LABELS "," CBM_SQL_TYPE_LIKE_LABELS
/* SQL mirror of cbm_label_is_relation() (Table/View/Model — data-lineage
* nodes), pinned by tests/test_store_nodes.c the same way as the sets above. */
#define CBM_SQL_RELATION_LABELS "'Table','View','Model'"
#endif /* CBM_CONSTANTS_H */