rawsql-ts
Version:
[beta]High-performance SQL parser and AST analyzer written in TypeScript. Provides fast parsing and advanced transformation capabilities.
133 lines • 5.97 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.UpstreamSelectQueryFinder = void 0;
const SelectQuery_1 = require("../models/SelectQuery");
const Clause_1 = require("../models/Clause");
const SelectableColumnCollector_1 = require("./SelectableColumnCollector");
const CTECollector_1 = require("./CTECollector");
/**
* UpstreamSelectQueryFinder searches upstream queries for the specified columns.
* If a query (including its upstream CTEs or subqueries) contains all columns,
* it returns the highest such SelectQuery. Otherwise, it searches downstream.
* For UNION queries, it checks each branch independently.
*/
class UpstreamSelectQueryFinder {
constructor(tableColumnResolver, options) {
this.options = options || {};
this.tableColumnResolver = tableColumnResolver;
// Pass the tableColumnResolver instead of options to fix type mismatch.
this.columnCollector = new SelectableColumnCollector_1.SelectableColumnCollector(this.tableColumnResolver);
}
/**
* Finds the highest SelectQuery containing all specified columns.
* @param query The root SelectQuery to search.
* @param columnNames A column name or array of column names to check for.
* @returns An array of SelectQuery objects, or an empty array if not found.
*/
find(query, columnNames) {
// Normalize columnNames to array
const namesArray = typeof columnNames === 'string' ? [columnNames] : columnNames;
// Use CTECollector to collect CTEs from the root query only once and reuse
const cteCollector = new CTECollector_1.CTECollector();
const ctes = cteCollector.collect(query);
const cteMap = new Map();
for (const cte of ctes) {
cteMap.set(cte.getSourceAliasName(), cte);
}
return this.findUpstream(query, namesArray, cteMap);
}
handleTableSource(src, columnNames, cteMap) {
// Handles the logic for TableSource in findUpstream
const cte = cteMap.get(src.table.name);
if (cte) {
// Remove the current CTE name from the map to prevent infinite recursion
const nextCteMap = new Map(cteMap);
nextCteMap.delete(src.table.name);
const result = this.findUpstream(cte.query, columnNames, nextCteMap);
if (result.length === 0) {
return null;
}
return result;
}
return null;
}
handleSubQuerySource(src, columnNames, cteMap) {
// Handles the logic for SubQuerySource in findUpstream
const result = this.findUpstream(src.query, columnNames, cteMap);
if (result.length === 0) {
return null;
}
return result;
}
/**
* Processes all source branches in a FROM clause and checks if all upstream queries contain the specified columns.
* Returns a flat array of SelectQuery if all branches are valid, otherwise null.
*/
processFromClauseBranches(fromClause, columnNames, cteMap) {
const sources = fromClause.getSources();
if (sources.length === 0)
return null;
let allBranchResults = [];
let allBranchesOk = true;
let validBranchCount = 0; // Count only filterable branches
for (const sourceExpr of sources) {
const src = sourceExpr.datasource;
let branchResult = null;
if (src instanceof Clause_1.TableSource) {
branchResult = this.handleTableSource(src, columnNames, cteMap);
validBranchCount++;
}
else if (src instanceof Clause_1.SubQuerySource) {
branchResult = this.handleSubQuerySource(src, columnNames, cteMap);
validBranchCount++;
}
else if (src instanceof SelectQuery_1.ValuesQuery) {
// Skip ValuesQuery: not filterable, do not count as a valid branch
continue;
}
else {
allBranchesOk = false;
break;
}
// If the branch result is null,
// it means it didn't find the required columns in this branch
if (branchResult === null) {
allBranchesOk = false;
break;
}
allBranchResults.push(branchResult);
}
// Check if all valid (filterable) branches are valid and contain the required columns
if (allBranchesOk && allBranchResults.length === validBranchCount) {
return allBranchResults.flat();
}
return null;
}
findUpstream(query, columnNames, cteMap) {
if (query instanceof SelectQuery_1.SimpleSelectQuery) {
const fromClause = query.fromClause;
if (fromClause) {
const branchResult = this.processFromClauseBranches(fromClause, columnNames, cteMap);
if (branchResult) {
return branchResult;
}
}
const columns = this.columnCollector.collect(query).map(col => col.name);
const normalize = (s) => this.options.ignoreCaseAndUnderscore ? s.toLowerCase().replace(/_/g, '') : s;
// Normalize both the columns and the required names for comparison.
const hasAll = columnNames.every(name => columns.some(col => normalize(col) === normalize(name)));
if (hasAll) {
return [query];
}
return [];
}
else if (query instanceof SelectQuery_1.BinarySelectQuery) {
const left = this.findUpstream(query.left, columnNames, cteMap);
const right = this.findUpstream(query.right, columnNames, cteMap);
return [...left, ...right];
}
return [];
}
}
exports.UpstreamSelectQueryFinder = UpstreamSelectQueryFinder;
//# sourceMappingURL=UpstreamSelectQueryFinder.js.map
;