"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;
//# sourceMappingURL=flinksql-relation.visitor.js.map