@memberjunction/generic-database-provider 5.13.0 → 5.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export { GenericDatabaseProvider } from './GenericDatabaseProvider.js';
2
2
  export { SqlLoggingSessionImpl } from './SqlLogger.js';
3
+ export { QueryCompositionEngine } from './queryCompositionEngine.js';
3
4
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAA0B,MAAM,8BAA8B,CAAC;AAE/F,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAA0B,MAAM,8BAA8B,CAAC;AAE/F,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAE,sBAAsB,EAAyC,MAAM,6BAA6B,CAAC"}
@@ -0,0 +1,278 @@
1
+ import { DatabasePlatform, UserInfo, QueryDependencySpec } from "@memberjunction/core";
2
+ /**
3
+ * Metadata about a single CTE generated during composition resolution.
4
+ */
5
+ export interface CompositionCTEInfo {
6
+ /** ID of the referenced query */
7
+ QueryID: string;
8
+ /** Name of the referenced query */
9
+ QueryName: string;
10
+ /** Category path as written in the reference */
11
+ CategoryPath: string;
12
+ /** Generated CTE alias name */
13
+ CTEName: string;
14
+ /** Original SQL of the referenced query before parameter resolution */
15
+ OriginalSQL: string;
16
+ /** SQL after parameter values have been substituted */
17
+ ResolvedSQL: string;
18
+ /** Parameter values applied (key → resolved value) */
19
+ Parameters: Record<string, string>;
20
+ }
21
+ /**
22
+ * Result returned by the composition engine after resolving all {{query:"..."}} tokens.
23
+ */
24
+ export interface CompositionResult {
25
+ /** The fully resolved SQL with CTEs prepended */
26
+ ResolvedSQL: string;
27
+ /** Metadata about each CTE generated */
28
+ CTEs: CompositionCTEInfo[];
29
+ /** Directed dependency graph: queryId → [dependsOnQueryIds] */
30
+ DependencyGraph: Map<string, string[]>;
31
+ /** Whether any composition tokens were found and resolved */
32
+ HasCompositions: boolean;
33
+ /** True if any resolved dependency query has UsesTemplate = true (depth-first, transitive) */
34
+ AnyDependencyUsesTemplates: boolean;
35
+ }
36
+ /**
37
+ * Parsed representation of a single {{query:"..."}} token found in SQL.
38
+ */
39
+ interface ParsedCompositionToken {
40
+ /** The full original token text including {{ and }} */
41
+ FullToken: string;
42
+ /** Category path segments (everything before the query name) */
43
+ CategorySegments: string[];
44
+ /** The query name (last path segment) */
45
+ QueryName: string;
46
+ /** Full path as written (CategorySegments joined with /) */
47
+ FullPath: string;
48
+ /** Parsed parameter mappings */
49
+ Parameters: ParsedParameter[];
50
+ }
51
+ /**
52
+ * A single parameter from a composition reference.
53
+ */
54
+ interface ParsedParameter {
55
+ /** Parameter name */
56
+ Name: string;
57
+ /** If quoted literal: the static value. Otherwise null. */
58
+ StaticValue: string | null;
59
+ /** If bare name: the pass-through parameter name from the outer query. Otherwise null. */
60
+ PassThroughName: string | null;
61
+ }
62
+ /**
63
+ * QueryCompositionEngine resolves {{query:"CategoryPath/QueryName(params)"}} tokens
64
+ * in query SQL into Common Table Expressions (CTEs). It handles:
65
+ *
66
+ * - Recursive resolution of nested compositions
67
+ * - Cycle detection via an in-progress set
68
+ * - Parameter modes: static literals and pass-through from outer query
69
+ * - Deduplication of identical query+params references
70
+ * - Platform-aware SQL resolution via QueryInfo.GetPlatformSQL()
71
+ *
72
+ * This engine runs BEFORE Nunjucks template processing, so regular {{param}}
73
+ * tokens are preserved for later substitution.
74
+ */
75
+ export declare class QueryCompositionEngine {
76
+ /**
77
+ * Checks whether SQL contains any {{query:"..."}} composition tokens.
78
+ * Use this as a fast guard before calling ResolveComposition().
79
+ * Only considers tokens outside of SQL comments.
80
+ */
81
+ HasCompositionTokens(sql: string): boolean;
82
+ /**
83
+ * Parses all {{query:"..."}} tokens from SQL without resolving them.
84
+ * Useful for dependency extraction during the save pipeline.
85
+ * Only considers tokens outside of SQL comments.
86
+ *
87
+ * Uses MJLexer for structured tokenization instead of regex, providing
88
+ * full parsing of category paths, query names, and parameter lists.
89
+ *
90
+ * @param sql - The SQL text to parse
91
+ * @returns Array of parsed token metadata
92
+ */
93
+ ParseCompositionTokens(sql: string): ParsedCompositionToken[];
94
+ /**
95
+ * Resolves all {{query:"..."}} composition tokens in the given SQL into CTEs.
96
+ *
97
+ * @param sql - The SQL containing composition tokens
98
+ * @param platform - Target database platform for SQL resolution
99
+ * @param contextUser - User context for permission checks on referenced queries
100
+ * @param outerParams - Parameter values from the outer/parent query (for pass-through resolution)
101
+ * @param inlineDependencies - Optional inline dependency specs for transient query testing.
102
+ * When provided, these are checked first before falling back to Metadata.Provider.Queries.
103
+ * Inline dependencies skip governance validation (Reusable, IsApproved, UserCanRun).
104
+ * @returns CompositionResult with fully resolved SQL and provenance metadata
105
+ * @throws Error if a referenced query is not found, not composable, or creates a cycle
106
+ */
107
+ ResolveComposition(sql: string, platform: DatabasePlatform, contextUser: UserInfo, outerParams?: Record<string, string>, inlineDependencies?: QueryDependencySpec[]): CompositionResult;
108
+ /**
109
+ * Recursively resolves composition tokens in SQL, building up CTE entries.
110
+ */
111
+ private resolveTokensRecursive;
112
+ /**
113
+ * Looks up a query by category path + name, checking inline dependencies first,
114
+ * then falling back to the metadata provider.
115
+ *
116
+ * For inline dependencies, creates a synthetic QueryInfo with the SQL and flags set
117
+ * appropriately. Inline queries skip governance validation (Reusable, IsApproved, etc.)
118
+ * since they are inherently authorized by the caller.
119
+ */
120
+ private lookupQueryWithInline;
121
+ /**
122
+ * Finds a matching inline dependency spec for the given token.
123
+ */
124
+ private findInlineDependency;
125
+ /**
126
+ * Builds a synthetic QueryInfo from an inline dependency spec.
127
+ * Sets flags so that GetPlatformSQL returns the inline SQL directly.
128
+ */
129
+ private buildSyntheticQueryInfo;
130
+ /**
131
+ * Looks up a query by category path + name from the metadata provider only.
132
+ */
133
+ private lookupQueryFromMetadata;
134
+ /**
135
+ * Validates that a referenced query is eligible for composition.
136
+ */
137
+ private validateQueryComposable;
138
+ /**
139
+ * Resolves parameter values for a composition reference.
140
+ * Static values are used directly; pass-through values are looked up from outer params.
141
+ */
142
+ private resolveParameters;
143
+ /**
144
+ * Substitutes resolved parameter values into a query's SQL.
145
+ * - Static values: replaces {{paramName}} with 'value'
146
+ * - Pass-through values: renames {{paramName}} to {{outerParamName}} so
147
+ * the downstream Nunjucks processor can resolve it from the outer query's parameters.
148
+ */
149
+ private substituteStaticParams;
150
+ /**
151
+ * Builds a deduplication key for a CTE based on query ID and sorted parameter values.
152
+ */
153
+ private buildDeduplicationKey;
154
+ /**
155
+ * Generates a SQL-safe CTE name from the query name + short hash for uniqueness.
156
+ */
157
+ private generateCTEName;
158
+ /**
159
+ * Resolves a DatabasePlatform string to the corresponding SQLDialect instance.
160
+ */
161
+ private getDialect;
162
+ /**
163
+ * Simple string hash for generating short, deterministic suffixes.
164
+ */
165
+ private simpleHash;
166
+ /**
167
+ * Assembles CTE entries into a WITH clause prepended to the main SQL.
168
+ *
169
+ * Handles the case where a dependency query's SQL itself contains a WITH clause
170
+ * (inner CTEs). SQL does not allow nested WITH clauses, so inner CTEs are "hoisted"
171
+ * out as sibling CTE definitions preceding the dependency's own CTE.
172
+ */
173
+ private assembleCTEs;
174
+ /**
175
+ * Extracts inner CTE definitions from SQL that starts with a WITH clause.
176
+ *
177
+ * Delegates to {@link SQLParser.ExtractCTEs} which uses AST parsing first
178
+ * (via node-sql-parser), falling back to a paren-depth regex approach when
179
+ * AST parsing fails (e.g. SQL contains Nunjucks template tokens).
180
+ *
181
+ * @param sql SQL starting with a WITH clause
182
+ * @param platform Database platform, used to select the AST dialect
183
+ */
184
+ private hoistInnerCTEs;
185
+ /**
186
+ * Strips a trailing ORDER BY clause from SQL that will be wrapped in a CTE.
187
+ *
188
+ * SQL Server disallows ORDER BY inside CTEs unless TOP, OFFSET, or FOR XML is present.
189
+ * PostgreSQL allows ORDER BY in CTEs, so no stripping is needed there.
190
+ *
191
+ * Uses a 4-tier strategy:
192
+ * 1. Fast exit — no ORDER keyword at all, or dialect allows ORDER BY in CTEs
193
+ * 2. AST path — parse (with Nunjucks preprocessing if needed), check if ORDER BY is legal
194
+ * (TOP/OFFSET/FOR XML via AST nodes), null out orderby if not, regenerate.
195
+ * Handles window functions, UNION/EXCEPT, subqueries, string literals, and Nunjucks templates.
196
+ * 3. Regex fallback — paren-depth heuristic for SQL the parser still can't handle
197
+ * (e.g. STRING_AGG WITHIN GROUP)
198
+ * 4. OFFSET 0 ROWS injection — last resort when both AST and regex fail to strip ORDER BY.
199
+ * Injects OFFSET 0 ROWS after the ORDER BY clause to make it legal in CTEs.
200
+ * This is semantically neutral (returns all rows starting from 0) but switches
201
+ * SQL Server into paging mode internally, which may affect query plan shape.
202
+ */
203
+ private stripTrailingOrderBy;
204
+ /**
205
+ * Attempts to strip the top-level ORDER BY clause using AST parsing.
206
+ * Tries direct parsing first, then MJPlaceholder-preprocessed parsing if the SQL
207
+ * contains MJ template syntax. Handles UNION/EXCEPT by walking the _next chain.
208
+ */
209
+ private stripOrderByViaAST;
210
+ /**
211
+ * Core AST stripping: parse, analyze, and regenerate SQL without ORDER BY.
212
+ */
213
+ private tryASTStrip;
214
+ /**
215
+ * Walks the _next chain (UNION/EXCEPT/INTERSECT) to find the statement
216
+ * that carries the ORDER BY clause.
217
+ */
218
+ private findOrderByStatement;
219
+ /**
220
+ * Nunjucks-aware ORDER BY stripping: preprocess templates into placeholder SQL,
221
+ * parse with AST to confirm top-level ORDER BY exists, then use the position-aware
222
+ * scanner on the original SQL to strip only the last top-level ORDER BY.
223
+ */
224
+ private tryNunjucksAwareStrip;
225
+ /**
226
+ * Checks AST properties to determine if ORDER BY is legal in a CTE context.
227
+ */
228
+ private isOrderByLegalInCTE;
229
+ /**
230
+ * Strips the last top-level ORDER BY clause using position-aware scanning.
231
+ * Skips strings, comments, Nunjucks tags, and tracks paren depth.
232
+ */
233
+ private stripLastTopLevelOrderBy;
234
+ /**
235
+ * Finds character positions of all ORDER BY keywords at the outermost level
236
+ * (paren depth 0, not inside strings, comments, or MJ template tokens).
237
+ *
238
+ * Uses MJLexer to skip MJ tokens ({{ }}, {% %}, {# #}), then scans only
239
+ * SQL_TEXT segments for ORDER BY keywords while tracking paren depth and
240
+ * respecting SQL string literals and comments.
241
+ */
242
+ private findTopLevelOrderByPositions;
243
+ /**
244
+ * Preprocesses Nunjucks templates into valid SQL for AST parsing.
245
+ * Uses MJPlaceholderSubstitution for context-aware placeholder generation.
246
+ */
247
+ private preprocessNunjucks;
248
+ /**
249
+ * Injects OFFSET 0 ROWS after the last top-level ORDER BY clause to make it
250
+ * legal in a CTE without changing the result set. Uses the position-aware scanner
251
+ * to find the correct insertion point after the ORDER BY columns.
252
+ */
253
+ private injectOffset0Rows;
254
+ /**
255
+ * Regex-based fallback for stripping trailing ORDER BY.
256
+ * Uses parenthesis depth counting to avoid stripping ORDER BY inside subqueries.
257
+ */
258
+ private stripOrderByViaRegex;
259
+ /**
260
+ * Strips SQL comments from the input string so that composition tokens
261
+ * inside comments are not treated as real references.
262
+ * Handles both single-line (-- ...) and multi-line block comments.
263
+ * Preserves string literals (single-quoted) to avoid stripping inside them.
264
+ */
265
+ private stripSQLComments;
266
+ /**
267
+ * Escapes {{ and }} inside SQL comments so that Nunjucks doesn't try to parse them.
268
+ * This is needed because dependency queries may carry comments containing
269
+ * {{query:"..."}} examples or documentation that would otherwise cause
270
+ * Nunjucks "expected variable end" errors.
271
+ *
272
+ * Only modifies content inside -- single-line and block comments.
273
+ * Leaves string literals and normal SQL untouched.
274
+ */
275
+ private escapeTemplateTokensInComments;
276
+ }
277
+ export {};
278
+ //# sourceMappingURL=queryCompositionEngine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queryCompositionEngine.d.ts","sourceRoot":"","sources":["../src/queryCompositionEngine.ts"],"names":[],"mappings":"AAGA,OAAO,EAAuB,gBAAgB,EAAE,QAAQ,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAY5G;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAC/B,iCAAiC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,mCAAmC;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,gDAAgD;IAChD,YAAY,EAAE,MAAM,CAAC;IACrB,+BAA+B;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,WAAW,EAAE,MAAM,CAAC;IACpB,uDAAuD;IACvD,WAAW,EAAE,MAAM,CAAC;IACpB,sDAAsD;IACtD,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACtC;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAC9B,iDAAiD;IACjD,WAAW,EAAE,MAAM,CAAC;IACpB,wCAAwC;IACxC,IAAI,EAAE,kBAAkB,EAAE,CAAC;IAC3B,+DAA+D;IAC/D,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACvC,6DAA6D;IAC7D,eAAe,EAAE,OAAO,CAAC;IACzB,8FAA8F;IAC9F,0BAA0B,EAAE,OAAO,CAAC;CACvC;AAED;;GAEG;AACH,UAAU,sBAAsB;IAC5B,uDAAuD;IACvD,SAAS,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,yCAAyC;IACzC,SAAS,EAAE,MAAM,CAAC;IAClB,4DAA4D;IAC5D,QAAQ,EAAE,MAAM,CAAC;IACjB,gCAAgC;IAChC,UAAU,EAAE,eAAe,EAAE,CAAC;CACjC;AAED;;GAEG;AACH,UAAU,eAAe;IACrB,qBAAqB;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,0FAA0F;IAC1F,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AA6BD;;;;;;;;;;;;GAYG;AACH,qBAAa,sBAAsB;IAC/B;;;;OAIG;IACI,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAOjD;;;;;;;;;;OAUG;IACI,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,sBAAsB,EAAE;IA0BpE;;;;;;;;;;;;OAYG;IACI,kBAAkB,CACrB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,gBAAgB,EAC1B,WAAW,EAAE,QAAQ,EACrB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACpC,kBAAkB,CAAC,EAAE,mBAAmB,EAAE,GAC3C,iBAAiB;IA2CpB;;OAEG;IACH,OAAO,CAAC,sBAAsB;IA+H9B;;;;;;;OAOG;IACH,OAAO,CAAC,qBAAqB;IAwB7B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAoB5B;;;OAGG;IACH,OAAO,CAAC,uBAAuB;IAY/B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAmC/B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IA0B/B;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAoBzB;;;;;OAKG;IACH,OAAO,CAAC,sBAAsB;IAuB9B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAS7B;;OAEG;IACH,OAAO,CAAC,eAAe;IAevB;;OAEG;IACH,OAAO,CAAC,UAAU;IAQlB;;OAEG;IACH,OAAO,CAAC,UAAU;IAUlB;;;;;;OAMG;IACH,OAAO,CAAC,YAAY;IAmCpB;;;;;;;;;OASG;IACH,OAAO,CAAC,cAAc;IAgBtB;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,CAAC,oBAAoB;IAwB5B;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IAa1B;;OAEG;IACH,OAAO,CAAC,WAAW;IAqBnB;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAM5B;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IAoB7B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAa3B;;;OAGG;IACH,OAAO,CAAC,wBAAwB;IAQhC;;;;;;;OAOG;IACH,OAAO,CAAC,4BAA4B;IAwDpC;;;OAGG;IACH,OAAO,CAAC,kBAAkB;IAI1B;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IASzB;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAgB5B;;;;;OAKG;IACH,OAAO,CAAC,gBAAgB;IAgDxB;;;;;;;;OAQG;IACH,OAAO,CAAC,8BAA8B;CAkDzC"}