@@ -275,12 +275,31 @@ static TSNode resolve_ocaml_func_name(TSNode node) {
275275 return null_node ;
276276}
277277
278- // SQL: resolve create_function name from object_reference→identifier or direct identifier.
278+ // Last identifier (DFS pre-order) under `node`. For a schema-qualified
279+ // object_reference (schema.table) this is the table name; the schema prefix is
280+ // ignored. Leaves *found false and returns `best` unchanged if none is present.
281+ static TSNode sql_last_identifier (TSNode node , TSNode best , bool * found ) {
282+ if (strcmp (ts_node_type (node ), "identifier" ) == 0 ) {
283+ best = node ;
284+ * found = true;
285+ }
286+ uint32_t cc = ts_node_child_count (node );
287+ for (uint32_t i = 0 ; i < cc ; i ++ ) {
288+ best = sql_last_identifier (ts_node_child (node , i ), best , found );
289+ }
290+ return best ;
291+ }
292+
293+ // SQL: resolve create_function / create_table / create_view name. The name sits
294+ // on an object_reference; for a schema-qualified name (schema.table) take the
295+ // last identifier (the table), not the first (the schema).
279296static TSNode resolve_sql_func_name (TSNode node ) {
280297 TSNode obj_ref = cbm_find_child_by_kind (node , "object_reference" );
281298 if (!ts_node_is_null (obj_ref )) {
282- TSNode id = cbm_find_child_by_kind (obj_ref , "identifier" );
283- if (!ts_node_is_null (id )) {
299+ bool found = false;
300+ TSNode empty = {0 };
301+ TSNode id = sql_last_identifier (obj_ref , empty , & found );
302+ if (found ) {
284303 return id ;
285304 }
286305 }
@@ -4024,13 +4043,75 @@ static bool extract_config_class_def(CBMExtractCtx *ctx, TSNode node, const char
40244043 return true;
40254044}
40264045
4046+ // Collect FROM/JOIN table references (tree-sitter-sql `relation` nodes) anywhere
4047+ // under `node` and emit them as usages scoped to enclosing_qn. pass_usages then
4048+ // resolves each ref_name to the referenced Table/View def and creates a USAGE
4049+ // lineage edge (e.g. a view -> the tables it selects from). Emitting them here
4050+ // (rather than via the generic identifier walker) sets the correct enclosing
4051+ // scope and bypasses the is_definition_name suppression that drops them.
4052+ static void collect_sql_relation_usages (CBMExtractCtx * ctx , TSNode node , const char * enclosing_qn ) {
4053+ if (strcmp (ts_node_type (node ), "relation" ) == 0 ) {
4054+ TSNode nm = resolve_sql_func_name (node ); // object_reference -> identifier
4055+ if (!ts_node_is_null (nm )) {
4056+ char * tname = cbm_node_text (ctx -> arena , nm , ctx -> source );
4057+ if (tname && tname [0 ]) {
4058+ CBMUsage usage = {0 };
4059+ usage .ref_name = tname ;
4060+ usage .enclosing_func_qn = enclosing_qn ;
4061+ cbm_usages_push (& ctx -> result -> usages , ctx -> arena , usage );
4062+ }
4063+ }
4064+ }
4065+ uint32_t n = ts_node_child_count (node );
4066+ for (uint32_t i = 0 ; i < n ; i ++ ) {
4067+ collect_sql_relation_usages (ctx , ts_node_child (node , i ), enclosing_qn );
4068+ }
4069+ }
4070+
4071+ // Handle SQL DDL relation defs: CREATE TABLE / VIEW / MATERIALIZED VIEW become
4072+ // first-class Table/View nodes rather than generic Variable nodes. The relation
4073+ // name sits on an object_reference child (the same shape create_function uses),
4074+ // so resolve_sql_func_name locates it. Also emits FROM/JOIN dependencies as
4075+ // usages so lineage edges form. Returns true if handled.
4076+ static bool extract_sql_ddl_class_def (CBMExtractCtx * ctx , TSNode node , const char * kind ) {
4077+ if (ctx -> language != CBM_LANG_SQL ) {
4078+ return false;
4079+ }
4080+ const char * label ;
4081+ if (strcmp (kind , "create_table" ) == 0 ) {
4082+ label = "Table" ;
4083+ } else if (strcmp (kind , "create_view" ) == 0 || strcmp (kind , "create_materialized_view" ) == 0 ) {
4084+ label = "View" ;
4085+ } else {
4086+ return false;
4087+ }
4088+ TSNode name_node = resolve_sql_func_name (node );
4089+ if (ts_node_is_null (name_node )) {
4090+ return false;
4091+ }
4092+ char * name = cbm_node_text (ctx -> arena , name_node , ctx -> source );
4093+ if (!name || !name [0 ]) {
4094+ return false;
4095+ }
4096+ push_simple_class_def (ctx , node , name , label );
4097+ // Must match push_simple_class_def's QN exactly (qn_safe_segment included)
4098+ // or pass_usages cannot find the enclosing def for the lineage source.
4099+ const char * qn =
4100+ cbm_fqn_compute (ctx -> arena , ctx -> project , ctx -> rel_path , qn_safe_segment (ctx -> arena , name ));
4101+ collect_sql_relation_usages (ctx , node , qn );
4102+ return true;
4103+ }
4104+
40274105static void extract_class_def (CBMExtractCtx * ctx , TSNode node , const CBMLangSpec * spec ) {
40284106 CBMArena * a = ctx -> arena ;
40294107 const char * kind = ts_node_type (node );
40304108
40314109 if (extract_config_class_def (ctx , node , kind )) {
40324110 return ;
40334111 }
4112+ if (extract_sql_ddl_class_def (ctx , node , kind )) {
4113+ return ;
4114+ }
40344115
40354116 TSNode name_node = ts_node_child_by_field_name (node , TS_FIELD ("name" ));
40364117 // ObjC: class name is first identifier child
0 commit comments