flink-sql-language-server
Version:
A LSP-based language server for Apache Flink SQL
1,001 lines (1,000 loc) • 42.3 kB
JavaScript
"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;