Skip to content

Commit 6d1b255

Browse files
committed
feat: implement lineage methods in dbtIntegrationAdapter
1 parent 8b4477b commit 6d1b255

File tree

2 files changed

+166
-202
lines changed

2 files changed

+166
-202
lines changed

src/manifest/dbtIntegrationAdapter.ts

Lines changed: 163 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import {
1212
DBTCommandFactory,
1313
DBTCommand,
1414
RUN_RESULTS_FILE,
15+
RESOURCE_TYPE_MODEL,
16+
RESOURCE_TYPE_SOURCE,
1517
} from "../dbt_client/dbtIntegration";
1618
import { ProjectHealthcheck } from "../dbt_client/dbtCoreIntegration";
1719
import {
@@ -89,6 +91,7 @@ export class DBTIntegrationAdapter extends EventEmitter implements DBTFacade {
8991
private targetWatchers: FSWatcher[] = [];
9092
private currentTargetPath?: string;
9193
private isWatchingTargetFiles = false;
94+
private lastParsedManifest?: ParsedManifest;
9295

9396
constructor(
9497
private dbtConfiguration: DBTConfiguration,
@@ -497,7 +500,7 @@ export class DBTIntegrationAdapter extends EventEmitter implements DBTFacade {
497500
metricMetaMap,
498501
);
499502

500-
return {
503+
const parsedManifest = {
501504
nodeMetaMap,
502505
macroMetaMap,
503506
metricMetaMap,
@@ -508,6 +511,11 @@ export class DBTIntegrationAdapter extends EventEmitter implements DBTFacade {
508511
exposureMetaMap,
509512
modelDepthMap,
510513
};
514+
515+
// Store reference to last parsed manifest
516+
this.lastParsedManifest = parsedManifest;
517+
518+
return parsedManifest;
511519
}
512520

513521
private readAndParseManifestFile(targetPath: string): any {
@@ -1022,25 +1030,164 @@ export class DBTIntegrationAdapter extends EventEmitter implements DBTFacade {
10221030
return this.currentIntegration.getCatalog();
10231031
}
10241032

1025-
// Lineage and Relationships - these methods require access to manifest
1026-
// Since the adapter doesn't have direct manifest access, we'd need to implement
1027-
// these by parsing the manifest file or delegating to a manifest service
1028-
getNonEphemeralParents(_keys: string[]): string[] {
1029-
// This would require manifest parsing logic
1030-
// For now, return empty array
1031-
return [];
1033+
// Lineage and Relationships
1034+
getNonEphemeralParents(keys: string[]): string[] {
1035+
if (!this.lastParsedManifest) {
1036+
throw Error(
1037+
"No manifest has been generated. Maybe dbt project has not been parsed yet?",
1038+
);
1039+
}
1040+
const { nodeMetaMap, graphMetaMap } = this.lastParsedManifest;
1041+
const { parents } = graphMetaMap;
1042+
const parentSet = new Set<string>();
1043+
const queue = keys;
1044+
const visited: Record<string, boolean> = {};
1045+
while (queue.length > 0) {
1046+
const curr = queue.shift()!;
1047+
if (visited[curr]) {
1048+
continue;
1049+
}
1050+
visited[curr] = true;
1051+
const parent = parents.get(curr);
1052+
if (!parent) {
1053+
continue;
1054+
}
1055+
for (const n of parent.nodes) {
1056+
const splits = n.key.split(".");
1057+
const resource_type = splits[0];
1058+
if (resource_type !== RESOURCE_TYPE_MODEL) {
1059+
parentSet.add(n.key);
1060+
continue;
1061+
}
1062+
if (
1063+
nodeMetaMap.lookupByUniqueId(n.key)?.config.materialized ===
1064+
"ephemeral"
1065+
) {
1066+
queue.push(n.key);
1067+
} else {
1068+
parentSet.add(n.key);
1069+
}
1070+
}
1071+
}
1072+
return Array.from(parentSet);
10321073
}
10331074

1034-
getChildrenModels({ table: _table }: { table: string }): Table[] {
1035-
// This would require manifest parsing logic
1036-
// For now, return empty array
1037-
return [];
1075+
getChildrenModels({ table }: { table: string }): Table[] {
1076+
return this.getConnectedTables("children", table);
1077+
}
1078+
1079+
getParentModels({ table }: { table: string }): Table[] {
1080+
return this.getConnectedTables("parents", table);
1081+
}
1082+
1083+
private getConnectedTables(key: keyof GraphMetaMap, table: string): Table[] {
1084+
if (!this.lastParsedManifest) {
1085+
throw Error(
1086+
"No manifest has been generated. Maybe dbt project has not been parsed yet?",
1087+
);
1088+
}
1089+
const { graphMetaMap, nodeMetaMap } = this.lastParsedManifest;
1090+
const node = nodeMetaMap.lookupByBaseName(table);
1091+
if (!node) {
1092+
throw Error("nodeMetaMap has no entries for " + table);
1093+
}
1094+
const dependencyNodes = graphMetaMap[key];
1095+
const dependencyNode = dependencyNodes.get(node.uniqueId);
1096+
if (!dependencyNode) {
1097+
throw Error("graphMetaMap[" + key + "] has no entries for " + table);
1098+
}
1099+
const tables: Map<string, Table> = new Map();
1100+
dependencyNode.nodes.forEach(({ url, key }) => {
1101+
const _node = this.createTable(url, key);
1102+
if (!_node) {
1103+
return;
1104+
}
1105+
if (!tables.has(_node.table)) {
1106+
tables.set(_node.table, _node);
1107+
}
1108+
});
1109+
return Array.from(tables.values()).sort((a, b) =>
1110+
a.table.localeCompare(b.table),
1111+
);
1112+
}
1113+
1114+
private createTable(
1115+
tableUrl: string | undefined,
1116+
key: string,
1117+
): Table | undefined {
1118+
if (!this.lastParsedManifest) {
1119+
throw new Error("The dbt manifest is not available");
1120+
}
1121+
const splits = key.split(".");
1122+
const nodeType = splits[0];
1123+
const { graphMetaMap, testMetaMap } = this.lastParsedManifest;
1124+
const upstreamCount = this.getConnectedNodeCount(
1125+
graphMetaMap["children"],
1126+
key,
1127+
);
1128+
const downstreamCount = this.getConnectedNodeCount(
1129+
graphMetaMap["parents"],
1130+
key,
1131+
);
1132+
if (nodeType === RESOURCE_TYPE_SOURCE) {
1133+
const { sourceMetaMap } = this.lastParsedManifest;
1134+
const schema = splits[2];
1135+
const table = splits[3];
1136+
const _node = sourceMetaMap.get(schema);
1137+
if (!_node) {
1138+
return;
1139+
}
1140+
const _table = _node.tables.find((t) => t.name === table);
1141+
if (!_table) {
1142+
return;
1143+
}
1144+
return {
1145+
table: key,
1146+
label: table,
1147+
url: tableUrl,
1148+
upstreamCount,
1149+
downstreamCount,
1150+
nodeType,
1151+
isExternalProject: _node.is_external_project,
1152+
tests: (graphMetaMap["tests"].get(key)?.nodes || []).map((n) => {
1153+
const testKey = n.label.split(".")[0];
1154+
return { ...testMetaMap.get(testKey), key: testKey };
1155+
}),
1156+
columns: _table.columns,
1157+
description: _table?.description,
1158+
};
1159+
} else {
1160+
const { nodeMetaMap } = this.lastParsedManifest;
1161+
const node = nodeMetaMap.lookupByUniqueId(key);
1162+
if (!node) {
1163+
return;
1164+
}
1165+
return {
1166+
table: key,
1167+
label: node.name,
1168+
url: tableUrl,
1169+
upstreamCount,
1170+
downstreamCount,
1171+
nodeType,
1172+
isExternalProject: node.package_name !== this.getProjectName(),
1173+
tests: (graphMetaMap["tests"].get(key)?.nodes || []).map((n) => {
1174+
const testKey = n.label.split(".")[0];
1175+
return { ...testMetaMap.get(testKey), key: testKey };
1176+
}),
1177+
columns: node.columns,
1178+
description: node?.description,
1179+
};
1180+
}
10381181
}
10391182

1040-
getParentModels({ table: _table }: { table: string }): Table[] {
1041-
// This would require manifest parsing logic
1042-
// For now, return empty array
1043-
return [];
1183+
private getConnectedNodeCount(
1184+
connectionGraph: Map<
1185+
string,
1186+
{ nodes: { key: string; label: string; url?: string }[] }
1187+
>,
1188+
key: string,
1189+
): number {
1190+
return connectionGraph.get(key)?.nodes.length || 0;
10441191
}
10451192

10461193
// Target File Watcher Operations

0 commit comments

Comments
 (0)