import { DatabasePlatform, UserInfo, QueryDependencySpec } from '@memberjunction/core'; import { MJQueryParameterEntity } from '@memberjunction/core-entities'; import { CompositionResult, CompositionCTEInfo } from './queryCompositionEngine.js'; import { PagingWrappedSQL } from './queryPagingEngine.js'; import { type QueryTemplateInput } from '@memberjunction/query-processor'; /** * Diagnostic metadata for a single composition operation. */ export interface CompositionDiagnostic { /** Name of the dependency query resolved */ DepName: string; /** Category path of the dependency */ CategoryPath: string; /** Generated CTE name in the output */ CTEName: string; /** Whether ORDER BY was stripped from this dep */ OrderByStripped: boolean; /** Number of inner CTEs hoisted from this dep */ InnerCTEsHoisted: number; } /** * Pipeline trace capturing the SQL state after each pass. * Used for error diagnosis — when SQL Server rejects the output, the * caller can see what each pass produced to identify which pass * mangled the SQL. */ export interface RenderTrace { /** SQL after composition ({{ }}/{% %} tokens still present) */ AfterComposition: string; /** Composition metadata: which deps resolved, which CTEs generated */ CompositionDiagnostics: CompositionDiagnostic[]; /** SQL after Nunjucks template evaluation (fully resolved, executable) */ AfterTemplates: string; /** SQL after paging (final) — only set if paging was applied */ AfterPaging: string | null; } /** * Context passed to {@link RenderPipeline.Run} controlling each pipeline stage. */ export interface RenderContext { /** Target database platform */ Platform: DatabasePlatform; /** User context for permission checks on referenced queries */ ContextUser?: UserInfo; /** Parameter values from the caller */ Parameters?: Record; /** Formal parameter definitions (for validation). Null = skip validation. */ ParameterDefinitions?: MJQueryParameterEntity[]; /** Whether the outer query uses Nunjucks templates */ UsesTemplate?: boolean; /** Inline dependency specs for transient query testing */ Dependencies?: QueryDependencySpec[]; /** Original (pre-composition) SQL — used for template input metadata */ OriginalSQL?: string; /** Full query metadata object (saved query path). When provided, passed * directly to processQueryTemplate for parameter validation. When omitted, * a synthetic QueryTemplateInput is built from OriginalSQL + ParameterDefinitions. */ QueryInfo?: QueryTemplateInput; /** Paging parameters (omit to skip paging). Mutually exclusive with {@link MaxRows}. */ Paging?: { StartRow: number; MaxRows: number; }; /** MaxRows safety limit. Mutually exclusive with {@link Paging}. */ MaxRows?: number; } /** * Result returned by {@link RenderPipeline.Run}. */ export interface RenderResult { /** The final SQL ready for execution */ FinalSQL: string; /** Parameters that were applied during template processing */ AppliedParameters: Record; /** Whether composition tokens were found and resolved */ HasCompositions: boolean; /** Metadata about each CTE generated during composition */ CTEs: CompositionCTEInfo[]; /** Pipeline trace for error diagnosis */ Trace: RenderTrace; /** Paging result (if paging was applied) */ PagingResult: PagingWrappedSQL | null; } /** * RenderPipeline wraps the existing composition → template → paging calls * into a single entry point with enforced ordering and diagnostic tracing. * * **Pipeline order** (this is a data dependency, not a convention): * 1. **Composition** resolves `{{query:"..."}}` tokens into CTEs. Must run * BEFORE Nunjucks because pass-through parameters rename `{{ innerParam }}` * to `{{ outerParam }}` — Nunjucks needs those tokens intact. * 2. **Nunjucks** evaluates `{{ param | filter }}` and `{% if/for %}` blocks. * Must run AFTER composition and BEFORE paging. * 3. **Paging** detects ORDER BY and injects OFFSET/FETCH or LIMIT/OFFSET. * Must run AFTER Nunjucks because it needs to detect whether * `{% if SortField %} ORDER BY ... {% endif %}` resolved to an actual * ORDER BY or not — that depends on runtime parameter values. * * The `Trace` field on the result captures the SQL state after each pass, * enabling diagnosis when SQL Server rejects the output. */ export declare class RenderPipeline { /** * Runs the full rendering pipeline: composition → Nunjucks → paging. * * @param sql - Raw SQL with potential composition tokens and template expressions * @param ctx - Pipeline context (platform, user, parameters, paging, etc.) * @returns Final SQL, applied parameters, composition metadata, and diagnostic trace */ static Run(sql: string, ctx: RenderContext): RenderResult; /** * Defense-in-depth guard on the fully-resolved query: throws when the * rendered SQL contains a write statement anywhere — a DML mutation * (INSERT / UPDATE / DELETE / MERGE / REPLACE), DDL (DROP / CREATE / ALTER / * TRUNCATE / RENAME), or EXEC / CALL / GRANT / REVOKE / USE. * * {@link SQLParser.HasWriteStatement} inspects EVERY top-level statement, so * it catches both a single rendered mutation and a stacked-statement * injection payload (e.g. `SELECT 1; DROP TABLE x`) that the first-statement * `StatementKind` alone would miss. It is AST-precise: matching is on the * statement type, so it cannot false-positive on legitimate read queries — * the `REPLACE()` string function, parenthesized SELECTs, trailing * semicolons, or benign `SET` / `DECLARE` prefixes all pass. * * A second, token-based check rejects stacked statements (an internal * statement-separating `;`). Unlike the AST check it fires even when the * trailing payload makes the SQL unparseable — e.g. `SELECT 1; EXEC * xp_cmdshell '…'` or `SELECT 1; WAITFOR DELAY '…'` — which is the * stacked-injection class the AST scan misses (the whole string fails to * parse, so there is no AST to classify). A rendered read query must be a * single statement. * * The broader dangerous-keyword scan * ({@link SQLExpressionValidator.validateFullQuery}) deliberately stays on * the ad-hoc execution path (untrusted free-text input); it is unsuitable as * a blanket gate here because it rejects legitimate read constructs (the * `REPLACE()` string function, parenthesized SELECTs, etc.). */ private static assertSafeToExecute; /** * Resolves only the composition step — for callers that need composition * without the full pipeline (e.g., save-time token analysis). * * Wraps {@link QueryCompositionEngine.ResolveComposition} to maintain the * existing public API while the internal implementation may change. */ static ResolveCompositionOnly(sql: string, platform: DatabasePlatform, contextUser: UserInfo, outerParams?: Record, inlineDependencies?: QueryDependencySpec[]): CompositionResult; /** * Checks whether SQL contains composition tokens. * Convenience wrapper for callers that need a fast guard. */ static HasCompositionTokens(sql: string): boolean; private static runComposition; private static runTemplates; } //# sourceMappingURL=renderPipeline.d.ts.map