@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.
- package/dist/expression-generator.d.ts +18 -0
- package/dist/expression-generator.d.ts.map +1 -0
- package/dist/expression-generator.js +756 -0
- package/dist/expression-generator.js.map +1 -0
- package/dist/generators/all.d.ts +11 -0
- package/dist/generators/all.d.ts.map +1 -0
- package/dist/generators/all.js +15 -0
- package/dist/generators/all.js.map +1 -0
- package/dist/generators/any.d.ts +11 -0
- package/dist/generators/any.d.ts.map +1 -0
- package/dist/generators/any.js +20 -0
- package/dist/generators/any.js.map +1 -0
- package/dist/generators/average.d.ts +10 -0
- package/dist/generators/average.d.ts.map +1 -0
- package/dist/generators/average.js +15 -0
- package/dist/generators/average.js.map +1 -0
- package/dist/generators/count.d.ts +10 -0
- package/dist/generators/count.d.ts.map +1 -0
- package/dist/generators/count.js +11 -0
- package/dist/generators/count.js.map +1 -0
- package/dist/generators/delete.d.ts +10 -0
- package/dist/generators/delete.d.ts.map +1 -0
- package/dist/generators/delete.js +24 -0
- package/dist/generators/delete.js.map +1 -0
- package/dist/generators/distinct.d.ts +11 -0
- package/dist/generators/distinct.d.ts.map +1 -0
- package/dist/generators/distinct.js +12 -0
- package/dist/generators/distinct.js.map +1 -0
- package/dist/generators/first.d.ts +12 -0
- package/dist/generators/first.d.ts.map +1 -0
- package/dist/generators/first.js +13 -0
- package/dist/generators/first.js.map +1 -0
- package/dist/generators/from.d.ts +10 -0
- package/dist/generators/from.d.ts.map +1 -0
- package/dist/generators/from.js +41 -0
- package/dist/generators/from.js.map +1 -0
- package/dist/generators/groupby.d.ts +10 -0
- package/dist/generators/groupby.d.ts.map +1 -0
- package/dist/generators/groupby.js +36 -0
- package/dist/generators/groupby.js.map +1 -0
- package/dist/generators/insert.d.ts +11 -0
- package/dist/generators/insert.d.ts.map +1 -0
- package/dist/generators/insert.js +61 -0
- package/dist/generators/insert.js.map +1 -0
- package/dist/generators/join.d.ts +10 -0
- package/dist/generators/join.d.ts.map +1 -0
- package/dist/generators/join.js +246 -0
- package/dist/generators/join.js.map +1 -0
- package/dist/generators/last.d.ts +13 -0
- package/dist/generators/last.d.ts.map +1 -0
- package/dist/generators/last.js +16 -0
- package/dist/generators/last.js.map +1 -0
- package/dist/generators/max.d.ts +10 -0
- package/dist/generators/max.d.ts.map +1 -0
- package/dist/generators/max.js +15 -0
- package/dist/generators/max.js.map +1 -0
- package/dist/generators/min.d.ts +10 -0
- package/dist/generators/min.d.ts.map +1 -0
- package/dist/generators/min.js +15 -0
- package/dist/generators/min.js.map +1 -0
- package/dist/generators/orderby.d.ts +10 -0
- package/dist/generators/orderby.d.ts.map +1 -0
- package/dist/generators/orderby.js +66 -0
- package/dist/generators/orderby.js.map +1 -0
- package/dist/generators/select.d.ts +10 -0
- package/dist/generators/select.d.ts.map +1 -0
- package/dist/generators/select.js +34 -0
- package/dist/generators/select.js.map +1 -0
- package/dist/generators/single.d.ts +12 -0
- package/dist/generators/single.d.ts.map +1 -0
- package/dist/generators/single.js +13 -0
- package/dist/generators/single.js.map +1 -0
- package/dist/generators/skip.d.ts +10 -0
- package/dist/generators/skip.d.ts.map +1 -0
- package/dist/generators/skip.js +18 -0
- package/dist/generators/skip.js.map +1 -0
- package/dist/generators/sum.d.ts +10 -0
- package/dist/generators/sum.d.ts.map +1 -0
- package/dist/generators/sum.js +15 -0
- package/dist/generators/sum.js.map +1 -0
- package/dist/generators/take.d.ts +10 -0
- package/dist/generators/take.d.ts.map +1 -0
- package/dist/generators/take.js +18 -0
- package/dist/generators/take.js.map +1 -0
- package/dist/generators/thenby.d.ts +11 -0
- package/dist/generators/thenby.d.ts.map +1 -0
- package/dist/generators/thenby.js +58 -0
- package/dist/generators/thenby.js.map +1 -0
- package/dist/generators/update.d.ts +11 -0
- package/dist/generators/update.d.ts.map +1 -0
- package/dist/generators/update.js +69 -0
- package/dist/generators/update.js.map +1 -0
- package/dist/generators/where.d.ts +10 -0
- package/dist/generators/where.d.ts.map +1 -0
- package/dist/generators/where.js +12 -0
- package/dist/generators/where.js.map +1 -0
- package/dist/index.d.ts +172 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +234 -0
- package/dist/index.js.map +1 -0
- package/dist/sql-generator.d.ts +9 -0
- package/dist/sql-generator.d.ts.map +1 -0
- package/dist/sql-generator.js +325 -0
- package/dist/sql-generator.js.map +1 -0
- package/dist/types.d.ts +62 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- 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
|