UNPKG

flink-sql-language-server

Version:

A LSP-based language server for Apache Flink SQL

1,001 lines (1,000 loc) 42.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.relationPrimaryToString = exports.RELATION_SEPARATOR = exports.FlinkSQLRelationVisitor = exports.ViewRelation = exports.InsertRelation = exports.QueryRelation = exports.TableRelation = exports.Relation = exports.Column = exports.COUNT_STAR_LABEL = exports.STAR_LABEL = void 0; const tree_1 = require("antlr4ts/tree"); const FlinkSQLParser_1 = require("../lib/FlinkSQLParser"); const protocol_translation_1 = require("../protocol-translation"); const utils_1 = require("../utils"); exports.STAR_LABEL = '*'; exports.COUNT_STAR_LABEL = 'COUNT(*)'; class Column { constructor(id, label, range, data, isAssumed) { this.id = id; this.label = label; this.range = range; this.data = data; this.isAssumed = isAssumed; this.columnReferences = []; } } exports.Column = Column; class Relation { constructor(id, columns, parent, range) { this.id = id; this.columns = columns; this.parent = parent; this.range = range; } findColumn(columnName) { return this.columns.find(c => c.label == columnName); } resolveColumn(columnName) { const col = this.findColumn(columnName); return col !== undefined ? { tableId: this.id, columnId: col.id, isAssumed: false } : undefined; } } exports.Relation = Relation; class TableRelation extends Relation { constructor(id, tablePrimary, columns, parent, range) { super(id, columns, parent, range); this.tablePrimary = tablePrimary; } getNextColumnId() { return `${this.id}_column_${this.columns.length + 1}`; } addColumn(columnName, range, isAssumed) { const oldColumn = this.columns.find(col => col.label === columnName); if (oldColumn) { return { column: oldColumn, ref: { tableId: this.id, columnId: oldColumn.id, isAssumed } }; } const column = new Column(this.getNextColumnId(), columnName, range, undefined, isAssumed); this.columns.push(column); return { column, ref: { tableId: this.id, columnId: column.id, isAssumed } }; } } exports.TableRelation = TableRelation; class QueryRelation extends Relation { constructor(id, parent, range) { super(id, [], parent, range); this.ctes = new Map(); this.relations = new Map(); this.columnIdSeq = 1; this.columnReferences = []; } getNextColumnId() { return `${this.id}_column_${this.columnIdSeq++}`; } findLocalRelation(tableName) { for (const rel of this.relations) { if (tableName.localeCompare(rel[0], undefined, { sensitivity: 'accent' }) == 0) return rel[1]; } return undefined; } findRelation(name) { const rel = this.findLocalRelation(name); if (rel) { return rel; } if (this.parent) { if (this.parent instanceof QueryRelation) { return this.parent.findRelation(name); } else if (this.parent instanceof ViewRelation) { if (this.parent.tablePrimary && relationPrimaryToString(this.parent.tablePrimary).localeCompare(name, undefined, { sensitivity: 'accent' }) == 0) { return this.parent; } } else if (this.parent instanceof InsertRelation) { if (this.parent.sinkTableRelation?.tablePrimary && relationPrimaryToString(this.parent.sinkTableRelation.tablePrimary).localeCompare(name, undefined, { sensitivity: 'accent' }) == 0) { return this.parent.sinkTableRelation; } } } return undefined; } findCTE(tableName) { for (const rel of this.ctes) { if (tableName.localeCompare(rel[0], undefined, { sensitivity: 'accent' }) == 0) return rel[1]; } return this.parent instanceof QueryRelation ? this.parent.findCTE(tableName) : undefined; } getCTENames() { const localCTEs = Array.from(this.ctes.keys()); if (this.parent !== undefined && this.parent instanceof QueryRelation) { return localCTEs.concat(this.parent.getCTENames()); } else { return localCTEs; } } resolveRelationColumnWithTableName(columnName, range, relation) { const col = relation.resolveColumn(columnName); if (col === undefined && relation instanceof TableRelation) { const columnObj = relation.addColumn(columnName, range, true); return columnObj.ref; } return col; } resolveRelationColumn(columnName, range) { const tables = []; for (const r of this.relations) { const rel = r[1]; const col = rel.resolveColumn(columnName); if (col) { return col; } if (rel instanceof TableRelation) { tables.push(rel); } } if (tables.length > 0) { const columnObj = tables[0].addColumn(columnName, range, true); return columnObj.ref; } return undefined; } } exports.QueryRelation = QueryRelation; class InsertRelation extends Relation { constructor(id, range, tablePrimary) { super(id, [], undefined, range); this.tablePrimary = tablePrimary; this.columnIdSeq = 1; this.columnsSpecified = false; } getNextColumnId() { return `${this.id}_column_${this.columnIdSeq++}`; } } exports.InsertRelation = InsertRelation; class ViewRelation extends Relation { constructor(id, tablePrimary, range) { super(id, [], undefined, range); this.tablePrimary = tablePrimary; this.columnIdSeq = 1; this.columnsSpecified = false; } getNextColumnId() { return `${this.id}_column_${this.columnIdSeq++}`; } } exports.ViewRelation = ViewRelation; class FlinkSQLRelationVisitor extends tree_1.AbstractParseTreeVisitor { constructor(getTable) { super(); this.getTable = getTable; this.relationInsertSeq = 1; this.relationViewSeq = 1; this.relationSeq = 1; this.relationDDLColumns = new Map(); this.tableDDLs = new Map(); this.viewDDLs = new Map(); } getNextInsertRelationId() { return `insert_${this.relationInsertSeq++}`; } getNextViewRelationId() { return `view_${this.relationViewSeq++}`; } getNextRelationId() { return `relation_${this.relationSeq++}`; } processClause(clause, ctx) { if (this.currentQueryRelation) { this.currentQueryRelation.currentClause = clause; const result = this.visitChildren(ctx); this.currentQueryRelation.currentClause = undefined; return result; } return this.visitChildren(ctx); } reportTableReferences() { for (const [alias, relation] of this.currentQueryRelation?.relations || []) { if (relation instanceof TableRelation || relation instanceof ViewRelation) { this.onRelation(relation, alias !== relation.id ? alias : undefined); } } } isStar(ctx) { const listener = new StarListener(); try { tree_1.ParseTreeWalker.DEFAULT.walk(listener, ctx); return listener.starContext; } catch (e) { return undefined; } } handleSubQueryRelation(rel) { if (this.currentQueryRelation) { this.currentQueryRelation.columns.forEach(c => { const matchCol = rel.findColumn(c.label); if (matchCol) { this.onColumnReference({ relationId: rel.id, columnId: matchCol.id }, { relationId: this.currentQueryRelation.id, columnId: c.id }); } }); } } addStarRelationColumn(rel, range, starCol) { if (this.currentQueryRelation) { let newStarCol = rel.columns.find(c => c.label === exports.STAR_LABEL); if (newStarCol === undefined) { newStarCol = new Column(rel.getNextColumnId(), exports.STAR_LABEL, range, undefined, true); rel.columns.unshift(newStarCol); } this.onColumnReference({ relationId: rel.id, columnId: newStarCol.id }, { relationId: this.currentQueryRelation.id, columnId: starCol.id }); } } mapColumnToRelation(col, relation, currentClause) { let newCol = relation.findColumn(col.label); if (newCol === undefined) { newCol = new Column(relation.getNextColumnId(), col.label, col.range, undefined, true); if (col.label === exports.STAR_LABEL || col.label === exports.COUNT_STAR_LABEL) { relation.columns.unshift(newCol); } else { relation.columns.push(newCol); } } this.onColumnReference({ relationId: this.currentQueryRelation.id, columnId: col.id }, { relationId: relation.id, columnId: newCol.id }, currentClause); } deriveColumnNameFromContext(ctx) { const listener = new ExpressionColumnListener(); try { tree_1.ParseTreeWalker.DEFAULT.walk(listener, ctx); return listener.columnNames; } catch (e) { return []; } } deriveColumnsFromDDL(ctx) { return ctx .tableElement() .map(t => { const columnDefinition = t.columnDefinition(); if (columnDefinition) { return { columnName: (0, utils_1.sanitizeText)(columnDefinition.columnName().text), range: (0, protocol_translation_1.rangeFromContext)(columnDefinition.columnName()) }; } return false; }) .filter(i => !!i); } relationPrimaryFromMultipart(names, type) { const len = names.length; if (len == 0) { throw new Error('Unexpected empty array for table name'); } else if (len == 1) { return { type, catalogName: this.catalogUsed, databaseName: this.databaseUsed, relationName: names[0] }; } else if (len == 2) { return { type, catalogName: this.catalogUsed, databaseName: names[0], relationName: names[1] }; } else { return { type, catalogName: names[len - 3], databaseName: names[len - 2], relationName: names[len - 1] }; } } visitStatement(ctx) { const result = this.visitChildren(ctx); this.currentQueryRelation = undefined; this.currentInsertRelation = undefined; this.currentViewRelation = undefined; return result; } visitUse(ctx) { if (ctx.CATALOG() && ctx.catalog()) { this.catalogUsed = (0, utils_1.sanitizeText)(ctx.catalog().text); } else if (ctx.database()) { const databaseName = ctx .database() .text.split('.') .map(t => (0, utils_1.sanitizeText)(t)); if (databaseName.length === 1) { this.catalogUsed = undefined; this.databaseUsed = databaseName[0]; } else if (databaseName.length === 2) { this.catalogUsed = databaseName[0]; this.databaseUsed = databaseName[1]; } } return this.defaultResult(); } visitCreateTable(ctx) { const tableName = ctx .table() .text.split('.') .map(t => (0, utils_1.sanitizeText)(t)); const tablePrimary = this.relationPrimaryFromMultipart(tableName, 'table'); const createdTable = new TableRelation(this.getNextRelationId(), tablePrimary, [], undefined, (0, protocol_translation_1.rangeFromContext)(ctx.table())); const createTableName = relationPrimaryToString(tablePrimary); if (ctx.defineValues()) { this.deriveColumnsFromDDL(ctx.defineValues()).forEach(col => { createdTable.addColumn(col.columnName, col.range, false); }); } if (ctx.likeTable()) { const likeTableName = ctx .likeTable() .table() .text.split('.') .map(t => (0, utils_1.sanitizeText)(t)); const likeTablePrimary = this.relationPrimaryFromMultipart(likeTableName, 'table'); let likeTable = this.tableDDLs.get(relationPrimaryToString(likeTablePrimary)); if (!likeTable) { const columns = (this.getTable?.(likeTablePrimary)?.columns ?? []).map(c => new Column(this.currentInsertRelation.getNextColumnId(), c.label, undefined, c, false)); likeTable = new TableRelation(this.getNextRelationId(), likeTablePrimary, columns, undefined, (0, protocol_translation_1.rangeFromContext)(ctx.likeTable().table())); } likeTable.columns.forEach(col => { const existed = createdTable.findColumn(col.label); if (!existed) { const newColumn = new Column(createdTable.getNextColumnId(), col.label, col.range, col.data, false); createdTable.columns.push(newColumn); this.onColumnReference({ relationId: likeTable.id, columnId: col.id }, { relationId: createdTable.id, columnId: newColumn.id }); } }); } else if (ctx.AS() && ctx.query()) { const result = this.visit(ctx.query()); if (this.currentQueryRelation) { if (this.currentQueryRelation.columns.length === 1 && this.currentQueryRelation.columns[0].label === exports.STAR_LABEL) { const starCol = this.currentQueryRelation.columns[0]; this.mapColumnToRelation(starCol, createdTable, 'create'); } else { this.currentQueryRelation.columns.forEach(c => { if (c.label !== exports.STAR_LABEL) { this.mapColumnToRelation(c, createdTable, 'create'); } }); } } this.relationDDLColumns.set(createTableName, [...createdTable.columns]); this.tableDDLs.set(createTableName, createdTable); this.onRelation(createdTable); return result; } this.relationDDLColumns.set(createTableName, [...createdTable.columns]); this.tableDDLs.set(createTableName, createdTable); this.onRelation(createdTable); return this.defaultResult(); } visitCreateView(ctx) { const viewName = ctx .view() .text.split('.') .map(t => (0, utils_1.sanitizeText)(t)); const viewPrimary = this.relationPrimaryFromMultipart(viewName, 'view'); viewPrimary.range = (0, protocol_translation_1.rangeFromContext)(ctx.view()); this.currentViewRelation = new ViewRelation(this.getNextViewRelationId(), viewPrimary, viewPrimary.range); this.currentViewRelation.columnsSpecified = ctx.columns() !== undefined; const result = this.visitChildren(ctx); if (ctx.columns()) { ctx .columns() .columnName() .forEach(c => { const viewCol = new Column(this.currentViewRelation.getNextColumnId(), (0, utils_1.sanitizeText)(c.text), (0, protocol_translation_1.rangeFromContext)(c), undefined, false); this.currentViewRelation.columns.push(viewCol); const queryCol = this.currentQueryRelation?.findColumn(viewCol.label); if (queryCol) { this.onColumnReference({ relationId: this.currentQueryRelation.id, columnId: queryCol.id }, { relationId: this.currentViewRelation.id, columnId: viewCol.id }, 'view'); } }); } else { if (this.currentQueryRelation) { if (this.currentQueryRelation.columns.length === 1 && this.currentQueryRelation.columns[0].label === exports.STAR_LABEL) { const starCol = this.currentQueryRelation.columns[0]; this.mapColumnToRelation(starCol, this.currentViewRelation, 'view'); } else { this.currentQueryRelation.columns.forEach(c => { if (c.label !== exports.STAR_LABEL) { this.mapColumnToRelation(c, this.currentViewRelation, 'view'); } }); } } } this.onRelation(this.currentViewRelation, viewPrimary.relationName); const viewPrimaryName = relationPrimaryToString(viewPrimary); this.viewDDLs.set(viewPrimaryName, this.currentViewRelation); this.relationDDLColumns.set(viewPrimaryName, [...this.currentViewRelation.columns]); return result; } visitInsert(ctx) { const tableName = ctx .table() .text.split('.') .map(t => (0, utils_1.sanitizeText)(t)); const tablePrimary = this.relationPrimaryFromMultipart(tableName, 'table'); tablePrimary.range = (0, protocol_translation_1.rangeFromContext)(ctx.table()); this.currentInsertRelation = new InsertRelation(this.getNextInsertRelationId(), tablePrimary.range, tablePrimary); const primaryName = relationPrimaryToString(tablePrimary); const tableDDL = this.tableDDLs.get(primaryName); const viewDDL = this.viewDDLs.get(primaryName); if (tableDDL !== undefined) { this.currentInsertRelation.sinkTableRelation = tableDDL; } else if (viewDDL !== undefined) { this.currentInsertRelation.sinkTableRelation = viewDDL; } else { const columns = (this.getTable?.(tablePrimary)?.columns ?? []).map(c => new Column(this.currentInsertRelation.getNextColumnId(), c.label, undefined, c, false)); this.currentInsertRelation.sinkTableRelation = new TableRelation(this.getNextRelationId(), tablePrimary, columns, undefined, tablePrimary.range); } this.currentInsertRelation.columnsSpecified = ctx.columns() !== undefined; const result = this.visitChildren(ctx); if (ctx.columns()) { ctx .columns() .columnName() .forEach(c => { const label = (0, utils_1.sanitizeText)(c.text); let sinkTableCol = this.currentInsertRelation.sinkTableRelation.findColumn(label); if (sinkTableCol) { sinkTableCol.range = (0, protocol_translation_1.rangeFromContext)(c); } else { sinkTableCol = new Column(this.currentInsertRelation.getNextColumnId(), (0, utils_1.sanitizeText)(c.text), (0, protocol_translation_1.rangeFromContext)(c), undefined, false); this.currentInsertRelation.sinkTableRelation.columns.push(sinkTableCol); } const queryCol = this.currentQueryRelation?.findColumn(label); if (queryCol) { this.onColumnReference({ relationId: this.currentQueryRelation.id, columnId: queryCol.id }, { relationId: this.currentInsertRelation.sinkTableRelation.id, columnId: sinkTableCol.id }, 'insert'); } }); } else { if (this.currentQueryRelation) { if (this.currentQueryRelation.columns.length === 1 && this.currentQueryRelation.columns[0].label === exports.STAR_LABEL) { const starCol = this.currentQueryRelation.columns[0]; this.mapColumnToRelation(starCol, this.currentInsertRelation.sinkTableRelation, 'insert'); } else { this.currentQueryRelation.columns.forEach(c => { if (c.label !== exports.STAR_LABEL) { this.mapColumnToRelation(c, this.currentInsertRelation.sinkTableRelation, 'insert'); } }); } } } this.onRelation(this.currentInsertRelation.sinkTableRelation, tablePrimary.relationName); return result; } visitQuery(ctx) { this.currentQueryRelation = new QueryRelation(this.getNextRelationId(), this.currentQueryRelation, (0, protocol_translation_1.rangeFromContext)(ctx)); const result = this.visitChildren(ctx); this.reportTableReferences(); this.lastQueryRelation = this.currentQueryRelation; if (this.currentQueryRelation.parent === undefined) { this.onRelation(this.currentQueryRelation); } else if (this.currentQueryRelation.parent instanceof QueryRelation) { this.currentQueryRelation = this.currentQueryRelation.parent; } return result; } visitQueryPrimary(ctx) { if (this.currentQueryRelation !== undefined) { this.currentQueryRelation.columnIdSeq = 1; this.reportTableReferences(); this.currentQueryRelation.relations = new Map(); } return this.visitChildren(ctx); } visitSelect(ctx) { let result = ctx.fromTable()?.accept(this) ?? this.defaultResult(); ctx.children?.forEach(c => { if (!(c instanceof FlinkSQLParser_1.FromTableContext)) { result = this.aggregateResult(result, c.accept(this)); } }); return result; } visitJoinCondition(ctx) { if (this.currentQueryRelation && ctx.USING() && ctx.columns()) { const columns = ctx .columns() .columnName() .map(c => ({ text: (0, utils_1.sanitizeText)(c.text), range: (0, protocol_translation_1.rangeFromContext)(c) })); const size = this.currentQueryRelation.relations.size; if (size >= 2) { let i = 0; const foundLeftCol = new Set(); this.currentQueryRelation.relations.forEach(r => { if (i == size - 1) { columns.forEach(c => { const col = r.resolveColumn(c.text); if (col) { this.onColumnReference({ relationId: col.tableId, columnId: col.columnId }, { relationId: this.currentQueryRelation.id, columnId: this.currentQueryRelation.currentColumnId }); } else { const col = new Column(r.getNextColumnId(), c.text, c.range, undefined, true); r.columns.push(col); } }); } else { columns.forEach((c, j) => { if (!foundLeftCol.has(j)) { const col = r.resolveColumn(c.text); if (col) { this.onColumnReference({ relationId: col.tableId, columnId: col.columnId }, { relationId: this.currentQueryRelation.id, columnId: this.currentQueryRelation.currentColumnId }); } foundLeftCol.add(j); } }); } i++; }); } } return this.visitChildren(ctx); } visitTableReference(ctx) { if (this.currentQueryRelation !== undefined) { if (ctx.tablePrimary().tableSource()?.table() !== undefined) { const tableName = ctx .tablePrimary() .tableSource() .table() .text.split('.') .map(t => (0, utils_1.sanitizeText)(t)); const alias = ctx.alias() !== undefined ? (0, utils_1.sanitizeText)(ctx.alias().text) : tableName[tableName.length - 1]; const tablePrimary = this.relationPrimaryFromMultipart(tableName, 'table'); const fullTableName = relationPrimaryToString(tablePrimary); const cte = this.currentQueryRelation.findCTE(fullTableName); if (cte !== undefined) { this.currentQueryRelation.relations.set(alias, cte); return this.defaultResult(); } const rel = this.currentQueryRelation.findRelation(fullTableName); if (rel !== undefined) { if (fullTableName !== alias) { this.currentQueryRelation.relations.set(alias, rel); } return this.defaultResult(); } const tableDDL = this.tableDDLs.get(fullTableName); if (tableDDL !== undefined) { this.currentQueryRelation.relations.set(alias, tableDDL); return this.defaultResult(); } const viewDDL = this.viewDDLs.get(fullTableName); if (viewDDL !== undefined) { this.currentQueryRelation.relations.set(alias, viewDDL); return this.defaultResult(); } const range = (0, protocol_translation_1.rangeFromContext)(ctx); tablePrimary.range = range; tablePrimary.alias = ctx.alias() !== undefined ? (0, utils_1.sanitizeText)(ctx.alias().text) : undefined; let columns = []; if (this.getTable !== undefined) { const metadata = this.getTable(tablePrimary); columns = metadata?.columns.map(col => new Column(col.id, col.label, undefined, col.data, false)) || []; } const relation = new TableRelation(this.getNextRelationId(), tablePrimary, columns, this.currentQueryRelation, range); this.currentQueryRelation.relations.set(alias, relation); } else if (ctx.tablePrimary().tableQuery()) { const result = this.visitChildren(ctx); const relation = this.lastQueryRelation; if (relation !== undefined) { const alias = ctx.alias() !== undefined ? (0, utils_1.sanitizeText)(ctx.alias().text) : undefined; const relationKey = alias ?? relation.id; const columnAliases = ctx .aliases() ?.alias() .map(c => (0, utils_1.sanitizeText)(c.text)); if (columnAliases !== undefined) { relation.columns.forEach((c, i) => { c.label = columnAliases[i] ?? c.label; }); } this.currentQueryRelation.relations.set(relationKey, relation); this.onRelation(relation, alias); return result; } else { throw new Error('Expecting sub-query relation to be in stack'); } } } return this.defaultResult(); } visitProjectItem(ctx) { if (this.currentQueryRelation) { if (ctx.alias() === undefined) { const star = this.isStar(ctx.expr()); if (star !== undefined) { return super.visitChildren(ctx.expr()); } } const columnId = this.currentQueryRelation.getNextColumnId(); let column = this.currentQueryRelation.columns.find(c => c.id == columnId); if (column === undefined) { const range = (0, protocol_translation_1.rangeFromContext)(ctx); if (ctx.alias() !== undefined) { column = new Column(columnId, (0, utils_1.sanitizeText)(ctx.alias().text), range, undefined, true); } else { column = new Column(columnId, this.deriveColumnNameFromContext(ctx.expr())[0]?.columnName ?? columnId, range, undefined, true); } this.currentQueryRelation.columns.push(column); } this.currentQueryRelation.currentColumnId = columnId; this.currentQueryRelation.columnReferences = []; const result = this.visitChildren(ctx); column.columnReferences.push(...this.currentQueryRelation.columnReferences); this.currentQueryRelation.currentColumnId = undefined; return result; } else { throw new Error('Expecting query relation to be in stack'); } } visitColumnReference(ctx) { if (this.currentQueryRelation) { const columnName = (0, utils_1.sanitizeText)(ctx.columnName().text); const range = (0, protocol_translation_1.rangeFromContext)(ctx); const col = this.currentQueryRelation.resolveRelationColumn(columnName, range); if (col !== undefined) { this.currentQueryRelation.columnReferences.push(col); this.onColumnReference({ relationId: col.tableId, columnId: col.columnId }, { relationId: this.currentQueryRelation.id, columnId: this.currentQueryRelation.currentColumnId }); } else { for (const r of this.currentQueryRelation.relations) { const rel = r[1]; if (rel instanceof TableRelation) { const tableDDL = this.tableDDLs.get(relationPrimaryToString(rel.tablePrimary)); if (tableDDL?.columns.find(col => col.label === columnName)) { const newRef = rel.addColumn(columnName, range, false); this.currentQueryRelation.columnReferences.push(newRef.ref); this.onColumnReference({ relationId: newRef.ref.tableId, columnId: newRef.ref.columnId }, { relationId: this.currentQueryRelation.id, columnId: this.currentQueryRelation.currentColumnId }); } } } const currentCol = this.currentQueryRelation.columns.find(col => col.label === columnName); const starCol = this.lastQueryRelation?.columns.find(col => col.label === exports.STAR_LABEL); if (currentCol && starCol) { this.onColumnReference({ relationId: this.lastQueryRelation.id, columnId: starCol.id }, { relationId: this.currentQueryRelation.id, columnId: currentCol.id }); } } } return this.defaultResult(); } visitDereference(ctx) { if (this.currentQueryRelation) { const tableNameParts = ctx .table() .text.split('.') .map(t => (0, utils_1.sanitizeText)(t)); const tablePrimary = this.relationPrimaryFromMultipart(tableNameParts, 'table'); const tableName = relationPrimaryToString(tablePrimary); const columnName = (0, utils_1.sanitizeText)(ctx.columnName().text); const range = (0, protocol_translation_1.rangeFromContext)(ctx); let col; let relation; const ddl = this.tableDDLs.get(tableName); if (ddl !== undefined) { relation = ddl; } const cte = this.currentQueryRelation.findCTE(tableName); if (cte !== undefined) { relation = cte; } const rel = this.currentQueryRelation.findRelation(tableName); if (rel !== undefined) { relation = rel; } if (relation !== undefined) { col = this.currentQueryRelation.resolveRelationColumnWithTableName(columnName, range, relation); } if (col !== undefined) { this.currentQueryRelation.columnReferences.push(col); if (this.currentQueryRelation.currentColumnId !== undefined) { this.onColumnReference({ relationId: col.tableId, columnId: col.columnId }, { relationId: this.currentQueryRelation.id, columnId: this.currentQueryRelation.currentColumnId }); } } } return this.defaultResult(); } visitFunctionCall(ctx) { if ((0, utils_1.sanitizeText)(ctx.functionName().text.toLowerCase()) == 'count' && ctx.expr().length == 1 && this.isStar(ctx.expr()[0]) !== undefined) { const star = this.isStar(ctx.expr()[0]); if (this.currentQueryRelation && star !== undefined) { const range = (0, protocol_translation_1.rangeFromContext)(ctx); const countStarCol = new Column(this.currentQueryRelation.getNextColumnId(), exports.COUNT_STAR_LABEL, range, undefined, true); this.currentQueryRelation.columns.unshift(countStarCol); if (star.table() !== undefined) { const tableName = (0, utils_1.sanitizeText)(star.table().tableName().text); const rel = this.currentQueryRelation.findLocalRelation(tableName); if (rel !== undefined) { this.addStarRelationColumn(rel, range, countStarCol); } } else { for (const r of this.currentQueryRelation.relations) { this.addStarRelationColumn(r[1], range, countStarCol); } } return this.defaultResult(); } } return this.visitChildren(ctx); } visitStar(ctx) { if (this.currentQueryRelation) { const range = (0, protocol_translation_1.rangeFromContext)(ctx); const starCol = new Column(this.currentQueryRelation.getNextColumnId(), exports.STAR_LABEL, range, undefined, true); this.currentQueryRelation.columns.unshift(starCol); if (ctx.table() !== undefined) { const tableName = (0, utils_1.sanitizeText)(ctx.table().tableName().text); const rel = this.currentQueryRelation.findLocalRelation(tableName); if (rel !== undefined) { this.addStarRelationColumn(rel, range, starCol); } } else { for (const r of this.currentQueryRelation.relations) { this.addStarRelationColumn(r[1], range, starCol); } } } return this.defaultResult(); } visitComparison(ctx) { const result = super.visitChildren(ctx); if (this.currentQueryRelation && ctx._right && ctx._right) { const leftColumnExpr = this.deriveColumnNameFromContext(ctx._left)[0]; const rightColumnExpr = this.deriveColumnNameFromContext(ctx._right)[0]; if (leftColumnExpr && rightColumnExpr) { let leftRel; let rightRel; let leftColumn; let rightColumn; if (leftColumnExpr.tableName !== undefined) { leftRel = this.currentQueryRelation.findLocalRelation(leftColumnExpr.tableName); leftColumn = leftRel?.findColumn(leftColumnExpr.columnName); } if (rightColumnExpr.tableName !== undefined) { rightRel = this.currentQueryRelation.findLocalRelation(rightColumnExpr.tableName); rightColumn = rightRel?.findColumn(rightColumnExpr.columnName); } if (leftRel && leftColumn && rightRel && rightColumn && leftColumn.id !== rightColumn.id) { this.onColumnReference({ relationId: rightRel.id, columnId: rightColumn.id }, { relationId: leftRel.id, columnId: leftColumn.id }, 'join'); } } } return result; } visitExists(ctx) { const result = this.visitChildren(ctx); const rel = this.lastQueryRelation; if (rel !== undefined) { this.onRelation(rel); this.handleSubQueryRelation(rel); } return result; } visitPredicate(ctx) { const result = this.visitChildren(ctx); if (ctx.parenthesisQuery()) { const rel = this.lastQueryRelation; if (rel !== undefined) { this.onRelation(rel); this.handleSubQueryRelation(rel); } } return result; } visitSubQuery(ctx) { const result = this.visitChildren(ctx); const rel = this.lastQueryRelation; if (rel !== undefined) { this.onRelation(rel); this.handleSubQueryRelation(rel); } return result; } visitWithItem(ctx) { const result = this.visitChildren(ctx); const relation = this.lastQueryRelation; if (relation !== undefined) { const alias = (0, utils_1.sanitizeText)(ctx.tableName().text); const columnAliases = ctx .columns() ?.columnName() .map(t => (0, utils_1.sanitizeText)(t.text)); if (columnAliases !== undefined) { relation.columns.forEach((c, i) => { c.label = columnAliases[i] ?? c.label; }); } this.onRelation(relation, alias); this.currentQueryRelation?.ctes.set(alias, relation); return result; } else { throw new Error('Expecting CTE query relation to be in stack'); } } visitSelectClause(ctx) { return this.processClause('select', ctx); } visitFromTable(ctx) { return this.processClause('from', ctx); } visitWhere(ctx) { return this.processClause('where', ctx); } visitGroupBy(ctx) { return this.processClause('group by', ctx); } visitHaving(ctx) { return this.processClause('having', ctx); } visitOrderBy(ctx) { return this.processClause('order by', ctx); } } exports.FlinkSQLRelationVisitor = FlinkSQLRelationVisitor; class ExpressionColumnListener { constructor() { this.columnNames = []; } enterColumnReference(ctx) { this.columnNames.push({ columnName: (0, utils_1.sanitizeText)(ctx.columnName().text) }); } enterDereference(ctx) { this.columnNames.push({ tableName: (0, utils_1.sanitizeText)(ctx.table().tableName().text), columnName: (0, utils_1.sanitizeText)(ctx.columnName().text) }); } } class StarListener { enterStar(ctx) { this.starContext = ctx; } } exports.RELATION_SEPARATOR = '__F__'; function relationPrimaryToString(relationPrimary) { return `${relationPrimary.catalogName ? `${relationPrimary.catalogName}${exports.RELATION_SEPARATOR}` : ''}${relationPrimary.databaseName ? `${relationPrimary.databaseName}${exports.RELATION_SEPARATOR}` : ''}${relationPrimary.relationName}`; } exports.relationPrimaryToString = relationPrimaryToString;