/** * SQLParser — Unified parser for MemberJunction's SQL superset * * Parses SQL that may contain Nunjucks templates ({{ }}, {% %}), composition * tokens ({{query:"..."}}), and standard SQL into a structured AST. * * Three-pass pipeline: * 1. MJLexer tokenizes MJ extensions with position tracking * 2. MJPlaceholderSubstitution replaces tokens with SQL-safe placeholders * 3. node-sql-parser produces the standard SQL AST * * The result contains both the SQL AST and the MJ token structure, * enabling SQL-level analysis and faithful MJ SQL reconstruction. */ import NodeSqlParser from 'node-sql-parser'; import type { SQLParserDialect } from '@memberjunction/sql-dialect'; import { type RowCapInfo } from './ASTDialectAdapter.js'; import { MJToken, MJTemplateExpr, MJCompositionRef, MJConditionalBlock, MJFilter, PlaceholderEntry, PlaceholderSubstitutionResult, MJParseResult, MJASTWalkResult } from './mj-ast-types.js'; /** Result of Astify — the full parsed representation of MJ SQL */ export interface MJAstifyResult { /** The node-sql-parser AST of the placeholder-substituted SQL (null if parsing failed) */ ast: NodeSqlParser.AST | NodeSqlParser.AST[] | null; /** MJ token analysis with boolean flags */ mjParse: MJParseResult; /** Map from placeholder string → original MJ token */ positionMap: Map; /** Tokens that were stripped (block tags, comments) */ strippedTokens: MJToken[]; /** The clean SQL that was parsed (for debugging) */ cleanSQL: string; /** Whether AST parsing succeeded */ astParsed: boolean; /** The SQL dialect used */ dialect: string; } /** A table/view reference extracted from SQL */ export interface SQLTableReference { /** The table or view name as it appears in SQL */ TableName: string; /** The schema name, defaults to 'dbo' if unspecified */ SchemaName: string; /** The alias used in the query, or the table name if no alias */ Alias: string; } /** A column reference extracted from SQL */ export interface SQLColumnReference { /** Column name */ ColumnName: string; /** Table alias or name prefix (e.g., "t" in "t.Name"), or null if unqualified */ TableQualifier: string | null; } /** Result of extracting CTE definitions from a WITH clause */ export interface SQLCTEExtraction { /** Individual CTE definitions as "name AS (...)" strings, in declaration order */ CTEDefinitions: string[]; /** The main statement after all CTE definitions */ MainStatement: string; /** Whether AST parsing was used (true) or regex fallback (false) */ UsedASTParsing: boolean; } /** A column in the SELECT clause with its output alias and source info */ export interface SQLSelectColumn { /** The output column name (the AS alias if present, otherwise the source column name) */ OutputName: string; /** The source column name (before AS alias) */ SourceColumn: string; /** Table alias or name qualifier (e.g., "u" in "u.Name"), or null if unqualified */ TableQualifier: string | null; /** Whether this column uses an expression (not a simple column ref) */ IsExpression: boolean; } /** Deterministic parameter info extracted from template expressions */ export interface MJParameterInfo { name: string; type: 'string' | 'number' | 'date' | 'boolean' | 'array' | 'unknown'; isRequired: boolean; defaultValue: string | number | null; filters: MJFilter[]; usageLocations: string[]; } /** * Combined result of parsing SQL for table and column references. * This is the original SQLParser.Parse() return type, preserved for backward compatibility. */ export interface SQLParseResult { /** All table/view references found (FROM, JOIN, subqueries, CTEs) */ Tables: SQLTableReference[]; /** All column references found (SELECT, WHERE, JOIN ON, GROUP BY, ORDER BY) */ Columns: SQLColumnReference[]; /** Whether parsing succeeded via AST (true) or fell back to regex (false) */ UsedASTParsing: boolean; } /** Options for Parse() and ParseWithTemplatePreprocessing() methods */ export interface SQLParseOptions { /** The SQL string to parse */ sql: string; /** SQL dialect for parsing and identifier quoting */ dialect?: SQLParserDialect; } /** * High-level classification of the statement at the root of a parsed AST. * * Callers that need to decide whether a statement can be safely modified * (row caps, paging, etc.) use this in preference to inspecting AST shapes * directly. */ export type SQLStatementKind = /** Plain SELECT */ 'select' /** SELECT … INTO #tmp FROM … — a write disguised as a SELECT */ | 'select-into' /** UNION / INTERSECT / EXCEPT */ | 'set-op' /** INSERT / UPDATE / DELETE / MERGE / etc. */ | 'mutation' /** Anything else or an unrecognized shape */ | 'other'; /** * Dialect-neutral description of the outermost row cap on a parsed SELECT. * Defined in {@link ./ASTDialectAdapter}; re-exported here so consumers can * import it from `@memberjunction/sql-parser` without referencing the adapter. * Returned by {@link SQLParser.OuterCap}. */ export type { RowCapInfo } from './ASTDialectAdapter.js'; /** * Unified parser for MemberJunction's SQL superset. * * Handles standard SQL, Nunjucks template expressions (`{{ var | filter }}`), * Nunjucks block tags (`{% if %}...{% endif %}`), composition tokens * (`{{query:"Path/Name(params)"}}`), and template comments (`{# ... #}`). * * Two-tier API: * - **Instance** (`new SQLParser(sql, dialect)`) — parses once (with a * preprocessing fallback) and exposes typed, dialect-neutral AST * inspection/mutation (`StatementKind`, `OuterCap`, `SetOuterCap`, * `ToSQL`) plus extraction helpers (`ExtractCTEs`, `ExtractTableRefs`, * `ExtractColumnRefs`, `ExtractSelectColumns`). * - **Static utilities** — pure string/token operations that need no parsed * state (`StripComments`, `Tokenize`, `Analyze`, `ParseSQL`, `SqlifyAST`, * `HasUnwrappableTrailingClause`, the MJ-template helpers, etc.). */ export declare class SQLParser { private readonly _sql; private readonly _dialect; private readonly _adapter; private readonly _ast; /** alias → original bracketed identifier; set when bracket preprocessing was applied */ private readonly _aliasMap; /** trailing `OPTION (...)` clause split off during preprocessing; re-appended on ToSQL */ private readonly _trailingOption; /** * Parse SQL into an instance exposing dialect-neutral AST inspection, * mutation, and extraction. * * On a direct-parse failure, applies preprocessing fallbacks — splitting a * trailing `OPTION (...)` clause and aliasing bracket-quoted identifiers * whose interior contains parser-defeating characters (`[Active People]`, * `[my-cte]`) — so a wider class of SQL becomes AST-addressable. {@link ToSQL} * transparently restores both transforms. * * Never throws on unparseable SQL — check {@link IsValid}. */ constructor(sql: string, dialect: SQLParserDialect); /** Whether the SQL parsed into an AST (directly or via preprocessing). */ get IsValid(): boolean; /** The raw AST. Prefer the typed accessors; this is an escape hatch. */ get AST(): NodeSqlParser.AST | NodeSqlParser.AST[] | null; /** The dialect supplied at construction. */ get Dialect(): SQLParserDialect; /** * Fixes a known node-sql-parser bug where CAST(x AS NVARCHAR(MAX)) is * serialized as CAST(x AS NVARCHARmax). Applies to NVARCHAR, VARCHAR, * and VARBINARY — all SQL Server types that accept (MAX). */ private static fixMaxTypeSerialization; /** * Parse MJ SQL into an extended AST. * * Pipeline: * 1. MJLexer tokenizes MJ extensions * 2. MJPlaceholder replaces tokens with SQL-safe placeholders * 3. node-sql-parser produces the SQL AST * * @param sql The MJ SQL string to parse * @param dialect SQL dialect ('TransactSQL' | 'PostgresQL', default: 'TransactSQL') */ static Astify(sql: string, dialect: SQLParserDialect): MJAstifyResult; /** * Reconstruct MJ SQL from an Astify result. * * For plain SQL: uses node-sql-parser's sqlify for normalized output. * For MJ SQL: reconstructs from tokens, preserving all Nunjucks and composition syntax verbatim. */ static Sqlify(result: MJAstifyResult): string; /** * Parse plain SQL into a node-sql-parser AST. * Handles FOR XML multi-directive workaround automatically. * Returns null if parsing fails. */ static ParseSQL(sql: string, dialect: SQLParserDialect): NodeSqlParser.AST | NodeSqlParser.AST[] | null; /** * Convert a node-sql-parser AST (or array of ASTs) back to a SQL string. * This is a thin wrapper around node-sql-parser's sqlify. */ static SqlifyAST(ast: NodeSqlParser.AST | NodeSqlParser.AST[], dialect: SQLParserDialect): string; /** * High-level classification of the parsed statement: * `'select'` | `'select-into'` | `'set-op'` | `'mutation'` | `'other'`. */ get StatementKind(): SQLStatementKind; /** * node-sql-parser statement `type` values that write/modify rather than read. * Used by {@link HasWriteStatement} to reject such statements anywhere in a * rendered read query (including a stacked injection payload). */ private static readonly WRITE_STATEMENT_TYPES; /** * Whether ANY top-level statement is a write — a DML mutation * (INSERT/UPDATE/DELETE/MERGE/REPLACE), DDL (DROP/CREATE/ALTER/TRUNCATE/ * RENAME), or EXEC/CALL/GRANT/REVOKE/USE. * * Unlike {@link StatementKind} (which classifies only the first statement), * this inspects EVERY top-level statement, so a stacked payload such as * `SELECT 1; DROP TABLE x` is detected. Benign session prefixes (`SET`, * `DECLARE`) and SELECTs are not writes. Matching is on the AST statement * type, so the `REPLACE()` string function inside a SELECT is never * mistaken for a `REPLACE` statement. */ get HasWriteStatement(): boolean; /** * The outermost row cap on the parsed SELECT, dialect-neutral, or `null` * when none is present. Backed by the dialect's ASTDialectAdapter, so the * caller never needs to know whether the cap was a `TOP` or `LIMIT`. */ get OuterCap(): RowCapInfo | null; /** * Sets the outermost row cap. The adapter writes the dialect's form * (`TOP N` on SQL Server, `LIMIT N` elsewhere), preserving any existing * OFFSET. No-op when the root isn't a SELECT. */ SetOuterCap(cap: number): void; /** * Removes the outermost row cap (TOP on SQL Server, LIMIT on PostgreSQL) * from the parsed SELECT. No-op when the root isn't a SELECT. * * Used by the count-SQL builder (`queryPagingEngine.stripCountBody`) so a * paged query's count reflects the full set rather than the capped subset. * It clears both forms, so a PostgreSQL count drops an explicit `LIMIT` — * consistent with how SQL Server's `TOP` is dropped. */ ClearOuterCap(): void; /** * Removes the top-level ORDER BY from the parsed statement, following the * set-op (`UNION` / `INTERSECT` / `EXCEPT`) chain so an ORDER BY on any * branch is cleared. No-op when there is none. Dialect-universal — the * `orderby` field has the same shape across dialects. * * Used by the count-SQL builder: ORDER BY is irrelevant for a COUNT and is * illegal inside a SQL Server CTE without TOP. */ ClearOrderBy(): void; /** * Serialize the (possibly mutated) AST back to SQL, restoring any * preprocessing transforms applied at construction (bracket-identifier * aliases, then the trailing `OPTION (...)` clause). * * Throws if the SQL was not parseable (check {@link IsValid} first). */ ToSQL(): string; /** * Internal helper: unwrap the root statement from an AST (or AST array) * and present it as a mutable record. Returns `null` if the input is null * or empty. */ private static unwrapRoot; /** * Token-aware scan for SQL clauses that cannot legally appear inside a * derived table — wrapping a query that contains one of these in * `SELECT ... FROM () AS t` would produce invalid SQL. * * Detects (case-insensitive, outside string literals and quoted * identifiers): * - `FOR JSON …` * - `FOR XML …` * - `OPTION (…)` * * The dialect determines which identifier quoting styles are recognized * (`[…]` for SQL Server, `` `…` `` for MySQL, `"…"` always). */ static HasUnwrappableTrailingClause(sql: string, dialect: SQLParserDialect): boolean; /** * Token-aware detection of stacked statements: a statement-separating * semicolon that has real content after it (outside string literals, * quoted identifiers, and comments). A single trailing semicolon — or a run * of them followed only by whitespace/comments — is allowed. * * Unlike an AST check, this fires even when the trailing payload makes the * SQL unparseable (`SELECT 1; EXEC xp_cmdshell '…'`, `SELECT 1; WAITFOR * DELAY '…'`), which is exactly the stacked-injection class an AST scan * misses (the whole string fails to parse, so the AST is null). A rendered * read query must be a single statement, so any internal `;` is rejected. */ static HasStackedStatements(sql: string, dialect: SQLParserDialect): boolean; /** * Strip line and block comments from SQL, preserving content inside * string literals and quoted identifiers. Block comments support nesting. * * The dialect determines which identifier quoting styles are recognized: * SQL Server uses `[…]`, PostgreSQL uses `"…"`, MySQL uses `` `…` ``. * Double-quoted identifiers are honored on every dialect. */ static StripComments(sql: string, dialect: SQLParserDialect): string; /** * Convert a single AST expression node to a SQL string. * Useful for extracting ORDER BY terms, column expressions, etc. * * node-sql-parser's exprToSQL always produces backtick-quoted identifiers * regardless of dialect. This method converts backticks to the dialect's * native identifier quoting via `dialect.QuoteIdentifier()`. */ static ExprToSQL(expr: unknown, dialect: SQLParserDialect): string; /** * Tokenize MJ SQL into an ordered list of tokens. * Returns MJ tokens (template expressions, composition refs, block tags, comments) * interleaved with SQL_TEXT tokens for the plain SQL segments. */ static Tokenize(sql: string): MJToken[]; /** * Tokenize and return a summary with boolean flags. * Useful for quick checks like "does this SQL have MJ extensions?" */ static Analyze(sql: string): MJParseResult; /** * Replace MJ tokens with SQL-safe placeholders. * * Context-aware: `sqlString` → string literal, `sqlNumber` → numeric literal, * `sqlIdentifier` → bare identifier. Block tags and comments are stripped. * Returns a position map for reversing the substitution. */ static Substitute(sql: string): PlaceholderSubstitutionResult; /** * Parse SQL and extract table + column references. * This is the original SQLParser.Parse() method signature. * Automatically handles MJ Nunjucks templates via placeholder substitution. * * @param options Parse options, or just a SQL string for backward compatibility */ static Parse(options: SQLParseOptions | string, dialect?: SQLParserDialect): SQLParseResult; /** * Parse SQL with MJ Nunjucks template preprocessing. * This is the original SQLParser.ParseWithTemplatePreprocessing() signature. * Now equivalent to Parse() since all methods handle templates automatically. * * @param options Parse options, or just a SQL string for backward compatibility */ static ParseWithTemplatePreprocessing(options: SQLParseOptions | string, dialect?: SQLParserDialect): SQLParseResult; /** * Extract all table/view references from this instance's SQL. * Convenience delegate to the static overload. */ ExtractTableRefs(): SQLTableReference[]; /** * Extract all table/view references from SQL. * Handles Nunjucks templates via placeholder substitution before AST parsing. */ static ExtractTableRefs(sql: string, dialect: SQLParserDialect): SQLTableReference[]; /** * Extract all column references from this instance's SQL. * Convenience delegate to the static overload. */ ExtractColumnRefs(): SQLColumnReference[]; /** * Extract all column references from SQL. * Handles Nunjucks templates via placeholder substitution before AST parsing. */ static ExtractColumnRefs(sql: string, dialect: SQLParserDialect): SQLColumnReference[]; /** * Extract SELECT clause columns from this instance's SQL. * Convenience delegate to the static overload. */ ExtractSelectColumns(): SQLSelectColumn[]; /** * Extracts the SELECT clause columns with their output names, source columns, and table qualifiers. * * Handles: * - Simple columns: `u.Name` → { OutputName: "Name", SourceColumn: "Name", TableQualifier: "u" } * - AS aliases: `e.Name AS EntityName` → { OutputName: "EntityName", SourceColumn: "Name", TableQualifier: "e" } * - Expressions: `COUNT(*)` → { OutputName: "COUNT(*)", SourceColumn: "COUNT(*)", IsExpression: true } * - MJ template tokens are replaced with placeholders before AST parsing. */ static ExtractSelectColumns(sql: string, dialect: SQLParserDialect): SQLSelectColumn[]; /** * Walks an AST statement node to extract SELECT clause column definitions. */ private static extractSelectColumnsFromAST; /** * Unwraps an AST identifier node to a plain string. * node-sql-parser represents PostgreSQL double-quoted identifiers as * `{ expr: { type: 'double_quote_string', value: 'Name' } }` instead * of a plain string `'Name'`. This helper normalizes both representations. */ private static unwrapIdentifier; /** * Best-effort string representation of an AST expression node (for expression columns). */ private static exprToString; /** * Extract CTE definitions from this instance's SQL. * Convenience delegate to the static overload. */ ExtractCTEs(): SQLCTEExtraction | null; /** * Extract CTE definitions from SQL starting with a WITH clause. * * Given: `WITH A AS (SELECT 1), B AS (SELECT 2) SELECT A.x, B.y FROM A, B` * Returns: CTEDefinitions: ["A AS (...)", "B AS (...)"], MainStatement: "SELECT ..." * * Uses AST parsing first (produces bracket-quoted identifiers for SQL Server), * falls back to paren-depth scanning if AST fails (e.g., Nunjucks-templated SQL). */ static ExtractCTEs(sql: string, dialect: SQLParserDialect): SQLCTEExtraction | null; /** * Strips leading whitespace AND SQL comments from a string. * Handles block comments and line comments (-- ...). * Returns the remainder starting at the first non-whitespace, non-comment character. */ private static skipLeadingCommentsAndWhitespace; /** * Renames a template variable in all `{{ variable | filters }}` expressions throughout the SQL. * Preserves the filter chain and whitespace formatting. * * Example: `RenameTemplateVariable("WHERE x = {{ region | sqlString }}", "region", "userRegion")` * → `"WHERE x = {{ userRegion | sqlString }}"` * * Uses MJLexer for deterministic token identification — no regex guessing. */ static RenameTemplateVariable(sql: string, oldName: string, newName: string): string; /** * Substitutes a template variable with a literal value in all `{{ variable | filters }}` expressions. * The entire expression (including filters) is replaced with the literal value, since filters * are irrelevant when injecting a concrete value. * * Example: `SubstituteTemplateVariable("WHERE x = {{ region | sqlString }}", "region", "'West'")` * → `"WHERE x = 'West'"` * * Uses MJLexer for deterministic token identification — no regex guessing. */ static SubstituteTemplateVariable(sql: string, variableName: string, literalValue: string): string; /** * Rebuilds a `{{ variable | filter1 | filter2(args) }}` template expression string * from a variable name and filter chain. */ private static rebuildTemplateExpr; /** * Extract all {{ variable | filter }} expressions from SQL. * Returns structured info for each expression including variable name and filter chain. */ static ExtractTemplateExpressions(sql: string): MJTemplateExpr[]; /** * Extract all {{query:"..."}} composition references from SQL. * Returns structured info including category path, query name, and parsed parameters. */ static ExtractCompositionRefs(sql: string): MJCompositionRef[]; /** * Extract conditional blocks, pairing if/elif/else/endif into structured trees. */ static ExtractConditionalBlocks(sql: string): MJConditionalBlock[]; /** * Extract parameter metadata from template expressions. * * Walks tokens with a lexical-scope stack so that loop-local variables * introduced by `{% for X in Y %}` blocks are NOT registered as query * parameters (they're rebound on each iteration; callers can't supply * them). The iterable side of `{% for %}` (`Y`) IS registered as a * parameter — typically `array` and required, unless wrapped in * `{% if Y %}` which makes it optional. * * Skip-Brain Bug B: previously this function treated `{{ kw }}` inside * `{% for kw in OrgKeywords %}` as a required parameter and failed to * register `OrgKeywords` at all. See `__tests__/extract-parameter-info-loops.test.ts` * and `SKIP-QUERY-RENDERING-BUGS.md` (Bug B) at the repo root. * * Infers type from filters, isRequired from conditional block context. */ static ExtractParameterInfo(sql: string): MJParameterInfo[]; /** * Walk the AST from an Astify result and annotate where MJ placeholders appear. * * Returns an MJASTWalkResult with annotations mapping each placeholder to its * SQL clause context (SELECT, WHERE, ORDER BY, etc.) and the resolved MJ node. * * This enables queries like: * - "Which template expressions appear in the WHERE clause?" * - "What composition ref is in the FROM clause?" * - "Does the ORDER BY reference an MJ token?" * * @param result The MJAstifyResult from Astify() * @returns Walk result with annotations, or empty result if AST parsing failed */ static WalkAST(result: MJAstifyResult): MJASTWalkResult; /** * Recursively walks an AST node, checking values against the placeholder map. */ private static walkASTNode; /** * Walks any AST value (node, array, or primitive) checking for placeholder matches. */ private static walkASTValue; /** * Checks if a value matches a placeholder and creates an annotation if so. */ private static checkPlaceholder; /** * Builds the MJASTWalkResult from a flat list of annotations. */ private static buildWalkResult; /** * Parse SQL safely, returning null on failure. * * Includes a workaround for node-sql-parser's inability to handle multi-directive * FOR XML clauses (e.g., `FOR XML PATH('M'), ROOT('R')`). The parser handles * `FOR XML PATH('M')` fine but chokes on the comma-separated directives. * We strip the extra directives before parsing and restore them on the AST's `for` property. */ /** Resolves overloaded Parse() args: (string, dialect?) or (options) */ private static resolveParseArgs; private static parseSQL; /** * Last-resort parse used when a direct parse fails. Rewrites SQL into a * form node-sql-parser accepts: * 1. Split a trailing `OPTION (...)` query hint (SQL Server). * 2. Alias bracket-quoted identifiers whose interior contains * parser-defeating characters (`[Active People]`, `[my-cte]`). * * Returns the AST plus the data needed to restore the original SQL on * {@link ToSQL}. `ast` is `null` when even the rewritten SQL is unparseable * (or when no transform applied — nothing new to try). */ private static preprocessForParse; /** * Token-aware scan for a trailing `OPTION (...)` query hint at the * outermost level (outside string literals, quoted identifiers, and * comments). Returns the SQL without the clause plus the clause text, or * `null` when there is no trailing OPTION. */ private static splitTrailingOption; /** * Token-aware scan that aliases bracket-quoted identifiers whose interior * contains characters node-sql-parser can't handle (`[Active People]`, * `[my-cte]`, `[dbo.table]`). Aliases are stable, collision-safe tokens * (`_mjid_`). Applies only to bracket-quoting dialects (SQL Server). * * Returns the rewritten SQL plus a forward map (original interior → alias). */ private static readonly BRACKET_ALIAS_PREFIX; private static aliasBracketIdentifiers; /** * Reverses bracket-identifier aliasing on a sqlify result. node-sql-parser * may emit the alias bare, bracketed, or backticked; all three forms are * restored to the original bracketed identifier. Aliases are unique * `BRACKET_ALIAS_PREFIX` tokens, so replacement is unambiguous. */ private static restoreAliases; /** * Advances past a quoted span starting at `start` (whose opening char is * `sql[start]`), honoring the doubled-delimiter escape (`''`, `]]`, `""`, * `` `` ``). Returns the index just past the closing delimiter. */ private static skipQuotedFrom; /** * Detects FOR XML clauses that node-sql-parser can't handle and simplifies them. * * node-sql-parser handles: FOR XML PATH, FOR XML PATH('arg'), FOR XML RAW, FOR XML AUTO * node-sql-parser fails on: FOR XML RAW('arg'), and any comma-separated directives * * Simplifies to a form the parser accepts, preserving the original for restoration. * Returns null if no problematic FOR XML pattern is found. */ private static stripForXmlDirectives; /** * Restores the original FOR XML clause text on the AST's `for` property. * This ensures that when the AST is inspected (e.g., for isOrderByLegalInCTE checks), * the FOR XML presence is correctly detected. */ private static restoreForXmlOnAST; /** Get clean SQL (MJ tokens replaced with placeholders) for AST parsing */ private static getCleanSQL; private static extractTablesViaAST; private static extractTablesViaRegex; private static deduplicateTables; private static buildColumnRefs; private static extractCTEsViaAST; private static extractCTEsViaRegex; private static skipWhitespace; private static findMatchingCloseParen; private static walkASTForExtraction; private static walkFromItem; private static walkExpression; private static inferTypeFromFilters; private static extractDefaultValue; /** * Extracts the primary variable name from an {% if %} condition expression. * Handles patterns like: * "Status" → "Status" * "Status and Status.length > 0" → "Status" * "StartDate and StartDate.length" → "StartDate" */ private static extractConditionVariable; /** * Extracts a SQL literal value from an {% else %} branch body. * Looks for patterns like: * `= 'Attended'` → "Attended" * `= 42` → 42 * `= 3.14` → 3.14 * * Only returns a value when exactly one literal is found, to avoid * ambiguity from complex else bodies with multiple conditions. */ private static extractLiteralFromElseBody; /** * Enriches parameters that lack default values by inspecting {% else %} branches * of conditional blocks. When a block guards a parameter with {% if Var %} and * the else branch contains a single literal value (e.g., `= 'Attended'`), that * literal is assigned as the parameter's default. * * Only sets defaults on parameters that don't already have one (from a `| default()` filter). */ private static enrichDefaultsFromElseBranches; private static findConditionalVariables; /** Jinja2 keywords that should never be treated as user-defined variables. */ private static readonly JINJA_KEYWORDS; private static addConditionVariables; private static addNodeToFragment; } //# sourceMappingURL=sql-parser.d.ts.map