@tinqerjs/better-sqlite3-adapter 0.0.21

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 (109) hide show
  1. package/dist/expression-generator.d.ts +18 -0
  2. package/dist/expression-generator.d.ts.map +1 -0
  3. package/dist/expression-generator.js +756 -0
  4. package/dist/expression-generator.js.map +1 -0
  5. package/dist/generators/all.d.ts +11 -0
  6. package/dist/generators/all.d.ts.map +1 -0
  7. package/dist/generators/all.js +15 -0
  8. package/dist/generators/all.js.map +1 -0
  9. package/dist/generators/any.d.ts +11 -0
  10. package/dist/generators/any.d.ts.map +1 -0
  11. package/dist/generators/any.js +20 -0
  12. package/dist/generators/any.js.map +1 -0
  13. package/dist/generators/average.d.ts +10 -0
  14. package/dist/generators/average.d.ts.map +1 -0
  15. package/dist/generators/average.js +15 -0
  16. package/dist/generators/average.js.map +1 -0
  17. package/dist/generators/count.d.ts +10 -0
  18. package/dist/generators/count.d.ts.map +1 -0
  19. package/dist/generators/count.js +11 -0
  20. package/dist/generators/count.js.map +1 -0
  21. package/dist/generators/delete.d.ts +10 -0
  22. package/dist/generators/delete.d.ts.map +1 -0
  23. package/dist/generators/delete.js +24 -0
  24. package/dist/generators/delete.js.map +1 -0
  25. package/dist/generators/distinct.d.ts +11 -0
  26. package/dist/generators/distinct.d.ts.map +1 -0
  27. package/dist/generators/distinct.js +12 -0
  28. package/dist/generators/distinct.js.map +1 -0
  29. package/dist/generators/first.d.ts +12 -0
  30. package/dist/generators/first.d.ts.map +1 -0
  31. package/dist/generators/first.js +13 -0
  32. package/dist/generators/first.js.map +1 -0
  33. package/dist/generators/from.d.ts +10 -0
  34. package/dist/generators/from.d.ts.map +1 -0
  35. package/dist/generators/from.js +41 -0
  36. package/dist/generators/from.js.map +1 -0
  37. package/dist/generators/groupby.d.ts +10 -0
  38. package/dist/generators/groupby.d.ts.map +1 -0
  39. package/dist/generators/groupby.js +36 -0
  40. package/dist/generators/groupby.js.map +1 -0
  41. package/dist/generators/insert.d.ts +11 -0
  42. package/dist/generators/insert.d.ts.map +1 -0
  43. package/dist/generators/insert.js +61 -0
  44. package/dist/generators/insert.js.map +1 -0
  45. package/dist/generators/join.d.ts +10 -0
  46. package/dist/generators/join.d.ts.map +1 -0
  47. package/dist/generators/join.js +246 -0
  48. package/dist/generators/join.js.map +1 -0
  49. package/dist/generators/last.d.ts +13 -0
  50. package/dist/generators/last.d.ts.map +1 -0
  51. package/dist/generators/last.js +16 -0
  52. package/dist/generators/last.js.map +1 -0
  53. package/dist/generators/max.d.ts +10 -0
  54. package/dist/generators/max.d.ts.map +1 -0
  55. package/dist/generators/max.js +15 -0
  56. package/dist/generators/max.js.map +1 -0
  57. package/dist/generators/min.d.ts +10 -0
  58. package/dist/generators/min.d.ts.map +1 -0
  59. package/dist/generators/min.js +15 -0
  60. package/dist/generators/min.js.map +1 -0
  61. package/dist/generators/orderby.d.ts +10 -0
  62. package/dist/generators/orderby.d.ts.map +1 -0
  63. package/dist/generators/orderby.js +66 -0
  64. package/dist/generators/orderby.js.map +1 -0
  65. package/dist/generators/select.d.ts +10 -0
  66. package/dist/generators/select.d.ts.map +1 -0
  67. package/dist/generators/select.js +34 -0
  68. package/dist/generators/select.js.map +1 -0
  69. package/dist/generators/single.d.ts +12 -0
  70. package/dist/generators/single.d.ts.map +1 -0
  71. package/dist/generators/single.js +13 -0
  72. package/dist/generators/single.js.map +1 -0
  73. package/dist/generators/skip.d.ts +10 -0
  74. package/dist/generators/skip.d.ts.map +1 -0
  75. package/dist/generators/skip.js +18 -0
  76. package/dist/generators/skip.js.map +1 -0
  77. package/dist/generators/sum.d.ts +10 -0
  78. package/dist/generators/sum.d.ts.map +1 -0
  79. package/dist/generators/sum.js +15 -0
  80. package/dist/generators/sum.js.map +1 -0
  81. package/dist/generators/take.d.ts +10 -0
  82. package/dist/generators/take.d.ts.map +1 -0
  83. package/dist/generators/take.js +18 -0
  84. package/dist/generators/take.js.map +1 -0
  85. package/dist/generators/thenby.d.ts +11 -0
  86. package/dist/generators/thenby.d.ts.map +1 -0
  87. package/dist/generators/thenby.js +58 -0
  88. package/dist/generators/thenby.js.map +1 -0
  89. package/dist/generators/update.d.ts +11 -0
  90. package/dist/generators/update.d.ts.map +1 -0
  91. package/dist/generators/update.js +69 -0
  92. package/dist/generators/update.js.map +1 -0
  93. package/dist/generators/where.d.ts +10 -0
  94. package/dist/generators/where.d.ts.map +1 -0
  95. package/dist/generators/where.js +12 -0
  96. package/dist/generators/where.js.map +1 -0
  97. package/dist/index.d.ts +172 -0
  98. package/dist/index.d.ts.map +1 -0
  99. package/dist/index.js +234 -0
  100. package/dist/index.js.map +1 -0
  101. package/dist/sql-generator.d.ts +9 -0
  102. package/dist/sql-generator.d.ts.map +1 -0
  103. package/dist/sql-generator.js +325 -0
  104. package/dist/sql-generator.js.map +1 -0
  105. package/dist/types.d.ts +62 -0
  106. package/dist/types.d.ts.map +1 -0
  107. package/dist/types.js +5 -0
  108. package/dist/types.js.map +1 -0
  109. package/package.json +44 -0
@@ -0,0 +1,756 @@
1
+ /**
2
+ * Converts expression trees to SQL fragments
3
+ */
4
+ /**
5
+ * Generate SQL for any expression
6
+ */
7
+ export function generateExpression(expr, context) {
8
+ if (isBooleanExpression(expr)) {
9
+ return generateBooleanExpression(expr, context);
10
+ }
11
+ if (isValueExpression(expr)) {
12
+ return generateValueExpression(expr, context);
13
+ }
14
+ if (isObjectExpression(expr)) {
15
+ return generateObjectExpression(expr, context);
16
+ }
17
+ if (isConditionalExpression(expr)) {
18
+ return generateConditionalExpression(expr, context);
19
+ }
20
+ if (isArrayExpression(expr)) {
21
+ throw new Error("Array expressions not yet supported");
22
+ }
23
+ // Check for reference type directly since it might not be in a union yet
24
+ if (expr.type === "reference") {
25
+ return generateReferenceExpression(expr, context);
26
+ }
27
+ throw new Error(`Unknown expression type: ${expr.type}`);
28
+ }
29
+ /**
30
+ * Generate SQL for boolean expressions
31
+ */
32
+ export function generateBooleanExpression(expr, context) {
33
+ // Handle reference type which can appear in boolean context (e.g., row.dept ? ... : ...)
34
+ if (expr.type === "reference") {
35
+ const refExpr = expr;
36
+ // For null checking in LEFT JOINs, check a primary key or first column instead of *
37
+ // Extract table alias from reference
38
+ if (refExpr.source && refExpr.source.type === "joinParam") {
39
+ const aliases = Array.from(context.tableAliases.values());
40
+ const alias = aliases[refExpr.source.paramIndex] || `t${refExpr.source.paramIndex}`;
41
+ // Assume 'id' column exists for null check (common pattern)
42
+ return `"${alias}"."id" IS NOT NULL`;
43
+ }
44
+ // Fallback
45
+ return `"t1"."id" IS NOT NULL`;
46
+ }
47
+ switch (expr.type) {
48
+ case "comparison":
49
+ return generateComparisonExpression(expr, context);
50
+ case "logical":
51
+ return generateLogicalExpression(expr, context);
52
+ case "not":
53
+ return generateNotExpression(expr, context);
54
+ case "booleanColumn":
55
+ return `"${expr.name}"`;
56
+ case "booleanConstant":
57
+ return expr.value ? "TRUE" : "FALSE";
58
+ case "booleanMethod":
59
+ return generateBooleanMethodExpression(expr, context);
60
+ case "caseInsensitiveFunction":
61
+ return generateCaseInsensitiveFunctionExpression(expr, context);
62
+ case "in":
63
+ return generateInExpression(expr, context);
64
+ case "isNull":
65
+ return generateIsNullExpression(expr, context);
66
+ default:
67
+ throw new Error(`Unsupported boolean expression type: ${expr.type}`);
68
+ }
69
+ }
70
+ /**
71
+ * Generate SQL for value expressions
72
+ */
73
+ export function generateValueExpression(expr, context) {
74
+ switch (expr.type) {
75
+ case "column":
76
+ return generateColumnExpression(expr, context);
77
+ case "constant":
78
+ return generateConstantExpression(expr);
79
+ case "param":
80
+ return generateParameterExpression(expr, context);
81
+ case "arithmetic":
82
+ return generateArithmeticExpression(expr, context);
83
+ case "concat":
84
+ return generateConcatExpression(expr, context);
85
+ case "stringMethod":
86
+ return generateStringMethodExpression(expr, context);
87
+ case "aggregate":
88
+ return generateAggregateExpression(expr, context);
89
+ case "windowFunction":
90
+ return generateWindowFunctionExpression(expr, context);
91
+ case "coalesce":
92
+ return generateCoalesceExpression(expr, context);
93
+ case "case":
94
+ return generateCaseExpression(expr, context);
95
+ case "reference":
96
+ return generateReferenceExpression(expr, context);
97
+ case "allColumns":
98
+ return "*";
99
+ default:
100
+ throw new Error(`Unsupported value expression type: ${expr.type}`);
101
+ }
102
+ }
103
+ /**
104
+ * Generate SQL for comparison expressions
105
+ */
106
+ function generateComparisonExpression(expr, context) {
107
+ // Handle cases where left or right side might be boolean expressions
108
+ const left = generateExpressionForComparison(expr.left, context);
109
+ const right = generateExpressionForComparison(expr.right, context);
110
+ // Special handling for NULL comparisons
111
+ if (right === "NULL") {
112
+ if (expr.operator === "==") {
113
+ return `${left} IS NULL`;
114
+ }
115
+ else if (expr.operator === "!=") {
116
+ return `${left} IS NOT NULL`;
117
+ }
118
+ }
119
+ if (left === "NULL") {
120
+ if (expr.operator === "==") {
121
+ return `${right} IS NULL`;
122
+ }
123
+ else if (expr.operator === "!=") {
124
+ return `${right} IS NOT NULL`;
125
+ }
126
+ }
127
+ const operator = mapComparisonOperator(expr.operator);
128
+ return `${left} ${operator} ${right}`;
129
+ }
130
+ /**
131
+ * Generate expression for use in comparisons - handles both value and boolean expressions
132
+ */
133
+ function generateExpressionForComparison(expr, context) {
134
+ // Check if it's a boolean expression
135
+ if (isBooleanExpression(expr)) {
136
+ return generateBooleanExpression(expr, context);
137
+ }
138
+ // Check if it's a value expression
139
+ if (isValueExpression(expr)) {
140
+ return generateValueExpression(expr, context);
141
+ }
142
+ // Handle other expression types
143
+ return generateExpression(expr, context);
144
+ }
145
+ /**
146
+ * Map JavaScript comparison operators to SQL
147
+ */
148
+ function mapComparisonOperator(op) {
149
+ switch (op) {
150
+ case "==":
151
+ case "===":
152
+ return "=";
153
+ case "!=":
154
+ case "!==":
155
+ return "!=";
156
+ case ">":
157
+ return ">";
158
+ case ">=":
159
+ return ">=";
160
+ case "<":
161
+ return "<";
162
+ case "<=":
163
+ return "<=";
164
+ default:
165
+ return op;
166
+ }
167
+ }
168
+ /**
169
+ * Generate SQL for logical expressions
170
+ */
171
+ function generateLogicalExpression(expr, context) {
172
+ const left = generateBooleanExpression(expr.left, context);
173
+ const right = generateBooleanExpression(expr.right, context);
174
+ const operator = expr.operator === "and" ? "AND" : "OR";
175
+ return `(${left} ${operator} ${right})`;
176
+ }
177
+ /**
178
+ * Generate SQL for NOT expressions
179
+ */
180
+ function generateNotExpression(expr, context) {
181
+ // Special handling for NOT IN with array parameters
182
+ if (expr.expression.type === "in") {
183
+ const inExpr = expr.expression;
184
+ if (!Array.isArray(inExpr.list) && inExpr.list.type === "param") {
185
+ const value = generateValueExpression(inExpr.value, context);
186
+ const paramExpr = inExpr.list;
187
+ const paramName = paramExpr.property || paramExpr.param;
188
+ // Check if this parameter is an array in the runtime params
189
+ const paramValue = context.params?.[paramName];
190
+ if (!Array.isArray(paramValue)) {
191
+ throw new Error(`Expected array parameter '${paramName}' but got ${typeof paramValue}`);
192
+ }
193
+ if (paramValue.length === 0) {
194
+ // Empty NOT IN list always returns true
195
+ return "TRUE";
196
+ }
197
+ // Expand array parameters into NOT IN clause with indexed parameters
198
+ // e.g., params.ids = [3,6,4,5] becomes NOT IN (@ids_0, @ids_1, @ids_2, @ids_3)
199
+ const listValues = paramValue.map((_, index) => context.formatParameter(`${paramName}_${index}`));
200
+ return `${value} NOT IN (${listValues.join(", ")})`;
201
+ }
202
+ }
203
+ const operand = generateBooleanExpression(expr.expression, context);
204
+ // Check if operand is a simple column reference (no operators)
205
+ if (!operand.includes(" ") && !operand.includes("(")) {
206
+ return `NOT ${operand}`;
207
+ }
208
+ return `NOT (${operand})`;
209
+ }
210
+ /**
211
+ * Generate SQL for reference expressions (entire table/object references)
212
+ */
213
+ function generateReferenceExpression(expr, context) {
214
+ // A reference expression like { u, d } needs special handling
215
+ // In SELECT context, we'd want to expand all columns from the referenced table
216
+ // Handle new source-based references
217
+ if (expr.source) {
218
+ const aliases = Array.from(context.tableAliases.values());
219
+ switch (expr.source.type) {
220
+ case "joinParam": {
221
+ // Map parameter references to table aliases
222
+ if (expr.source.paramIndex < aliases.length) {
223
+ // Return all columns from this table (will be expanded in SELECT generation)
224
+ return `"${aliases[expr.source.paramIndex]}".*`;
225
+ }
226
+ return `"t${expr.source.paramIndex}".*`;
227
+ }
228
+ case "table": {
229
+ // Explicit table alias
230
+ return `"${expr.source.alias}".*`;
231
+ }
232
+ default:
233
+ // Should not happen, but handle gracefully
234
+ return `"t0".*`;
235
+ }
236
+ }
237
+ // Handle regular table references
238
+ if (expr.table) {
239
+ const alias = context.tableAliases.get(expr.table) || expr.table;
240
+ return `"${alias}".*`;
241
+ }
242
+ // Fallback
243
+ return `"t0".*`;
244
+ }
245
+ /**
246
+ * Generate SQL for column references
247
+ */
248
+ function generateColumnExpression(expr, context) {
249
+ // Handle GROUP BY key references
250
+ if (context.groupByKey) {
251
+ // Handle g.key - single column or expression group by
252
+ if (expr.name === "key" && !expr.table) {
253
+ // Return the GROUP BY expression
254
+ if (context.groupByKey.type === "column") {
255
+ // Simple column - check if it maps to a source column
256
+ const columnExpr = context.groupByKey;
257
+ if (context.symbolTable) {
258
+ const sourceRef = context.symbolTable.entries.get(columnExpr.name);
259
+ if (sourceRef) {
260
+ return `"${sourceRef.tableAlias}"."${sourceRef.columnName}"`;
261
+ }
262
+ }
263
+ // For non-JOIN queries, use unqualified column name
264
+ return `"${columnExpr.name}"`;
265
+ }
266
+ else {
267
+ // Complex expression (including objects, method calls, etc.)
268
+ return generateExpression(context.groupByKey, context);
269
+ }
270
+ }
271
+ // Handle g.key.property - composite group by with object key
272
+ if (expr.table === "key" && context.groupByKey.type === "object") {
273
+ // Look up the property in the composite key
274
+ const objExpr = context.groupByKey;
275
+ const keyProperty = objExpr.properties[expr.name];
276
+ if (keyProperty) {
277
+ return generateExpression(keyProperty, context);
278
+ }
279
+ }
280
+ }
281
+ // Handle ColumnSource for proper table alias resolution
282
+ if (expr.source) {
283
+ const aliases = Array.from(context.tableAliases.values());
284
+ let tableAlias;
285
+ switch (expr.source.type) {
286
+ case "joinParam":
287
+ // Direct parameter references
288
+ tableAlias = aliases[expr.source.paramIndex] || `t${expr.source.paramIndex}`;
289
+ return `"${tableAlias}"."${expr.name}"`;
290
+ case "joinResult":
291
+ // Nested JOIN property access
292
+ tableAlias = aliases[expr.source.tableIndex] || `t${expr.source.tableIndex}`;
293
+ return `"${tableAlias}"."${expr.name}"`;
294
+ case "spread":
295
+ // Spread operator source
296
+ tableAlias = aliases[expr.source.sourceIndex] || `t${expr.source.sourceIndex}`;
297
+ return `"${tableAlias}"."${expr.name}"`;
298
+ case "table":
299
+ // Explicit table alias
300
+ return `"${expr.source.alias}"."${expr.name}"`;
301
+ case "direct":
302
+ // Direct table access (no qualifier needed)
303
+ return `"${expr.name}"`;
304
+ }
305
+ }
306
+ // Check symbol table for JOIN result references
307
+ if (context.symbolTable) {
308
+ // First check for direct property name
309
+ const sourceRef = context.symbolTable.entries.get(expr.name);
310
+ if (sourceRef) {
311
+ // If it's a reference node (marked with "*"), we need special handling
312
+ if (sourceRef.columnName === "*" && expr.table) {
313
+ // This is accessing a property through a reference
314
+ // The symbol table entry tells us which table the reference points to
315
+ return `"${sourceRef.tableAlias}"."${expr.name}"`;
316
+ }
317
+ return `"${sourceRef.tableAlias}"."${sourceRef.columnName}"`;
318
+ }
319
+ // If there's a table prefix, try to build a path
320
+ if (expr.table) {
321
+ const path = `${expr.table}.${expr.name}`;
322
+ const pathRef = context.symbolTable.entries.get(path);
323
+ if (pathRef) {
324
+ return `"${pathRef.tableAlias}"."${pathRef.columnName}"`;
325
+ }
326
+ // Check if the table itself is a reference in the symbol table
327
+ const tableRef = context.symbolTable.entries.get(expr.table);
328
+ if (tableRef && tableRef.columnName === "*") {
329
+ // This is a reference node - resolve to the actual table
330
+ return `"${tableRef.tableAlias}"."${expr.name}"`;
331
+ }
332
+ }
333
+ }
334
+ // Regular column handling
335
+ if (expr.table) {
336
+ // Check if the table is a reference from JOIN result shape
337
+ // When we have joined.c.id, it becomes column with table="c" and name="id"
338
+ // We need to check if "c" is actually a reference in the symbol table
339
+ if (context.symbolTable) {
340
+ const tableRef = context.symbolTable.entries.get(expr.table);
341
+ if (tableRef && tableRef.columnName === "*") {
342
+ // This is a reference node - use the mapped table alias
343
+ return `"${tableRef.tableAlias}"."${expr.name}"`;
344
+ }
345
+ }
346
+ // Check if the table contains a dot (like "o.amount" from bad parsing)
347
+ // This happens when ORDER BY expression isn't properly parsed
348
+ if (expr.table.includes(".")) {
349
+ // This is a mis-parsed expression, try to resolve it through symbol table
350
+ const parts = expr.table.split(".");
351
+ if (parts.length === 2 && context.symbolTable) {
352
+ const tableRef = context.symbolTable.entries.get(parts[0]);
353
+ if (tableRef && tableRef.columnName === "*") {
354
+ // Use the resolved table alias and the field name
355
+ return `"${tableRef.tableAlias}"."${parts[1]}"`;
356
+ }
357
+ }
358
+ // If we can't resolve it, return as-is (will likely fail)
359
+ return `"${expr.table}"`;
360
+ }
361
+ const alias = context.tableAliases.get(expr.table) || expr.table;
362
+ return `"${alias}"."${expr.name}"`;
363
+ }
364
+ // No table specified - only use alias if we have JOINs
365
+ // For single-table queries, use unqualified column names
366
+ if (context.hasJoins) {
367
+ const firstAlias = context.tableAliases.values().next().value || "t0";
368
+ return `"${firstAlias}"."${expr.name}"`;
369
+ }
370
+ return `"${expr.name}"`;
371
+ }
372
+ /**
373
+ * Generate SQL for constants
374
+ */
375
+ function generateConstantExpression(expr) {
376
+ if (expr.value === null || expr.value === undefined) {
377
+ return "NULL";
378
+ }
379
+ if (typeof expr.value === "string") {
380
+ // Escape single quotes in strings
381
+ const escaped = expr.value.replace(/'/g, "''");
382
+ return `'${escaped}'`;
383
+ }
384
+ if (typeof expr.value === "boolean") {
385
+ return expr.value ? "TRUE" : "FALSE";
386
+ }
387
+ return String(expr.value);
388
+ }
389
+ /**
390
+ * Generate SQL for parameter references
391
+ */
392
+ function generateParameterExpression(expr, context) {
393
+ // Handle array indexing
394
+ if (expr.index !== undefined) {
395
+ // For array access, we need to extract the value at runtime
396
+ // The parameter should reference the array element directly
397
+ // e.g., params.roles[0] becomes roles[0] in the parameter
398
+ const baseName = expr.property || expr.param;
399
+ const indexedName = `${baseName}[${expr.index}]`;
400
+ // Store the array access for runtime resolution
401
+ // The query executor will need to resolve this
402
+ return context.formatParameter(indexedName);
403
+ }
404
+ // Extract only the last property name for the parameter
405
+ const paramName = expr.property || expr.param;
406
+ return context.formatParameter(paramName);
407
+ }
408
+ /**
409
+ * Generate SQL for arithmetic expressions
410
+ */
411
+ function generateArithmeticExpression(expr, context) {
412
+ const left = generateValueExpression(expr.left, context);
413
+ const right = generateValueExpression(expr.right, context);
414
+ // In PostgreSQL, use || for string concatenation
415
+ if (expr.operator === "+") {
416
+ // Check if either operand is definitely a string
417
+ const isStringConcat =
418
+ // String constants
419
+ (expr.left.type === "constant" &&
420
+ typeof expr.left.value === "string") ||
421
+ (expr.right.type === "constant" &&
422
+ typeof expr.right.value === "string") ||
423
+ // String method results (toLowerCase, toUpperCase, substring, etc.)
424
+ expr.left.type === "stringMethod" ||
425
+ expr.right.type === "stringMethod" ||
426
+ // Check if expressions are likely to produce strings
427
+ isLikelyStringExpression(expr.left) ||
428
+ isLikelyStringExpression(expr.right) ||
429
+ // Check for string-related parameter names (heuristic)
430
+ (expr.left.type === "param" && isLikelyStringParam(expr.left)) ||
431
+ (expr.right.type === "param" && isLikelyStringParam(expr.right)) ||
432
+ // If both operands are parameters, assume string concat to be safe
433
+ (expr.left.type === "param" && expr.right.type === "param");
434
+ if (isStringConcat) {
435
+ return `(${left} || ${right})`;
436
+ }
437
+ }
438
+ return `(${left} ${expr.operator} ${right})`;
439
+ }
440
+ /**
441
+ * Check if a parameter expression is likely a string based on naming patterns
442
+ */
443
+ function isLikelyStringParam(expr) {
444
+ const param = expr.param.toLowerCase();
445
+ // Check for common string parameter patterns
446
+ const stringPatterns = [
447
+ /text/i,
448
+ /name/i,
449
+ /title/i,
450
+ /description/i,
451
+ /message/i,
452
+ /suffix/i,
453
+ /prefix/i,
454
+ /email/i,
455
+ /url/i,
456
+ /path/i,
457
+ /label/i,
458
+ /firstname/i,
459
+ /lastname/i,
460
+ /string/i,
461
+ /content/i,
462
+ /body/i,
463
+ ];
464
+ return stringPatterns.some((pattern) => pattern.test(param));
465
+ }
466
+ /**
467
+ * Check if an expression is likely to produce a string value
468
+ */
469
+ function isLikelyStringExpression(expr) {
470
+ // Check for COALESCE with string-like columns
471
+ if (expr.type === "coalesce") {
472
+ const coalesceExpr = expr;
473
+ // If any expression in COALESCE is string-like, the result is string-like
474
+ return coalesceExpr.expressions.some((e) => {
475
+ if (e.type === "column") {
476
+ const col = e;
477
+ // Check if column name suggests it's a string
478
+ return /text|name|title|description|message|email|url|path|label/i.test(col.name);
479
+ }
480
+ if (e.type === "constant") {
481
+ return typeof e.value === "string";
482
+ }
483
+ if (e.type === "stringMethod") {
484
+ return true;
485
+ }
486
+ return false;
487
+ });
488
+ }
489
+ return false;
490
+ }
491
+ /**
492
+ * Generate SQL for string concatenation
493
+ */
494
+ function generateConcatExpression(expr, context) {
495
+ const left = generateValueExpression(expr.left, context);
496
+ const right = generateValueExpression(expr.right, context);
497
+ // PostgreSQL uses || for concatenation
498
+ return `${left} || ${right}`;
499
+ }
500
+ /**
501
+ * Generate SQL for string method expressions
502
+ */
503
+ function generateStringMethodExpression(expr, context) {
504
+ const object = generateValueExpression(expr.object, context);
505
+ switch (expr.method) {
506
+ case "toLowerCase":
507
+ return `LOWER(${object})`;
508
+ case "toUpperCase":
509
+ return `UPPER(${object})`;
510
+ default:
511
+ throw new Error(`Unsupported string method: ${expr.method}`);
512
+ }
513
+ }
514
+ /**
515
+ * Generate SQL for IS NULL / IS NOT NULL expressions
516
+ */
517
+ function generateIsNullExpression(expr, context) {
518
+ const value = generateValueExpression(expr.expression, context);
519
+ return expr.negated ? `${value} IS NOT NULL` : `${value} IS NULL`;
520
+ }
521
+ /**
522
+ * Generate SQL for IN expressions
523
+ */
524
+ function generateInExpression(expr, context) {
525
+ const value = generateValueExpression(expr.value, context);
526
+ // Handle list as array expression, array of values, or parameter
527
+ if (!Array.isArray(expr.list) && expr.list.type === "param") {
528
+ // Handle parameter that represents an array
529
+ const paramExpr = expr.list;
530
+ // Use property if it exists (e.g., params.targetIds), otherwise use param
531
+ const paramName = paramExpr.property || paramExpr.param;
532
+ // Check if this parameter is an array in the runtime params
533
+ const paramValue = context.params?.[paramName];
534
+ if (!Array.isArray(paramValue)) {
535
+ throw new Error(`Expected array parameter '${paramName}' but got ${typeof paramValue}`);
536
+ }
537
+ if (paramValue.length === 0) {
538
+ // Empty IN list always returns false
539
+ return "FALSE";
540
+ }
541
+ // Expand array parameters into IN clause with indexed parameters
542
+ // e.g., params.ids = [3,6,4,5] becomes IN (@ids_0, @ids_1, @ids_2, @ids_3)
543
+ const listValues = paramValue.map((_, index) => context.formatParameter(`${paramName}_${index}`));
544
+ return `${value} IN (${listValues.join(", ")})`;
545
+ }
546
+ let listValues;
547
+ if (Array.isArray(expr.list)) {
548
+ listValues = expr.list.map((item) => generateValueExpression(item, context));
549
+ }
550
+ else if (expr.list.type === "array") {
551
+ const arrayExpr = expr.list;
552
+ listValues = arrayExpr.elements.map((item) => generateExpression(item, context));
553
+ }
554
+ else {
555
+ throw new Error("IN expression requires an array or array parameter");
556
+ }
557
+ if (listValues.length === 0) {
558
+ // Empty IN list always returns false
559
+ return "FALSE";
560
+ }
561
+ return `${value} IN (${listValues.join(", ")})`;
562
+ }
563
+ /**
564
+ * Generate SQL for boolean method expressions
565
+ */
566
+ function generateBooleanMethodExpression(expr, context) {
567
+ const object = generateValueExpression(expr.object, context);
568
+ switch (expr.method) {
569
+ case "startsWith":
570
+ if (expr.arguments && expr.arguments.length > 0) {
571
+ const prefix = generateValueExpression(expr.arguments[0], context);
572
+ return `${object} LIKE ${prefix} || '%'`;
573
+ }
574
+ throw new Error("startsWith requires an argument");
575
+ case "endsWith":
576
+ if (expr.arguments && expr.arguments.length > 0) {
577
+ const suffix = generateValueExpression(expr.arguments[0], context);
578
+ return `${object} LIKE '%' || ${suffix}`;
579
+ }
580
+ throw new Error("endsWith requires an argument");
581
+ case "includes":
582
+ case "contains":
583
+ if (expr.arguments && expr.arguments.length > 0) {
584
+ const search = generateValueExpression(expr.arguments[0], context);
585
+ return `${object} LIKE '%' || ${search} || '%'`;
586
+ }
587
+ throw new Error("includes/contains requires an argument");
588
+ default:
589
+ throw new Error(`Unsupported boolean method: ${expr.method}`);
590
+ }
591
+ }
592
+ /**
593
+ * Generate SQL for case-insensitive function expressions
594
+ */
595
+ function generateCaseInsensitiveFunctionExpression(expr, context) {
596
+ const left = generateValueExpression(expr.arguments[0], context);
597
+ const right = generateValueExpression(expr.arguments[1], context);
598
+ switch (expr.function) {
599
+ case "iequals":
600
+ return `LOWER(${left}) = LOWER(${right})`;
601
+ case "istartsWith":
602
+ return `LOWER(${left}) LIKE LOWER(${right}) || '%'`;
603
+ case "iendsWith":
604
+ return `LOWER(${left}) LIKE '%' || LOWER(${right})`;
605
+ case "icontains":
606
+ return `LOWER(${left}) LIKE '%' || LOWER(${right}) || '%'`;
607
+ default:
608
+ throw new Error(`Unsupported case-insensitive function: ${expr.function}`);
609
+ }
610
+ }
611
+ /**
612
+ * Generate SQL for aggregate expressions
613
+ */
614
+ function generateAggregateExpression(expr, context) {
615
+ const func = expr.function.toUpperCase();
616
+ // COUNT(*) special case
617
+ if (func === "COUNT" && !expr.expression) {
618
+ return "COUNT(*)";
619
+ }
620
+ // Aggregate with expression (e.g., SUM(amount), COUNT(id))
621
+ if (expr.expression) {
622
+ const innerExpr = generateValueExpression(expr.expression, context);
623
+ return `${func}(${innerExpr})`;
624
+ }
625
+ // Default to COUNT(*) for other aggregates without expression
626
+ return `${func}(*)`;
627
+ }
628
+ /**
629
+ * Generate SQL for window function expressions
630
+ */
631
+ function generateWindowFunctionExpression(expr, context) {
632
+ // Map function name to SQL
633
+ let funcName;
634
+ switch (expr.function) {
635
+ case "rowNumber":
636
+ funcName = "ROW_NUMBER";
637
+ break;
638
+ case "rank":
639
+ funcName = "RANK";
640
+ break;
641
+ case "denseRank":
642
+ funcName = "DENSE_RANK";
643
+ break;
644
+ }
645
+ // Build OVER clause parts
646
+ const overParts = [];
647
+ // PARTITION BY clause (optional)
648
+ if (expr.partitionBy.length > 0) {
649
+ const partitions = expr.partitionBy.map((p) => generateValueExpression(p, context));
650
+ overParts.push(`PARTITION BY ${partitions.join(", ")}`);
651
+ }
652
+ // ORDER BY clause (required)
653
+ const orders = expr.orderBy.map((o) => {
654
+ const orderExpr = generateValueExpression(o.expression, context);
655
+ const direction = o.direction === "asc" ? "ASC" : "DESC";
656
+ return `${orderExpr} ${direction}`;
657
+ });
658
+ overParts.push(`ORDER BY ${orders.join(", ")}`);
659
+ // Build complete OVER clause
660
+ const overClause = overParts.join(" ");
661
+ return `${funcName}() OVER (${overClause})`;
662
+ }
663
+ /**
664
+ * Generate SQL for coalesce expressions
665
+ */
666
+ function generateCoalesceExpression(expr, context) {
667
+ const expressions = expr.expressions.map((e) => generateValueExpression(e, context));
668
+ return `COALESCE(${expressions.join(", ")})`;
669
+ }
670
+ /**
671
+ * Generate SQL for conditional expressions (ternary)
672
+ */
673
+ function generateConditionalExpression(expr, context) {
674
+ const condition = generateBooleanExpression(expr.condition, context);
675
+ const thenExpr = generateExpression(expr.then, context);
676
+ const elseExpr = generateExpression(expr.else, context);
677
+ // Use SQL CASE expression
678
+ return `CASE WHEN ${condition} THEN ${thenExpr} ELSE ${elseExpr} END`;
679
+ }
680
+ /**
681
+ * Generate SQL for CASE expressions (from ternary operator)
682
+ */
683
+ function generateCaseExpression(expr, context) {
684
+ // Handle multiple WHEN conditions
685
+ if (!expr.conditions || expr.conditions.length === 0) {
686
+ throw new Error("CASE expression must have at least one condition");
687
+ }
688
+ const whenClauses = expr.conditions
689
+ .map((cond) => {
690
+ const when = generateBooleanExpression(cond.when, context);
691
+ const then = generateExpression(cond.then, context);
692
+ return `WHEN ${when} THEN ${then}`;
693
+ })
694
+ .join(" ");
695
+ const elseClause = expr.else ? ` ELSE ${generateExpression(expr.else, context)}` : "";
696
+ return `CASE ${whenClauses}${elseClause} END`;
697
+ }
698
+ /**
699
+ * Generate SQL for object expressions (used in SELECT)
700
+ */
701
+ function generateObjectExpression(expr, context) {
702
+ if (!expr.properties) {
703
+ throw new Error("Object expression must have properties");
704
+ }
705
+ const parts = Object.entries(expr.properties).map(([key, value]) => {
706
+ // Handle spread operator (AllColumnsExpression with special key)
707
+ if (key === "__spread__" && value.type === "allColumns") {
708
+ return "*";
709
+ }
710
+ let sqlValue = generateExpression(value, context);
711
+ // If it's a boolean expression in SELECT context, wrap it in a CASE to return boolean value
712
+ if (isBooleanExpression(value) &&
713
+ value.type !== "booleanColumn" &&
714
+ value.type !== "booleanConstant") {
715
+ sqlValue = `CASE WHEN ${sqlValue} THEN TRUE ELSE FALSE END`;
716
+ }
717
+ return `${sqlValue} AS "${key}"`;
718
+ });
719
+ return parts.join(", ");
720
+ }
721
+ // Type guards
722
+ function isBooleanExpression(expr) {
723
+ return [
724
+ "comparison",
725
+ "logical",
726
+ "not",
727
+ "booleanColumn",
728
+ "booleanConstant",
729
+ "booleanMethod",
730
+ "exists",
731
+ ].includes(expr.type);
732
+ }
733
+ function isValueExpression(expr) {
734
+ return [
735
+ "column",
736
+ "constant",
737
+ "param",
738
+ "arithmetic",
739
+ "concat",
740
+ "stringMethod",
741
+ "case",
742
+ "aggregate",
743
+ "windowFunction",
744
+ "coalesce",
745
+ ].includes(expr.type);
746
+ }
747
+ function isObjectExpression(expr) {
748
+ return expr.type === "object";
749
+ }
750
+ function isArrayExpression(expr) {
751
+ return expr.type === "array";
752
+ }
753
+ function isConditionalExpression(expr) {
754
+ return expr.type === "conditional";
755
+ }
756
+ //# sourceMappingURL=expression-generator.js.map