UNPKG

rawsql-ts

Version:

High-performance SQL parser and AST analyzer written in TypeScript. Provides fast parsing and advanced transformation capabilities.

373 lines 15 kB
import { SubQuerySource, TableSource } from "../models/Clause"; import { BinarySelectQuery, SimpleSelectQuery } from "../models/SelectQuery"; import { ColumnReference } from "../models/ValueComponent"; import { ClauseScopedColumnReferenceCollector } from "./ClauseScopedColumnReferenceCollector"; import { SelectOutputCollector } from "./SelectOutputCollector"; const clauseOrder = [ "select", "where", "joinOn", "groupBy", "having", "orderBy", "window", "limitOffset" ]; /** * Infers the subset of wildcard columns required by downstream references. * * This collector only returns metadata. It does not rewrite SQL and does not attempt full * table-column discovery. When ownership is ambiguous, the requirement is reported as unresolved. */ export class WildcardColumnInferenceCollector { constructor() { this.clauseCollector = new ClauseScopedColumnReferenceCollector(); this.requirements = new Map(); this.visitedQueries = new Set(); this.nextObjectId = 0; this.objectIds = new WeakMap(); } /** * Collect wildcard column inference metadata for CTEs and derived tables visible in a SELECT query. */ collect(query) { this.requirements.clear(); this.visitedQueries.clear(); this.nextObjectId = 0; this.objectIds = new WeakMap(); this.collectFromSelectQuery(query, []); return [...this.requirements.values()].map(item => this.inferTarget(item)); } collectFromSelectQuery(query, commonTables) { var _a, _b, _c, _d; if (this.visitedQueries.has(query)) { return; } this.visitedQueries.add(query); if (query instanceof BinarySelectQuery) { const rightCommonTables = this.mergeCommonTables(commonTables, this.collectLeadingCommonTables(query.left)); this.collectFromSelectQuery(query.left, commonTables); this.collectFromSelectQuery(query.right, rightCommonTables); return; } if (!(query instanceof SimpleSelectQuery)) { return; } const scopedCommonTables = this.mergeCommonTables(commonTables, (_b = (_a = query.withClause) === null || _a === void 0 ? void 0 : _a.tables) !== null && _b !== void 0 ? _b : []); this.collectConsumerRequirements(query, scopedCommonTables); this.collectNestedSources(query.fromClause, scopedCommonTables); for (const commonTable of (_d = (_c = query.withClause) === null || _c === void 0 ? void 0 : _c.tables) !== null && _d !== void 0 ? _d : []) { this.collectCteQuery(commonTable.query, scopedCommonTables); } } collectCteQuery(query, commonTables) { if (this.isSelectQuery(query)) { this.collectFromSelectQuery(query, commonTables); } } collectConsumerRequirements(query, commonTables) { var _a, _b; if (!query.fromClause) { return; } const targets = this.collectConsumerTargets(query.fromClause, commonTables); if (targets.length === 0) { return; } const targetByMatchName = new Map(); for (const target of targets) { for (const name of this.getTargetMatchNames(target)) { const existing = (_a = targetByMatchName.get(name)) !== null && _a !== void 0 ? _a : []; existing.push(target); targetByMatchName.set(name, existing); } } const singleUnqualifiedTarget = query.fromClause.getSources().length === 1 && targets.length === 1 ? targets[0] : null; for (const reference of this.flattenReferences(this.clauseCollector.collect(query))) { if (reference.column === "*") { continue; } const matchedTarget = reference.namespace ? this.getUniqueTarget((_b = targetByMatchName.get(reference.namespace)) !== null && _b !== void 0 ? _b : []) : singleUnqualifiedTarget; if (matchedTarget) { this.addRequirement(matchedTarget, reference.column, this.createRequiredBy(reference)); } } } collectConsumerTargets(fromClause, commonTables) { const targets = []; for (const source of fromClause.getSources()) { const cteTarget = this.createCteTarget(source, commonTables); if (cteTarget) { targets.push(cteTarget); continue; } const derivedTarget = this.createDerivedTableTarget(source); if (derivedTarget) { targets.push(derivedTarget); } } return targets; } createCteTarget(source, commonTables) { if (!(source.datasource instanceof TableSource)) { return null; } const tableName = source.datasource.getSourceName(); const commonTable = commonTables.find(item => item.getSourceAliasName() === tableName); if (!commonTable || !this.isSelectQuery(commonTable.query)) { return null; } return { key: `cte:${this.getObjectId(commonTable)}`, targetKind: "cte", targetName: commonTable.getSourceAliasName(), targetAlias: source.aliasExpression ? source.getAliasName() : commonTable.getSourceAliasName(), query: commonTable.query }; } createDerivedTableTarget(source) { if (!(source.datasource instanceof SubQuerySource)) { return null; } return { key: `derived:${this.getObjectId(source.datasource.query)}`, targetKind: "derivedTable", targetName: null, targetAlias: source.getAliasName(), query: source.datasource.query }; } collectNestedSources(fromClause, commonTables) { if (!fromClause) { return; } for (const source of fromClause.getSources()) { if (source.datasource instanceof SubQuerySource) { this.collectFromSelectQuery(source.datasource.query, commonTables); } } } addRequirement(target, column, requiredBy) { var _a; let requirement = this.requirements.get(target.key); if (!requirement) { requirement = { descriptor: target, requiredByColumn: new Map() }; this.requirements.set(target.key, requirement); } const existing = (_a = requirement.requiredByColumn.get(column)) !== null && _a !== void 0 ? _a : []; existing.push(requiredBy); requirement.requiredByColumn.set(column, existing); } inferTarget(requirement) { const descriptor = requirement.descriptor; const baseResult = { targetKind: descriptor.targetKind, targetName: descriptor.targetName, targetAlias: descriptor.targetAlias, wildcards: [], explicitColumns: [], requiredColumns: [], unresolvedColumns: [] }; if (!(descriptor.query instanceof SimpleSelectQuery)) { for (const [outputName, requiredBy] of requirement.requiredByColumn) { baseResult.unresolvedColumns.push(this.createUnresolved(outputName, "unsupportedQueryShape", [], requiredBy)); } return baseResult; } const outputMetadata = this.collectTargetOutputMetadata(descriptor.query); baseResult.wildcards = outputMetadata.wildcards; baseResult.explicitColumns = outputMetadata.explicitColumns; for (const [outputName, requiredBy] of requirement.requiredByColumn) { const explicitMatches = outputMetadata.explicitColumns.filter(item => item.outputName === outputName); const candidateWildcards = outputMetadata.wildcards; if (explicitMatches.length > 0 && candidateWildcards.length > 0) { baseResult.unresolvedColumns.push(this.createUnresolved(outputName, "duplicateOutputOwnership", candidateWildcards, requiredBy)); continue; } if (explicitMatches.length > 0) { continue; } if (outputMetadata.unqualifiedWildcardWithMultipleSources) { baseResult.unresolvedColumns.push(this.createUnresolved(outputName, "unqualifiedWildcardMultipleSources", candidateWildcards, requiredBy)); continue; } if (candidateWildcards.length === 0) { baseResult.unresolvedColumns.push(this.createUnresolved(outputName, "noWildcardSupplier", [], requiredBy)); continue; } if (candidateWildcards.length > 1) { baseResult.unresolvedColumns.push(this.createUnresolved(outputName, "multipleWildcardSuppliers", candidateWildcards, requiredBy)); continue; } const wildcard = candidateWildcards[0]; baseResult.requiredColumns.push({ outputName, sourceAlias: wildcard.sourceAlias, sourceName: wildcard.sourceName, sourceColumnName: outputName, wildcard, requiredByClause: this.collectRequiredByClauses(requiredBy), requiredBy }); } return baseResult; } collectTargetOutputMetadata(query) { const wildcards = []; let unqualifiedWildcardWithMultipleSources = false; query.selectClause.items.forEach((item, outputIndex) => { const wildcard = this.collectWildcardMetadata(item, outputIndex, query.fromClause); if (wildcard === "unqualifiedWildcardMultipleSources") { unqualifiedWildcardWithMultipleSources = true; wildcards.push({ kind: "unqualified", outputIndex, sourceAlias: null, sourceName: null }); return; } if (wildcard) { wildcards.push(wildcard); } }); const explicitColumns = new SelectOutputCollector() .collect(query) .filter(item => !(item.value instanceof ColumnReference && item.value.column.name === "*")) .map(item => ({ outputName: item.name, sourceAlias: item.sourceAlias, sourceName: item.sourceName, sourceColumnName: item.sourceColumnName })); return { wildcards, explicitColumns, unqualifiedWildcardWithMultipleSources }; } collectWildcardMetadata(item, outputIndex, fromClause) { if (!(item.value instanceof ColumnReference) || item.value.column.name !== "*") { return null; } if (!fromClause) { return null; } if (item.value.namespaces === null) { const sources = fromClause.getSources(); if (sources.length !== 1) { return "unqualifiedWildcardMultipleSources"; } return { kind: "unqualified", outputIndex, sourceAlias: sources[0].getAliasName(), sourceName: this.getSourceName(sources[0]) }; } const namespace = item.value.getNamespace(); const source = this.getUniqueSource(fromClause.getSources().filter(candidate => this.getSourceMatchNames(candidate).includes(namespace))); return { kind: "qualified", outputIndex, sourceAlias: namespace, sourceName: source ? this.getSourceName(source) : null }; } createUnresolved(outputName, reason, candidateWildcards, requiredBy) { return { outputName, reason, candidateWildcards, requiredByClause: this.collectRequiredByClauses(requiredBy), requiredBy }; } flattenReferences(references) { return clauseOrder.flatMap(clause => references[clause]); } createRequiredBy(reference) { return { clause: reference.clause, qualifiedName: reference.qualifiedName, namespace: reference.namespace, column: reference.column }; } collectRequiredByClauses(requiredBy) { const clauses = new Set(requiredBy.map(item => item.clause)); return clauseOrder.filter(clause => clauses.has(clause)); } getTargetMatchNames(target) { if (target.targetAlias) { return [target.targetAlias]; } return target.targetName ? [target.targetName] : []; } getUniqueTarget(targets) { return targets.length === 1 ? targets[0] : null; } getUniqueSource(sources) { return sources.length === 1 ? sources[0] : null; } getSourceMatchNames(source) { const names = new Set(); const aliasName = source.getAliasName(); if (aliasName) { names.add(aliasName); } if (source.datasource instanceof TableSource) { names.add(source.datasource.table.name); names.add(source.datasource.getSourceName()); } return [...names]; } getSourceName(source) { if (source.datasource instanceof TableSource) { return source.datasource.getSourceName(); } return null; } collectLeadingCommonTables(query) { var _a, _b; if (query instanceof SimpleSelectQuery) { return (_b = (_a = query.withClause) === null || _a === void 0 ? void 0 : _a.tables) !== null && _b !== void 0 ? _b : []; } if (query instanceof BinarySelectQuery) { return this.collectLeadingCommonTables(query.left); } return []; } mergeCommonTables(left, right) { const merged = [...left]; for (const table of right) { const existingIndex = merged.findIndex(item => item.getSourceAliasName() === table.getSourceAliasName()); if (existingIndex === -1) { merged.push(table); } else { merged[existingIndex] = table; } } return merged; } getObjectId(value) { const existing = this.objectIds.get(value); if (existing !== undefined) { return existing; } const id = this.nextObjectId++; this.objectIds.set(value, id); return id; } isSelectQuery(query) { return !!query && typeof query === "object" && "__selectQueryType" in query && query.__selectQueryType === "SelectQuery"; } } //# sourceMappingURL=WildcardColumnInferenceCollector.js.map