rawsql-ts
Version:
High-performance SQL parser and AST analyzer written in TypeScript. Provides fast parsing and advanced transformation capabilities.
291 lines • 11.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SelectOutputCollector = void 0;
const Clause_1 = require("../models/Clause");
const InsertQuery_1 = require("../models/InsertQuery");
const DeleteQuery_1 = require("../models/DeleteQuery");
const MergeQuery_1 = require("../models/MergeQuery");
const SelectQuery_1 = require("../models/SelectQuery");
const UpdateQuery_1 = require("../models/UpdateQuery");
const ValueComponent_1 = require("../models/ValueComponent");
const CTECollector_1 = require("./CTECollector");
/**
* Collects SELECT output columns without collapsing duplicate output names.
*
* This collector is intended for callers that need stable SELECT-list positions.
* It keeps output order and assigns outputIndex after supported wildcard expansion.
*/
class SelectOutputCollector {
constructor(tableColumnResolver = null, initialCommonTables = null) {
this.tableColumnResolver = tableColumnResolver;
this.commonTableCollector = new CTECollector_1.CTECollector();
this.commonTables = initialCommonTables !== null && initialCommonTables !== void 0 ? initialCommonTables : [];
}
collect(arg) {
const rawColumns = this.collectRaw(arg);
return rawColumns.map((item, outputIndex) => ({
...item,
outputIndex
}));
}
collectRaw(arg) {
if (arg instanceof SelectQuery_1.SimpleSelectQuery) {
return this.collectSimpleSelectQuery(arg);
}
if (arg instanceof Clause_1.SourceExpression) {
return this.collectSourceExpression(arg.getAliasName(), arg);
}
if (arg instanceof Clause_1.FromClause) {
return this.collectFromClause(arg, true);
}
return [];
}
collectSimpleSelectQuery(query) {
const previousCommonTables = this.commonTables;
if (this.commonTables.length === 0) {
this.commonTables = this.commonTableCollector.collect(query);
}
try {
const outputs = [];
for (const item of query.selectClause.items) {
outputs.push(...this.collectSelectItem(item, query.fromClause));
}
return outputs;
}
finally {
this.commonTables = previousCommonTables;
}
}
collectSelectItem(item, fromClause) {
if (item.identifier) {
return [this.createExplicitOutput(item.identifier.name, item.value, fromClause)];
}
if (!(item.value instanceof ValueComponent_1.ColumnReference)) {
return [];
}
const columnName = item.value.column.name;
if (columnName === "*") {
return this.expandWildcard(item.value, fromClause);
}
return [this.createExplicitOutput(columnName, item.value, fromClause)];
}
expandWildcard(value, fromClause) {
var _a;
if (!fromClause) {
return [];
}
if (value.namespaces === null) {
return this.collectFromClause(fromClause, true);
}
const sourceName = value.getNamespace();
if (fromClause.getSourceAliasName() === sourceName) {
return this.collectFromClause(fromClause, false);
}
const join = (_a = fromClause.joins) === null || _a === void 0 ? void 0 : _a.find(item => this.getJoinSourceName(item) === sourceName);
return join ? this.collectJoinClause(join) : [];
}
collectFromClause(clause, joinCascade) {
const outputs = this.collectSourceExpression(clause.getSourceAliasName(), clause.source);
if (clause.joins && joinCascade) {
for (const join of clause.joins) {
outputs.push(...this.collectJoinClause(join));
}
}
return outputs;
}
collectJoinClause(clause) {
return this.collectSourceExpression(this.getJoinSourceName(clause), clause.source);
}
getJoinSourceName(clause) {
return clause.source.getAliasName();
}
collectSourceExpression(sourceName, source) {
var _a;
if ((_a = source.aliasExpression) === null || _a === void 0 ? void 0 : _a.columns) {
return source.aliasExpression.columns.map(column => ({
name: column.name,
value: new ValueComponent_1.ColumnReference(sourceName ? [sourceName] : null, column.name),
sourceAlias: sourceName,
sourceName: null,
sourceColumnName: column.name
}));
}
const cte = this.findCommonTable(source);
if (cte) {
const innerCommonTables = this.commonTables.filter(item => item.getSourceAliasName() !== cte.getSourceAliasName());
const innerOutputs = this.collectCteQuery(cte.query, innerCommonTables);
return this.qualifyOutputs(innerOutputs, sourceName, cte.getSourceAliasName());
}
if (source.datasource instanceof Clause_1.TableSource) {
return this.collectTableSource(sourceName, source.datasource);
}
if (source.datasource instanceof Clause_1.SubQuerySource) {
const innerOutputs = this.collectNestedSelectQuery(source.datasource.query);
return this.qualifyOutputs(innerOutputs, sourceName, null);
}
if (source.datasource instanceof Clause_1.ParenSource) {
return [];
}
return [];
}
findCommonTable(source) {
var _a;
if (!(source.datasource instanceof Clause_1.TableSource)) {
return null;
}
const tableName = source.datasource.getSourceName();
return (_a = this.commonTables.find(item => item.getSourceAliasName() === tableName)) !== null && _a !== void 0 ? _a : null;
}
collectTableSource(sourceName, source) {
if (!this.tableColumnResolver) {
return [];
}
const tableName = source.getSourceName();
const qualifier = sourceName !== null && sourceName !== void 0 ? sourceName : tableName;
return this.tableColumnResolver(tableName).map(column => ({
name: column,
value: new ValueComponent_1.ColumnReference([qualifier], column),
sourceAlias: sourceName,
sourceName: tableName,
sourceColumnName: column
}));
}
collectNestedSelectQuery(query) {
const collector = new SelectOutputCollector(this.tableColumnResolver, this.commonTables);
return collector.collect(query).map(({ name, value, sourceAlias, sourceName, sourceColumnName }) => ({
name,
value,
sourceAlias,
sourceName,
sourceColumnName
}));
}
collectCteQuery(query, commonTables) {
if (this.isSelectQuery(query)) {
const collector = new SelectOutputCollector(this.tableColumnResolver, commonTables);
return collector.collect(query).map(({ name, value, sourceAlias, sourceName, sourceColumnName }) => ({
name,
value,
sourceAlias,
sourceName,
sourceColumnName
}));
}
return this.collectReturningOutputs(query);
}
collectReturningOutputs(query) {
if (!(query instanceof InsertQuery_1.InsertQuery || query instanceof UpdateQuery_1.UpdateQuery || query instanceof DeleteQuery_1.DeleteQuery || query instanceof MergeQuery_1.MergeQuery)) {
return [];
}
if (!query.returningClause) {
return [];
}
return this.extractReturningOutputs(query.returningClause);
}
extractReturningOutputs(clause) {
var _a, _b;
const outputs = [];
for (const item of clause.items) {
const name = (_b = (_a = item.identifier) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : this.extractSelectItemName(item);
if (name) {
outputs.push(this.createExplicitOutput(name, item.value));
}
}
return outputs;
}
extractSelectItemName(item) {
if (item.identifier) {
return item.identifier.name;
}
if (item.value instanceof ValueComponent_1.ColumnReference) {
return item.value.column.name;
}
return null;
}
qualifyOutputs(outputs, sourceAlias, sourceName) {
return outputs.map(item => ({
name: item.name,
value: new ValueComponent_1.ColumnReference(sourceAlias ? [sourceAlias] : null, item.name),
sourceAlias,
sourceName,
sourceColumnName: item.name
}));
}
createExplicitOutput(name, value, fromClause = null) {
if (value instanceof ValueComponent_1.ColumnReference && value.column.name !== "*") {
const sourceMetadata = this.resolveColumnSourceMetadata(value, fromClause);
if (sourceMetadata) {
return {
name,
value,
...sourceMetadata
};
}
}
return {
name,
value,
sourceAlias: null,
sourceName: null,
sourceColumnName: null
};
}
resolveColumnSourceMetadata(value, fromClause) {
if (!fromClause || value.namespaces === null) {
return null;
}
const namespace = value.getNamespace();
const matches = fromClause.getSources().filter(source => this.getSourceMatchNames(source).includes(namespace));
if (matches.length !== 1) {
return null;
}
const source = matches[0];
const sourceAlias = this.getColumnReferenceSourceAlias(source, namespace);
return {
sourceAlias,
sourceName: this.getColumnReferenceSourceName(source),
sourceColumnName: value.column.name
};
}
getSourceMatchNames(source) {
const names = new Set();
if (source.aliasExpression) {
names.add(source.aliasExpression.table.name);
return [...names];
}
const aliasName = source.getAliasName();
if (aliasName) {
names.add(aliasName);
}
if (source.datasource instanceof Clause_1.TableSource) {
names.add(source.datasource.table.name);
names.add(source.datasource.getSourceName());
}
return [...names];
}
getColumnReferenceSourceAlias(source, namespace) {
if (source.aliasExpression) {
return source.aliasExpression.table.name;
}
return namespace;
}
getColumnReferenceSourceName(source) {
var _a;
const cte = this.findCommonTable(source);
if (cte) {
return cte.getSourceAliasName();
}
if ((_a = source.aliasExpression) === null || _a === void 0 ? void 0 : _a.columns) {
return null;
}
if (source.datasource instanceof Clause_1.TableSource) {
return source.datasource.getSourceName();
}
return null;
}
isSelectQuery(query) {
return "__selectQueryType" in query && query.__selectQueryType === "SelectQuery";
}
}
exports.SelectOutputCollector = SelectOutputCollector;
//# sourceMappingURL=SelectOutputCollector.js.map