UNPKG

flink-sql-language-server

Version:

A LSP-based language server for Apache Flink SQL

227 lines (226 loc) 8.88 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.LineageVisitor = void 0; const flinksql_relation_visitor_1 = require("./flinksql-relation.visitor"); class LineageVisitor extends flinksql_relation_visitor_1.FlinkSQLRelationVisitor { constructor(options, getTableMetadata) { super(getTableMetadata); this.options = options; this.getTableMetadata = getTableMetadata; this.relations = []; this.edges = []; this.edgeIdSeq = 1; this.queryIdSeq = 1; this.viewIdSeq = 1; } findColumnByEdgeIdentity(identity) { return this.relations .find(r => r.relation.id === identity.relationId) ?.relation.columns.find(c => c.id === identity.columnId); } defaultResult() { return; } getLineage() { const cleanedNodes = []; const edges = [...this.edges]; if (this.options.mergeLeaves) { const deduplicateTable = new Map(); const usedRelations = new Map(); this.relations.forEach(r => { if (r.relationPrimary === undefined) { cleanedNodes.push(r.relation); return; } const key = (0, flinksql_relation_visitor_1.relationPrimaryToString)(r.relationPrimary); const entry = usedRelations.get(key); if (entry !== undefined) { if (r.relation.type === 'view') { usedRelations.delete(key); usedRelations.set(key, r.relation.id); deduplicateTable.set(entry, r.relation.id); } else { deduplicateTable.set(r.relation.id, entry); } } else { usedRelations.set(key, r.relation.id); r.relation.label = r.relationPrimary.relationName; cleanedNodes.push(r.relation); } }); const usedColumns = new Map(); edges.forEach(e => { const remapEdge = (identity) => { const originalIdentity = { ...identity }; const remappedSourceTable = deduplicateTable.get(identity.relationId); if (remappedSourceTable !== undefined) { identity.relationId = remappedSourceTable; } if (identity.columnId !== undefined) { const column = this.findColumnByEdgeIdentity(originalIdentity); if (column !== undefined) { const columns = usedColumns.get(identity.relationId); if (columns !== undefined) { const sameColumn = columns.find(c => c.label === column.label); if (sameColumn !== undefined) { identity.columnId = sameColumn.id; return; } columns.push(column); } else { usedColumns.set(identity.relationId, [column]); } } } }; remapEdge(e.source); remapEdge(e.target); }); cleanedNodes.forEach(n => { if (n.data !== undefined) { const tableColumns = usedColumns.get(n.id); if (tableColumns !== undefined) { n.columns = [...new Set([...n.columns, ...tableColumns])]; } } }); } else { this.relations.forEach(t => cleanedNodes.push(t.relation)); } const sourceNodes = new Set(); const targetNodes = new Set(); const isStarOrCountStar = (label) => { return label === flinksql_relation_visitor_1.STAR_LABEL || label === flinksql_relation_visitor_1.COUNT_STAR_LABEL; }; edges.forEach(e => { sourceNodes.add(e.source.relationId); targetNodes.add(e.target.relationId); }); cleanedNodes.forEach(t => { if (!sourceNodes.has(t.id)) { t.isTargetOnly = true; } if (!targetNodes.has(t.id)) { t.isSourceOnly = true; } if (t.data !== undefined) { const ddlColumns = this.relationDDLColumns.get((0, flinksql_relation_visitor_1.relationPrimaryToString)(t.data)) ?? this.getTableMetadata?.(t.data)?.columns; if (!this.options.ignoreDDLs) { t.columns = t.columns.map(col => ({ ...col, inconsistent: !isStarOrCountStar(col.label) && !!ddlColumns && ddlColumns.every(d => d.label !== col.label) })); } } }); return { nodes: cleanedNodes, edges }; } aggregateResult(_aggregate, _nextResult) { return; } onColumnReference(source, target, currentClause) { this.edges.push({ id: `${this.edgeIdSeq++}`, type: 'edge', edgeType: currentClause ?? this.currentQueryRelation?.currentClause, source: { relationId: source.relationId, columnId: source.columnId }, target: { relationId: target.relationId, columnId: target.columnId } }); } onRelation(relation, alias) { const old = this.relations.findIndex(rel => rel.relation.id === relation.id); if (old !== -1) { this.relations.splice(old, 1); } const columns = relation.columns.map(c => { return { id: c.id, label: c.label, range: c.range, isAssumed: c.isAssumed }; }); if (relation instanceof flinksql_relation_visitor_1.TableRelation) { this.relations.push({ relationPrimary: relation.tablePrimary, relation: { type: 'table', id: relation.id, label: alias !== undefined && alias != relation.tablePrimary.relationName ? `${relation.tablePrimary.relationName} -> ${alias}` : relation.tablePrimary.relationName, range: relation.range, isSourceOnly: false, isTargetOnly: false, data: relation.tablePrimary, columns } }); } else if (relation instanceof flinksql_relation_visitor_1.QueryRelation) { let label = `query_${this.queryIdSeq++}`; if (alias !== undefined) { label += ` -> ${alias}`; } this.relations.push({ relation: { type: 'query', id: relation.id, label, isSourceOnly: false, isTargetOnly: false, range: relation.range, columns } }); } else if (relation instanceof flinksql_relation_visitor_1.InsertRelation) { this.relations.push({ relationPrimary: relation.sinkTableRelation?.tablePrimary, relation: { type: 'insert', id: relation.id, label: relation.sinkTableRelation?.tablePrimary.relationName || relation.id, range: relation.range, isSourceOnly: false, isTargetOnly: false, data: relation.tablePrimary, columns } }); } else { let label = `view_${this.viewIdSeq++}`; if (alias !== undefined) { label = alias; } this.relations.push({ relationPrimary: relation.tablePrimary, relation: { type: 'view', id: relation.id, label, range: relation.range, isSourceOnly: false, isTargetOnly: false, data: relation.tablePrimary, columns } }); } } } exports.LineageVisitor = LineageVisitor;