rawsql-ts 0.19.0 → 0.21.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.
Files changed (85) hide show
  1. package/README.md +12 -13
  2. package/dist/esm/index.d.ts +1 -11
  3. package/dist/esm/index.js +1 -11
  4. package/dist/esm/index.js.map +1 -1
  5. package/dist/esm/index.min.js +5 -41
  6. package/dist/esm/index.min.js.map +4 -4
  7. package/dist/esm/transformers/DynamicQueryBuilder.d.ts +4 -27
  8. package/dist/esm/transformers/DynamicQueryBuilder.js +10 -27
  9. package/dist/esm/transformers/DynamicQueryBuilder.js.map +1 -1
  10. package/dist/esm/transformers/SSSQLFilterBuilder.d.ts +48 -1
  11. package/dist/esm/transformers/SSSQLFilterBuilder.js +578 -31
  12. package/dist/esm/transformers/SSSQLFilterBuilder.js.map +1 -1
  13. package/dist/esm/utils/SchemaManager.d.ts +3 -19
  14. package/dist/esm/utils/SchemaManager.js +2 -62
  15. package/dist/esm/utils/SchemaManager.js.map +1 -1
  16. package/dist/index.js +2 -18
  17. package/dist/index.js.map +1 -1
  18. package/dist/index.min.js +5 -41
  19. package/dist/index.min.js.map +4 -4
  20. package/dist/src/index.d.ts +1 -11
  21. package/dist/src/transformers/DynamicQueryBuilder.d.ts +4 -27
  22. package/dist/src/transformers/SSSQLFilterBuilder.d.ts +48 -1
  23. package/dist/src/utils/SchemaManager.d.ts +3 -19
  24. package/dist/transformers/DynamicQueryBuilder.js +10 -27
  25. package/dist/transformers/DynamicQueryBuilder.js.map +1 -1
  26. package/dist/transformers/SSSQLFilterBuilder.js +576 -29
  27. package/dist/transformers/SSSQLFilterBuilder.js.map +1 -1
  28. package/dist/tsconfig.browser.tsbuildinfo +1 -1
  29. package/dist/utils/SchemaManager.js +2 -63
  30. package/dist/utils/SchemaManager.js.map +1 -1
  31. package/package.json +10 -2
  32. package/dist/esm/transformers/EnhancedJsonMapping.d.ts +0 -194
  33. package/dist/esm/transformers/EnhancedJsonMapping.js +0 -217
  34. package/dist/esm/transformers/EnhancedJsonMapping.js.map +0 -1
  35. package/dist/esm/transformers/JsonMappingConverter.d.ts +0 -200
  36. package/dist/esm/transformers/JsonMappingConverter.js +0 -388
  37. package/dist/esm/transformers/JsonMappingConverter.js.map +0 -1
  38. package/dist/esm/transformers/JsonMappingUnifier.d.ts +0 -100
  39. package/dist/esm/transformers/JsonMappingUnifier.js +0 -207
  40. package/dist/esm/transformers/JsonMappingUnifier.js.map +0 -1
  41. package/dist/esm/transformers/ModelDrivenJsonMapping.d.ts +0 -62
  42. package/dist/esm/transformers/ModelDrivenJsonMapping.js +0 -115
  43. package/dist/esm/transformers/ModelDrivenJsonMapping.js.map +0 -1
  44. package/dist/esm/transformers/PostgresArrayEntityCteBuilder.d.ts +0 -138
  45. package/dist/esm/transformers/PostgresArrayEntityCteBuilder.js +0 -454
  46. package/dist/esm/transformers/PostgresArrayEntityCteBuilder.js.map +0 -1
  47. package/dist/esm/transformers/PostgresJsonQueryBuilder.d.ts +0 -88
  48. package/dist/esm/transformers/PostgresJsonQueryBuilder.js +0 -241
  49. package/dist/esm/transformers/PostgresJsonQueryBuilder.js.map +0 -1
  50. package/dist/esm/transformers/PostgresObjectEntityCteBuilder.d.ts +0 -165
  51. package/dist/esm/transformers/PostgresObjectEntityCteBuilder.js +0 -343
  52. package/dist/esm/transformers/PostgresObjectEntityCteBuilder.js.map +0 -1
  53. package/dist/esm/transformers/TypeTransformationPostProcessor.d.ts +0 -108
  54. package/dist/esm/transformers/TypeTransformationPostProcessor.js +0 -354
  55. package/dist/esm/transformers/TypeTransformationPostProcessor.js.map +0 -1
  56. package/dist/esm/utils/JsonSchemaValidator.d.ts +0 -81
  57. package/dist/esm/utils/JsonSchemaValidator.js +0 -211
  58. package/dist/esm/utils/JsonSchemaValidator.js.map +0 -1
  59. package/dist/src/transformers/EnhancedJsonMapping.d.ts +0 -194
  60. package/dist/src/transformers/JsonMappingConverter.d.ts +0 -200
  61. package/dist/src/transformers/JsonMappingUnifier.d.ts +0 -100
  62. package/dist/src/transformers/ModelDrivenJsonMapping.d.ts +0 -62
  63. package/dist/src/transformers/PostgresArrayEntityCteBuilder.d.ts +0 -138
  64. package/dist/src/transformers/PostgresJsonQueryBuilder.d.ts +0 -88
  65. package/dist/src/transformers/PostgresObjectEntityCteBuilder.d.ts +0 -165
  66. package/dist/src/transformers/TypeTransformationPostProcessor.d.ts +0 -108
  67. package/dist/src/utils/JsonSchemaValidator.d.ts +0 -81
  68. package/dist/transformers/EnhancedJsonMapping.js +0 -223
  69. package/dist/transformers/EnhancedJsonMapping.js.map +0 -1
  70. package/dist/transformers/JsonMappingConverter.js +0 -392
  71. package/dist/transformers/JsonMappingConverter.js.map +0 -1
  72. package/dist/transformers/JsonMappingUnifier.js +0 -216
  73. package/dist/transformers/JsonMappingUnifier.js.map +0 -1
  74. package/dist/transformers/ModelDrivenJsonMapping.js +0 -122
  75. package/dist/transformers/ModelDrivenJsonMapping.js.map +0 -1
  76. package/dist/transformers/PostgresArrayEntityCteBuilder.js +0 -458
  77. package/dist/transformers/PostgresArrayEntityCteBuilder.js.map +0 -1
  78. package/dist/transformers/PostgresJsonQueryBuilder.js +0 -245
  79. package/dist/transformers/PostgresJsonQueryBuilder.js.map +0 -1
  80. package/dist/transformers/PostgresObjectEntityCteBuilder.js +0 -347
  81. package/dist/transformers/PostgresObjectEntityCteBuilder.js.map +0 -1
  82. package/dist/transformers/TypeTransformationPostProcessor.js +0 -363
  83. package/dist/transformers/TypeTransformationPostProcessor.js.map +0 -1
  84. package/dist/utils/JsonSchemaValidator.js +0 -215
  85. package/dist/utils/JsonSchemaValidator.js.map +0 -1
@@ -2,16 +2,40 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.refreshSssqlQuery = exports.scaffoldSssqlQuery = exports.SSSQLFilterBuilder = void 0;
4
4
  const Clause_1 = require("../models/Clause");
5
+ const SelectQuery_1 = require("../models/SelectQuery");
5
6
  const ValueComponent_1 = require("../models/ValueComponent");
6
7
  const SelectQueryParser_1 = require("../parsers/SelectQueryParser");
7
8
  const UpstreamSelectQueryFinder_1 = require("./UpstreamSelectQueryFinder");
8
- const SelectableColumnCollector_1 = require("./SelectableColumnCollector");
9
9
  const ColumnReferenceCollector_1 = require("./ColumnReferenceCollector");
10
+ const SelectableColumnCollector_1 = require("./SelectableColumnCollector");
11
+ const ParameterCollector_1 = require("./ParameterCollector");
12
+ const SqlFormatter_1 = require("./SqlFormatter");
13
+ const CTECollector_1 = require("./CTECollector");
10
14
  const PruneOptionalConditionBranches_1 = require("./PruneOptionalConditionBranches");
15
+ const SUPPORTED_SCALAR_OPERATORS = new Set(["=", "<>", "<", "<=", ">", ">=", "like", "ilike"]);
16
+ let formatter = null;
11
17
  const normalizeIdentifier = (value) => value.trim().toLowerCase();
18
+ const normalizeSql = (value) => value.replace(/\s+/g, " ").trim().toLowerCase();
12
19
  const normalizeColumnReferenceKey = (reference) => {
13
20
  return `${normalizeIdentifier(reference.getNamespace())}.${normalizeIdentifier(reference.column.name)}`;
14
21
  };
22
+ const normalizeColumnReferenceText = (reference) => {
23
+ const namespace = reference.getNamespace();
24
+ return namespace ? `${namespace}.${reference.column.name}` : reference.column.name;
25
+ };
26
+ const normalizeScalarOperator = (value) => {
27
+ if (!value) {
28
+ return "=";
29
+ }
30
+ const normalized = value.trim().toLowerCase();
31
+ if (normalized === "!=") {
32
+ return "<>";
33
+ }
34
+ if (SUPPORTED_SCALAR_OPERATORS.has(normalized)) {
35
+ return normalized;
36
+ }
37
+ throw new Error(`Unsupported SSSQL operator '${value}'.`);
38
+ };
15
39
  const isExplicitEqualityScaffoldValue = (value) => {
16
40
  var _a;
17
41
  if (value === null || value === undefined) {
@@ -43,26 +67,69 @@ const makeParameterName = (filterName) => {
43
67
  .replace(/\./g, "_")
44
68
  .replace(/[^a-zA-Z0-9_]/g, "_");
45
69
  };
46
- const buildOptionalEqualityBranch = (column, parameterName) => {
47
- const parameter = new ValueComponent_1.ParameterExpression(parameterName);
70
+ const unwrapParens = (expression) => {
71
+ let candidate = expression;
72
+ while (candidate instanceof ValueComponent_1.ParenExpression) {
73
+ candidate = candidate.expression;
74
+ }
75
+ return candidate;
76
+ };
77
+ const isBinaryOperator = (expression, operator) => {
78
+ return expression instanceof ValueComponent_1.BinaryExpression && expression.operator.value.trim().toLowerCase() === operator;
79
+ };
80
+ const collectTopLevelAndTerms = (expression) => {
81
+ const candidate = unwrapParens(expression);
82
+ if (!isBinaryOperator(candidate, "and")) {
83
+ return [expression];
84
+ }
85
+ return [
86
+ ...collectTopLevelAndTerms(candidate.left),
87
+ ...collectTopLevelAndTerms(candidate.right)
88
+ ];
89
+ };
90
+ const collectTopLevelOrTerms = (expression) => {
91
+ const candidate = unwrapParens(expression);
92
+ if (!isBinaryOperator(candidate, "or")) {
93
+ return [expression];
94
+ }
95
+ return [
96
+ ...collectTopLevelOrTerms(candidate.left),
97
+ ...collectTopLevelOrTerms(candidate.right)
98
+ ];
99
+ };
100
+ const getGuardedParameterName = (expression) => {
101
+ const candidate = unwrapParens(expression);
102
+ if (!isBinaryOperator(candidate, "is")) {
103
+ return null;
104
+ }
105
+ if (!(candidate.left instanceof ValueComponent_1.ParameterExpression)) {
106
+ return null;
107
+ }
108
+ const right = unwrapParens(candidate.right);
109
+ const isNull = (right instanceof ValueComponent_1.LiteralValue && right.value === null)
110
+ || (right instanceof ValueComponent_1.RawString && right.value.trim().toLowerCase() === "null");
111
+ if (!isNull) {
112
+ return null;
113
+ }
114
+ return candidate.left.name.value;
115
+ };
116
+ const buildOptionalScalarBranch = (column, parameterName, operator) => {
117
+ const guard = new ValueComponent_1.BinaryExpression(new ValueComponent_1.ParameterExpression(parameterName), "is", new ValueComponent_1.LiteralValue(null));
118
+ const predicate = new ValueComponent_1.BinaryExpression(new ValueComponent_1.ColumnReference(column.getNamespace() || null, column.column.name), operator, new ValueComponent_1.ParameterExpression(parameterName));
119
+ return new ValueComponent_1.ParenExpression(new ValueComponent_1.BinaryExpression(guard, "or", predicate));
120
+ };
121
+ const buildOptionalExistsBranch = (parameterName, subquery, kind) => {
48
122
  const guard = new ValueComponent_1.BinaryExpression(new ValueComponent_1.ParameterExpression(parameterName), "is", new ValueComponent_1.LiteralValue(null));
49
- const equality = new ValueComponent_1.BinaryExpression(new ValueComponent_1.ColumnReference(column.getNamespace() || null, column.column.name), "=", parameter);
50
- return new ValueComponent_1.ParenExpression(new ValueComponent_1.BinaryExpression(guard, "or", equality));
123
+ const existsExpression = new ValueComponent_1.UnaryExpression("exists", new ValueComponent_1.InlineQuery(subquery));
124
+ const predicate = kind === "exists"
125
+ ? existsExpression
126
+ : new ValueComponent_1.UnaryExpression("not", existsExpression);
127
+ return new ValueComponent_1.ParenExpression(new ValueComponent_1.BinaryExpression(guard, "or", predicate));
51
128
  };
52
129
  const rebuildWhereWithoutTerm = (query, termToRemove) => {
53
130
  if (!query.whereClause) {
54
131
  return;
55
132
  }
56
- const collectTopLevelAndTerms = (expression) => {
57
- if (expression instanceof ValueComponent_1.BinaryExpression &&
58
- expression.operator.value.trim().toLowerCase() === "and") {
59
- return [
60
- ...collectTopLevelAndTerms(expression.left),
61
- ...collectTopLevelAndTerms(expression.right)
62
- ];
63
- }
64
- return [expression];
65
- };
66
133
  const terms = collectTopLevelAndTerms(query.whereClause.condition).filter(term => term !== termToRemove);
67
134
  if (terms.length === 0) {
68
135
  query.whereClause = null;
@@ -74,6 +141,213 @@ const rebuildWhereWithoutTerm = (query, termToRemove) => {
74
141
  }
75
142
  query.whereClause = new Clause_1.WhereClause(rebuilt);
76
143
  };
144
+ const formatSqlComponent = (component) => {
145
+ formatter !== null && formatter !== void 0 ? formatter : (formatter = new SqlFormatter_1.SqlFormatter());
146
+ return formatter.format(component).formattedSql;
147
+ };
148
+ const enforceSubqueryConstraints = (sql) => {
149
+ if (!sql.trim()) {
150
+ throw new Error("SSSQL EXISTS/NOT EXISTS scaffold query must not be empty.");
151
+ }
152
+ if (sql.includes(";")) {
153
+ throw new Error("SSSQL EXISTS/NOT EXISTS scaffold query must not contain semicolons or multiple statements.");
154
+ }
155
+ if (/\blateral\b/i.test(sql)) {
156
+ throw new Error("LATERAL is not supported in SSSQL EXISTS/NOT EXISTS scaffold.");
157
+ }
158
+ };
159
+ const substituteAnchorPlaceholders = (sql, formattedColumns) => {
160
+ const usedIndexes = new Set();
161
+ const replaced = sql.replace(/\$c(\d+)/g, (_, indexDigits) => {
162
+ const index = Number(indexDigits);
163
+ if (!Number.isInteger(index)) {
164
+ throw new Error(`Invalid placeholder '$c${indexDigits}' in SSSQL scaffold query.`);
165
+ }
166
+ if (index < 0 || index >= formattedColumns.length) {
167
+ throw new Error(`Placeholder '$c${index}' references a missing SSSQL scaffold anchor column.`);
168
+ }
169
+ usedIndexes.add(index);
170
+ return formattedColumns[index];
171
+ });
172
+ if (formattedColumns.length === 0) {
173
+ return replaced;
174
+ }
175
+ for (let index = 0; index < formattedColumns.length; index += 1) {
176
+ if (!usedIndexes.has(index)) {
177
+ throw new Error(`Missing placeholder '$c${index}' for SSSQL scaffold anchor column.`);
178
+ }
179
+ }
180
+ return replaced;
181
+ };
182
+ const getScalarBranchDetails = (expression, parameterName) => {
183
+ const meaningfulTerms = collectTopLevelOrTerms(expression)
184
+ .filter(term => getGuardedParameterName(term) !== parameterName);
185
+ if (meaningfulTerms.length !== 1) {
186
+ return null;
187
+ }
188
+ const predicate = unwrapParens(meaningfulTerms[0]);
189
+ if (!(predicate instanceof ValueComponent_1.BinaryExpression)) {
190
+ return null;
191
+ }
192
+ const left = unwrapParens(predicate.left);
193
+ const right = unwrapParens(predicate.right);
194
+ if (left instanceof ValueComponent_1.ColumnReference && right instanceof ValueComponent_1.ParameterExpression && right.name.value === parameterName) {
195
+ try {
196
+ return {
197
+ operator: normalizeScalarOperator(predicate.operator.value),
198
+ target: normalizeColumnReferenceText(left)
199
+ };
200
+ }
201
+ catch {
202
+ return null;
203
+ }
204
+ }
205
+ if (right instanceof ValueComponent_1.ColumnReference && left instanceof ValueComponent_1.ParameterExpression && left.name.value === parameterName) {
206
+ try {
207
+ return {
208
+ operator: normalizeScalarOperator(predicate.operator.value),
209
+ target: normalizeColumnReferenceText(right)
210
+ };
211
+ }
212
+ catch {
213
+ return null;
214
+ }
215
+ }
216
+ return null;
217
+ };
218
+ const hasSelectQuery = (value) => {
219
+ return typeof value === "object" && value !== null && "selectQuery" in value;
220
+ };
221
+ const collectColumnReferencesDeep = (value) => {
222
+ const references = [];
223
+ const visited = new WeakSet();
224
+ const walk = (candidate) => {
225
+ if (!candidate || typeof candidate !== "object") {
226
+ return;
227
+ }
228
+ if (candidate instanceof ValueComponent_1.ColumnReference) {
229
+ references.push(candidate);
230
+ return;
231
+ }
232
+ if (visited.has(candidate)) {
233
+ return;
234
+ }
235
+ visited.add(candidate);
236
+ if (Array.isArray(candidate)) {
237
+ for (const item of candidate) {
238
+ walk(item);
239
+ }
240
+ return;
241
+ }
242
+ for (const child of Object.values(candidate)) {
243
+ walk(child);
244
+ }
245
+ };
246
+ walk(value);
247
+ return references;
248
+ };
249
+ const getExistsBranchKind = (expression, parameterName) => {
250
+ const meaningfulTerms = collectTopLevelOrTerms(expression)
251
+ .filter(term => getGuardedParameterName(term) !== parameterName);
252
+ if (meaningfulTerms.length !== 1) {
253
+ return null;
254
+ }
255
+ const predicate = unwrapParens(meaningfulTerms[0]);
256
+ const isInlineQueryValue = (value) => {
257
+ return value instanceof ValueComponent_1.InlineQuery || hasSelectQuery(value);
258
+ };
259
+ if (predicate instanceof ValueComponent_1.UnaryExpression && predicate.operator.value.trim().toLowerCase() === "exists") {
260
+ return isInlineQueryValue(unwrapParens(predicate.expression)) ? "exists" : null;
261
+ }
262
+ if (predicate instanceof ValueComponent_1.UnaryExpression && predicate.operator.value.trim().toLowerCase() === "not exists") {
263
+ return isInlineQueryValue(unwrapParens(predicate.expression)) ? "not-exists" : null;
264
+ }
265
+ if (predicate instanceof ValueComponent_1.UnaryExpression &&
266
+ predicate.operator.value.trim().toLowerCase() === "not" &&
267
+ unwrapParens(predicate.expression) instanceof ValueComponent_1.UnaryExpression) {
268
+ const nested = unwrapParens(predicate.expression);
269
+ if (nested.operator.value.trim().toLowerCase() === "exists"
270
+ && isInlineQueryValue(unwrapParens(nested.expression))) {
271
+ return "not-exists";
272
+ }
273
+ }
274
+ return null;
275
+ };
276
+ const getExistsPredicateDetails = (expression, parameterName) => {
277
+ const meaningfulTerms = collectTopLevelOrTerms(expression)
278
+ .filter(term => getGuardedParameterName(term) !== parameterName);
279
+ if (meaningfulTerms.length !== 1) {
280
+ return null;
281
+ }
282
+ const predicate = unwrapParens(meaningfulTerms[0]);
283
+ const isInlineQueryValue = (value) => {
284
+ return value instanceof ValueComponent_1.InlineQuery || hasSelectQuery(value);
285
+ };
286
+ if (predicate instanceof ValueComponent_1.UnaryExpression && predicate.operator.value.trim().toLowerCase() === "exists") {
287
+ const candidate = unwrapParens(predicate.expression);
288
+ if (isInlineQueryValue(candidate)) {
289
+ return {
290
+ kind: "exists",
291
+ subquery: candidate.selectQuery
292
+ };
293
+ }
294
+ return null;
295
+ }
296
+ if (predicate instanceof ValueComponent_1.UnaryExpression && predicate.operator.value.trim().toLowerCase() === "not exists") {
297
+ const candidate = unwrapParens(predicate.expression);
298
+ if (isInlineQueryValue(candidate)) {
299
+ return {
300
+ kind: "not-exists",
301
+ subquery: candidate.selectQuery
302
+ };
303
+ }
304
+ return null;
305
+ }
306
+ if (predicate instanceof ValueComponent_1.UnaryExpression &&
307
+ predicate.operator.value.trim().toLowerCase() === "not" &&
308
+ unwrapParens(predicate.expression) instanceof ValueComponent_1.UnaryExpression) {
309
+ const nested = unwrapParens(predicate.expression);
310
+ const candidate = unwrapParens(nested.expression);
311
+ if (nested.operator.value.trim().toLowerCase() === "exists" && isInlineQueryValue(candidate)) {
312
+ return {
313
+ kind: "not-exists",
314
+ subquery: candidate.selectQuery
315
+ };
316
+ }
317
+ }
318
+ return null;
319
+ };
320
+ const getBranchInfo = (branch) => {
321
+ const scalar = getScalarBranchDetails(branch.expression, branch.parameterName);
322
+ if (scalar) {
323
+ return {
324
+ parameterName: branch.parameterName,
325
+ kind: "scalar",
326
+ operator: scalar.operator,
327
+ target: scalar.target,
328
+ query: branch.query,
329
+ expression: branch.expression,
330
+ sql: formatSqlComponent(branch.expression)
331
+ };
332
+ }
333
+ const existsKind = getExistsBranchKind(branch.expression, branch.parameterName);
334
+ if (existsKind) {
335
+ return {
336
+ parameterName: branch.parameterName,
337
+ kind: existsKind,
338
+ query: branch.query,
339
+ expression: branch.expression,
340
+ sql: formatSqlComponent(branch.expression)
341
+ };
342
+ }
343
+ return {
344
+ parameterName: branch.parameterName,
345
+ kind: "expression",
346
+ query: branch.query,
347
+ expression: branch.expression,
348
+ sql: formatSqlComponent(branch.expression)
349
+ };
350
+ };
77
351
  /**
78
352
  * Builds and refreshes truthful SSSQL optional filter branches.
79
353
  * Runtime callers should use pruning, not dynamic predicate injection.
@@ -83,49 +357,180 @@ class SSSQLFilterBuilder {
83
357
  this.tableColumnResolver = tableColumnResolver;
84
358
  this.finder = new UpstreamSelectQueryFinder_1.UpstreamSelectQueryFinder(this.tableColumnResolver);
85
359
  }
360
+ list(query) {
361
+ const parsed = this.parseQuery(query);
362
+ return (0, PruneOptionalConditionBranches_1.collectSupportedOptionalConditionBranches)(parsed).map(getBranchInfo);
363
+ }
86
364
  scaffold(query, filters) {
87
365
  const parsed = this.parseQuery(query);
88
366
  for (const [filterName, filterValue] of Object.entries(filters)) {
89
367
  if (!isExplicitEqualityScaffoldValue(filterValue)) {
90
- throw new Error(`SSSQL scaffold only supports equality filters in v1. Use refresh for pre-authored branches: '${filterName}'.`);
368
+ throw new Error(`SSSQL scaffold only supports equality filters in v1. Use structured scaffold or refresh for pre-authored branches: '${filterName}'.`);
91
369
  }
92
- const target = this.resolveTarget(parsed, filterName);
93
- target.query.appendWhere(buildOptionalEqualityBranch(target.column, target.parameterName));
370
+ this.scaffoldBranch(parsed, {
371
+ target: filterName,
372
+ parameterName: makeParameterName(filterName),
373
+ operator: "="
374
+ });
375
+ }
376
+ return parsed;
377
+ }
378
+ scaffoldBranch(query, spec) {
379
+ const parsed = this.parseQuery(query);
380
+ if (spec.kind === "exists" || spec.kind === "not-exists") {
381
+ this.scaffoldExistsBranch(parsed, spec);
382
+ return parsed;
94
383
  }
384
+ this.scaffoldScalarBranch(parsed, spec);
95
385
  return parsed;
96
386
  }
97
387
  refresh(query, filters) {
98
388
  const parsed = this.parseQuery(query);
99
389
  for (const [filterName, filterValue] of Object.entries(filters)) {
100
- const target = this.resolveTarget(parsed, filterName);
101
- const matches = (0, PruneOptionalConditionBranches_1.collectSupportedOptionalConditionBranches)(parsed)
102
- .filter(branch => branch.parameterName === target.parameterName);
390
+ let parameterName = filterName;
391
+ let target = null;
392
+ let matches = (0, PruneOptionalConditionBranches_1.collectSupportedOptionalConditionBranches)(parsed)
393
+ .filter(branch => branch.parameterName === parameterName);
394
+ if (matches.length === 0) {
395
+ target = this.resolveTarget(parsed, filterName);
396
+ parameterName = target.parameterName;
397
+ matches = (0, PruneOptionalConditionBranches_1.collectSupportedOptionalConditionBranches)(parsed)
398
+ .filter(branch => branch.parameterName === parameterName);
399
+ }
103
400
  if (matches.length === 0) {
401
+ if (!target) {
402
+ target = this.resolveTarget(parsed, filterName);
403
+ parameterName = target.parameterName;
404
+ }
104
405
  if (!isExplicitEqualityScaffoldValue(filterValue)) {
105
406
  throw new Error(`No existing SSSQL branch was found for '${filterName}', and v1 scaffold only supports equality filters.`);
106
407
  }
107
- target.query.appendWhere(buildOptionalEqualityBranch(target.column, target.parameterName));
408
+ this.scaffoldScalarBranch(parsed, {
409
+ target: filterName,
410
+ parameterName: target.parameterName,
411
+ operator: "="
412
+ });
108
413
  continue;
109
414
  }
110
415
  if (matches.length > 1) {
111
- throw new Error(`Multiple SSSQL branches matched parameter ':${target.parameterName}'. Refresh is ambiguous.`);
416
+ throw new Error(`Multiple SSSQL branches matched parameter ':${parameterName}'. Refresh is ambiguous.`);
112
417
  }
113
418
  const [match] = matches;
114
419
  if (!match) {
115
420
  continue;
116
421
  }
117
- if (match.query === target.query) {
422
+ const correlatedPlan = this.buildCorrelatedRefreshPlan(parsed, match);
423
+ if (correlatedPlan) {
424
+ if (correlatedPlan.target.query === match.query) {
425
+ continue;
426
+ }
427
+ this.rebaseMovedBranchByAlias(match.expression, correlatedPlan.sourceAlias, correlatedPlan.target.column);
428
+ rebuildWhereWithoutTerm(match.query, match.expression);
429
+ correlatedPlan.target.query.appendWhere(match.expression);
118
430
  continue;
119
431
  }
120
- this.rebaseMovedBranch(match.expression, match.query, target.column);
432
+ if (!target) {
433
+ target = this.resolveTarget(parsed, filterName);
434
+ }
435
+ if (match.query !== target.query) {
436
+ this.rebaseMovedBranch(match.expression, match.query, target.column);
437
+ rebuildWhereWithoutTerm(match.query, match.expression);
438
+ target.query.appendWhere(match.expression);
439
+ }
440
+ }
441
+ return parsed;
442
+ }
443
+ remove(query, spec) {
444
+ const parsed = this.parseQuery(query);
445
+ const matches = this.findMatchingBranchInfos(parsed, spec);
446
+ if (matches.length === 0) {
447
+ return parsed;
448
+ }
449
+ if (matches.length > 1) {
450
+ throw new Error(`Multiple SSSQL branches matched parameter ':${spec.parameterName}'. Remove is ambiguous.`);
451
+ }
452
+ const [match] = matches;
453
+ if (!match) {
454
+ return parsed;
455
+ }
456
+ rebuildWhereWithoutTerm(match.query, match.expression);
457
+ return parsed;
458
+ }
459
+ removeAll(query) {
460
+ const parsed = this.parseQuery(query);
461
+ const matches = this.list(parsed);
462
+ for (const match of matches) {
121
463
  rebuildWhereWithoutTerm(match.query, match.expression);
122
- target.query.appendWhere(match.expression);
123
464
  }
124
465
  return parsed;
125
466
  }
126
467
  parseQuery(query) {
127
468
  return typeof query === "string" ? SelectQueryParser_1.SelectQueryParser.parse(query) : query;
128
469
  }
470
+ findMatchingBranchInfos(root, spec) {
471
+ const normalizedOperator = spec.operator ? normalizeScalarOperator(spec.operator) : undefined;
472
+ const normalizedTarget = spec.target ? normalizeIdentifier(spec.target) : undefined;
473
+ return this.list(root).filter(branch => {
474
+ if (branch.parameterName !== spec.parameterName) {
475
+ return false;
476
+ }
477
+ if (spec.kind && branch.kind !== spec.kind) {
478
+ return false;
479
+ }
480
+ if (normalizedOperator && branch.operator !== normalizedOperator) {
481
+ return false;
482
+ }
483
+ if (normalizedTarget && (!branch.target || normalizeIdentifier(branch.target) !== normalizedTarget)) {
484
+ return false;
485
+ }
486
+ return true;
487
+ });
488
+ }
489
+ scaffoldScalarBranch(root, spec) {
490
+ var _a;
491
+ const target = this.resolveTarget(root, spec.target);
492
+ const parameterName = ((_a = spec.parameterName) === null || _a === void 0 ? void 0 : _a.trim()) || target.parameterName;
493
+ const operator = normalizeScalarOperator(spec.operator);
494
+ const branch = buildOptionalScalarBranch(target.column, parameterName, operator);
495
+ const branchSql = normalizeSql(formatSqlComponent(branch));
496
+ const duplicate = this.list(root).find(existing => existing.query === target.query &&
497
+ normalizeSql(existing.sql) === branchSql);
498
+ if (duplicate) {
499
+ return;
500
+ }
501
+ target.query.appendWhere(branch);
502
+ }
503
+ scaffoldExistsBranch(root, spec) {
504
+ const parameterName = spec.parameterName.trim();
505
+ if (!parameterName) {
506
+ throw new Error("SSSQL EXISTS/NOT EXISTS scaffold requires parameterName.");
507
+ }
508
+ if (spec.anchorColumns.length === 0) {
509
+ throw new Error("SSSQL EXISTS/NOT EXISTS scaffold requires at least one anchorColumn.");
510
+ }
511
+ const anchorTargets = spec.anchorColumns.map(anchorColumn => this.resolveTarget(root, anchorColumn));
512
+ const targetQueries = [...new Set(anchorTargets.map(target => target.query))];
513
+ if (targetQueries.length !== 1) {
514
+ throw new Error("SSSQL EXISTS/NOT EXISTS scaffold anchor columns must resolve within one query scope.");
515
+ }
516
+ const targetQuery = targetQueries[0];
517
+ const formattedColumns = anchorTargets.map(target => formatSqlComponent(target.column));
518
+ const substitutedSql = substituteAnchorPlaceholders(spec.query, formattedColumns).trim();
519
+ enforceSubqueryConstraints(substitutedSql);
520
+ const subquery = SelectQueryParser_1.SelectQueryParser.parse(substitutedSql);
521
+ const parameterNames = new Set(ParameterCollector_1.ParameterCollector.collect(subquery).map(parameter => parameter.name.value));
522
+ if (parameterNames.size !== 1 || !parameterNames.has(parameterName)) {
523
+ throw new Error(`SSSQL ${spec.kind.toUpperCase()} scaffold query must reference only parameter ':${parameterName}'.`);
524
+ }
525
+ const branch = buildOptionalExistsBranch(parameterName, subquery, spec.kind);
526
+ const branchSql = normalizeSql(formatSqlComponent(branch));
527
+ const duplicate = this.list(root).find(existing => existing.query === targetQuery &&
528
+ normalizeSql(existing.sql) === branchSql);
529
+ if (duplicate) {
530
+ return;
531
+ }
532
+ targetQuery.appendWhere(branch);
533
+ }
129
534
  resolveTarget(root, filterName) {
130
535
  var _a;
131
536
  const qualified = parseQualifiedFilterName(filterName);
@@ -213,14 +618,140 @@ class SSSQLFilterBuilder {
213
618
  }
214
619
  return (_a = source.getAliasName()) !== null && _a !== void 0 ? _a : source.datasource.table.name;
215
620
  }
621
+ buildCorrelatedRefreshPlan(root, branch) {
622
+ const details = getExistsPredicateDetails(branch.expression, branch.parameterName);
623
+ if (!details) {
624
+ return null;
625
+ }
626
+ const sourceAliases = this.collectSourceAliases(branch.query);
627
+ const candidatesByKey = new Map();
628
+ for (const reference of new ColumnReferenceCollector_1.ColumnReferenceCollector().collect(details.subquery)) {
629
+ const namespace = normalizeIdentifier(reference.getNamespace());
630
+ if (!namespace || !sourceAliases.has(namespace)) {
631
+ continue;
632
+ }
633
+ const column = normalizeIdentifier(reference.column.name);
634
+ const key = `${namespace}.${column}`;
635
+ if (!candidatesByKey.has(key)) {
636
+ candidatesByKey.set(key, { namespace, column });
637
+ }
638
+ }
639
+ const candidates = [...candidatesByKey.values()];
640
+ if (candidates.length === 0) {
641
+ throw new Error(`SSSQL refresh could not infer a correlated anchor for ':${branch.parameterName}'.`);
642
+ }
643
+ if (candidates.length > 1) {
644
+ const listed = candidates.map(candidate => `${candidate.namespace}.${candidate.column}`).join(", ");
645
+ throw new Error(`SSSQL refresh found multiple correlated anchor candidates for ':${branch.parameterName}' (${listed}).`);
646
+ }
647
+ const [anchor] = candidates;
648
+ if (!anchor) {
649
+ throw new Error(`SSSQL refresh could not infer a correlated anchor for ':${branch.parameterName}'.`);
650
+ }
651
+ return {
652
+ target: this.resolveCorrelatedAnchorTarget(root, branch.query, anchor, branch.parameterName),
653
+ sourceAlias: anchor.namespace
654
+ };
655
+ }
656
+ collectSourceAliases(query) {
657
+ var _a, _b;
658
+ const aliases = new Set();
659
+ for (const source of (_b = (_a = query.fromClause) === null || _a === void 0 ? void 0 : _a.getSources()) !== null && _b !== void 0 ? _b : []) {
660
+ const sourceAlias = this.getSourceAlias(source);
661
+ if (sourceAlias) {
662
+ aliases.add(sourceAlias);
663
+ }
664
+ }
665
+ return aliases;
666
+ }
667
+ resolveCorrelatedAnchorTarget(root, sourceQuery, anchor, parameterName) {
668
+ const sourceExpression = this.findSourceExpressionByAlias(sourceQuery, anchor.namespace, parameterName);
669
+ const upstreamQuery = this.resolveSourceExpressionToUpstreamQuery(root, sourceExpression, parameterName);
670
+ if (!upstreamQuery) {
671
+ return {
672
+ query: sourceQuery,
673
+ column: new ValueComponent_1.ColumnReference(anchor.namespace, anchor.column),
674
+ parameterName
675
+ };
676
+ }
677
+ return this.resolveAnchorTargetInQuery(upstreamQuery, anchor, parameterName);
678
+ }
679
+ findSourceExpressionByAlias(query, alias, parameterName) {
680
+ var _a, _b;
681
+ const matches = ((_b = (_a = query.fromClause) === null || _a === void 0 ? void 0 : _a.getSources()) !== null && _b !== void 0 ? _b : [])
682
+ .filter(source => this.getSourceAlias(source) === alias);
683
+ if (matches.length === 0) {
684
+ throw new Error(`SSSQL refresh could not resolve correlated alias '${alias}' for ':${parameterName}'.`);
685
+ }
686
+ if (matches.length > 1) {
687
+ throw new Error(`SSSQL refresh found multiple correlated sources for alias '${alias}' and ':${parameterName}'.`);
688
+ }
689
+ return matches[0];
690
+ }
691
+ resolveSourceExpressionToUpstreamQuery(root, source, parameterName) {
692
+ if (source.datasource instanceof Clause_1.SubQuerySource) {
693
+ if (source.datasource.query instanceof SelectQuery_1.SimpleSelectQuery) {
694
+ return source.datasource.query;
695
+ }
696
+ throw new Error(`SSSQL refresh requires a simple query anchor for ':${parameterName}'.`);
697
+ }
698
+ if (!(source.datasource instanceof Clause_1.TableSource)) {
699
+ return null;
700
+ }
701
+ const cteName = normalizeIdentifier(source.datasource.table.name);
702
+ const cteMatches = new CTECollector_1.CTECollector()
703
+ .collect(root)
704
+ .filter(cte => normalizeIdentifier(cte.getSourceAliasName()) === cteName);
705
+ if (cteMatches.length === 0) {
706
+ return null;
707
+ }
708
+ if (cteMatches.length > 1) {
709
+ throw new Error(`SSSQL refresh found multiple CTE anchors for ':${parameterName}' (${source.datasource.table.name}).`);
710
+ }
711
+ const [cte] = cteMatches;
712
+ if (!cte) {
713
+ return null;
714
+ }
715
+ const cteQuery = cte.query;
716
+ if (!(cteQuery instanceof SelectQuery_1.SimpleSelectQuery)) {
717
+ throw new Error(`SSSQL refresh requires a simple CTE anchor for ':${parameterName}'.`);
718
+ }
719
+ return cteQuery;
720
+ }
721
+ resolveAnchorTargetInQuery(query, anchor, parameterName) {
722
+ const collector = new SelectableColumnCollector_1.SelectableColumnCollector(this.tableColumnResolver, false, SelectableColumnCollector_1.DuplicateDetectionMode.FullName, { upstream: true });
723
+ const matches = collector.collect(query)
724
+ .filter((entry) => entry.value instanceof ValueComponent_1.ColumnReference)
725
+ .filter(entry => normalizeIdentifier(entry.name) === anchor.column);
726
+ if (matches.length === 0) {
727
+ throw new Error(`SSSQL refresh could not resolve correlated anchor column '${anchor.column}' for ':${parameterName}'.`);
728
+ }
729
+ if (matches.length > 1) {
730
+ throw new Error(`SSSQL refresh found multiple correlated anchor columns '${anchor.column}' for ':${parameterName}'.`);
731
+ }
732
+ return {
733
+ query,
734
+ column: matches[0].value,
735
+ parameterName
736
+ };
737
+ }
738
+ getSourceAlias(source) {
739
+ const explicitAlias = source.getAliasName();
740
+ if (explicitAlias) {
741
+ return normalizeIdentifier(explicitAlias);
742
+ }
743
+ if (source.datasource instanceof Clause_1.TableSource) {
744
+ return normalizeIdentifier(source.datasource.table.name);
745
+ }
746
+ return null;
747
+ }
216
748
  rebaseMovedBranch(expression, sourceQuery, targetColumn) {
217
749
  var _a, _b, _c;
218
750
  const targetNamespace = targetColumn.qualifiedName.namespaces
219
751
  ? targetColumn.qualifiedName.namespaces.map(namespace => namespace.name)
220
752
  : null;
221
753
  const targetColumnName = normalizeIdentifier(targetColumn.column.name);
222
- const sourceAliases = new Set(new ColumnReferenceCollector_1.ColumnReferenceCollector()
223
- .collect(expression)
754
+ const sourceAliases = new Set(collectColumnReferencesDeep(expression)
224
755
  .filter(reference => normalizeIdentifier(reference.column.name) === targetColumnName)
225
756
  .map(reference => normalizeIdentifier(reference.getNamespace()))
226
757
  .filter(namespace => namespace.length > 0));
@@ -239,13 +770,29 @@ class SSSQLFilterBuilder {
239
770
  if (!availableAliases.has(sourceAlias)) {
240
771
  return;
241
772
  }
242
- for (const reference of new ColumnReferenceCollector_1.ColumnReferenceCollector().collect(expression)) {
773
+ for (const reference of collectColumnReferencesDeep(expression)) {
243
774
  if (normalizeIdentifier(reference.getNamespace()) !== sourceAlias) {
244
775
  continue;
245
776
  }
246
777
  reference.qualifiedName.namespaces = (_c = targetNamespace === null || targetNamespace === void 0 ? void 0 : targetNamespace.map(namespace => new ValueComponent_1.IdentifierString(namespace))) !== null && _c !== void 0 ? _c : null;
247
778
  }
248
779
  }
780
+ rebaseMovedBranchByAlias(expression, sourceAlias, targetColumn) {
781
+ var _a;
782
+ const normalizedSourceAlias = normalizeIdentifier(sourceAlias);
783
+ if (!normalizedSourceAlias) {
784
+ return;
785
+ }
786
+ const targetNamespace = targetColumn.qualifiedName.namespaces
787
+ ? targetColumn.qualifiedName.namespaces.map(namespace => namespace.name)
788
+ : null;
789
+ for (const reference of collectColumnReferencesDeep(expression)) {
790
+ if (normalizeIdentifier(reference.getNamespace()) !== normalizedSourceAlias) {
791
+ continue;
792
+ }
793
+ reference.qualifiedName.namespaces = (_a = targetNamespace === null || targetNamespace === void 0 ? void 0 : targetNamespace.map(namespace => new ValueComponent_1.IdentifierString(namespace))) !== null && _a !== void 0 ? _a : null;
794
+ }
795
+ }
249
796
  }
250
797
  exports.SSSQLFilterBuilder = SSSQLFilterBuilder;
251
798
  const scaffoldSssqlQuery = (sqlContent, filters) => ({