rawsql-ts
Version:
High-performance SQL parser and AST analyzer written in TypeScript. Provides fast parsing and advanced transformation capabilities.
915 lines • 177 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SqlPrintTokenParser = exports.PRESETS = exports.ParameterStyle = void 0;
const Clause_1 = require("../models/Clause");
const HintClause_1 = require("../models/HintClause");
const SelectQuery_1 = require("../models/SelectQuery");
const SqlPrintToken_1 = require("../models/SqlPrintToken");
const SelectQueryWithClauseHelper_1 = require("../utils/SelectQueryWithClauseHelper");
const ValueComponent_1 = require("../models/ValueComponent");
const ParameterCollector_1 = require("../transformers/ParameterCollector");
const IdentifierDecorator_1 = require("./IdentifierDecorator");
const ParameterDecorator_1 = require("./ParameterDecorator");
const InsertQuery_1 = require("../models/InsertQuery");
const UpdateQuery_1 = require("../models/UpdateQuery");
const DeleteQuery_1 = require("../models/DeleteQuery");
const CreateTableQuery_1 = require("../models/CreateTableQuery");
const MergeQuery_1 = require("../models/MergeQuery");
const DDLStatements_1 = require("../models/DDLStatements");
var ParameterStyle;
(function (ParameterStyle) {
ParameterStyle["Anonymous"] = "anonymous";
ParameterStyle["Indexed"] = "indexed";
ParameterStyle["Named"] = "named";
})(ParameterStyle || (exports.ParameterStyle = ParameterStyle = {}));
exports.PRESETS = {
mysql: {
identifierEscape: { start: '`', end: '`' },
parameterSymbol: '?',
parameterStyle: ParameterStyle.Anonymous,
constraintStyle: 'mysql',
},
postgres: {
identifierEscape: { start: '"', end: '"' },
parameterSymbol: '$',
parameterStyle: ParameterStyle.Indexed,
castStyle: 'postgres',
constraintStyle: 'postgres',
},
postgresWithNamedParams: {
identifierEscape: { start: '"', end: '"' },
parameterSymbol: ':',
parameterStyle: ParameterStyle.Named,
castStyle: 'postgres',
constraintStyle: 'postgres',
},
sqlserver: {
identifierEscape: { start: '[', end: ']' },
parameterSymbol: '@',
parameterStyle: ParameterStyle.Named,
constraintStyle: 'postgres',
},
sqlite: {
identifierEscape: { start: '"', end: '"' },
parameterSymbol: ':',
parameterStyle: ParameterStyle.Named,
constraintStyle: 'postgres',
},
oracle: {
identifierEscape: { start: '"', end: '"' },
parameterSymbol: ':',
parameterStyle: ParameterStyle.Named,
constraintStyle: 'postgres',
},
clickhouse: {
identifierEscape: { start: '`', end: '`' },
parameterSymbol: '?',
parameterStyle: ParameterStyle.Anonymous,
constraintStyle: 'postgres',
},
firebird: {
identifierEscape: { start: '"', end: '"' },
parameterSymbol: '?',
parameterStyle: ParameterStyle.Anonymous,
},
db2: {
identifierEscape: { start: '"', end: '"' },
parameterSymbol: '?',
parameterStyle: ParameterStyle.Anonymous,
},
snowflake: {
identifierEscape: { start: '"', end: '"' },
parameterSymbol: '?',
parameterStyle: ParameterStyle.Anonymous,
},
cloudspanner: {
identifierEscape: { start: '`', end: '`' },
parameterSymbol: '@',
parameterStyle: ParameterStyle.Named,
},
duckdb: {
identifierEscape: { start: '"', end: '"' },
parameterSymbol: '?',
parameterStyle: ParameterStyle.Anonymous,
},
cockroachdb: {
identifierEscape: { start: '"', end: '"' },
parameterSymbol: '$',
parameterStyle: ParameterStyle.Indexed,
castStyle: 'postgres',
},
athena: {
identifierEscape: { start: '"', end: '"' },
parameterSymbol: '?',
parameterStyle: ParameterStyle.Anonymous,
},
bigquery: {
identifierEscape: { start: '`', end: '`' },
parameterSymbol: '@',
parameterStyle: ParameterStyle.Named,
},
hive: {
identifierEscape: { start: '`', end: '`' },
parameterSymbol: '?',
parameterStyle: ParameterStyle.Anonymous,
},
mariadb: {
identifierEscape: { start: '`', end: '`' },
parameterSymbol: '?',
parameterStyle: ParameterStyle.Anonymous,
},
redshift: {
identifierEscape: { start: '"', end: '"' },
parameterSymbol: '$',
parameterStyle: ParameterStyle.Indexed,
castStyle: 'postgres',
},
flinksql: {
identifierEscape: { start: '`', end: '`' },
parameterSymbol: '?',
parameterStyle: ParameterStyle.Anonymous,
},
mongodb: {
identifierEscape: { start: '"', end: '"' },
parameterSymbol: '?',
parameterStyle: ParameterStyle.Anonymous,
},
};
class SqlPrintTokenParser {
static getSelfHandlingComponentTypes() {
if (!this._selfHandlingComponentTypes) {
this._selfHandlingComponentTypes = new Set([
SelectQuery_1.SimpleSelectQuery.kind,
Clause_1.SelectItem.kind,
ValueComponent_1.CaseKeyValuePair.kind,
ValueComponent_1.SwitchCaseArgument.kind,
ValueComponent_1.ColumnReference.kind,
ValueComponent_1.LiteralValue.kind,
ValueComponent_1.ParameterExpression.kind,
Clause_1.TableSource.kind,
Clause_1.SourceAliasExpression.kind,
ValueComponent_1.TypeValue.kind,
ValueComponent_1.FunctionCall.kind,
ValueComponent_1.IdentifierString.kind,
ValueComponent_1.QualifiedName.kind
]);
}
return this._selfHandlingComponentTypes;
}
constructor(options) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
this.handlers = new Map();
this.index = 1;
this.joinConditionContexts = [];
if (options === null || options === void 0 ? void 0 : options.preset) {
const preset = options.preset;
options = { ...preset, ...options };
}
this.parameterDecorator = new ParameterDecorator_1.ParameterDecorator({
prefix: typeof (options === null || options === void 0 ? void 0 : options.parameterSymbol) === 'string' ? options.parameterSymbol : (_b = (_a = options === null || options === void 0 ? void 0 : options.parameterSymbol) === null || _a === void 0 ? void 0 : _a.start) !== null && _b !== void 0 ? _b : ':',
suffix: typeof (options === null || options === void 0 ? void 0 : options.parameterSymbol) === 'object' ? options.parameterSymbol.end : '',
style: (_c = options === null || options === void 0 ? void 0 : options.parameterStyle) !== null && _c !== void 0 ? _c : 'named'
});
this.identifierDecorator = new IdentifierDecorator_1.IdentifierDecorator({
start: (_e = (_d = options === null || options === void 0 ? void 0 : options.identifierEscape) === null || _d === void 0 ? void 0 : _d.start) !== null && _e !== void 0 ? _e : '"',
end: (_g = (_f = options === null || options === void 0 ? void 0 : options.identifierEscape) === null || _f === void 0 ? void 0 : _f.end) !== null && _g !== void 0 ? _g : '"'
});
this.castStyle = (_h = options === null || options === void 0 ? void 0 : options.castStyle) !== null && _h !== void 0 ? _h : 'standard';
this.constraintStyle = (_j = options === null || options === void 0 ? void 0 : options.constraintStyle) !== null && _j !== void 0 ? _j : 'postgres';
this.normalizeJoinConditionOrder = (_k = options === null || options === void 0 ? void 0 : options.joinConditionOrderByDeclaration) !== null && _k !== void 0 ? _k : false;
this.handlers.set(ValueComponent_1.ValueList.kind, (expr) => this.visitValueList(expr));
this.handlers.set(ValueComponent_1.ColumnReference.kind, (expr) => this.visitColumnReference(expr));
this.handlers.set(ValueComponent_1.QualifiedName.kind, (expr) => this.visitQualifiedName(expr));
this.handlers.set(ValueComponent_1.FunctionCall.kind, (expr) => this.visitFunctionCall(expr));
this.handlers.set(ValueComponent_1.UnaryExpression.kind, (expr) => this.visitUnaryExpression(expr));
this.handlers.set(ValueComponent_1.BinaryExpression.kind, (expr) => this.visitBinaryExpression(expr));
this.handlers.set(ValueComponent_1.LiteralValue.kind, (expr) => this.visitLiteralValue(expr));
this.handlers.set(ValueComponent_1.ParameterExpression.kind, (expr) => this.visitParameterExpression(expr));
this.handlers.set(ValueComponent_1.SwitchCaseArgument.kind, (expr) => this.visitSwitchCaseArgument(expr));
this.handlers.set(ValueComponent_1.CaseKeyValuePair.kind, (expr) => this.visitCaseKeyValuePair(expr));
this.handlers.set(ValueComponent_1.RawString.kind, (expr) => this.visitRawString(expr));
this.handlers.set(ValueComponent_1.IdentifierString.kind, (expr) => this.visitIdentifierString(expr));
this.handlers.set(ValueComponent_1.ParenExpression.kind, (expr) => this.visitParenExpression(expr));
this.handlers.set(ValueComponent_1.CastExpression.kind, (expr) => this.visitCastExpression(expr));
this.handlers.set(ValueComponent_1.CaseExpression.kind, (expr) => this.visitCaseExpression(expr));
this.handlers.set(ValueComponent_1.ArrayExpression.kind, (expr) => this.visitArrayExpression(expr));
this.handlers.set(ValueComponent_1.ArrayQueryExpression.kind, (expr) => this.visitArrayQueryExpression(expr));
this.handlers.set(ValueComponent_1.ArraySliceExpression.kind, (expr) => this.visitArraySliceExpression(expr));
this.handlers.set(ValueComponent_1.ArrayIndexExpression.kind, (expr) => this.visitArrayIndexExpression(expr));
this.handlers.set(ValueComponent_1.BetweenExpression.kind, (expr) => this.visitBetweenExpression(expr));
this.handlers.set(ValueComponent_1.StringSpecifierExpression.kind, (expr) => this.visitStringSpecifierExpression(expr));
this.handlers.set(ValueComponent_1.TypeValue.kind, (expr) => this.visitTypeValue(expr));
this.handlers.set(ValueComponent_1.TupleExpression.kind, (expr) => this.visitTupleExpression(expr));
this.handlers.set(ValueComponent_1.InlineQuery.kind, (expr) => this.visitInlineQuery(expr));
this.handlers.set(ValueComponent_1.WindowFrameExpression.kind, (expr) => this.visitWindowFrameExpression(expr));
this.handlers.set(ValueComponent_1.WindowFrameSpec.kind, (expr) => this.visitWindowFrameSpec(expr));
this.handlers.set(ValueComponent_1.WindowFrameBoundStatic.kind, (expr) => this.visitWindowFrameBoundStatic(expr));
this.handlers.set(ValueComponent_1.WindowFrameBoundaryValue.kind, (expr) => this.visitWindowFrameBoundaryValue(expr));
this.handlers.set(Clause_1.PartitionByClause.kind, (expr) => this.visitPartitionByClause(expr));
this.handlers.set(Clause_1.OrderByClause.kind, (expr) => this.visitOrderByClause(expr));
this.handlers.set(Clause_1.OrderByItem.kind, (expr) => this.visitOrderByItem(expr));
// select
this.handlers.set(Clause_1.SelectItem.kind, (expr) => this.visitSelectItem(expr));
this.handlers.set(Clause_1.SelectClause.kind, (expr) => this.visitSelectClause(expr));
this.handlers.set(Clause_1.Distinct.kind, (expr) => this.visitDistinct(expr));
this.handlers.set(Clause_1.DistinctOn.kind, (expr) => this.visitDistinctOn(expr));
this.handlers.set(HintClause_1.HintClause.kind, (expr) => this.visitHintClause(expr));
// from
this.handlers.set(Clause_1.TableSource.kind, (expr) => this.visitTableSource(expr));
this.handlers.set(Clause_1.FunctionSource.kind, (expr) => this.visitFunctionSource(expr));
this.handlers.set(Clause_1.SourceExpression.kind, (expr) => this.visitSourceExpression(expr));
this.handlers.set(Clause_1.SourceAliasExpression.kind, (expr) => this.visitSourceAliasExpression(expr));
this.handlers.set(Clause_1.FromClause.kind, (expr) => this.visitFromClause(expr));
this.handlers.set(Clause_1.JoinClause.kind, (expr) => this.visitJoinClause(expr));
this.handlers.set(Clause_1.JoinOnClause.kind, (expr) => this.visitJoinOnClause(expr));
this.handlers.set(Clause_1.JoinUsingClause.kind, (expr) => this.visitJoinUsingClause(expr));
// where
this.handlers.set(Clause_1.WhereClause.kind, (expr) => this.visitWhereClause(expr));
// group
this.handlers.set(Clause_1.GroupByClause.kind, (expr) => this.visitGroupByClause(expr));
this.handlers.set(Clause_1.HavingClause.kind, (expr) => this.visitHavingClause(expr));
this.handlers.set(Clause_1.WindowsClause.kind, (expr) => this.visitWindowClause(expr));
this.handlers.set(Clause_1.WindowFrameClause.kind, (expr) => this.visitWindowFrameClause(expr));
this.handlers.set(Clause_1.LimitClause.kind, (expr) => this.visitLimitClause(expr));
this.handlers.set(Clause_1.OffsetClause.kind, (expr) => this.visitOffsetClause(expr));
this.handlers.set(Clause_1.FetchClause.kind, (expr) => this.visitFetchClause(expr));
this.handlers.set(Clause_1.FetchExpression.kind, (expr) => this.visitFetchExpression(expr));
this.handlers.set(Clause_1.ForClause.kind, (expr) => this.visitForClause(expr));
// With
this.handlers.set(Clause_1.WithClause.kind, (expr) => this.visitWithClause(expr));
this.handlers.set(Clause_1.CommonTable.kind, (expr) => this.visitCommonTable(expr));
// Query
this.handlers.set(SelectQuery_1.SimpleSelectQuery.kind, (expr) => this.visitSimpleQuery(expr));
this.handlers.set(Clause_1.SubQuerySource.kind, (expr) => this.visitSubQuerySource(expr));
this.handlers.set(SelectQuery_1.BinarySelectQuery.kind, (expr) => this.visitBinarySelectQuery(expr));
this.handlers.set(SelectQuery_1.ValuesQuery.kind, (expr) => this.visitValuesQuery(expr));
this.handlers.set(ValueComponent_1.TupleExpression.kind, (expr) => this.visitTupleExpression(expr));
this.handlers.set(InsertQuery_1.InsertQuery.kind, (expr) => this.visitInsertQuery(expr));
this.handlers.set(Clause_1.InsertClause.kind, (expr) => this.visitInsertClause(expr));
this.handlers.set(UpdateQuery_1.UpdateQuery.kind, (expr) => this.visitUpdateQuery(expr));
this.handlers.set(Clause_1.UpdateClause.kind, (expr) => this.visitUpdateClause(expr));
this.handlers.set(DeleteQuery_1.DeleteQuery.kind, (expr) => this.visitDeleteQuery(expr));
this.handlers.set(Clause_1.DeleteClause.kind, (expr) => this.visitDeleteClause(expr));
this.handlers.set(Clause_1.UsingClause.kind, (expr) => this.visitUsingClause(expr));
this.handlers.set(Clause_1.SetClause.kind, (expr) => this.visitSetClause(expr));
this.handlers.set(Clause_1.SetClauseItem.kind, (expr) => this.visitSetClauseItem(expr));
this.handlers.set(Clause_1.ReturningClause.kind, (expr) => this.visitReturningClause(expr));
this.handlers.set(CreateTableQuery_1.CreateTableQuery.kind, (expr) => this.visitCreateTableQuery(expr));
this.handlers.set(CreateTableQuery_1.TableColumnDefinition.kind, (expr) => this.visitTableColumnDefinition(expr));
this.handlers.set(CreateTableQuery_1.ColumnConstraintDefinition.kind, (expr) => this.visitColumnConstraintDefinition(expr));
this.handlers.set(CreateTableQuery_1.TableConstraintDefinition.kind, (expr) => this.visitTableConstraintDefinition(expr));
this.handlers.set(CreateTableQuery_1.ReferenceDefinition.kind, (expr) => this.visitReferenceDefinition(expr));
this.handlers.set(DDLStatements_1.CreateIndexStatement.kind, (expr) => this.visitCreateIndexStatement(expr));
this.handlers.set(DDLStatements_1.IndexColumnDefinition.kind, (expr) => this.visitIndexColumnDefinition(expr));
this.handlers.set(DDLStatements_1.CreateSequenceStatement.kind, (expr) => this.visitCreateSequenceStatement(expr));
this.handlers.set(DDLStatements_1.AlterSequenceStatement.kind, (expr) => this.visitAlterSequenceStatement(expr));
this.handlers.set(DDLStatements_1.DropTableStatement.kind, (expr) => this.visitDropTableStatement(expr));
this.handlers.set(DDLStatements_1.DropIndexStatement.kind, (expr) => this.visitDropIndexStatement(expr));
this.handlers.set(DDLStatements_1.AlterTableStatement.kind, (expr) => this.visitAlterTableStatement(expr));
this.handlers.set(DDLStatements_1.AlterTableAddConstraint.kind, (expr) => this.visitAlterTableAddConstraint(expr));
this.handlers.set(DDLStatements_1.AlterTableDropConstraint.kind, (expr) => this.visitAlterTableDropConstraint(expr));
this.handlers.set(DDLStatements_1.AlterTableAddColumn.kind, (expr) => this.visitAlterTableAddColumn(expr));
this.handlers.set(DDLStatements_1.AlterTableDropColumn.kind, (expr) => this.visitAlterTableDropColumn(expr));
this.handlers.set(DDLStatements_1.AlterTableAlterColumnDefault.kind, (expr) => this.visitAlterTableAlterColumnDefault(expr));
this.handlers.set(DDLStatements_1.DropConstraintStatement.kind, (expr) => this.visitDropConstraintStatement(expr));
this.handlers.set(DDLStatements_1.ExplainStatement.kind, (expr) => this.visitExplainStatement(expr));
this.handlers.set(DDLStatements_1.AnalyzeStatement.kind, (expr) => this.visitAnalyzeStatement(expr));
this.handlers.set(MergeQuery_1.MergeQuery.kind, (expr) => this.visitMergeQuery(expr));
this.handlers.set(MergeQuery_1.MergeWhenClause.kind, (expr) => this.visitMergeWhenClause(expr));
this.handlers.set(MergeQuery_1.MergeUpdateAction.kind, (expr) => this.visitMergeUpdateAction(expr));
this.handlers.set(MergeQuery_1.MergeDeleteAction.kind, (expr) => this.visitMergeDeleteAction(expr));
this.handlers.set(MergeQuery_1.MergeInsertAction.kind, (expr) => this.visitMergeInsertAction(expr));
this.handlers.set(MergeQuery_1.MergeDoNothingAction.kind, (expr) => this.visitMergeDoNothingAction(expr));
}
/**
* Pretty-prints a BinarySelectQuery (e.g., UNION, INTERSECT, EXCEPT).
* This will recursively print left and right queries, separated by the operator.
* @param arg BinarySelectQuery
*/
visitBinarySelectQuery(arg) {
const token = new SqlPrintToken_1.SqlPrintToken(SqlPrintToken_1.SqlPrintTokenType.container, '');
// Handle positioned comments for BinarySelectQuery (unified spec)
if (arg.positionedComments && arg.positionedComments.length > 0) {
this.addPositionedCommentsToToken(token, arg);
// Clear positioned comments to prevent duplicate processing
arg.positionedComments = null;
}
else if (arg.headerComments && arg.headerComments.length > 0) {
if (this.shouldMergeHeaderComments(arg.headerComments)) {
const mergedHeaderComment = this.createHeaderMultiLineCommentBlock(arg.headerComments);
token.innerTokens.push(mergedHeaderComment);
}
else {
const headerCommentBlocks = this.createCommentBlocks(arg.headerComments, true);
token.innerTokens.push(...headerCommentBlocks);
}
token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
}
token.innerTokens.push(this.visit(arg.left));
token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
token.innerTokens.push(new SqlPrintToken_1.SqlPrintToken(SqlPrintToken_1.SqlPrintTokenType.keyword, arg.operator.value, SqlPrintToken_1.SqlPrintTokenContainerType.BinarySelectQueryOperator));
token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
token.innerTokens.push(this.visit(arg.right));
return token;
}
/**
* Returns an array of tokens representing a comma followed by a space.
* This is a common pattern in SQL pretty-printing.
*/
static commaSpaceTokens() {
return [SqlPrintTokenParser.COMMA_TOKEN, SqlPrintTokenParser.SPACE_TOKEN];
}
static argumentCommaSpaceTokens() {
return [SqlPrintTokenParser.ARGUMENT_SPLIT_COMMA_TOKEN, SqlPrintTokenParser.SPACE_TOKEN];
}
visitQualifiedName(arg) {
const token = new SqlPrintToken_1.SqlPrintToken(SqlPrintToken_1.SqlPrintTokenType.container, '', SqlPrintToken_1.SqlPrintTokenContainerType.QualifiedName);
if (arg.namespaces) {
for (let i = 0; i < arg.namespaces.length; i++) {
token.innerTokens.push(arg.namespaces[i].accept(this));
token.innerTokens.push(SqlPrintTokenParser.DOT_TOKEN);
}
}
// Handle name and its comments carefully
// We need to prevent double processing by temporarily clearing the name's comments,
// then process them at the QualifiedName level
const originalNameComments = arg.name.positionedComments;
const originalNameLegacyComments = arg.name.comments;
// Temporarily clear name's comments to prevent double processing
arg.name.positionedComments = null;
arg.name.comments = null;
const nameToken = arg.name.accept(this);
token.innerTokens.push(nameToken);
// Restore original comments
arg.name.positionedComments = originalNameComments;
arg.name.comments = originalNameLegacyComments;
// Apply the name's comments to the qualified name token
if (this.hasPositionedComments(arg.name) || this.hasLegacyComments(arg.name)) {
this.addComponentComments(token, arg.name);
}
// Also handle any comments directly on the QualifiedName itself
this.addComponentComments(token, arg);
return token;
}
visitPartitionByClause(arg) {
// Print as: partition by ...
const token = new SqlPrintToken_1.SqlPrintToken(SqlPrintToken_1.SqlPrintTokenType.keyword, 'partition by', SqlPrintToken_1.SqlPrintTokenContainerType.PartitionByClause);
token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
token.innerTokens.push(this.visit(arg.value));
return token;
}
visitOrderByClause(arg) {
// Print as: order by ...
const token = new SqlPrintToken_1.SqlPrintToken(SqlPrintToken_1.SqlPrintTokenType.keyword, 'order by', SqlPrintToken_1.SqlPrintTokenContainerType.OrderByClause);
token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
for (let i = 0; i < arg.order.length; i++) {
if (i > 0)
token.innerTokens.push(...SqlPrintTokenParser.commaSpaceTokens());
token.innerTokens.push(this.visit(arg.order[i]));
}
return token;
}
/**
* Print an OrderByItem (expression [asc|desc] [nulls first|last])
*/
visitOrderByItem(arg) {
// arg: OrderByItem
const token = new SqlPrintToken_1.SqlPrintToken(SqlPrintToken_1.SqlPrintTokenType.container, '', SqlPrintToken_1.SqlPrintTokenContainerType.OrderByItem);
token.innerTokens.push(this.visit(arg.value));
if (arg.sortDirection && arg.sortDirection !== Clause_1.SortDirection.Ascending) {
token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
token.innerTokens.push(new SqlPrintToken_1.SqlPrintToken(SqlPrintToken_1.SqlPrintTokenType.keyword, 'desc'));
}
if (arg.nullsPosition) {
if (arg.nullsPosition === Clause_1.NullsSortDirection.First) {
token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
token.innerTokens.push(new SqlPrintToken_1.SqlPrintToken(SqlPrintToken_1.SqlPrintTokenType.keyword, 'nulls first'));
}
else if (arg.nullsPosition === Clause_1.NullsSortDirection.Last) {
token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
token.innerTokens.push(new SqlPrintToken_1.SqlPrintToken(SqlPrintToken_1.SqlPrintTokenType.keyword, 'nulls last'));
}
}
return token;
}
parse(arg) {
// reset parameter index before parsing
this.index = 1;
const token = this.visit(arg);
const paramsRaw = ParameterCollector_1.ParameterCollector.collect(arg).sort((a, b) => { var _a, _b; return ((_a = a.index) !== null && _a !== void 0 ? _a : 0) - ((_b = b.index) !== null && _b !== void 0 ? _b : 0); });
const style = this.parameterDecorator.style;
if (style === ParameterStyle.Named) {
// Named: { name: value, ... }
const paramsObj = {};
for (const p of paramsRaw) {
const key = p.name.value;
if (paramsObj.hasOwnProperty(key)) {
if (paramsObj[key] !== p.value) {
throw new Error(`Duplicate parameter name '${key}' with different values detected during query composition.`);
}
// If value is the same, skip (already set)
continue;
}
paramsObj[key] = p.value;
}
return { token, params: paramsObj };
}
else if (style === ParameterStyle.Indexed) {
// Indexed: [value1, value2, ...] (sorted by index)
const paramsArr = paramsRaw.map(p => p.value);
return { token, params: paramsArr };
}
else if (style === ParameterStyle.Anonymous) {
// Anonymous: [value1, value2, ...] (sorted by index, name is empty)
const paramsArr = paramsRaw.map(p => p.value);
return { token, params: paramsArr };
}
// Fallback (just in case)
return { token, params: [] };
}
/**
* Check if a component handles its own comments
*/
componentHandlesOwnComments(component) {
// First check if component has a handlesOwnComments method
if ('handlesOwnComments' in component && typeof component.handlesOwnComments === 'function') {
return component.handlesOwnComments();
}
return SqlPrintTokenParser.getSelfHandlingComponentTypes().has(component.getKind());
}
visit(arg) {
const handler = this.handlers.get(arg.getKind());
if (handler) {
const token = handler(arg);
if (!this.componentHandlesOwnComments(arg)) {
this.addComponentComments(token, arg);
}
return token;
}
throw new Error(`[SqlPrintTokenParser] No handler for kind: ${arg.getKind().toString()}`);
}
/**
* Check if a component has positioned comments
*/
hasPositionedComments(component) {
var _a, _b;
return ((_b = (_a = component.positionedComments) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 0;
}
/**
* Check if a component has legacy comments
*/
hasLegacyComments(component) {
var _a, _b;
return ((_b = (_a = component.comments) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 0;
}
/**
* Centralized comment handling - checks positioned comments first, falls back to legacy
*/
addComponentComments(token, component) {
if (this.hasPositionedComments(component)) {
this.addPositionedCommentsToToken(token, component);
}
else if (this.hasLegacyComments(component)) {
this.addCommentsToToken(token, component.comments);
}
}
/**
* Adds positioned comment tokens to a SqlPrintToken for inline formatting
*/
addPositionedCommentsToToken(token, component) {
if (!this.hasPositionedComments(component)) {
return;
}
// Handle 'before' comments - add inline at the beginning with spaces
const beforeComments = component.getPositionedComments('before');
if (beforeComments.length > 0) {
const commentBlocks = this.createCommentBlocks(beforeComments);
for (let i = commentBlocks.length - 1; i >= 0; i--) {
token.innerTokens.unshift(commentBlocks[i]);
}
}
// Handle 'after' comments - add inline after the main content
const afterComments = component.getPositionedComments('after');
if (afterComments.length > 0) {
const commentBlocks = this.createCommentBlocks(afterComments);
// Append after comments with spaces for inline formatting
for (const commentBlock of commentBlocks) {
token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
token.innerTokens.push(commentBlock);
}
}
// Clear positioned comments to prevent duplicate processing (unified spec)
// Only clear for specific component types that are known to have duplication issues
const componentsWithDuplicationIssues = [
SqlPrintToken_1.SqlPrintTokenContainerType.CaseExpression,
SqlPrintToken_1.SqlPrintTokenContainerType.SwitchCaseArgument,
SqlPrintToken_1.SqlPrintTokenContainerType.CaseKeyValuePair,
SqlPrintToken_1.SqlPrintTokenContainerType.SelectClause, // SELECT clauses have manual + automatic processing
SqlPrintToken_1.SqlPrintTokenContainerType.LiteralValue,
SqlPrintToken_1.SqlPrintTokenContainerType.IdentifierString,
SqlPrintToken_1.SqlPrintTokenContainerType.DistinctOn,
SqlPrintToken_1.SqlPrintTokenContainerType.SourceAliasExpression,
SqlPrintToken_1.SqlPrintTokenContainerType.SimpleSelectQuery,
SqlPrintToken_1.SqlPrintTokenContainerType.WhereClause // WHERE clauses also have duplication issues
];
if (token.containerType && componentsWithDuplicationIssues.includes(token.containerType)) {
component.positionedComments = null;
}
}
/**
* Adds comment tokens to a SqlPrintToken based on the comments array
*/
addCommentsToToken(token, comments) {
if (!(comments === null || comments === void 0 ? void 0 : comments.length)) {
return;
}
const commentBlocks = this.createCommentBlocks(comments);
this.insertCommentBlocksWithSpacing(token, commentBlocks);
}
/**
* Creates inline comment sequence for multiple comments without newlines
*/
createInlineCommentSequence(comments) {
const commentTokens = [];
for (let i = 0; i < comments.length; i++) {
const comment = comments[i];
if (comment.trim()) {
// Add comment token directly
const commentToken = new SqlPrintToken_1.SqlPrintToken(SqlPrintToken_1.SqlPrintTokenType.comment, this.formatComment(comment));
commentTokens.push(commentToken);
// Add space between comments (except after last comment)
if (i < comments.length - 1) {
const spaceToken = new SqlPrintToken_1.SqlPrintToken(SqlPrintToken_1.SqlPrintTokenType.space, ' ');
commentTokens.push(spaceToken);
}
}
}
return commentTokens;
}
/**
* Creates CommentBlock containers for the given comments.
* Each CommentBlock contains: Comment -> CommentNewline -> Space.
* @param comments Raw comment strings to convert into CommentBlock tokens.
* @param isHeaderComment Marks the generated blocks as originating from header comments when true.
*/
createCommentBlocks(comments, isHeaderComment = false) {
// Create individual comment blocks for each comment entry
const commentBlocks = [];
for (const comment of comments) {
// Accept comments that have content after trim OR are separator lines OR are empty (for structure preservation)
const trimmed = comment.trim();
const isSeparatorLine = /^[-=_+*#]+$/.test(trimmed);
if (trimmed || isSeparatorLine || comment === '') {
commentBlocks.push(this.createSingleCommentBlock(comment, isHeaderComment));
}
}
return commentBlocks;
}
/**
* Determines if a comment should be merged with consecutive comments
*/
shouldMergeComment(trimmed) {
const isSeparatorLine = /^[-=_+*#]+$/.test(trimmed);
// Don't merge line comments unless they are separator-only lines
if (!isSeparatorLine && trimmed.startsWith('--')) {
return false;
}
// Don't merge if it's already a proper multi-line block comment
if (trimmed.startsWith('/*') && trimmed.endsWith('*/')) {
const inner = trimmed.slice(2, -2).trim();
if (!inner) {
return false;
}
if (trimmed.includes('\n')) {
return false;
}
}
// Merge all other content including separator lines, plain text, and single-line block comments
// Separator lines within comment blocks should be merged together
return true;
}
/**
* Creates a multi-line block comment structure from consecutive comments
* Returns a CommentBlock containing multiple comment lines for proper LinePrinter integration
*/
/**
* Creates a single CommentBlock with the standard structure:
* Comment -> CommentNewline -> Space
*
* This structure supports both formatting modes:
* - Multiline mode: Comment + newline (space is filtered as leading space)
* - Oneliner mode: Comment + space (commentNewline is skipped)
*/
createSingleCommentBlock(comment, isHeaderComment = false) {
const commentBlock = new SqlPrintToken_1.SqlPrintToken(SqlPrintToken_1.SqlPrintTokenType.container, '', SqlPrintToken_1.SqlPrintTokenContainerType.CommentBlock);
if (isHeaderComment) {
commentBlock.markAsHeaderComment();
}
// Add comment token - preserve original format for line comments
const commentToken = new SqlPrintToken_1.SqlPrintToken(SqlPrintToken_1.SqlPrintTokenType.comment, this.formatComment(comment));
commentBlock.innerTokens.push(commentToken);
// Add conditional newline token for multiline mode
const commentNewlineToken = new SqlPrintToken_1.SqlPrintToken(SqlPrintToken_1.SqlPrintTokenType.commentNewline, '');
commentBlock.innerTokens.push(commentNewlineToken);
// Add space token for oneliner mode spacing
const spaceToken = new SqlPrintToken_1.SqlPrintToken(SqlPrintToken_1.SqlPrintTokenType.space, ' ');
commentBlock.innerTokens.push(spaceToken);
return commentBlock;
}
/**
* Formats a comment, preserving line comment format for -- comments
* and converting others to block format for safety
*/
formatComment(comment) {
const trimmed = comment.trim();
if (!trimmed) {
return '/* */';
}
const isSeparatorLine = /^[-=_+*#]+$/.test(trimmed);
if (isSeparatorLine) {
return this.formatBlockComment(trimmed);
}
if (trimmed.startsWith('--')) {
return this.formatLineComment(trimmed.slice(2));
}
if (trimmed.startsWith('/*') && trimmed.endsWith('*/')) {
return this.formatBlockComment(trimmed);
}
return this.formatBlockComment(trimmed);
}
/**
* Inserts comment blocks into a token and handles spacing logic.
* Adds separator spaces for clause-level containers and manages duplicate space removal.
*/
insertCommentBlocksWithSpacing(token, commentBlocks) {
// For SelectItem, append comment blocks after ensuring spacing
if (token.containerType === SqlPrintToken_1.SqlPrintTokenContainerType.SelectItem) {
if (token.innerTokens.length > 0) {
const lastToken = token.innerTokens[token.innerTokens.length - 1];
if (lastToken.type !== SqlPrintToken_1.SqlPrintTokenType.space) {
token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
}
}
token.innerTokens.push(...commentBlocks);
return;
}
// Special handling for SelectClause to add space between keyword and comment
if (token.containerType === SqlPrintToken_1.SqlPrintTokenContainerType.SelectClause) {
// For SelectClause, comments need to be inserted after the keyword with a space separator
// Current structure: [keyword text, space, other tokens...]
// Desired structure: [keyword text, space, comments, space, other tokens...]
token.innerTokens.unshift(SqlPrintTokenParser.SPACE_TOKEN, ...commentBlocks);
return;
}
// Special handling for IdentifierString to add space before comment
if (token.containerType === SqlPrintToken_1.SqlPrintTokenContainerType.IdentifierString) {
if (token.innerTokens.length > 0) {
const lastToken = token.innerTokens[token.innerTokens.length - 1];
if (lastToken.type !== SqlPrintToken_1.SqlPrintTokenType.space) {
token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
}
}
token.innerTokens.push(...commentBlocks);
return;
}
token.innerTokens.unshift(...commentBlocks);
// Add a separator space after comments only for certain container types
// where comments need to be separated from main content
const needsSeparatorSpace = this.shouldAddSeparatorSpace(token.containerType);
if (needsSeparatorSpace) {
const separatorSpace = new SqlPrintToken_1.SqlPrintToken(SqlPrintToken_1.SqlPrintTokenType.space, ' ');
token.innerTokens.splice(commentBlocks.length, 0, separatorSpace);
// Remove the original space token after our separator if it exists
// This prevents duplicate spaces when comments are added
if (token.innerTokens.length > commentBlocks.length + 1 &&
token.innerTokens[commentBlocks.length + 1].type === SqlPrintToken_1.SqlPrintTokenType.space) {
token.innerTokens.splice(commentBlocks.length + 1, 1);
}
}
else {
// For containers that don't need separator space, still remove duplicate spaces
if (token.innerTokens.length > commentBlocks.length &&
token.innerTokens[commentBlocks.length].type === SqlPrintToken_1.SqlPrintTokenType.space) {
token.innerTokens.splice(commentBlocks.length, 1);
}
}
}
/**
* Handles positioned comments for ParenExpression with special spacing rules.
* ParenExpression comments should be adjacent to parentheses without separator spaces.
*/
addPositionedCommentsToParenExpression(token, component) {
if (!component.positionedComments) {
return;
}
// For ParenExpression: (/* comment */ content /* comment */)
// Comments should be placed immediately after opening paren and before closing paren
// Handle 'before' comments - place after opening parenthesis without space
const beforeComments = component.getPositionedComments('before');
if (beforeComments.length > 0) {
const commentBlocks = this.createCommentBlocks(beforeComments);
// Insert after opening paren (index 1) without separator space
let insertIndex = 1;
for (const commentBlock of commentBlocks) {
token.innerTokens.splice(insertIndex, 0, commentBlock);
insertIndex++;
}
}
// Handle 'after' comments - place before closing parenthesis without space
const afterComments = component.getPositionedComments('after');
if (afterComments.length > 0) {
const commentBlocks = this.createCommentBlocks(afterComments);
const closingIndex = token.innerTokens.length - 1;
let insertIndex = closingIndex + 1;
for (const commentBlock of commentBlocks) {
token.innerTokens.splice(insertIndex, 0, SqlPrintTokenParser.SPACE_TOKEN, commentBlock);
insertIndex += 2;
}
}
}
/**
* Determines whether a separator space should be added after comments for the given container type.
*
* Clause-level containers (SELECT, FROM, WHERE, etc.) need separator spaces because:
* - Comments appear before the main clause content
* - A space is needed to separate comment block from SQL tokens
*
* Item-level containers (SelectItem, etc.) don't need separator spaces because:
* - Comments are inline with the item content
* - Spacing is handled by existing token structure
*/
shouldAddSeparatorSpace(containerType) {
return this.isClauseLevelContainer(containerType);
}
/**
* Checks if the container type represents a SQL clause (as opposed to an item within a clause).
*/
isClauseLevelContainer(containerType) {
switch (containerType) {
case SqlPrintToken_1.SqlPrintTokenContainerType.SelectClause:
case SqlPrintToken_1.SqlPrintTokenContainerType.FromClause:
case SqlPrintToken_1.SqlPrintTokenContainerType.WhereClause:
case SqlPrintToken_1.SqlPrintTokenContainerType.GroupByClause:
case SqlPrintToken_1.SqlPrintTokenContainerType.HavingClause:
case SqlPrintToken_1.SqlPrintTokenContainerType.OrderByClause:
case SqlPrintToken_1.SqlPrintTokenContainerType.LimitClause:
case SqlPrintToken_1.SqlPrintTokenContainerType.OffsetClause:
case SqlPrintToken_1.SqlPrintTokenContainerType.WithClause:
case SqlPrintToken_1.SqlPrintTokenContainerType.SimpleSelectQuery:
return true;
default:
return false;
}
}
/**
* Formats a comment string as a block comment with security sanitization.
* Prevents SQL injection by removing dangerous comment sequences.
*/
formatBlockComment(comment) {
const hasDelimiters = comment.startsWith('/*') && comment.endsWith('*/');
const rawContent = hasDelimiters ? comment.slice(2, -2) : comment;
const escapedContent = this.escapeCommentDelimiters(rawContent);
const normalized = escapedContent.replace(/\r?\n/g, '\n');
const lines = normalized
.split('\n')
.map(line => line.replace(/\s+/g, ' ').trim())
.filter(line => line.length > 0);
if (lines.length === 0) {
return '/* */';
}
const isSeparatorLine = lines.length === 1 && /^[-=_+*#]+$/.test(lines[0]);
if (!hasDelimiters) {
// Flatten free-form comments to a single block to avoid leaking multi-line structures.
if (isSeparatorLine) {
return `/* ${lines[0]} */`;
}
const flattened = lines.join(' ');
return `/* ${flattened} */`;
}
if (isSeparatorLine || lines.length === 1) {
return `/* ${lines[0]} */`;
}
const body = lines.map(line => ` ${line}`).join('\n');
return `/*\n${body}\n*/`;
}
shouldMergeHeaderComments(comments) {
if (comments.length <= 1) {
return false;
}
return comments.some(comment => {
const trimmed = comment.trim();
return /^[-=_+*#]{3,}$/.test(trimmed) || trimmed.startsWith('- ') || trimmed.startsWith('* ');
});
}
createHeaderMultiLineCommentBlock(headerComments) {
const commentBlock = new SqlPrintToken_1.SqlPrintToken(SqlPrintToken_1.SqlPrintTokenType.container, '', SqlPrintToken_1.SqlPrintTokenContainerType.CommentBlock);
commentBlock.markAsHeaderComment();
if (headerComments.length === 0) {
const commentToken = new SqlPrintToken_1.SqlPrintToken(SqlPrintToken_1.SqlPrintTokenType.comment, '/* */');
commentBlock.innerTokens.push(commentToken);
}
else {
const openToken = new SqlPrintToken_1.SqlPrintToken(SqlPrintToken_1.SqlPrintTokenType.comment, '/*');
commentBlock.innerTokens.push(openToken);
commentBlock.innerTokens.push(new SqlPrintToken_1.SqlPrintToken(SqlPrintToken_1.SqlPrintTokenType.commentNewline, ''));
for (const line of headerComments) {
const sanitized = this.escapeCommentDelimiters(line);
const lineToken = new SqlPrintToken_1.SqlPrintToken(SqlPrintToken_1.SqlPrintTokenType.comment, ` ${sanitized}`);
commentBlock.innerTokens.push(lineToken);
commentBlock.innerTokens.push(new SqlPrintToken_1.SqlPrintToken(SqlPrintToken_1.SqlPrintTokenType.commentNewline, ''));
}
const closeToken = new SqlPrintToken_1.SqlPrintToken(SqlPrintToken_1.SqlPrintTokenType.comment, '*/');
commentBlock.innerTokens.push(closeToken);
}
commentBlock.innerTokens.push(new SqlPrintToken_1.SqlPrintToken(SqlPrintToken_1.SqlPrintTokenType.commentNewline, ''));
commentBlock.innerTokens.push(new SqlPrintToken_1.SqlPrintToken(SqlPrintToken_1.SqlPrintTokenType.space, ' '));
return commentBlock;
}
/**
* Formats text as a single-line comment while sanitizing unsafe sequences.
*/
formatLineComment(content) {
// Normalize content to a single line and remove dangerous sequences
const sanitized = this.sanitizeLineCommentContent(content);
if (!sanitized) {
return '--';
}
return `-- ${sanitized}`;
}
/**
* Sanitizes content intended for a single-line comment.
*/
sanitizeLineCommentContent(content) {
// Replace comment delimiters to avoid nested comment injection
let sanitized = this.escapeCommentDelimiters(content)
.replace(/\r?\n/g, ' ')
.replace(/\u2028|\u2029/g, ' ')
.replace(/\s+/g, ' ')
.trim();
if (sanitized.startsWith('--')) {
sanitized = sanitized.slice(2).trimStart();
}
return sanitized;
}
escapeCommentDelimiters(content) {
return content
.replace(/\/\*/g, '\\/\\*')
.replace(/\*\//g, '*\\/');
}
visitValueList(arg) {
const token = new SqlPrintToken_1.SqlPrintToken(SqlPrintToken_1.SqlPrintTokenType.container, '', SqlPrintToken_1.SqlPrintTokenContainerType.ValueList);
for (let i = 0; i < arg.values.length; i++) {
if (i > 0) {
token.innerTokens.push(...SqlPrintTokenParser.argumentCommaSpaceTokens());
}
token.innerTokens.push(this.visit(arg.values[i]));
}
return token;
}
visitColumnReference(arg) {
const token = new SqlPrintToken_1.SqlPrintToken(SqlPrintToken_1.SqlPrintTokenType.container, '', SqlPrintToken_1.SqlPrintTokenContainerType.ColumnReference);
token.innerTokens.push(arg.qualifiedName.accept(this));
this.addComponentComments(token, arg);
return token;
}
visitFunctionCall(arg) {
const token = new SqlPrintToken_1.SqlPrintToken(SqlPrintToken_1.SqlPrintTokenType.container, '', SqlPrintToken_1.SqlPrintTokenContainerType.FunctionCall);
token.innerTokens.push(arg.qualifiedName.accept(this));
token.innerTokens.push(SqlPrintTokenParser.PAREN_OPEN_TOKEN);
if (arg.argument) {
this.relocateGroupingSetComments(arg);
token.innerTokens.push(this.visit(arg.argument));
}
if (arg.internalOrderBy) {
token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
token.innerTokens.push(this.visit(arg.internalOrderBy));
}
// Use FunctionCall comments if available, otherwise use static token
if (arg.comments && arg.comments.length > 0) {
const closingParenToken = new SqlPrintToken_1.SqlPrintToken(SqlPrintToken_1.SqlPrintTokenType.parenthesis, ')');
this.addCommentsToToken(closingParenToken, arg.comments);
token.innerTokens.push(closingParenToken);
// Clear the comments from arg to prevent duplicate output by the general comment handler
arg.comments = null;
}
else {
token.innerTokens.push(SqlPrintTokenParser.PAREN_CLOSE_TOKEN);
}
if (arg.filterCondition) {
// Emit FILTER clause so the aggregate preserves its predicate.
token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
token.innerTokens.push(new SqlPrintToken_1.SqlPrintToken(SqlPrintToken_1.SqlPrintTokenType.keyword, 'filter'));
token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
token.innerTokens.push(SqlPrintTokenParser.PAREN_OPEN_TOKEN);
token.innerTokens.push(new SqlPrintToken_1.SqlPrintToken(SqlPrintToken_1.SqlPrintTokenType.keyword, 'where'));
token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
token.innerTokens.push(this.visit(arg.filterCondition));
token.innerTokens.push(SqlPrintTokenParser.PAREN_CLOSE_TOKEN);
}
if (arg.withOrdinality) {
token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
token.innerTokens.push(new SqlPrintToken_1.SqlPrintToken(SqlPrintToken_1.SqlPrintTokenType.keyword, 'with ordinality'));
}
if (arg.over) {
token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
token.innerTokens.push(new SqlPrintToken_1.SqlPrintToken(SqlPrintToken_1.SqlPrintTokenTyp