UNPKG

rawsql-ts

Version:

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

212 lines 10.8 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 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"); /** * 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) 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) { // Create a subquery source from the binary query 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 const q = new SelectQuery_1.SimpleSelectQuery({ selectClause, fromClause }); return CTENormalizer_1.CTENormalizer.normalize(q); } /** * 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) { return new CreateTableQuery_1.CreateTableQuery({ tableName, isTemporary, asSelectQuery: query }); } /** * Converts a SELECT query to an INSERT query (INSERT INTO ... SELECT ...) * @param selectQuery The SELECT query to use as the source * @param tableName The name of the table to insert into * @param columns Optional: array of column names. If omitted, columns are inferred from the selectQuery * @returns An InsertQuery instance */ static buildInsertQuery(selectQuery, tableName) { let cols; const count = selectQuery.selectClause.items.length; // Try to infer columns from the selectQuery const collector = new SelectValueCollector_1.SelectValueCollector(); const items = collector.collect(selectQuery); cols = items.map(item => item.name); if (!cols.length || count !== cols.length) { throw new Error(`Columns cannot be inferred from the selectQuery. ` + `Make sure you are not using wildcards or unnamed columns.\n` + `Select clause column count: ${count}, ` + `Columns with valid names: ${cols.length}\n` + `Detected column names: [${cols.join(", ")}]`); } // Generate SourceExpression (supports only table name, does not support alias or schema) const sourceExpr = SourceExpressionParser_1.SourceExpressionParser.parse(tableName); return new InsertQuery_1.InsertQuery({ insertClause: new Clause_1.InsertClause(sourceExpr, cols), selectQuery: selectQuery }); } /** * Builds an UPDATE query from a SELECT query, table name, and primary key(s). * @param selectQuery The SELECT query providing new values (must select all columns to update and PKs) * @param updateTableExprRaw The table name to update * @param primaryKeys The primary key column name(s) * @returns UpdateQuery instance */ static buildUpdateQuery(selectQuery, selectSourceName, updateTableExprRaw, primaryKeys) { const updateClause = new Clause_1.UpdateClause(SourceExpressionParser_1.SourceExpressionParser.parse(updateTableExprRaw)); const pkArray = Array.isArray(primaryKeys) ? primaryKeys : [primaryKeys]; const selectCollector = new SelectValueCollector_1.SelectValueCollector(); const selectItems = selectCollector.collect(selectQuery); const cteCollector = new CTECollector_1.CTECollector(); const collectedCTEs = cteCollector.collect(selectQuery); const cteDisabler = new CTEDisabler_1.CTEDisabler(); cteDisabler.execute(selectQuery); for (const pk of pkArray) { if (!selectItems.some(item => item.name === pk)) { throw new Error(`Primary key column '${pk}' is not present in selectQuery select list.`); } } const updateSourceName = updateClause.getSourceAliasName(); if (!updateSourceName) { throw new Error(`Source expression does not have an alias. Please provide an alias for the source expression.`); } const setColumns = selectItems.filter(item => !pkArray.includes(item.name)); const setItems = setColumns.map(col => new Clause_1.SetClauseItem(col.name, new ValueComponent_1.ColumnReference(updateSourceName, col.name))); const setClause = new Clause_1.SetClause(setItems); const from = new Clause_1.FromClause(selectQuery.toSource(selectSourceName), null); let where = null; for (const pk of pkArray) { const cond = new ValueComponent_1.BinaryExpression(new ValueComponent_1.ColumnReference(updateSourceName, pk), '=', new ValueComponent_1.ColumnReference(selectSourceName, pk)); where = where ? new ValueComponent_1.BinaryExpression(where, 'and', cond) : cond; } const whereClause = new Clause_1.WhereClause(where); const updateQuery = new UpdateQuery_1.UpdateQuery({ updateClause: updateClause, setClause: setClause, fromClause: from, whereClause: whereClause, withClause: collectedCTEs.length > 0 ? new Clause_1.WithClause(false, collectedCTEs) : undefined, }); return updateQuery; } } exports.QueryBuilder = QueryBuilder; //# sourceMappingURL=QueryBuilder.js.map