UNPKG

rawsql-ts

Version:

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

633 lines 32.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.QueryBuilder = void 0; const Clause_1 = require("../models/Clause"); const UpdateQuery_1 = require("../models/UpdateQuery"); const DeleteQuery_1 = require("../models/DeleteQuery"); const MergeQuery_1 = require("../models/MergeQuery"); const ValueComponent_1 = require("../models/ValueComponent"); const SelectValueCollector_1 = require("./SelectValueCollector"); const SelectQuery_1 = require("../models/SelectQuery"); const CTECollector_1 = require("./CTECollector"); const CTENormalizer_1 = require("./CTENormalizer"); const CreateTableQuery_1 = require("../models/CreateTableQuery"); const InsertQuery_1 = require("../models/InsertQuery"); const CTEDisabler_1 = require("./CTEDisabler"); const SourceExpressionParser_1 = require("../parsers/SourceExpressionParser"); const InsertQuerySelectValuesConverter_1 = require("./InsertQuerySelectValuesConverter"); /** * QueryBuilder provides static methods to build or convert various SQL query objects. */ class QueryBuilder { /** * Builds a BinarySelectQuery by combining an array of SelectQuery using the specified operator. * Throws if less than two queries are provided. * @param queries Array of SelectQuery to combine * @param operator SQL operator to use (e.g. 'union', 'union all', 'intersect', 'except') * @returns BinarySelectQuery */ static buildBinaryQuery(queries, operator) { if (!queries || queries.length === 0) { throw new Error("No queries provided to combine."); } if (queries.length === 1) { throw new Error("At least two queries are required to create a BinarySelectQuery."); } // Always create a new BinarySelectQuery instance (never mutate input) // Note: ValuesQuery requires conversion to SimpleSelectQuery because it lacks SELECT clause structure // BinarySelectQuery and SimpleSelectQuery can be used directly in binary operations const wrap = (q) => q instanceof SelectQuery_1.ValuesQuery ? QueryBuilder.buildSimpleQuery(q) : q; let result = new SelectQuery_1.BinarySelectQuery(wrap(queries[0]), operator, wrap(queries[1])); CTENormalizer_1.CTENormalizer.normalize(result); for (let i = 2; i < queries.length; i++) { result.appendSelectQuery(operator, wrap(queries[i])); } return result; } constructor() { // This class is not meant to be instantiated. } /** * Converts a SELECT query to a standard SimpleSelectQuery form. * @param query The query to convert * @returns A SimpleSelectQuery */ static buildSimpleQuery(query) { if (query instanceof SelectQuery_1.SimpleSelectQuery) { return query; } else if (query instanceof SelectQuery_1.BinarySelectQuery) { return QueryBuilder.buildSimpleBinaryQuery(query); } else if (query instanceof SelectQuery_1.ValuesQuery) { return QueryBuilder.buildSimpleValuesQuery(query); } throw new Error("Unsupported query type for buildSimpleQuery"); } static buildSimpleBinaryQuery(query) { // Extract ORDER BY from the rightmost query in the binary tree and remove it const extractedOrderBy = QueryBuilder.extractAndRemoveOrderByFromBinaryQuery(query); // Create a subquery source from the binary query (now without ORDER BY) const subQuerySource = new Clause_1.SubQuerySource(query); // Create a source expression with alias const sourceExpr = new Clause_1.SourceExpression(subQuerySource, new Clause_1.SourceAliasExpression("bq", null)); // Create FROM clause with the source expression const fromClause = new Clause_1.FromClause(sourceExpr, null); // Create SELECT clause with * (all columns) const selectClause = QueryBuilder.createSelectAllClause(); // Create the final simple select query with extracted ORDER BY const q = new SelectQuery_1.SimpleSelectQuery({ selectClause, fromClause, orderByClause: extractedOrderBy }); return CTENormalizer_1.CTENormalizer.normalize(q); } /** * Extracts ORDER BY clause from the rightmost query in a binary query tree and removes it. * This clarifies the semantics by moving the ORDER BY from the ambiguous position * in the UNION to the explicit outer SimpleSelectQuery level. * * NOTE: ORDER BY in UNION context applies to the entire result set, not individual subqueries. * Therefore, table prefixes (e.g., "a.column") in ORDER BY are invalid SQL and would cause * syntax errors. Valid ORDER BY clauses should only reference column names without prefixes * or use positional notation (ORDER BY 1, 2). Since we only process valid SQL, the current * implementation correctly handles legitimate cases without additional prefix processing. * * @param query BinarySelectQuery to process * @returns Extracted OrderByClause or null if none found */ static extractAndRemoveOrderByFromBinaryQuery(query) { return QueryBuilder.findAndRemoveRightmostOrderBy(query); } /** * Recursively finds and removes ORDER BY from the rightmost query in a binary tree. * * @param query Current query being processed * @returns Extracted OrderByClause or null */ static findAndRemoveRightmostOrderBy(query) { if (query instanceof SelectQuery_1.BinarySelectQuery) { // For binary queries, check right side first (rightmost takes precedence) const rightOrderBy = QueryBuilder.findAndRemoveRightmostOrderBy(query.right); if (rightOrderBy) { return rightOrderBy; } // If no ORDER BY on right side, check left side return QueryBuilder.findAndRemoveRightmostOrderBy(query.left); } else if (query instanceof SelectQuery_1.SimpleSelectQuery) { // Extract ORDER BY from SimpleSelectQuery and remove it const orderBy = query.orderByClause; if (orderBy) { query.orderByClause = null; return orderBy; } } return null; } /** * Converts a ValuesQuery to a SimpleSelectQuery with sequentially numbered columns or user-specified columns * * @param query The VALUES query to convert * @param columns Optional: column names * @returns A SimpleSelectQuery */ static buildSimpleValuesQuery(query) { // Figure out how many columns are in the VALUES clause const columnCount = query.tuples.length > 0 ? query.tuples[0].values.length : 0; if (query.tuples.length === 0) { throw new Error("Empty VALUES clause cannot be converted to a SimpleSelectQuery"); } if (!query.columnAliases) { throw new Error("Column aliases are required to convert a VALUES clause to SimpleSelectQuery. Please specify column aliases."); } if (query.columnAliases.length !== columnCount) { throw new Error(`The number of column aliases (${query.columnAliases.length}) does not match the number of columns in the first tuple (${columnCount}).`); } // Create a subquery source from the VALUES query const subQuerySource = new Clause_1.SubQuerySource(query); const sourceExpr = new Clause_1.SourceExpression(subQuerySource, new Clause_1.SourceAliasExpression("vq", query.columnAliases)); // Create FROM clause with the source expression const fromClause = new Clause_1.FromClause(sourceExpr, null); // Create SELECT clause with all columns const selectItems = query.columnAliases.map(name => new Clause_1.SelectItem(new ValueComponent_1.ColumnReference("vq", name), name)); const selectClause = new Clause_1.SelectClause(selectItems, null); // Create the final simple select query return new SelectQuery_1.SimpleSelectQuery({ selectClause, fromClause }); } /** * Creates a SELECT clause with a single * (all columns) item * * @returns A SELECT clause with * */ static createSelectAllClause() { // Create a column reference for * const columnRef = new ValueComponent_1.ColumnReference(null, "*"); // Create a SelectItem with the column reference const selectItem = new Clause_1.SelectItem(columnRef, "*"); // Create and return a SelectClause with the item return new Clause_1.SelectClause([selectItem], null); } /** * Converts a SELECT query to a CREATE TABLE query (CREATE [TEMPORARY] TABLE ... AS SELECT ...) * @param query The SELECT query to use as the source * @param tableName The name of the table to create * @param isTemporary If true, creates a temporary table * @returns A CreateTableQuery instance */ static buildCreateTableQuery(query, tableName, isTemporary = false, ifNotExists = false) { return new CreateTableQuery_1.CreateTableQuery({ tableName, isTemporary, ifNotExists, asSelectQuery: query }); } /** * Converts a SELECT query to an INSERT query (INSERT INTO ... SELECT ...). */ static buildInsertQuery(selectQuery, targetOrOptions, explicitColumns) { var _a; // Derive normalized options while preserving the legacy signature for backward compatibility. const options = QueryBuilder.normalizeInsertOptions(targetOrOptions, explicitColumns); // Determine the final column order either from user-provided options or by inferring from the select list. const columnNames = QueryBuilder.prepareInsertColumns(selectQuery, (_a = options.columns) !== null && _a !== void 0 ? _a : null); // Promote WITH clauses to the INSERT statement so the SELECT body remains self-contained. const withClause = QueryBuilder.extractWithClause(selectQuery); const sourceExpr = SourceExpressionParser_1.SourceExpressionParser.parse(options.target); return new InsertQuery_1.InsertQuery({ withClause: withClause !== null && withClause !== void 0 ? withClause : undefined, insertClause: new Clause_1.InsertClause(sourceExpr, columnNames), selectQuery }); } /** * Converts an INSERT ... VALUES query into INSERT ... SELECT form using UNION ALL. * @param insertQuery The VALUES-based InsertQuery to convert. * @returns A new InsertQuery that selects rows instead of using VALUES. */ static convertInsertValuesToSelect(insertQuery) { return InsertQuerySelectValuesConverter_1.InsertQuerySelectValuesConverter.toSelectUnion(insertQuery); } /** * Converts an INSERT ... SELECT (optionally with UNION ALL) into INSERT ... VALUES form. * @param insertQuery The SELECT-based InsertQuery to convert. * @returns A new InsertQuery that uses VALUES tuples. */ static convertInsertSelectToValues(insertQuery) { return InsertQuerySelectValuesConverter_1.InsertQuerySelectValuesConverter.toValues(insertQuery); } /** * Builds an UPDATE query from a SELECT query and conversion options. */ static buildUpdateQuery(selectQuery, selectSourceOrOptions, updateTableExprRaw, primaryKeys) { var _a; // Normalize the function arguments into a single configuration object. const options = QueryBuilder.normalizeUpdateOptions(selectSourceOrOptions, updateTableExprRaw, primaryKeys); // Collect select-list metadata and align columns before mutating the query during WITH extraction. const updateColumns = QueryBuilder.prepareUpdateColumns(selectQuery, options.primaryKeys, (_a = options.columns) !== null && _a !== void 0 ? _a : null); const updateClause = new Clause_1.UpdateClause(SourceExpressionParser_1.SourceExpressionParser.parse(options.target)); const targetAlias = updateClause.getSourceAliasName(); if (!targetAlias) { throw new Error(`Source expression does not have an alias. Please provide an alias for the source expression.`); } // Move CTE definitions to the UPDATE statement for cleaner SQL. const withClause = QueryBuilder.extractWithClause(selectQuery); const setItems = updateColumns.map(column => new Clause_1.SetClauseItem(column, QueryBuilder.toColumnReference(options.sourceAlias, column))); if (setItems.length === 0) { throw new Error(`No updatable columns found. Ensure the select list contains at least one column other than the specified primary keys.`); } const setClause = new Clause_1.SetClause(setItems); const fromClause = new Clause_1.FromClause(selectQuery.toSource(options.sourceAlias), null); const whereClause = new Clause_1.WhereClause(QueryBuilder.buildEqualityPredicate(targetAlias, options.sourceAlias, options.primaryKeys)); return new UpdateQuery_1.UpdateQuery({ updateClause, setClause, fromClause, whereClause, withClause: withClause !== null && withClause !== void 0 ? withClause : undefined }); } /** * Builds a DELETE query that deletes the rows matched by the SELECT query output. */ static buildDeleteQuery(selectQuery, options) { var _a; // Normalise options to guarantee arrays and alias defaults. const normalized = QueryBuilder.normalizeDeleteOptions(options); const predicateColumns = QueryBuilder.prepareDeleteColumns(selectQuery, normalized.primaryKeys, (_a = normalized.columns) !== null && _a !== void 0 ? _a : null); const deleteClause = new Clause_1.DeleteClause(SourceExpressionParser_1.SourceExpressionParser.parse(normalized.target)); const targetAlias = deleteClause.getSourceAliasName(); if (!targetAlias) { throw new Error(`Source expression does not have an alias. Please provide an alias for the delete target.`); } const withClause = QueryBuilder.extractWithClause(selectQuery); // Build correlated EXISTS predicate instead of Postgres-specific USING clause. const predicate = QueryBuilder.buildEqualityPredicate(targetAlias, normalized.sourceAlias, predicateColumns); const sourceExpression = selectQuery.toSource(normalized.sourceAlias); const existsSelectClause = new Clause_1.SelectClause([new Clause_1.SelectItem(new ValueComponent_1.LiteralValue(1))]); const existsSubquery = new SelectQuery_1.SimpleSelectQuery({ selectClause: existsSelectClause, fromClause: new Clause_1.FromClause(sourceExpression, null), whereClause: new Clause_1.WhereClause(predicate) }); const whereClause = new Clause_1.WhereClause(new ValueComponent_1.UnaryExpression('exists', new ValueComponent_1.InlineQuery(existsSubquery))); return new DeleteQuery_1.DeleteQuery({ deleteClause, whereClause, withClause: withClause !== null && withClause !== void 0 ? withClause : undefined }); } /** * Builds a MERGE query (upsert) that coordinates actions based on row matches. */ static buildMergeQuery(selectQuery, options) { var _a, _b, _c, _d, _e, _f, _g; // Ensure the configuration is fully expanded before inspection. const normalized = QueryBuilder.normalizeMergeOptions(options); const mergeColumnPlan = QueryBuilder.prepareMergeColumns(selectQuery, normalized.primaryKeys, (_a = normalized.updateColumns) !== null && _a !== void 0 ? _a : null, (_b = normalized.insertColumns) !== null && _b !== void 0 ? _b : null, (_c = normalized.matchedAction) !== null && _c !== void 0 ? _c : 'update', (_d = normalized.notMatchedAction) !== null && _d !== void 0 ? _d : 'insert'); const targetExpression = SourceExpressionParser_1.SourceExpressionParser.parse(normalized.target); const targetAlias = targetExpression.getAliasName(); if (!targetAlias) { throw new Error(`Source expression does not have an alias. Please provide an alias for the merge target.`); } const withClause = QueryBuilder.extractWithClause(selectQuery); const onCondition = QueryBuilder.buildEqualityPredicate(targetAlias, normalized.sourceAlias, normalized.primaryKeys); const sourceExpression = selectQuery.toSource(normalized.sourceAlias); const whenClauses = []; const matchedAction = (_e = normalized.matchedAction) !== null && _e !== void 0 ? _e : 'update'; if (matchedAction === 'update') { if (mergeColumnPlan.updateColumns.length === 0) { throw new Error(`No columns available for MERGE update action. Provide updateColumns or ensure the select list includes non-key columns.`); } const setItems = mergeColumnPlan.updateColumns.map(column => new Clause_1.SetClauseItem(column, QueryBuilder.toColumnReference(normalized.sourceAlias, column))); whenClauses.push(new MergeQuery_1.MergeWhenClause("matched", new MergeQuery_1.MergeUpdateAction(new Clause_1.SetClause(setItems)))); } else if (matchedAction === 'delete') { whenClauses.push(new MergeQuery_1.MergeWhenClause("matched", new MergeQuery_1.MergeDeleteAction())); } else if (matchedAction === 'doNothing') { whenClauses.push(new MergeQuery_1.MergeWhenClause("matched", new MergeQuery_1.MergeDoNothingAction())); } const notMatchedAction = (_f = normalized.notMatchedAction) !== null && _f !== void 0 ? _f : 'insert'; if (notMatchedAction === 'insert') { if (mergeColumnPlan.insertColumns.length === 0) { throw new Error('Unable to infer MERGE insert columns. Provide insertColumns explicitly.'); } const insertValues = new ValueComponent_1.ValueList(mergeColumnPlan.insertColumns.map(column => QueryBuilder.toColumnReference(normalized.sourceAlias, column))); whenClauses.push(new MergeQuery_1.MergeWhenClause("not_matched", new MergeQuery_1.MergeInsertAction({ columns: mergeColumnPlan.insertColumns, values: insertValues }))); } else if (notMatchedAction === 'doNothing') { whenClauses.push(new MergeQuery_1.MergeWhenClause("not_matched", new MergeQuery_1.MergeDoNothingAction())); } const notMatchedBySourceAction = (_g = normalized.notMatchedBySourceAction) !== null && _g !== void 0 ? _g : 'doNothing'; if (notMatchedBySourceAction === 'delete') { whenClauses.push(new MergeQuery_1.MergeWhenClause("not_matched_by_source", new MergeQuery_1.MergeDeleteAction())); } else if (notMatchedBySourceAction === 'doNothing') { whenClauses.push(new MergeQuery_1.MergeWhenClause("not_matched_by_source", new MergeQuery_1.MergeDoNothingAction())); } if (whenClauses.length === 0) { throw new Error(`At least one MERGE action must be generated. Adjust the merge conversion options.`); } return new MergeQuery_1.MergeQuery({ withClause: withClause !== null && withClause !== void 0 ? withClause : undefined, target: targetExpression, source: sourceExpression, onCondition, whenClauses }); } static normalizeInsertOptions(targetOrOptions, explicitColumns) { if (typeof targetOrOptions === 'string') { return { target: targetOrOptions, columns: explicitColumns }; } if (explicitColumns && explicitColumns.length > 0) { return { ...targetOrOptions, columns: explicitColumns }; } return { ...targetOrOptions }; } static normalizeUpdateOptions(selectSourceOrOptions, updateTableExprRaw, primaryKeys) { var _a; if (typeof selectSourceOrOptions === 'string') { if (!updateTableExprRaw) { throw new Error('updateTableExprRaw is required when using the legacy buildUpdateQuery signature.'); } if (primaryKeys === undefined) { throw new Error('primaryKeys are required when using the legacy buildUpdateQuery signature.'); } return { target: updateTableExprRaw, primaryKeys: QueryBuilder.normalizeColumnArray(primaryKeys), sourceAlias: selectSourceOrOptions }; } return { target: selectSourceOrOptions.target, primaryKeys: QueryBuilder.normalizeColumnArray(selectSourceOrOptions.primaryKeys), sourceAlias: (_a = selectSourceOrOptions.sourceAlias) !== null && _a !== void 0 ? _a : 'src', columns: selectSourceOrOptions.columns }; } static normalizeDeleteOptions(options) { var _a; return { ...options, primaryKeys: QueryBuilder.normalizeColumnArray(options.primaryKeys), sourceAlias: (_a = options.sourceAlias) !== null && _a !== void 0 ? _a : 'src' }; } static normalizeMergeOptions(options) { var _a; return { ...options, primaryKeys: QueryBuilder.normalizeColumnArray(options.primaryKeys), sourceAlias: (_a = options.sourceAlias) !== null && _a !== void 0 ? _a : 'src' }; } static normalizeColumnArray(columns) { const array = Array.isArray(columns) ? columns : [columns]; const normalized = array.map(col => col.trim()).filter(col => col.length > 0); if (!normalized.length) { throw new Error('At least one column must be specified.'); } return normalized; } static collectSelectItems(selectQuery) { const collector = new SelectValueCollector_1.SelectValueCollector(); return collector.collect(selectQuery); } static collectSelectColumnNames(selectQuery) { const items = QueryBuilder.collectSelectItems(selectQuery); const names = []; for (const item of items) { if (!item.name || item.name === '*') { throw new Error(`Columns cannot be inferred from the selectQuery. ` + `Make sure you are not using wildcards or unnamed columns.`); } if (!names.includes(item.name)) { names.push(item.name); } } if (!names.length) { throw new Error('Unable to determine any column names from selectQuery.'); } return names; } static ensurePrimaryKeys(selectColumns, primaryKeys) { const available = new Set(selectColumns); for (const pk of primaryKeys) { if (!available.has(pk)) { throw new Error(`Primary key column '${pk}' is not present in selectQuery select list.`); } } } static prepareInsertColumns(selectQuery, optionColumns) { const selectColumns = QueryBuilder.collectSelectColumnNames(selectQuery); if (optionColumns && optionColumns.length > 0) { const normalized = QueryBuilder.normalizeColumnArray(optionColumns); const uniqueNormalized = normalized.filter((name, idx) => normalized.indexOf(name) === idx); const missing = uniqueNormalized.filter(name => !selectColumns.includes(name)); if (missing.length > 0) { throw new Error(`Columns specified in conversion options were not found in selectQuery select list: [${missing.join(', ')}].`); } QueryBuilder.rebuildSelectClause(selectQuery, uniqueNormalized); QueryBuilder.ensureSelectClauseSize(selectQuery, uniqueNormalized.length); return uniqueNormalized; } QueryBuilder.ensureSelectClauseSize(selectQuery, selectColumns.length); return selectColumns; } static prepareUpdateColumns(selectQuery, primaryKeys, explicitColumns) { const selectColumns = QueryBuilder.collectSelectColumnNames(selectQuery); QueryBuilder.ensurePrimaryKeys(selectColumns, primaryKeys); const primaryKeySet = new Set(primaryKeys); const updateCandidates = selectColumns.filter(name => !primaryKeySet.has(name)); let updateColumnsOrdered; if (explicitColumns && explicitColumns.length > 0) { const normalized = QueryBuilder.normalizeColumnArray(explicitColumns); const uniqueNormalized = normalized.filter((name, idx) => normalized.indexOf(name) === idx); const missing = uniqueNormalized.filter(name => primaryKeySet.has(name) || !updateCandidates.includes(name)); if (missing.length > 0) { throw new Error(`Provided update columns were not found in selectQuery output or are primary keys: [${missing.join(', ')}].`); } updateColumnsOrdered = uniqueNormalized; } else { updateColumnsOrdered = Array.from(new Set(updateCandidates)); } const desiredOrder = Array.from(new Set([ ...primaryKeys, ...updateColumnsOrdered ])); QueryBuilder.rebuildSelectClause(selectQuery, desiredOrder); QueryBuilder.ensureSelectClauseSize(selectQuery, desiredOrder.length); return updateColumnsOrdered; } static prepareDeleteColumns(selectQuery, primaryKeys, explicitColumns) { const selectColumns = QueryBuilder.collectSelectColumnNames(selectQuery); QueryBuilder.ensurePrimaryKeys(selectColumns, primaryKeys); const primaryKeySet = new Set(primaryKeys); let matchColumns = []; if (explicitColumns && explicitColumns.length > 0) { const normalized = QueryBuilder.normalizeColumnArray(explicitColumns); const preferred = new Set(normalized); matchColumns = selectColumns.filter(name => preferred.has(name) && !primaryKeySet.has(name)); } const requiredColumns = new Set(); primaryKeys.forEach(key => requiredColumns.add(key)); matchColumns.forEach(col => requiredColumns.add(col)); const desiredOrder = selectColumns.filter(name => requiredColumns.has(name)); QueryBuilder.rebuildSelectClause(selectQuery, desiredOrder); QueryBuilder.ensureSelectClauseSize(selectQuery, desiredOrder.length); return desiredOrder; } static prepareMergeColumns(selectQuery, primaryKeys, explicitUpdateColumns, explicitInsertColumns, matchedAction, notMatchedAction) { const selectColumns = QueryBuilder.collectSelectColumnNames(selectQuery); QueryBuilder.ensurePrimaryKeys(selectColumns, primaryKeys); const primaryKeySet = new Set(primaryKeys); let updateColumnsOrdered = []; if (matchedAction === 'update') { const candidates = selectColumns.filter(name => !primaryKeySet.has(name)); if (explicitUpdateColumns && explicitUpdateColumns.length > 0) { const normalized = QueryBuilder.normalizeColumnArray(explicitUpdateColumns); const uniqueNormalized = normalized.filter((name, idx) => normalized.indexOf(name) === idx); const missing = uniqueNormalized.filter(name => primaryKeySet.has(name) || !candidates.includes(name)); if (missing.length > 0) { throw new Error(`Provided update columns were not found in selectQuery output or are primary keys: [${missing.join(', ')}].`); } updateColumnsOrdered = uniqueNormalized; } else { updateColumnsOrdered = Array.from(new Set(candidates)); } } let insertColumnsOrdered = []; if (notMatchedAction === 'insert') { if (explicitInsertColumns && explicitInsertColumns.length > 0) { const normalized = QueryBuilder.normalizeColumnArray(explicitInsertColumns); const uniqueNormalized = normalized.filter((name, idx) => normalized.indexOf(name) === idx); const missing = uniqueNormalized.filter(name => !selectColumns.includes(name)); if (missing.length > 0) { throw new Error(`Provided insert columns were not found in selectQuery output: [${missing.join(', ')}].`); } insertColumnsOrdered = uniqueNormalized; } else { insertColumnsOrdered = Array.from(new Set(selectColumns)); } } const desiredOrder = Array.from(new Set([ ...primaryKeys, ...updateColumnsOrdered, ...insertColumnsOrdered, ...selectColumns ])).filter(name => selectColumns.includes(name)); QueryBuilder.rebuildSelectClause(selectQuery, desiredOrder); QueryBuilder.ensureSelectClauseSize(selectQuery, desiredOrder.length); const finalUpdateColumns = matchedAction === 'update' ? updateColumnsOrdered : []; const finalInsertColumns = notMatchedAction === 'insert' ? insertColumnsOrdered : []; return { updateColumns: finalUpdateColumns, insertColumns: finalInsertColumns }; } static rebuildSelectClause(selectQuery, desiredColumns) { const itemMap = new Map(); for (const item of selectQuery.selectClause.items) { const name = QueryBuilder.getSelectItemName(item); if (!name) { continue; } if (!itemMap.has(name)) { itemMap.set(name, item); } } const rebuiltItems = []; const seen = new Set(); for (const column of desiredColumns) { if (seen.has(column)) { continue; } const item = itemMap.get(column); if (!item) { throw new Error(`Column '${column}' not found in select clause.`); } rebuiltItems.push(item); seen.add(column); } if (!rebuiltItems.length) { throw new Error('Unable to rebuild select clause with the requested columns.'); } selectQuery.selectClause.items = rebuiltItems; } static getSelectItemName(item) { if (item.identifier) { return item.identifier.name; } if (item.value instanceof ValueComponent_1.ColumnReference) { return item.value.column.name; } return null; } static ensureSelectClauseSize(selectQuery, expected) { if (selectQuery.selectClause.items.length !== expected) { throw new Error(`Select clause column count (${selectQuery.selectClause.items.length}) does not match expected count (${expected}).`); } } static extractWithClause(selectQuery) { const cteCollector = new CTECollector_1.CTECollector(); const collected = cteCollector.collect(selectQuery); if (collected.length === 0) { return null; } const cteDisabler = new CTEDisabler_1.CTEDisabler(); cteDisabler.execute(selectQuery); return new Clause_1.WithClause(false, collected); } static buildEqualityPredicate(leftAlias, rightAlias, columns) { const uniqueColumns = QueryBuilder.mergeUniqueColumns(columns); if (!uniqueColumns.length) { throw new Error('At least one column is required to build a comparison predicate.'); } let predicate = null; for (const column of uniqueColumns) { const comparison = new ValueComponent_1.BinaryExpression(QueryBuilder.toColumnReference(leftAlias, column), '=', QueryBuilder.toColumnReference(rightAlias, column)); predicate = predicate ? new ValueComponent_1.BinaryExpression(predicate, 'and', comparison) : comparison; } return predicate; } static toColumnReference(alias, column) { return new ValueComponent_1.ColumnReference(alias, column); } static mergeUniqueColumns(columns) { const seen = new Set(); const result = []; for (const column of columns) { if (!seen.has(column)) { seen.add(column); result.push(column); } } return result; } } exports.QueryBuilder = QueryBuilder; //# sourceMappingURL=QueryBuilder.js.map