metal-orm 1.0.14 → 1.0.16
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/README.md +69 -67
- package/dist/decorators/index.cjs +1983 -224
- package/dist/decorators/index.cjs.map +1 -1
- package/dist/decorators/index.d.cts +6 -6
- package/dist/decorators/index.d.ts +6 -6
- package/dist/decorators/index.js +1982 -224
- package/dist/decorators/index.js.map +1 -1
- package/dist/index.cjs +5284 -3751
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +524 -169
- package/dist/index.d.ts +524 -169
- package/dist/index.js +5197 -3736
- package/dist/index.js.map +1 -1
- package/dist/{select-CCp1oz9p.d.cts → select-BKZrMRCQ.d.cts} +555 -94
- package/dist/{select-CCp1oz9p.d.ts → select-BKZrMRCQ.d.ts} +555 -94
- package/package.json +1 -1
- package/src/codegen/naming-strategy.ts +64 -0
- package/src/codegen/typescript.ts +19 -21
- package/src/core/ast/adapters.ts +21 -0
- package/src/core/ast/aggregate-functions.ts +13 -13
- package/src/core/ast/builders.ts +56 -43
- package/src/core/ast/expression-builders.ts +34 -34
- package/src/core/ast/expression-nodes.ts +18 -16
- package/src/core/ast/expression-visitor.ts +122 -69
- package/src/core/ast/expression.ts +6 -4
- package/src/core/ast/join-metadata.ts +15 -0
- package/src/core/ast/join-node.ts +22 -20
- package/src/core/ast/join.ts +5 -5
- package/src/core/ast/query.ts +52 -88
- package/src/core/ast/types.ts +20 -0
- package/src/core/ast/window-functions.ts +55 -55
- package/src/core/ddl/dialects/base-schema-dialect.ts +20 -6
- package/src/core/ddl/dialects/mssql-schema-dialect.ts +32 -8
- package/src/core/ddl/dialects/mysql-schema-dialect.ts +21 -10
- package/src/core/ddl/dialects/postgres-schema-dialect.ts +52 -7
- package/src/core/ddl/dialects/sqlite-schema-dialect.ts +23 -9
- package/src/core/ddl/introspect/catalogs/index.ts +1 -0
- package/src/core/ddl/introspect/catalogs/postgres.ts +143 -0
- package/src/core/ddl/introspect/context.ts +9 -0
- package/src/core/ddl/introspect/functions/postgres.ts +26 -0
- package/src/core/ddl/introspect/mssql.ts +149 -149
- package/src/core/ddl/introspect/mysql.ts +99 -99
- package/src/core/ddl/introspect/postgres.ts +245 -154
- package/src/core/ddl/introspect/registry.ts +26 -0
- package/src/core/ddl/introspect/run-select.ts +25 -0
- package/src/core/ddl/introspect/sqlite.ts +7 -7
- package/src/core/ddl/introspect/types.ts +23 -19
- package/src/core/ddl/introspect/utils.ts +1 -1
- package/src/core/ddl/naming-strategy.ts +10 -0
- package/src/core/ddl/schema-dialect.ts +41 -0
- package/src/core/ddl/schema-diff.ts +211 -179
- package/src/core/ddl/schema-generator.ts +17 -90
- package/src/core/ddl/schema-introspect.ts +25 -32
- package/src/core/ddl/schema-plan-executor.ts +17 -0
- package/src/core/ddl/schema-types.ts +46 -39
- package/src/core/ddl/sql-writing.ts +170 -0
- package/src/core/dialect/abstract.ts +172 -126
- package/src/core/dialect/base/cte-compiler.ts +33 -0
- package/src/core/dialect/base/function-table-formatter.ts +132 -0
- package/src/core/dialect/base/groupby-compiler.ts +21 -0
- package/src/core/dialect/base/join-compiler.ts +26 -0
- package/src/core/dialect/base/orderby-compiler.ts +21 -0
- package/src/core/dialect/base/pagination-strategy.ts +32 -0
- package/src/core/dialect/base/returning-strategy.ts +56 -0
- package/src/core/dialect/base/sql-dialect.ts +181 -204
- package/src/core/dialect/dialect-factory.ts +91 -0
- package/src/core/dialect/mssql/functions.ts +101 -0
- package/src/core/dialect/mssql/index.ts +128 -126
- package/src/core/dialect/mysql/functions.ts +101 -0
- package/src/core/dialect/mysql/index.ts +20 -18
- package/src/core/dialect/postgres/functions.ts +95 -0
- package/src/core/dialect/postgres/index.ts +30 -28
- package/src/core/dialect/sqlite/functions.ts +115 -0
- package/src/core/dialect/sqlite/index.ts +30 -28
- package/src/core/driver/database-driver.ts +11 -0
- package/src/core/driver/mssql-driver.ts +20 -0
- package/src/core/driver/mysql-driver.ts +20 -0
- package/src/core/driver/postgres-driver.ts +20 -0
- package/src/core/driver/sqlite-driver.ts +20 -0
- package/src/core/execution/db-executor.ts +63 -0
- package/src/core/execution/executors/mssql-executor.ts +39 -0
- package/src/core/execution/executors/mysql-executor.ts +47 -0
- package/src/core/execution/executors/postgres-executor.ts +32 -0
- package/src/core/execution/executors/sqlite-executor.ts +31 -0
- package/src/core/functions/datetime.ts +132 -0
- package/src/core/functions/numeric.ts +179 -0
- package/src/core/functions/standard-strategy.ts +47 -0
- package/src/core/functions/text.ts +147 -0
- package/src/core/functions/types.ts +18 -0
- package/src/core/hydration/types.ts +57 -0
- package/src/decorators/bootstrap.ts +10 -0
- package/src/decorators/column.ts +13 -4
- package/src/decorators/relations.ts +15 -0
- package/src/index.ts +37 -19
- package/src/orm/entity-context.ts +30 -0
- package/src/orm/entity-meta.ts +2 -2
- package/src/orm/entity-metadata.ts +8 -6
- package/src/orm/entity.ts +72 -41
- package/src/orm/execute.ts +42 -25
- package/src/orm/execution-context.ts +12 -0
- package/src/orm/hydration-context.ts +14 -0
- package/src/orm/hydration.ts +25 -17
- package/src/orm/identity-map.ts +4 -0
- package/src/orm/interceptor-pipeline.ts +29 -0
- package/src/orm/lazy-batch.ts +50 -6
- package/src/orm/orm-session.ts +234 -0
- package/src/orm/orm.ts +58 -0
- package/src/orm/query-logger.ts +1 -1
- package/src/orm/relation-change-processor.ts +48 -3
- package/src/orm/relations/belongs-to.ts +45 -44
- package/src/orm/relations/has-many.ts +44 -43
- package/src/orm/relations/has-one.ts +140 -0
- package/src/orm/relations/many-to-many.ts +46 -45
- package/src/orm/transaction-runner.ts +1 -1
- package/src/orm/unit-of-work.ts +66 -61
- package/src/query-builder/delete.ts +22 -5
- package/src/query-builder/hydration-manager.ts +2 -1
- package/src/query-builder/hydration-planner.ts +8 -7
- package/src/query-builder/insert.ts +22 -5
- package/src/query-builder/relation-conditions.ts +9 -8
- package/src/query-builder/relation-service.ts +3 -2
- package/src/query-builder/select.ts +575 -64
- package/src/query-builder/update.ts +22 -5
- package/src/schema/column.ts +246 -246
- package/src/schema/relation.ts +35 -1
- package/src/schema/table.ts +28 -28
- package/src/schema/types.ts +41 -31
- package/src/orm/db-executor.ts +0 -11
- package/src/orm/orm-context.ts +0 -159
package/dist/decorators/index.js
CHANGED
|
@@ -100,11 +100,20 @@ function Entity(options = {}) {
|
|
|
100
100
|
|
|
101
101
|
// src/decorators/column.ts
|
|
102
102
|
var normalizeColumnInput = (input) => {
|
|
103
|
+
const asOptions = input;
|
|
104
|
+
const asDefinition = input;
|
|
103
105
|
const column = {
|
|
104
|
-
type:
|
|
105
|
-
args:
|
|
106
|
-
notNull:
|
|
107
|
-
primary:
|
|
106
|
+
type: asOptions.type ?? asDefinition.type,
|
|
107
|
+
args: asOptions.args ?? asDefinition.args,
|
|
108
|
+
notNull: asOptions.notNull ?? asDefinition.notNull,
|
|
109
|
+
primary: asOptions.primary ?? asDefinition.primary,
|
|
110
|
+
unique: asDefinition.unique,
|
|
111
|
+
default: asDefinition.default,
|
|
112
|
+
autoIncrement: asDefinition.autoIncrement,
|
|
113
|
+
generated: asDefinition.generated,
|
|
114
|
+
check: asDefinition.check,
|
|
115
|
+
references: asDefinition.references,
|
|
116
|
+
comment: asDefinition.comment
|
|
108
117
|
};
|
|
109
118
|
if (!column.type) {
|
|
110
119
|
throw new Error("Column decorator requires a column type");
|
|
@@ -153,6 +162,8 @@ function PrimaryKey(definition) {
|
|
|
153
162
|
|
|
154
163
|
// src/schema/relation.ts
|
|
155
164
|
var RelationKinds = {
|
|
165
|
+
/** One-to-one relationship */
|
|
166
|
+
HasOne: "HAS_ONE",
|
|
156
167
|
/** One-to-many relationship */
|
|
157
168
|
HasMany: "HAS_MANY",
|
|
158
169
|
/** Many-to-one relationship */
|
|
@@ -167,6 +178,13 @@ var hasMany = (target, foreignKey, localKey, cascade) => ({
|
|
|
167
178
|
localKey,
|
|
168
179
|
cascade
|
|
169
180
|
});
|
|
181
|
+
var hasOne = (target, foreignKey, localKey, cascade) => ({
|
|
182
|
+
type: RelationKinds.HasOne,
|
|
183
|
+
target,
|
|
184
|
+
foreignKey,
|
|
185
|
+
localKey,
|
|
186
|
+
cascade
|
|
187
|
+
});
|
|
170
188
|
var belongsTo = (target, foreignKey, localKey, cascade) => ({
|
|
171
189
|
type: RelationKinds.BelongsTo,
|
|
172
190
|
target,
|
|
@@ -227,6 +245,16 @@ function HasMany(options) {
|
|
|
227
245
|
cascade: options.cascade
|
|
228
246
|
}));
|
|
229
247
|
}
|
|
248
|
+
function HasOne(options) {
|
|
249
|
+
return createFieldDecorator((propertyName) => ({
|
|
250
|
+
kind: RelationKinds.HasOne,
|
|
251
|
+
propertyKey: propertyName,
|
|
252
|
+
target: options.target,
|
|
253
|
+
foreignKey: options.foreignKey,
|
|
254
|
+
localKey: options.localKey,
|
|
255
|
+
cascade: options.cascade
|
|
256
|
+
}));
|
|
257
|
+
}
|
|
230
258
|
function BelongsTo(options) {
|
|
231
259
|
return createFieldDecorator((propertyName) => ({
|
|
232
260
|
kind: RelationKinds.BelongsTo,
|
|
@@ -284,54 +312,1436 @@ var toOperand = (val) => {
|
|
|
284
312
|
if (typeof val === "string" || typeof val === "number" || typeof val === "boolean") {
|
|
285
313
|
return { type: "Literal", value: val };
|
|
286
314
|
}
|
|
287
|
-
return toNode(val);
|
|
315
|
+
return toNode(val);
|
|
316
|
+
};
|
|
317
|
+
var columnOperand = (col) => toNode(col);
|
|
318
|
+
var createBinaryExpression = (operator, left, right, escape) => {
|
|
319
|
+
const node = {
|
|
320
|
+
type: "BinaryExpression",
|
|
321
|
+
left: toNode(left),
|
|
322
|
+
operator,
|
|
323
|
+
right: toOperand(right)
|
|
324
|
+
};
|
|
325
|
+
if (escape !== void 0) {
|
|
326
|
+
node.escape = toLiteralNode(escape);
|
|
327
|
+
}
|
|
328
|
+
return node;
|
|
329
|
+
};
|
|
330
|
+
var eq = (left, right) => createBinaryExpression("=", left, right);
|
|
331
|
+
var and = (...operands) => ({
|
|
332
|
+
type: "LogicalExpression",
|
|
333
|
+
operator: "AND",
|
|
334
|
+
operands
|
|
335
|
+
});
|
|
336
|
+
var createInExpression = (operator, left, values) => ({
|
|
337
|
+
type: "InExpression",
|
|
338
|
+
left: toNode(left),
|
|
339
|
+
operator,
|
|
340
|
+
right: values.map((v) => toOperand(v))
|
|
341
|
+
});
|
|
342
|
+
var inList = (left, values) => createInExpression("IN", left, values);
|
|
343
|
+
var exists = (subquery) => ({
|
|
344
|
+
type: "ExistsExpression",
|
|
345
|
+
operator: "EXISTS",
|
|
346
|
+
subquery
|
|
347
|
+
});
|
|
348
|
+
var notExists = (subquery) => ({
|
|
349
|
+
type: "ExistsExpression",
|
|
350
|
+
operator: "NOT EXISTS",
|
|
351
|
+
subquery
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
// src/core/ast/aggregate-functions.ts
|
|
355
|
+
var buildAggregate = (name) => (col) => ({
|
|
356
|
+
type: "Function",
|
|
357
|
+
name,
|
|
358
|
+
args: [columnOperand(col)]
|
|
359
|
+
});
|
|
360
|
+
var count = buildAggregate("COUNT");
|
|
361
|
+
var sum = buildAggregate("SUM");
|
|
362
|
+
var avg = buildAggregate("AVG");
|
|
363
|
+
|
|
364
|
+
// src/core/functions/standard-strategy.ts
|
|
365
|
+
var StandardFunctionStrategy = class {
|
|
366
|
+
constructor() {
|
|
367
|
+
this.renderers = /* @__PURE__ */ new Map();
|
|
368
|
+
this.registerStandard();
|
|
369
|
+
}
|
|
370
|
+
registerStandard() {
|
|
371
|
+
this.add("ABS", ({ compiledArgs }) => `ABS(${compiledArgs[0]})`);
|
|
372
|
+
this.add("UPPER", ({ compiledArgs }) => `UPPER(${compiledArgs[0]})`);
|
|
373
|
+
this.add("LOWER", ({ compiledArgs }) => `LOWER(${compiledArgs[0]})`);
|
|
374
|
+
this.add("LENGTH", ({ compiledArgs }) => `LENGTH(${compiledArgs[0]})`);
|
|
375
|
+
this.add("TRIM", ({ compiledArgs }) => `TRIM(${compiledArgs[0]})`);
|
|
376
|
+
this.add("LTRIM", ({ compiledArgs }) => `LTRIM(${compiledArgs[0]})`);
|
|
377
|
+
this.add("RTRIM", ({ compiledArgs }) => `RTRIM(${compiledArgs[0]})`);
|
|
378
|
+
this.add("SUBSTRING", ({ compiledArgs }) => `SUBSTRING(${compiledArgs.join(", ")})`);
|
|
379
|
+
this.add("CONCAT", ({ compiledArgs }) => `CONCAT(${compiledArgs.join(", ")})`);
|
|
380
|
+
this.add("NOW", () => `NOW()`);
|
|
381
|
+
this.add("CURRENT_DATE", () => `CURRENT_DATE`);
|
|
382
|
+
this.add("CURRENT_TIME", () => `CURRENT_TIME`);
|
|
383
|
+
this.add("EXTRACT", ({ compiledArgs }) => `EXTRACT(${compiledArgs[0]} FROM ${compiledArgs[1]})`);
|
|
384
|
+
this.add("YEAR", ({ compiledArgs }) => `EXTRACT(YEAR FROM ${compiledArgs[0]})`);
|
|
385
|
+
this.add("MONTH", ({ compiledArgs }) => `EXTRACT(MONTH FROM ${compiledArgs[0]})`);
|
|
386
|
+
this.add("DAY", ({ compiledArgs }) => `EXTRACT(DAY FROM ${compiledArgs[0]})`);
|
|
387
|
+
this.add("DATE_ADD", ({ compiledArgs }) => `(${compiledArgs[0]} + INTERVAL ${compiledArgs[1]} ${compiledArgs[2]})`);
|
|
388
|
+
this.add("DATE_SUB", ({ compiledArgs }) => `(${compiledArgs[0]} - INTERVAL ${compiledArgs[1]} ${compiledArgs[2]})`);
|
|
389
|
+
this.add("DATE_DIFF", ({ compiledArgs }) => `DATEDIFF(${compiledArgs[0]}, ${compiledArgs[1]})`);
|
|
390
|
+
this.add("DATE_FORMAT", ({ compiledArgs }) => `DATE_FORMAT(${compiledArgs[0]}, ${compiledArgs[1]})`);
|
|
391
|
+
this.add("UNIX_TIMESTAMP", () => `UNIX_TIMESTAMP()`);
|
|
392
|
+
this.add("FROM_UNIXTIME", ({ compiledArgs }) => `FROM_UNIXTIME(${compiledArgs[0]})`);
|
|
393
|
+
this.add("END_OF_MONTH", ({ compiledArgs }) => `LAST_DAY(${compiledArgs[0]})`);
|
|
394
|
+
this.add("DAY_OF_WEEK", ({ compiledArgs }) => `DAYOFWEEK(${compiledArgs[0]})`);
|
|
395
|
+
this.add("WEEK_OF_YEAR", ({ compiledArgs }) => `WEEKOFYEAR(${compiledArgs[0]})`);
|
|
396
|
+
this.add("DATE_TRUNC", ({ compiledArgs }) => `DATE_TRUNC(${compiledArgs[0]}, ${compiledArgs[1]})`);
|
|
397
|
+
}
|
|
398
|
+
add(name, renderer) {
|
|
399
|
+
this.renderers.set(name, renderer);
|
|
400
|
+
}
|
|
401
|
+
getRenderer(name) {
|
|
402
|
+
return this.renderers.get(name);
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
// src/core/dialect/abstract.ts
|
|
407
|
+
var Dialect = class _Dialect {
|
|
408
|
+
/**
|
|
409
|
+
* Compiles a SELECT query AST to SQL
|
|
410
|
+
* @param ast - Query AST to compile
|
|
411
|
+
* @returns Compiled query with SQL and parameters
|
|
412
|
+
*/
|
|
413
|
+
compileSelect(ast) {
|
|
414
|
+
const ctx = this.createCompilerContext();
|
|
415
|
+
const normalized = this.normalizeSelectAst(ast);
|
|
416
|
+
const rawSql = this.compileSelectAst(normalized, ctx).trim();
|
|
417
|
+
const sql = rawSql.endsWith(";") ? rawSql : `${rawSql};`;
|
|
418
|
+
return {
|
|
419
|
+
sql,
|
|
420
|
+
params: [...ctx.params]
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
compileInsert(ast) {
|
|
424
|
+
const ctx = this.createCompilerContext();
|
|
425
|
+
const rawSql = this.compileInsertAst(ast, ctx).trim();
|
|
426
|
+
const sql = rawSql.endsWith(";") ? rawSql : `${rawSql};`;
|
|
427
|
+
return {
|
|
428
|
+
sql,
|
|
429
|
+
params: [...ctx.params]
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
compileUpdate(ast) {
|
|
433
|
+
const ctx = this.createCompilerContext();
|
|
434
|
+
const rawSql = this.compileUpdateAst(ast, ctx).trim();
|
|
435
|
+
const sql = rawSql.endsWith(";") ? rawSql : `${rawSql};`;
|
|
436
|
+
return {
|
|
437
|
+
sql,
|
|
438
|
+
params: [...ctx.params]
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
compileDelete(ast) {
|
|
442
|
+
const ctx = this.createCompilerContext();
|
|
443
|
+
const rawSql = this.compileDeleteAst(ast, ctx).trim();
|
|
444
|
+
const sql = rawSql.endsWith(";") ? rawSql : `${rawSql};`;
|
|
445
|
+
return {
|
|
446
|
+
sql,
|
|
447
|
+
params: [...ctx.params]
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
supportsReturning() {
|
|
451
|
+
return false;
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Compiles a WHERE clause
|
|
455
|
+
* @param where - WHERE expression
|
|
456
|
+
* @param ctx - Compiler context
|
|
457
|
+
* @returns SQL WHERE clause or empty string
|
|
458
|
+
*/
|
|
459
|
+
compileWhere(where, ctx) {
|
|
460
|
+
if (!where) return "";
|
|
461
|
+
return ` WHERE ${this.compileExpression(where, ctx)}`;
|
|
462
|
+
}
|
|
463
|
+
compileReturning(returning, ctx) {
|
|
464
|
+
if (!returning || returning.length === 0) return "";
|
|
465
|
+
throw new Error("RETURNING is not supported by this dialect.");
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Generates subquery for EXISTS expressions
|
|
469
|
+
* Rule: Always forces SELECT 1, ignoring column list
|
|
470
|
+
* Maintains FROM, JOINs, WHERE, GROUP BY, ORDER BY, LIMIT/OFFSET
|
|
471
|
+
* Does not add ';' at the end
|
|
472
|
+
* @param ast - Query AST
|
|
473
|
+
* @param ctx - Compiler context
|
|
474
|
+
* @returns SQL for EXISTS subquery
|
|
475
|
+
*/
|
|
476
|
+
compileSelectForExists(ast, ctx) {
|
|
477
|
+
const normalized = this.normalizeSelectAst(ast);
|
|
478
|
+
const full = this.compileSelectAst(normalized, ctx).trim().replace(/;$/, "");
|
|
479
|
+
if (normalized.setOps && normalized.setOps.length > 0) {
|
|
480
|
+
return `SELECT 1 FROM (${full}) AS _exists`;
|
|
481
|
+
}
|
|
482
|
+
const upper = full.toUpperCase();
|
|
483
|
+
const fromIndex = upper.indexOf(" FROM ");
|
|
484
|
+
if (fromIndex === -1) {
|
|
485
|
+
return full;
|
|
486
|
+
}
|
|
487
|
+
const tail = full.slice(fromIndex);
|
|
488
|
+
return `SELECT 1${tail}`;
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Creates a new compiler context
|
|
492
|
+
* @returns Compiler context with parameter management
|
|
493
|
+
*/
|
|
494
|
+
createCompilerContext() {
|
|
495
|
+
const params = [];
|
|
496
|
+
let counter = 0;
|
|
497
|
+
return {
|
|
498
|
+
params,
|
|
499
|
+
addParameter: (value) => {
|
|
500
|
+
counter += 1;
|
|
501
|
+
params.push(value);
|
|
502
|
+
return this.formatPlaceholder(counter);
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Formats a parameter placeholder
|
|
508
|
+
* @param index - Parameter index
|
|
509
|
+
* @returns Formatted placeholder string
|
|
510
|
+
*/
|
|
511
|
+
formatPlaceholder(index) {
|
|
512
|
+
return "?";
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Whether the current dialect supports a given set operation.
|
|
516
|
+
* Override in concrete dialects to restrict support.
|
|
517
|
+
*/
|
|
518
|
+
supportsSetOperation(kind) {
|
|
519
|
+
return true;
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Validates set-operation semantics:
|
|
523
|
+
* - Ensures the dialect supports requested operators.
|
|
524
|
+
* - Enforces that only the outermost compound query may have ORDER/LIMIT/OFFSET.
|
|
525
|
+
* @param ast - Query to validate
|
|
526
|
+
* @param isOutermost - Whether this node is the outermost compound query
|
|
527
|
+
*/
|
|
528
|
+
validateSetOperations(ast, isOutermost = true) {
|
|
529
|
+
const hasSetOps = !!(ast.setOps && ast.setOps.length);
|
|
530
|
+
if (!isOutermost && (ast.orderBy || ast.limit !== void 0 || ast.offset !== void 0)) {
|
|
531
|
+
throw new Error("ORDER BY / LIMIT / OFFSET are only allowed on the outermost compound query.");
|
|
532
|
+
}
|
|
533
|
+
if (hasSetOps) {
|
|
534
|
+
for (const op of ast.setOps) {
|
|
535
|
+
if (!this.supportsSetOperation(op.operator)) {
|
|
536
|
+
throw new Error(`Set operation ${op.operator} is not supported by this dialect.`);
|
|
537
|
+
}
|
|
538
|
+
this.validateSetOperations(op.query, false);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* Hoists CTEs from set-operation operands to the outermost query so WITH appears once.
|
|
544
|
+
* @param ast - Query AST
|
|
545
|
+
* @returns Normalized AST without inner CTEs and a list of hoisted CTEs
|
|
546
|
+
*/
|
|
547
|
+
hoistCtes(ast) {
|
|
548
|
+
let hoisted = [];
|
|
549
|
+
const normalizedSetOps = ast.setOps?.map((op) => {
|
|
550
|
+
const { normalized: child, hoistedCtes: childHoisted } = this.hoistCtes(op.query);
|
|
551
|
+
const childCtes = child.ctes ?? [];
|
|
552
|
+
if (childCtes.length) {
|
|
553
|
+
hoisted = hoisted.concat(childCtes);
|
|
554
|
+
}
|
|
555
|
+
hoisted = hoisted.concat(childHoisted);
|
|
556
|
+
const queryWithoutCtes = childCtes.length ? { ...child, ctes: void 0 } : child;
|
|
557
|
+
return { ...op, query: queryWithoutCtes };
|
|
558
|
+
});
|
|
559
|
+
const normalized = normalizedSetOps ? { ...ast, setOps: normalizedSetOps } : ast;
|
|
560
|
+
return { normalized, hoistedCtes: hoisted };
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Normalizes a SELECT AST before compilation (validation + CTE hoisting).
|
|
564
|
+
* @param ast - Query AST
|
|
565
|
+
* @returns Normalized query AST
|
|
566
|
+
*/
|
|
567
|
+
normalizeSelectAst(ast) {
|
|
568
|
+
this.validateSetOperations(ast, true);
|
|
569
|
+
const { normalized, hoistedCtes } = this.hoistCtes(ast);
|
|
570
|
+
const combinedCtes = [...normalized.ctes ?? [], ...hoistedCtes];
|
|
571
|
+
return combinedCtes.length ? { ...normalized, ctes: combinedCtes } : normalized;
|
|
572
|
+
}
|
|
573
|
+
constructor(functionStrategy) {
|
|
574
|
+
this.expressionCompilers = /* @__PURE__ */ new Map();
|
|
575
|
+
this.operandCompilers = /* @__PURE__ */ new Map();
|
|
576
|
+
this.functionStrategy = functionStrategy || new StandardFunctionStrategy();
|
|
577
|
+
this.registerDefaultOperandCompilers();
|
|
578
|
+
this.registerDefaultExpressionCompilers();
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Creates a new Dialect instance (for testing purposes)
|
|
582
|
+
* @param functionStrategy - Optional function strategy
|
|
583
|
+
* @returns New Dialect instance
|
|
584
|
+
*/
|
|
585
|
+
static create(functionStrategy) {
|
|
586
|
+
class TestDialect extends _Dialect {
|
|
587
|
+
constructor() {
|
|
588
|
+
super(...arguments);
|
|
589
|
+
this.dialect = "sqlite";
|
|
590
|
+
}
|
|
591
|
+
quoteIdentifier(id) {
|
|
592
|
+
return `"${id}"`;
|
|
593
|
+
}
|
|
594
|
+
compileSelectAst() {
|
|
595
|
+
throw new Error("Not implemented");
|
|
596
|
+
}
|
|
597
|
+
compileInsertAst() {
|
|
598
|
+
throw new Error("Not implemented");
|
|
599
|
+
}
|
|
600
|
+
compileUpdateAst() {
|
|
601
|
+
throw new Error("Not implemented");
|
|
602
|
+
}
|
|
603
|
+
compileDeleteAst() {
|
|
604
|
+
throw new Error("Not implemented");
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
return new TestDialect(functionStrategy);
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Registers an expression compiler for a specific node type
|
|
611
|
+
* @param type - Expression node type
|
|
612
|
+
* @param compiler - Compiler function
|
|
613
|
+
*/
|
|
614
|
+
registerExpressionCompiler(type, compiler) {
|
|
615
|
+
this.expressionCompilers.set(type, compiler);
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Registers an operand compiler for a specific node type
|
|
619
|
+
* @param type - Operand node type
|
|
620
|
+
* @param compiler - Compiler function
|
|
621
|
+
*/
|
|
622
|
+
registerOperandCompiler(type, compiler) {
|
|
623
|
+
this.operandCompilers.set(type, compiler);
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Compiles an expression node
|
|
627
|
+
* @param node - Expression node to compile
|
|
628
|
+
* @param ctx - Compiler context
|
|
629
|
+
* @returns Compiled SQL expression
|
|
630
|
+
*/
|
|
631
|
+
compileExpression(node, ctx) {
|
|
632
|
+
const compiler = this.expressionCompilers.get(node.type);
|
|
633
|
+
if (!compiler) {
|
|
634
|
+
throw new Error(`Unsupported expression node type "${node.type}" for ${this.constructor.name}`);
|
|
635
|
+
}
|
|
636
|
+
return compiler(node, ctx);
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Compiles an operand node
|
|
640
|
+
* @param node - Operand node to compile
|
|
641
|
+
* @param ctx - Compiler context
|
|
642
|
+
* @returns Compiled SQL operand
|
|
643
|
+
*/
|
|
644
|
+
compileOperand(node, ctx) {
|
|
645
|
+
const compiler = this.operandCompilers.get(node.type);
|
|
646
|
+
if (!compiler) {
|
|
647
|
+
throw new Error(`Unsupported operand node type "${node.type}" for ${this.constructor.name}`);
|
|
648
|
+
}
|
|
649
|
+
return compiler(node, ctx);
|
|
650
|
+
}
|
|
651
|
+
registerDefaultExpressionCompilers() {
|
|
652
|
+
this.registerExpressionCompiler("BinaryExpression", (binary, ctx) => {
|
|
653
|
+
const left = this.compileOperand(binary.left, ctx);
|
|
654
|
+
const right = this.compileOperand(binary.right, ctx);
|
|
655
|
+
const base = `${left} ${binary.operator} ${right}`;
|
|
656
|
+
if (binary.escape) {
|
|
657
|
+
const escapeOperand = this.compileOperand(binary.escape, ctx);
|
|
658
|
+
return `${base} ESCAPE ${escapeOperand}`;
|
|
659
|
+
}
|
|
660
|
+
return base;
|
|
661
|
+
});
|
|
662
|
+
this.registerExpressionCompiler("LogicalExpression", (logical, ctx) => {
|
|
663
|
+
if (logical.operands.length === 0) return "";
|
|
664
|
+
const parts = logical.operands.map((op) => {
|
|
665
|
+
const compiled = this.compileExpression(op, ctx);
|
|
666
|
+
return op.type === "LogicalExpression" ? `(${compiled})` : compiled;
|
|
667
|
+
});
|
|
668
|
+
return parts.join(` ${logical.operator} `);
|
|
669
|
+
});
|
|
670
|
+
this.registerExpressionCompiler("NullExpression", (nullExpr, ctx) => {
|
|
671
|
+
const left = this.compileOperand(nullExpr.left, ctx);
|
|
672
|
+
return `${left} ${nullExpr.operator}`;
|
|
673
|
+
});
|
|
674
|
+
this.registerExpressionCompiler("InExpression", (inExpr, ctx) => {
|
|
675
|
+
const left = this.compileOperand(inExpr.left, ctx);
|
|
676
|
+
const values = inExpr.right.map((v) => this.compileOperand(v, ctx)).join(", ");
|
|
677
|
+
return `${left} ${inExpr.operator} (${values})`;
|
|
678
|
+
});
|
|
679
|
+
this.registerExpressionCompiler("ExistsExpression", (existsExpr, ctx) => {
|
|
680
|
+
const subquerySql = this.compileSelectForExists(existsExpr.subquery, ctx);
|
|
681
|
+
return `${existsExpr.operator} (${subquerySql})`;
|
|
682
|
+
});
|
|
683
|
+
this.registerExpressionCompiler("BetweenExpression", (betweenExpr, ctx) => {
|
|
684
|
+
const left = this.compileOperand(betweenExpr.left, ctx);
|
|
685
|
+
const lower = this.compileOperand(betweenExpr.lower, ctx);
|
|
686
|
+
const upper = this.compileOperand(betweenExpr.upper, ctx);
|
|
687
|
+
return `${left} ${betweenExpr.operator} ${lower} AND ${upper}`;
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
registerDefaultOperandCompilers() {
|
|
691
|
+
this.registerOperandCompiler("Literal", (literal, ctx) => ctx.addParameter(literal.value));
|
|
692
|
+
this.registerOperandCompiler("Column", (column, _ctx) => {
|
|
693
|
+
return `${this.quoteIdentifier(column.table)}.${this.quoteIdentifier(column.name)}`;
|
|
694
|
+
});
|
|
695
|
+
this.registerOperandCompiler(
|
|
696
|
+
"Function",
|
|
697
|
+
(fnNode, ctx) => this.compileFunctionOperand(fnNode, ctx)
|
|
698
|
+
);
|
|
699
|
+
this.registerOperandCompiler("JsonPath", (path, _ctx) => this.compileJsonPath(path));
|
|
700
|
+
this.registerOperandCompiler("ScalarSubquery", (node, ctx) => {
|
|
701
|
+
const sql = this.compileSelectAst(node.query, ctx).trim().replace(/;$/, "");
|
|
702
|
+
return `(${sql})`;
|
|
703
|
+
});
|
|
704
|
+
this.registerOperandCompiler("CaseExpression", (node, ctx) => {
|
|
705
|
+
const parts = ["CASE"];
|
|
706
|
+
for (const { when, then } of node.conditions) {
|
|
707
|
+
parts.push(`WHEN ${this.compileExpression(when, ctx)} THEN ${this.compileOperand(then, ctx)}`);
|
|
708
|
+
}
|
|
709
|
+
if (node.else) {
|
|
710
|
+
parts.push(`ELSE ${this.compileOperand(node.else, ctx)}`);
|
|
711
|
+
}
|
|
712
|
+
parts.push("END");
|
|
713
|
+
return parts.join(" ");
|
|
714
|
+
});
|
|
715
|
+
this.registerOperandCompiler("WindowFunction", (node, ctx) => {
|
|
716
|
+
let result = `${node.name}(`;
|
|
717
|
+
if (node.args.length > 0) {
|
|
718
|
+
result += node.args.map((arg) => this.compileOperand(arg, ctx)).join(", ");
|
|
719
|
+
}
|
|
720
|
+
result += ") OVER (";
|
|
721
|
+
const parts = [];
|
|
722
|
+
if (node.partitionBy && node.partitionBy.length > 0) {
|
|
723
|
+
const partitionClause = "PARTITION BY " + node.partitionBy.map(
|
|
724
|
+
(col) => `${this.quoteIdentifier(col.table)}.${this.quoteIdentifier(col.name)}`
|
|
725
|
+
).join(", ");
|
|
726
|
+
parts.push(partitionClause);
|
|
727
|
+
}
|
|
728
|
+
if (node.orderBy && node.orderBy.length > 0) {
|
|
729
|
+
const orderClause = "ORDER BY " + node.orderBy.map(
|
|
730
|
+
(o) => `${this.quoteIdentifier(o.column.table)}.${this.quoteIdentifier(o.column.name)} ${o.direction}`
|
|
731
|
+
).join(", ");
|
|
732
|
+
parts.push(orderClause);
|
|
733
|
+
}
|
|
734
|
+
result += parts.join(" ");
|
|
735
|
+
result += ")";
|
|
736
|
+
return result;
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
// Default fallback, should be overridden by dialects if supported
|
|
740
|
+
compileJsonPath(node) {
|
|
741
|
+
throw new Error("JSON Path not supported by this dialect");
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Compiles a function operand, using the dialect's function strategy.
|
|
745
|
+
*/
|
|
746
|
+
compileFunctionOperand(fnNode, ctx) {
|
|
747
|
+
const compiledArgs = fnNode.args.map((arg) => this.compileOperand(arg, ctx));
|
|
748
|
+
const renderer = this.functionStrategy.getRenderer(fnNode.name);
|
|
749
|
+
if (renderer) {
|
|
750
|
+
return renderer({ node: fnNode, compiledArgs });
|
|
751
|
+
}
|
|
752
|
+
return `${fnNode.name}(${compiledArgs.join(", ")})`;
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
|
|
756
|
+
// src/core/dialect/base/function-table-formatter.ts
|
|
757
|
+
var FunctionTableFormatter = class {
|
|
758
|
+
/**
|
|
759
|
+
* Formats a function table node into SQL syntax.
|
|
760
|
+
* @param fn - The function table node containing schema, name, args, and aliases.
|
|
761
|
+
* @param ctx - Optional compiler context for operand compilation.
|
|
762
|
+
* @param dialect - The dialect instance for compiling operands.
|
|
763
|
+
* @returns SQL function table expression (e.g., "LATERAL schema.func(args) WITH ORDINALITY AS alias(col1, col2)").
|
|
764
|
+
*/
|
|
765
|
+
static format(fn, ctx, dialect) {
|
|
766
|
+
const schemaPart = this.formatSchema(fn, dialect);
|
|
767
|
+
const args = this.formatArgs(fn, ctx, dialect);
|
|
768
|
+
const base = this.formatBase(fn, schemaPart, args, dialect);
|
|
769
|
+
const lateral = this.formatLateral(fn);
|
|
770
|
+
const alias = this.formatAlias(fn, dialect);
|
|
771
|
+
const colAliases = this.formatColumnAliases(fn, dialect);
|
|
772
|
+
return `${lateral}${base}${alias}${colAliases}`;
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* Formats the schema prefix for the function name.
|
|
776
|
+
* @param fn - The function table node.
|
|
777
|
+
* @param dialect - The dialect instance for quoting identifiers.
|
|
778
|
+
* @returns Schema prefix (e.g., "schema.") or empty string.
|
|
779
|
+
* @internal
|
|
780
|
+
*/
|
|
781
|
+
static formatSchema(fn, dialect) {
|
|
782
|
+
if (!fn.schema) return "";
|
|
783
|
+
const quoted = dialect ? dialect.quoteIdentifier(fn.schema) : fn.schema;
|
|
784
|
+
return `${quoted}.`;
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* Formats function arguments into SQL syntax.
|
|
788
|
+
* @param fn - The function table node containing arguments.
|
|
789
|
+
* @param ctx - Optional compiler context for operand compilation.
|
|
790
|
+
* @param dialect - The dialect instance for compiling operands.
|
|
791
|
+
* @returns Comma-separated function arguments.
|
|
792
|
+
* @internal
|
|
793
|
+
*/
|
|
794
|
+
static formatArgs(fn, ctx, dialect) {
|
|
795
|
+
return (fn.args || []).map((a) => {
|
|
796
|
+
if (ctx && dialect) {
|
|
797
|
+
return dialect.compileOperand(a, ctx);
|
|
798
|
+
}
|
|
799
|
+
return String(a);
|
|
800
|
+
}).join(", ");
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* Formats the base function call with WITH ORDINALITY if present.
|
|
804
|
+
* @param fn - The function table node.
|
|
805
|
+
* @param schemaPart - Formatted schema prefix.
|
|
806
|
+
* @param args - Formatted function arguments.
|
|
807
|
+
* @param dialect - The dialect instance for quoting identifiers.
|
|
808
|
+
* @returns Base function call expression (e.g., "schema.func(args) WITH ORDINALITY").
|
|
809
|
+
* @internal
|
|
810
|
+
*/
|
|
811
|
+
static formatBase(fn, schemaPart, args, dialect) {
|
|
812
|
+
const ordinality = fn.withOrdinality ? " WITH ORDINALITY" : "";
|
|
813
|
+
const quoted = dialect ? dialect.quoteIdentifier(fn.name) : fn.name;
|
|
814
|
+
return `${schemaPart}${quoted}(${args})${ordinality}`;
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Formats the LATERAL keyword if present.
|
|
818
|
+
* @param fn - The function table node.
|
|
819
|
+
* @returns "LATERAL " or empty string.
|
|
820
|
+
* @internal
|
|
821
|
+
*/
|
|
822
|
+
static formatLateral(fn) {
|
|
823
|
+
return fn.lateral ? "LATERAL " : "";
|
|
824
|
+
}
|
|
825
|
+
/**
|
|
826
|
+
* Formats the table alias for the function table.
|
|
827
|
+
* @param fn - The function table node.
|
|
828
|
+
* @param dialect - The dialect instance for quoting identifiers.
|
|
829
|
+
* @returns " AS alias" or empty string.
|
|
830
|
+
* @internal
|
|
831
|
+
*/
|
|
832
|
+
static formatAlias(fn, dialect) {
|
|
833
|
+
if (!fn.alias) return "";
|
|
834
|
+
const quoted = dialect ? dialect.quoteIdentifier(fn.alias) : fn.alias;
|
|
835
|
+
return ` AS ${quoted}`;
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* Formats column aliases for the function table result columns.
|
|
839
|
+
* @param fn - The function table node containing column aliases.
|
|
840
|
+
* @param dialect - The dialect instance for quoting identifiers.
|
|
841
|
+
* @returns "(col1, col2, ...)" or empty string.
|
|
842
|
+
* @internal
|
|
843
|
+
*/
|
|
844
|
+
static formatColumnAliases(fn, dialect) {
|
|
845
|
+
if (!fn.columnAliases || !fn.columnAliases.length) return "";
|
|
846
|
+
const aliases = fn.columnAliases.map((col) => dialect ? dialect.quoteIdentifier(col) : col).join(", ");
|
|
847
|
+
return `(${aliases})`;
|
|
848
|
+
}
|
|
849
|
+
};
|
|
850
|
+
|
|
851
|
+
// src/core/dialect/base/pagination-strategy.ts
|
|
852
|
+
var StandardLimitOffsetPagination = class {
|
|
853
|
+
/**
|
|
854
|
+
* Compiles LIMIT/OFFSET pagination clause.
|
|
855
|
+
* @param limit - The maximum number of rows to return.
|
|
856
|
+
* @param offset - The number of rows to skip.
|
|
857
|
+
* @returns SQL pagination clause with LIMIT and/or OFFSET.
|
|
858
|
+
*/
|
|
859
|
+
compilePagination(limit, offset) {
|
|
860
|
+
const parts = [];
|
|
861
|
+
if (limit !== void 0) parts.push(`LIMIT ${limit}`);
|
|
862
|
+
if (offset !== void 0) parts.push(`OFFSET ${offset}`);
|
|
863
|
+
return parts.length ? ` ${parts.join(" ")}` : "";
|
|
864
|
+
}
|
|
865
|
+
};
|
|
866
|
+
|
|
867
|
+
// src/core/dialect/base/cte-compiler.ts
|
|
868
|
+
var CteCompiler = class {
|
|
869
|
+
/**
|
|
870
|
+
* Compiles CTEs (WITH clauses) including recursive CTEs.
|
|
871
|
+
* @param ast - The SELECT query AST containing CTE definitions.
|
|
872
|
+
* @param ctx - The compiler context for expression compilation.
|
|
873
|
+
* @param quoteIdentifier - Function to quote identifiers according to dialect rules.
|
|
874
|
+
* @param compileSelectAst - Function to recursively compile SELECT query ASTs.
|
|
875
|
+
* @param normalizeSelectAst - Function to normalize SELECT query ASTs before compilation.
|
|
876
|
+
* @param stripTrailingSemicolon - Function to remove trailing semicolons from SQL.
|
|
877
|
+
* @returns SQL WITH clause string (e.g., "WITH cte_name AS (...) ") or empty string if no CTEs.
|
|
878
|
+
*/
|
|
879
|
+
static compileCtes(ast, ctx, quoteIdentifier, compileSelectAst, normalizeSelectAst, stripTrailingSemicolon) {
|
|
880
|
+
if (!ast.ctes || ast.ctes.length === 0) return "";
|
|
881
|
+
const hasRecursive = ast.ctes.some((cte) => cte.recursive);
|
|
882
|
+
const prefix = hasRecursive ? "WITH RECURSIVE " : "WITH ";
|
|
883
|
+
const cteDefs = ast.ctes.map((cte) => {
|
|
884
|
+
const name = quoteIdentifier(cte.name);
|
|
885
|
+
const cols = cte.columns && cte.columns.length ? `(${cte.columns.map((c) => quoteIdentifier(c)).join(", ")})` : "";
|
|
886
|
+
const query = stripTrailingSemicolon(compileSelectAst(normalizeSelectAst(cte.query), ctx));
|
|
887
|
+
return `${name}${cols} AS (${query})`;
|
|
888
|
+
}).join(", ");
|
|
889
|
+
return `${prefix}${cteDefs} `;
|
|
890
|
+
}
|
|
891
|
+
};
|
|
892
|
+
|
|
893
|
+
// src/core/dialect/base/returning-strategy.ts
|
|
894
|
+
var NoReturningStrategy = class {
|
|
895
|
+
/**
|
|
896
|
+
* Throws an error as RETURNING is not supported.
|
|
897
|
+
* @param returning - Columns to return (causes error if non-empty).
|
|
898
|
+
* @param _ctx - Compiler context (unused).
|
|
899
|
+
* @throws Error indicating RETURNING is not supported.
|
|
900
|
+
*/
|
|
901
|
+
compileReturning(returning, _ctx) {
|
|
902
|
+
if (!returning || returning.length === 0) return "";
|
|
903
|
+
throw new Error("RETURNING is not supported by this dialect.");
|
|
904
|
+
}
|
|
905
|
+
/**
|
|
906
|
+
* Formats column names for RETURNING clause.
|
|
907
|
+
* @param returning - Columns to format.
|
|
908
|
+
* @param quoteIdentifier - Function to quote identifiers according to dialect rules.
|
|
909
|
+
* @returns Simple comma-separated column names.
|
|
910
|
+
*/
|
|
911
|
+
formatReturningColumns(returning, quoteIdentifier) {
|
|
912
|
+
return returning.map((column) => {
|
|
913
|
+
const tablePart = column.table ? `${quoteIdentifier(column.table)}.` : "";
|
|
914
|
+
const aliasPart = column.alias ? ` AS ${quoteIdentifier(column.alias)}` : "";
|
|
915
|
+
return `${tablePart}${quoteIdentifier(column.name)}${aliasPart}`;
|
|
916
|
+
}).join(", ");
|
|
917
|
+
}
|
|
918
|
+
};
|
|
919
|
+
|
|
920
|
+
// src/core/dialect/base/join-compiler.ts
|
|
921
|
+
var JoinCompiler = class {
|
|
922
|
+
/**
|
|
923
|
+
* Compiles all JOIN clauses from a SELECT query AST.
|
|
924
|
+
* @param ast - The SELECT query AST containing join definitions.
|
|
925
|
+
* @param ctx - The compiler context for expression compilation.
|
|
926
|
+
* @param compileFrom - Function to compile table sources (tables or subqueries).
|
|
927
|
+
* @param compileExpression - Function to compile join condition expressions.
|
|
928
|
+
* @returns SQL JOIN clauses (e.g., " LEFT JOIN table ON condition") or empty string if no joins.
|
|
929
|
+
*/
|
|
930
|
+
static compileJoins(ast, ctx, compileFrom, compileExpression) {
|
|
931
|
+
if (!ast.joins || ast.joins.length === 0) return "";
|
|
932
|
+
const parts = ast.joins.map((j) => {
|
|
933
|
+
const table = compileFrom(j.table, ctx);
|
|
934
|
+
const cond = compileExpression(j.condition, ctx);
|
|
935
|
+
return `${j.kind} JOIN ${table} ON ${cond}`;
|
|
936
|
+
});
|
|
937
|
+
return ` ${parts.join(" ")}`;
|
|
938
|
+
}
|
|
939
|
+
};
|
|
940
|
+
|
|
941
|
+
// src/core/dialect/base/groupby-compiler.ts
|
|
942
|
+
var GroupByCompiler = class {
|
|
943
|
+
/**
|
|
944
|
+
* Compiles GROUP BY clause from a SELECT query AST.
|
|
945
|
+
* @param ast - The SELECT query AST containing grouping columns.
|
|
946
|
+
* @param quoteIdentifier - Function to quote identifiers according to dialect rules.
|
|
947
|
+
* @returns SQL GROUP BY clause (e.g., " GROUP BY table.col1, table.col2") or empty string if no grouping.
|
|
948
|
+
*/
|
|
949
|
+
static compileGroupBy(ast, quoteIdentifier) {
|
|
950
|
+
if (!ast.groupBy || ast.groupBy.length === 0) return "";
|
|
951
|
+
const cols = ast.groupBy.map((c) => `${quoteIdentifier(c.table)}.${quoteIdentifier(c.name)}`).join(", ");
|
|
952
|
+
return ` GROUP BY ${cols}`;
|
|
953
|
+
}
|
|
954
|
+
};
|
|
955
|
+
|
|
956
|
+
// src/core/dialect/base/orderby-compiler.ts
|
|
957
|
+
var OrderByCompiler = class {
|
|
958
|
+
/**
|
|
959
|
+
* Compiles ORDER BY clause from a SELECT query AST.
|
|
960
|
+
* @param ast - The SELECT query AST containing sort specifications.
|
|
961
|
+
* @param quoteIdentifier - Function to quote identifiers according to dialect rules.
|
|
962
|
+
* @returns SQL ORDER BY clause (e.g., " ORDER BY table.col1 ASC, table.col2 DESC") or empty string if no ordering.
|
|
963
|
+
*/
|
|
964
|
+
static compileOrderBy(ast, quoteIdentifier) {
|
|
965
|
+
if (!ast.orderBy || ast.orderBy.length === 0) return "";
|
|
966
|
+
const parts = ast.orderBy.map((o) => `${quoteIdentifier(o.column.table)}.${quoteIdentifier(o.column.name)} ${o.direction}`).join(", ");
|
|
967
|
+
return ` ORDER BY ${parts}`;
|
|
968
|
+
}
|
|
969
|
+
};
|
|
970
|
+
|
|
971
|
+
// src/core/dialect/base/sql-dialect.ts
|
|
972
|
+
var SqlDialectBase = class extends Dialect {
|
|
973
|
+
constructor() {
|
|
974
|
+
super(...arguments);
|
|
975
|
+
this.paginationStrategy = new StandardLimitOffsetPagination();
|
|
976
|
+
this.returningStrategy = new NoReturningStrategy();
|
|
977
|
+
}
|
|
978
|
+
compileSelectAst(ast, ctx) {
|
|
979
|
+
const hasSetOps = !!(ast.setOps && ast.setOps.length);
|
|
980
|
+
const ctes = CteCompiler.compileCtes(
|
|
981
|
+
ast,
|
|
982
|
+
ctx,
|
|
983
|
+
this.quoteIdentifier.bind(this),
|
|
984
|
+
this.compileSelectAst.bind(this),
|
|
985
|
+
this.normalizeSelectAst?.bind(this) ?? ((a) => a),
|
|
986
|
+
this.stripTrailingSemicolon.bind(this)
|
|
987
|
+
);
|
|
988
|
+
const baseAst = hasSetOps ? { ...ast, setOps: void 0, orderBy: void 0, limit: void 0, offset: void 0 } : ast;
|
|
989
|
+
const baseSelect = this.compileSelectCore(baseAst, ctx);
|
|
990
|
+
if (!hasSetOps) {
|
|
991
|
+
return `${ctes}${baseSelect}`;
|
|
992
|
+
}
|
|
993
|
+
return this.compileSelectWithSetOps(ast, baseSelect, ctes, ctx);
|
|
994
|
+
}
|
|
995
|
+
compileSelectWithSetOps(ast, baseSelect, ctes, ctx) {
|
|
996
|
+
const compound = ast.setOps.map((op) => `${op.operator} ${this.wrapSetOperand(this.compileSelectAst(op.query, ctx))}`).join(" ");
|
|
997
|
+
const orderBy = OrderByCompiler.compileOrderBy(ast, this.quoteIdentifier.bind(this));
|
|
998
|
+
const pagination = this.paginationStrategy.compilePagination(ast.limit, ast.offset);
|
|
999
|
+
const combined = `${this.wrapSetOperand(baseSelect)} ${compound}`;
|
|
1000
|
+
return `${ctes}${combined}${orderBy}${pagination}`;
|
|
1001
|
+
}
|
|
1002
|
+
compileInsertAst(ast, ctx) {
|
|
1003
|
+
const table = this.compileTableName(ast.into);
|
|
1004
|
+
const columnList = this.compileInsertColumnList(ast.columns);
|
|
1005
|
+
const values = this.compileInsertValues(ast.values, ctx);
|
|
1006
|
+
const returning = this.compileReturning(ast.returning, ctx);
|
|
1007
|
+
return `INSERT INTO ${table} (${columnList}) VALUES ${values}${returning}`;
|
|
1008
|
+
}
|
|
1009
|
+
compileReturning(returning, ctx) {
|
|
1010
|
+
return this.returningStrategy.compileReturning(returning, ctx);
|
|
1011
|
+
}
|
|
1012
|
+
compileInsertColumnList(columns) {
|
|
1013
|
+
return columns.map((column) => `${this.quoteIdentifier(column.table)}.${this.quoteIdentifier(column.name)}`).join(", ");
|
|
1014
|
+
}
|
|
1015
|
+
compileInsertValues(values, ctx) {
|
|
1016
|
+
return values.map((row) => `(${row.map((value) => this.compileOperand(value, ctx)).join(", ")})`).join(", ");
|
|
1017
|
+
}
|
|
1018
|
+
compileSelectCore(ast, ctx) {
|
|
1019
|
+
const columns = this.compileSelectColumns(ast, ctx);
|
|
1020
|
+
const from = this.compileFrom(ast.from, ctx);
|
|
1021
|
+
const joins = JoinCompiler.compileJoins(ast, ctx, this.compileFrom.bind(this), this.compileExpression.bind(this));
|
|
1022
|
+
const whereClause = this.compileWhere(ast.where, ctx);
|
|
1023
|
+
const groupBy = GroupByCompiler.compileGroupBy(ast, this.quoteIdentifier.bind(this));
|
|
1024
|
+
const having = this.compileHaving(ast, ctx);
|
|
1025
|
+
const orderBy = OrderByCompiler.compileOrderBy(ast, this.quoteIdentifier.bind(this));
|
|
1026
|
+
const pagination = this.paginationStrategy.compilePagination(ast.limit, ast.offset);
|
|
1027
|
+
return `SELECT ${this.compileDistinct(ast)}${columns} FROM ${from}${joins}${whereClause}${groupBy}${having}${orderBy}${pagination}`;
|
|
1028
|
+
}
|
|
1029
|
+
compileUpdateAst(ast, ctx) {
|
|
1030
|
+
const table = this.compileTableName(ast.table);
|
|
1031
|
+
const assignments = this.compileUpdateAssignments(ast.set, ctx);
|
|
1032
|
+
const whereClause = this.compileWhere(ast.where, ctx);
|
|
1033
|
+
const returning = this.compileReturning(ast.returning, ctx);
|
|
1034
|
+
return `UPDATE ${table} SET ${assignments}${whereClause}${returning}`;
|
|
1035
|
+
}
|
|
1036
|
+
compileUpdateAssignments(assignments, ctx) {
|
|
1037
|
+
return assignments.map((assignment) => {
|
|
1038
|
+
const col = assignment.column;
|
|
1039
|
+
const target = `${this.quoteIdentifier(col.table)}.${this.quoteIdentifier(col.name)}`;
|
|
1040
|
+
const value = this.compileOperand(assignment.value, ctx);
|
|
1041
|
+
return `${target} = ${value}`;
|
|
1042
|
+
}).join(", ");
|
|
1043
|
+
}
|
|
1044
|
+
compileDeleteAst(ast, ctx) {
|
|
1045
|
+
const table = this.compileTableName(ast.from);
|
|
1046
|
+
const whereClause = this.compileWhere(ast.where, ctx);
|
|
1047
|
+
const returning = this.compileReturning(ast.returning, ctx);
|
|
1048
|
+
return `DELETE FROM ${table}${whereClause}${returning}`;
|
|
1049
|
+
}
|
|
1050
|
+
formatReturningColumns(returning) {
|
|
1051
|
+
return this.returningStrategy.formatReturningColumns(returning, this.quoteIdentifier.bind(this));
|
|
1052
|
+
}
|
|
1053
|
+
compileDistinct(ast) {
|
|
1054
|
+
return ast.distinct ? "DISTINCT " : "";
|
|
1055
|
+
}
|
|
1056
|
+
compileSelectColumns(ast, ctx) {
|
|
1057
|
+
return ast.columns.map((c) => {
|
|
1058
|
+
const expr = this.compileOperand(c, ctx);
|
|
1059
|
+
if (c.alias) {
|
|
1060
|
+
if (c.alias.includes("(")) return c.alias;
|
|
1061
|
+
return `${expr} AS ${this.quoteIdentifier(c.alias)}`;
|
|
1062
|
+
}
|
|
1063
|
+
return expr;
|
|
1064
|
+
}).join(", ");
|
|
1065
|
+
}
|
|
1066
|
+
compileFrom(ast, ctx) {
|
|
1067
|
+
const tableSource = ast;
|
|
1068
|
+
if (tableSource.type === "FunctionTable") {
|
|
1069
|
+
return this.compileFunctionTable(tableSource, ctx);
|
|
1070
|
+
}
|
|
1071
|
+
return this.compileTableSource(tableSource);
|
|
1072
|
+
}
|
|
1073
|
+
compileFunctionTable(fn, ctx) {
|
|
1074
|
+
return FunctionTableFormatter.format(fn, ctx, this);
|
|
1075
|
+
}
|
|
1076
|
+
compileTableSource(table) {
|
|
1077
|
+
const base = this.compileTableName(table);
|
|
1078
|
+
return table.alias ? `${base} AS ${this.quoteIdentifier(table.alias)}` : base;
|
|
1079
|
+
}
|
|
1080
|
+
compileTableName(table) {
|
|
1081
|
+
if (table.schema) {
|
|
1082
|
+
return `${this.quoteIdentifier(table.schema)}.${this.quoteIdentifier(table.name)}`;
|
|
1083
|
+
}
|
|
1084
|
+
return this.quoteIdentifier(table.name);
|
|
1085
|
+
}
|
|
1086
|
+
compileHaving(ast, ctx) {
|
|
1087
|
+
if (!ast.having) return "";
|
|
1088
|
+
return ` HAVING ${this.compileExpression(ast.having, ctx)}`;
|
|
1089
|
+
}
|
|
1090
|
+
stripTrailingSemicolon(sql) {
|
|
1091
|
+
return sql.trim().replace(/;$/, "");
|
|
1092
|
+
}
|
|
1093
|
+
wrapSetOperand(sql) {
|
|
1094
|
+
const trimmed = this.stripTrailingSemicolon(sql);
|
|
1095
|
+
return `(${trimmed})`;
|
|
1096
|
+
}
|
|
1097
|
+
};
|
|
1098
|
+
|
|
1099
|
+
// src/core/dialect/postgres/functions.ts
|
|
1100
|
+
var PostgresFunctionStrategy = class extends StandardFunctionStrategy {
|
|
1101
|
+
constructor() {
|
|
1102
|
+
super();
|
|
1103
|
+
this.registerOverrides();
|
|
1104
|
+
}
|
|
1105
|
+
registerOverrides() {
|
|
1106
|
+
this.add("UTC_NOW", () => `(NOW() AT TIME ZONE 'UTC')`);
|
|
1107
|
+
this.add("UNIX_TIMESTAMP", () => `EXTRACT(EPOCH FROM NOW())::INTEGER`);
|
|
1108
|
+
this.add("FROM_UNIXTIME", ({ compiledArgs }) => {
|
|
1109
|
+
if (compiledArgs.length !== 1) throw new Error("FROM_UNIXTIME expects 1 argument");
|
|
1110
|
+
return `to_timestamp(${compiledArgs[0]})`;
|
|
1111
|
+
});
|
|
1112
|
+
this.add("EXTRACT", ({ compiledArgs }) => {
|
|
1113
|
+
if (compiledArgs.length !== 2) throw new Error("EXTRACT expects 2 arguments (part, date)");
|
|
1114
|
+
const [part, date] = compiledArgs;
|
|
1115
|
+
const partClean = part.replace(/['"]/g, "");
|
|
1116
|
+
return `EXTRACT(${partClean} FROM ${date})`;
|
|
1117
|
+
});
|
|
1118
|
+
this.add("YEAR", ({ compiledArgs }) => {
|
|
1119
|
+
if (compiledArgs.length !== 1) throw new Error("YEAR expects 1 argument");
|
|
1120
|
+
return `EXTRACT(YEAR FROM ${compiledArgs[0]})`;
|
|
1121
|
+
});
|
|
1122
|
+
this.add("MONTH", ({ compiledArgs }) => {
|
|
1123
|
+
if (compiledArgs.length !== 1) throw new Error("MONTH expects 1 argument");
|
|
1124
|
+
return `EXTRACT(MONTH FROM ${compiledArgs[0]})`;
|
|
1125
|
+
});
|
|
1126
|
+
this.add("DAY", ({ compiledArgs }) => {
|
|
1127
|
+
if (compiledArgs.length !== 1) throw new Error("DAY expects 1 argument");
|
|
1128
|
+
return `EXTRACT(DAY FROM ${compiledArgs[0]})`;
|
|
1129
|
+
});
|
|
1130
|
+
this.add("DATE_ADD", ({ node, compiledArgs }) => {
|
|
1131
|
+
if (compiledArgs.length !== 3) throw new Error("DATE_ADD expects 3 arguments (date, interval, unit)");
|
|
1132
|
+
const [date, interval] = compiledArgs;
|
|
1133
|
+
const unitArg = node.args[2];
|
|
1134
|
+
const unitClean = String(unitArg.value).replace(/['"]/g, "").toLowerCase();
|
|
1135
|
+
return `(${date} + (${interval} || ' ${unitClean}')::INTERVAL)`;
|
|
1136
|
+
});
|
|
1137
|
+
this.add("DATE_SUB", ({ node, compiledArgs }) => {
|
|
1138
|
+
if (compiledArgs.length !== 3) throw new Error("DATE_SUB expects 3 arguments (date, interval, unit)");
|
|
1139
|
+
const [date, interval] = compiledArgs;
|
|
1140
|
+
const unitArg = node.args[2];
|
|
1141
|
+
const unitClean = String(unitArg.value).replace(/['"]/g, "").toLowerCase();
|
|
1142
|
+
return `(${date} - (${interval} || ' ${unitClean}')::INTERVAL)`;
|
|
1143
|
+
});
|
|
1144
|
+
this.add("DATE_DIFF", ({ compiledArgs }) => {
|
|
1145
|
+
if (compiledArgs.length !== 2) throw new Error("DATE_DIFF expects 2 arguments");
|
|
1146
|
+
const [date1, date2] = compiledArgs;
|
|
1147
|
+
return `(${date1}::DATE - ${date2}::DATE)`;
|
|
1148
|
+
});
|
|
1149
|
+
this.add("DATE_FORMAT", ({ compiledArgs }) => {
|
|
1150
|
+
if (compiledArgs.length !== 2) throw new Error("DATE_FORMAT expects 2 arguments");
|
|
1151
|
+
const [date, format] = compiledArgs;
|
|
1152
|
+
return `TO_CHAR(${date}, ${format})`;
|
|
1153
|
+
});
|
|
1154
|
+
this.add("END_OF_MONTH", ({ compiledArgs }) => {
|
|
1155
|
+
if (compiledArgs.length !== 1) throw new Error("END_OF_MONTH expects 1 argument");
|
|
1156
|
+
return `(date_trunc('month', ${compiledArgs[0]}) + interval '1 month' - interval '1 day')::DATE`;
|
|
1157
|
+
});
|
|
1158
|
+
this.add("DAY_OF_WEEK", ({ compiledArgs }) => {
|
|
1159
|
+
if (compiledArgs.length !== 1) throw new Error("DAY_OF_WEEK expects 1 argument");
|
|
1160
|
+
return `EXTRACT(DOW FROM ${compiledArgs[0]})`;
|
|
1161
|
+
});
|
|
1162
|
+
this.add("WEEK_OF_YEAR", ({ compiledArgs }) => {
|
|
1163
|
+
if (compiledArgs.length !== 1) throw new Error("WEEK_OF_YEAR expects 1 argument");
|
|
1164
|
+
return `EXTRACT(WEEK FROM ${compiledArgs[0]})`;
|
|
1165
|
+
});
|
|
1166
|
+
this.add("DATE_TRUNC", ({ node, compiledArgs }) => {
|
|
1167
|
+
if (compiledArgs.length !== 2) throw new Error("DATE_TRUNC expects 2 arguments (part, date)");
|
|
1168
|
+
const [, date] = compiledArgs;
|
|
1169
|
+
const partArg = node.args[0];
|
|
1170
|
+
const partClean = String(partArg.value).replace(/['"]/g, "").toLowerCase();
|
|
1171
|
+
return `DATE_TRUNC('${partClean}', ${date})`;
|
|
1172
|
+
});
|
|
1173
|
+
}
|
|
1174
|
+
};
|
|
1175
|
+
|
|
1176
|
+
// src/core/dialect/postgres/index.ts
|
|
1177
|
+
var PostgresDialect = class extends SqlDialectBase {
|
|
1178
|
+
/**
|
|
1179
|
+
* Creates a new PostgresDialect instance
|
|
1180
|
+
*/
|
|
1181
|
+
constructor() {
|
|
1182
|
+
super(new PostgresFunctionStrategy());
|
|
1183
|
+
this.dialect = "postgres";
|
|
1184
|
+
}
|
|
1185
|
+
/**
|
|
1186
|
+
* Quotes an identifier using PostgreSQL double-quote syntax
|
|
1187
|
+
* @param id - Identifier to quote
|
|
1188
|
+
* @returns Quoted identifier
|
|
1189
|
+
*/
|
|
1190
|
+
quoteIdentifier(id) {
|
|
1191
|
+
return `"${id}"`;
|
|
1192
|
+
}
|
|
1193
|
+
/**
|
|
1194
|
+
* Compiles JSON path expression using PostgreSQL syntax
|
|
1195
|
+
* @param node - JSON path node
|
|
1196
|
+
* @returns PostgreSQL JSON path expression
|
|
1197
|
+
*/
|
|
1198
|
+
compileJsonPath(node) {
|
|
1199
|
+
const col = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
|
|
1200
|
+
return `${col}->>'${node.path}'`;
|
|
1201
|
+
}
|
|
1202
|
+
compileReturning(returning, ctx) {
|
|
1203
|
+
if (!returning || returning.length === 0) return "";
|
|
1204
|
+
const columns = this.formatReturningColumns(returning);
|
|
1205
|
+
return ` RETURNING ${columns}`;
|
|
1206
|
+
}
|
|
1207
|
+
supportsReturning() {
|
|
1208
|
+
return true;
|
|
1209
|
+
}
|
|
1210
|
+
};
|
|
1211
|
+
|
|
1212
|
+
// src/core/dialect/mysql/functions.ts
|
|
1213
|
+
var MysqlFunctionStrategy = class extends StandardFunctionStrategy {
|
|
1214
|
+
constructor() {
|
|
1215
|
+
super();
|
|
1216
|
+
this.registerOverrides();
|
|
1217
|
+
}
|
|
1218
|
+
registerOverrides() {
|
|
1219
|
+
this.add("NOW", () => `NOW()`);
|
|
1220
|
+
this.add("CURRENT_DATE", () => `CURDATE()`);
|
|
1221
|
+
this.add("CURRENT_TIME", () => `CURTIME()`);
|
|
1222
|
+
this.add("UTC_NOW", () => `UTC_TIMESTAMP()`);
|
|
1223
|
+
this.add("EXTRACT", ({ compiledArgs }) => {
|
|
1224
|
+
if (compiledArgs.length !== 2) throw new Error("EXTRACT expects 2 arguments (part, date)");
|
|
1225
|
+
const [part, date] = compiledArgs;
|
|
1226
|
+
const partClean = part.replace(/['"]/g, "");
|
|
1227
|
+
return `EXTRACT(${partClean} FROM ${date})`;
|
|
1228
|
+
});
|
|
1229
|
+
this.add("YEAR", ({ compiledArgs }) => {
|
|
1230
|
+
if (compiledArgs.length !== 1) throw new Error("YEAR expects 1 argument");
|
|
1231
|
+
return `YEAR(${compiledArgs[0]})`;
|
|
1232
|
+
});
|
|
1233
|
+
this.add("MONTH", ({ compiledArgs }) => {
|
|
1234
|
+
if (compiledArgs.length !== 1) throw new Error("MONTH expects 1 argument");
|
|
1235
|
+
return `MONTH(${compiledArgs[0]})`;
|
|
1236
|
+
});
|
|
1237
|
+
this.add("DAY", ({ compiledArgs }) => {
|
|
1238
|
+
if (compiledArgs.length !== 1) throw new Error("DAY expects 1 argument");
|
|
1239
|
+
return `DAY(${compiledArgs[0]})`;
|
|
1240
|
+
});
|
|
1241
|
+
this.add("DATE_ADD", ({ node, compiledArgs }) => {
|
|
1242
|
+
if (compiledArgs.length !== 3) throw new Error("DATE_ADD expects 3 arguments (date, interval, unit)");
|
|
1243
|
+
const [date, interval] = compiledArgs;
|
|
1244
|
+
const unitArg = node.args[2];
|
|
1245
|
+
const unitClean = String(unitArg.value).replace(/['"]/g, "").toUpperCase();
|
|
1246
|
+
return `DATE_ADD(${date}, INTERVAL ${interval} ${unitClean})`;
|
|
1247
|
+
});
|
|
1248
|
+
this.add("DATE_SUB", ({ node, compiledArgs }) => {
|
|
1249
|
+
if (compiledArgs.length !== 3) throw new Error("DATE_SUB expects 3 arguments (date, interval, unit)");
|
|
1250
|
+
const [date, interval] = compiledArgs;
|
|
1251
|
+
const unitArg = node.args[2];
|
|
1252
|
+
const unitClean = String(unitArg.value).replace(/['"]/g, "").toUpperCase();
|
|
1253
|
+
return `DATE_SUB(${date}, INTERVAL ${interval} ${unitClean})`;
|
|
1254
|
+
});
|
|
1255
|
+
this.add("DATE_DIFF", ({ compiledArgs }) => {
|
|
1256
|
+
if (compiledArgs.length !== 2) throw new Error("DATE_DIFF expects 2 arguments");
|
|
1257
|
+
const [date1, date2] = compiledArgs;
|
|
1258
|
+
return `DATEDIFF(${date1}, ${date2})`;
|
|
1259
|
+
});
|
|
1260
|
+
this.add("DATE_FORMAT", ({ compiledArgs }) => {
|
|
1261
|
+
if (compiledArgs.length !== 2) throw new Error("DATE_FORMAT expects 2 arguments");
|
|
1262
|
+
const [date, format] = compiledArgs;
|
|
1263
|
+
return `DATE_FORMAT(${date}, ${format})`;
|
|
1264
|
+
});
|
|
1265
|
+
this.add("END_OF_MONTH", ({ compiledArgs }) => {
|
|
1266
|
+
if (compiledArgs.length !== 1) throw new Error("END_OF_MONTH expects 1 argument");
|
|
1267
|
+
return `LAST_DAY(${compiledArgs[0]})`;
|
|
1268
|
+
});
|
|
1269
|
+
this.add("DAY_OF_WEEK", ({ compiledArgs }) => {
|
|
1270
|
+
if (compiledArgs.length !== 1) throw new Error("DAY_OF_WEEK expects 1 argument");
|
|
1271
|
+
return `DAYOFWEEK(${compiledArgs[0]})`;
|
|
1272
|
+
});
|
|
1273
|
+
this.add("WEEK_OF_YEAR", ({ compiledArgs }) => {
|
|
1274
|
+
if (compiledArgs.length !== 1) throw new Error("WEEK_OF_YEAR expects 1 argument");
|
|
1275
|
+
return `WEEKOFYEAR(${compiledArgs[0]})`;
|
|
1276
|
+
});
|
|
1277
|
+
this.add("DATE_TRUNC", ({ node, compiledArgs }) => {
|
|
1278
|
+
if (compiledArgs.length !== 2) throw new Error("DATE_TRUNC expects 2 arguments (part, date)");
|
|
1279
|
+
const [, date] = compiledArgs;
|
|
1280
|
+
const partArg = node.args[0];
|
|
1281
|
+
const partClean = String(partArg.value).replace(/['"]/g, "").toLowerCase();
|
|
1282
|
+
if (partClean === "year") {
|
|
1283
|
+
return `DATE_FORMAT(${date}, '%Y-01-01')`;
|
|
1284
|
+
} else if (partClean === "month") {
|
|
1285
|
+
return `DATE_FORMAT(${date}, '%Y-%m-01')`;
|
|
1286
|
+
} else if (partClean === "day") {
|
|
1287
|
+
return `DATE(${date})`;
|
|
1288
|
+
}
|
|
1289
|
+
return `DATE(${date})`;
|
|
1290
|
+
});
|
|
1291
|
+
}
|
|
1292
|
+
};
|
|
1293
|
+
|
|
1294
|
+
// src/core/dialect/mysql/index.ts
|
|
1295
|
+
var MySqlDialect = class extends SqlDialectBase {
|
|
1296
|
+
/**
|
|
1297
|
+
* Creates a new MySqlDialect instance
|
|
1298
|
+
*/
|
|
1299
|
+
constructor() {
|
|
1300
|
+
super(new MysqlFunctionStrategy());
|
|
1301
|
+
this.dialect = "mysql";
|
|
1302
|
+
}
|
|
1303
|
+
/**
|
|
1304
|
+
* Quotes an identifier using MySQL backtick syntax
|
|
1305
|
+
* @param id - Identifier to quote
|
|
1306
|
+
* @returns Quoted identifier
|
|
1307
|
+
*/
|
|
1308
|
+
quoteIdentifier(id) {
|
|
1309
|
+
return `\`${id}\``;
|
|
1310
|
+
}
|
|
1311
|
+
/**
|
|
1312
|
+
* Compiles JSON path expression using MySQL syntax
|
|
1313
|
+
* @param node - JSON path node
|
|
1314
|
+
* @returns MySQL JSON path expression
|
|
1315
|
+
*/
|
|
1316
|
+
compileJsonPath(node) {
|
|
1317
|
+
const col = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
|
|
1318
|
+
return `${col}->'${node.path}'`;
|
|
1319
|
+
}
|
|
1320
|
+
};
|
|
1321
|
+
|
|
1322
|
+
// src/core/dialect/sqlite/functions.ts
|
|
1323
|
+
var SqliteFunctionStrategy = class extends StandardFunctionStrategy {
|
|
1324
|
+
constructor() {
|
|
1325
|
+
super();
|
|
1326
|
+
this.registerOverrides();
|
|
1327
|
+
}
|
|
1328
|
+
registerOverrides() {
|
|
1329
|
+
this.add("NOW", () => `datetime('now', 'localtime')`);
|
|
1330
|
+
this.add("CURRENT_DATE", () => `date('now', 'localtime')`);
|
|
1331
|
+
this.add("CURRENT_TIME", () => `time('now', 'localtime')`);
|
|
1332
|
+
this.add("UTC_NOW", () => `datetime('now')`);
|
|
1333
|
+
this.add("EXTRACT", ({ compiledArgs }) => {
|
|
1334
|
+
if (compiledArgs.length !== 2) throw new Error("EXTRACT expects 2 arguments (part, date)");
|
|
1335
|
+
const [part, date] = compiledArgs;
|
|
1336
|
+
const partUpper = part.replace(/['"]/g, "").toUpperCase();
|
|
1337
|
+
const formatMap = {
|
|
1338
|
+
"YEAR": "%Y",
|
|
1339
|
+
"MONTH": "%m",
|
|
1340
|
+
"DAY": "%d",
|
|
1341
|
+
"HOUR": "%H",
|
|
1342
|
+
"MINUTE": "%M",
|
|
1343
|
+
"SECOND": "%S",
|
|
1344
|
+
"DOW": "%w",
|
|
1345
|
+
"WEEK": "%W"
|
|
1346
|
+
};
|
|
1347
|
+
const format = formatMap[partUpper] || "%Y";
|
|
1348
|
+
return `CAST(strftime('${format}', ${date}) AS INTEGER)`;
|
|
1349
|
+
});
|
|
1350
|
+
this.add("YEAR", ({ compiledArgs }) => {
|
|
1351
|
+
if (compiledArgs.length !== 1) throw new Error("YEAR expects 1 argument");
|
|
1352
|
+
return `CAST(strftime('%Y', ${compiledArgs[0]}) AS INTEGER)`;
|
|
1353
|
+
});
|
|
1354
|
+
this.add("MONTH", ({ compiledArgs }) => {
|
|
1355
|
+
if (compiledArgs.length !== 1) throw new Error("MONTH expects 1 argument");
|
|
1356
|
+
return `CAST(strftime('%m', ${compiledArgs[0]}) AS INTEGER)`;
|
|
1357
|
+
});
|
|
1358
|
+
this.add("DAY", ({ compiledArgs }) => {
|
|
1359
|
+
if (compiledArgs.length !== 1) throw new Error("DAY expects 1 argument");
|
|
1360
|
+
return `CAST(strftime('%d', ${compiledArgs[0]}) AS INTEGER)`;
|
|
1361
|
+
});
|
|
1362
|
+
this.add("DATE_ADD", ({ node, compiledArgs }) => {
|
|
1363
|
+
if (compiledArgs.length !== 3) throw new Error("DATE_ADD expects 3 arguments (date, interval, unit)");
|
|
1364
|
+
const [date, interval] = compiledArgs;
|
|
1365
|
+
const unitArg = node.args[2];
|
|
1366
|
+
const unitClean = String(unitArg.value).replace(/['"]/g, "").toLowerCase();
|
|
1367
|
+
return `datetime(${date}, '+' || ${interval} || ' ${unitClean}')`;
|
|
1368
|
+
});
|
|
1369
|
+
this.add("DATE_SUB", ({ node, compiledArgs }) => {
|
|
1370
|
+
if (compiledArgs.length !== 3) throw new Error("DATE_SUB expects 3 arguments (date, interval, unit)");
|
|
1371
|
+
const [date, interval] = compiledArgs;
|
|
1372
|
+
const unitArg = node.args[2];
|
|
1373
|
+
const unitClean = String(unitArg.value).replace(/['"]/g, "").toLowerCase();
|
|
1374
|
+
return `datetime(${date}, '-' || ${interval} || ' ${unitClean}')`;
|
|
1375
|
+
});
|
|
1376
|
+
this.add("DATE_DIFF", ({ compiledArgs }) => {
|
|
1377
|
+
if (compiledArgs.length !== 2) throw new Error("DATE_DIFF expects 2 arguments");
|
|
1378
|
+
const [date1, date2] = compiledArgs;
|
|
1379
|
+
return `CAST(julianday(${date1}) - julianday(${date2}) AS INTEGER)`;
|
|
1380
|
+
});
|
|
1381
|
+
this.add("DATE_FORMAT", ({ compiledArgs }) => {
|
|
1382
|
+
if (compiledArgs.length !== 2) throw new Error("DATE_FORMAT expects 2 arguments");
|
|
1383
|
+
const [date, format] = compiledArgs;
|
|
1384
|
+
return `strftime(${format}, ${date})`;
|
|
1385
|
+
});
|
|
1386
|
+
this.add("UNIX_TIMESTAMP", () => `CAST(strftime('%s', 'now') AS INTEGER)`);
|
|
1387
|
+
this.add("FROM_UNIXTIME", ({ compiledArgs }) => {
|
|
1388
|
+
if (compiledArgs.length !== 1) throw new Error("FROM_UNIXTIME expects 1 argument");
|
|
1389
|
+
return `datetime(${compiledArgs[0]}, 'unixepoch')`;
|
|
1390
|
+
});
|
|
1391
|
+
this.add("END_OF_MONTH", ({ compiledArgs }) => {
|
|
1392
|
+
if (compiledArgs.length !== 1) throw new Error("END_OF_MONTH expects 1 argument");
|
|
1393
|
+
return `date(${compiledArgs[0]}, 'start of month', '+1 month', '-1 day')`;
|
|
1394
|
+
});
|
|
1395
|
+
this.add("DAY_OF_WEEK", ({ compiledArgs }) => {
|
|
1396
|
+
if (compiledArgs.length !== 1) throw new Error("DAY_OF_WEEK expects 1 argument");
|
|
1397
|
+
return `CAST(strftime('%w', ${compiledArgs[0]}) AS INTEGER)`;
|
|
1398
|
+
});
|
|
1399
|
+
this.add("WEEK_OF_YEAR", ({ compiledArgs }) => {
|
|
1400
|
+
if (compiledArgs.length !== 1) throw new Error("WEEK_OF_YEAR expects 1 argument");
|
|
1401
|
+
return `CAST(strftime('%W', ${compiledArgs[0]}) AS INTEGER)`;
|
|
1402
|
+
});
|
|
1403
|
+
this.add("DATE_TRUNC", ({ node, compiledArgs }) => {
|
|
1404
|
+
if (compiledArgs.length !== 2) throw new Error("DATE_TRUNC expects 2 arguments (part, date)");
|
|
1405
|
+
const [, date] = compiledArgs;
|
|
1406
|
+
const partArg = node.args[0];
|
|
1407
|
+
const partClean = String(partArg.value).replace(/['"]/g, "").toLowerCase();
|
|
1408
|
+
if (partClean === "year") {
|
|
1409
|
+
return `date(${date}, 'start of year')`;
|
|
1410
|
+
} else if (partClean === "month") {
|
|
1411
|
+
return `date(${date}, 'start of month')`;
|
|
1412
|
+
} else if (partClean === "day") {
|
|
1413
|
+
return `date(${date})`;
|
|
1414
|
+
}
|
|
1415
|
+
return `date(${date}, 'start of ${partClean}')`;
|
|
1416
|
+
});
|
|
1417
|
+
}
|
|
1418
|
+
};
|
|
1419
|
+
|
|
1420
|
+
// src/core/dialect/sqlite/index.ts
|
|
1421
|
+
var SqliteDialect = class extends SqlDialectBase {
|
|
1422
|
+
/**
|
|
1423
|
+
* Creates a new SqliteDialect instance
|
|
1424
|
+
*/
|
|
1425
|
+
constructor() {
|
|
1426
|
+
super(new SqliteFunctionStrategy());
|
|
1427
|
+
this.dialect = "sqlite";
|
|
1428
|
+
}
|
|
1429
|
+
/**
|
|
1430
|
+
* Quotes an identifier using SQLite double-quote syntax
|
|
1431
|
+
* @param id - Identifier to quote
|
|
1432
|
+
* @returns Quoted identifier
|
|
1433
|
+
*/
|
|
1434
|
+
quoteIdentifier(id) {
|
|
1435
|
+
return `"${id}"`;
|
|
1436
|
+
}
|
|
1437
|
+
/**
|
|
1438
|
+
* Compiles JSON path expression using SQLite syntax
|
|
1439
|
+
* @param node - JSON path node
|
|
1440
|
+
* @returns SQLite JSON path expression
|
|
1441
|
+
*/
|
|
1442
|
+
compileJsonPath(node) {
|
|
1443
|
+
const col = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
|
|
1444
|
+
return `json_extract(${col}, '${node.path}')`;
|
|
1445
|
+
}
|
|
1446
|
+
compileReturning(returning, ctx) {
|
|
1447
|
+
if (!returning || returning.length === 0) return "";
|
|
1448
|
+
const columns = this.formatReturningColumns(returning);
|
|
1449
|
+
return ` RETURNING ${columns}`;
|
|
1450
|
+
}
|
|
1451
|
+
supportsReturning() {
|
|
1452
|
+
return true;
|
|
1453
|
+
}
|
|
1454
|
+
};
|
|
1455
|
+
|
|
1456
|
+
// src/core/dialect/mssql/functions.ts
|
|
1457
|
+
var MssqlFunctionStrategy = class extends StandardFunctionStrategy {
|
|
1458
|
+
constructor() {
|
|
1459
|
+
super();
|
|
1460
|
+
this.registerOverrides();
|
|
1461
|
+
}
|
|
1462
|
+
registerOverrides() {
|
|
1463
|
+
this.add("NOW", () => `GETDATE()`);
|
|
1464
|
+
this.add("CURRENT_DATE", () => `CAST(GETDATE() AS DATE)`);
|
|
1465
|
+
this.add("CURRENT_TIME", () => `CAST(GETDATE() AS TIME)`);
|
|
1466
|
+
this.add("UTC_NOW", () => `GETUTCDATE()`);
|
|
1467
|
+
this.add("EXTRACT", ({ compiledArgs }) => {
|
|
1468
|
+
if (compiledArgs.length !== 2) throw new Error("EXTRACT expects 2 arguments (part, date)");
|
|
1469
|
+
const [part, date] = compiledArgs;
|
|
1470
|
+
const partClean = part.replace(/['"]/g, "").toLowerCase();
|
|
1471
|
+
return `DATEPART(${partClean}, ${date})`;
|
|
1472
|
+
});
|
|
1473
|
+
this.add("YEAR", ({ compiledArgs }) => {
|
|
1474
|
+
if (compiledArgs.length !== 1) throw new Error("YEAR expects 1 argument");
|
|
1475
|
+
return `YEAR(${compiledArgs[0]})`;
|
|
1476
|
+
});
|
|
1477
|
+
this.add("MONTH", ({ compiledArgs }) => {
|
|
1478
|
+
if (compiledArgs.length !== 1) throw new Error("MONTH expects 1 argument");
|
|
1479
|
+
return `MONTH(${compiledArgs[0]})`;
|
|
1480
|
+
});
|
|
1481
|
+
this.add("DAY", ({ compiledArgs }) => {
|
|
1482
|
+
if (compiledArgs.length !== 1) throw new Error("DAY expects 1 argument");
|
|
1483
|
+
return `DAY(${compiledArgs[0]})`;
|
|
1484
|
+
});
|
|
1485
|
+
this.add("DATE_ADD", ({ node, compiledArgs }) => {
|
|
1486
|
+
if (compiledArgs.length !== 3) throw new Error("DATE_ADD expects 3 arguments (date, interval, unit)");
|
|
1487
|
+
const [date, interval] = compiledArgs;
|
|
1488
|
+
const unitArg = node.args[2];
|
|
1489
|
+
const unitClean = String(unitArg.value).replace(/['"]/g, "").toLowerCase();
|
|
1490
|
+
return `DATEADD(${unitClean}, ${interval}, ${date})`;
|
|
1491
|
+
});
|
|
1492
|
+
this.add("DATE_SUB", ({ node, compiledArgs }) => {
|
|
1493
|
+
if (compiledArgs.length !== 3) throw new Error("DATE_SUB expects 3 arguments (date, interval, unit)");
|
|
1494
|
+
const [date, interval] = compiledArgs;
|
|
1495
|
+
const unitArg = node.args[2];
|
|
1496
|
+
const unitClean = String(unitArg.value).replace(/['"]/g, "").toLowerCase();
|
|
1497
|
+
return `DATEADD(${unitClean}, -${interval}, ${date})`;
|
|
1498
|
+
});
|
|
1499
|
+
this.add("DATE_DIFF", ({ compiledArgs }) => {
|
|
1500
|
+
if (compiledArgs.length !== 2) throw new Error("DATE_DIFF expects 2 arguments");
|
|
1501
|
+
const [date1, date2] = compiledArgs;
|
|
1502
|
+
return `DATEDIFF(day, ${date2}, ${date1})`;
|
|
1503
|
+
});
|
|
1504
|
+
this.add("DATE_FORMAT", ({ compiledArgs }) => {
|
|
1505
|
+
if (compiledArgs.length !== 2) throw new Error("DATE_FORMAT expects 2 arguments");
|
|
1506
|
+
const [date, format] = compiledArgs;
|
|
1507
|
+
return `FORMAT(${date}, ${format})`;
|
|
1508
|
+
});
|
|
1509
|
+
this.add("UNIX_TIMESTAMP", () => `DATEDIFF(SECOND, '1970-01-01', GETUTCDATE())`);
|
|
1510
|
+
this.add("FROM_UNIXTIME", ({ compiledArgs }) => {
|
|
1511
|
+
if (compiledArgs.length !== 1) throw new Error("FROM_UNIXTIME expects 1 argument");
|
|
1512
|
+
return `DATEADD(SECOND, ${compiledArgs[0]}, '1970-01-01')`;
|
|
1513
|
+
});
|
|
1514
|
+
this.add("END_OF_MONTH", ({ compiledArgs }) => {
|
|
1515
|
+
if (compiledArgs.length !== 1) throw new Error("END_OF_MONTH expects 1 argument");
|
|
1516
|
+
return `EOMONTH(${compiledArgs[0]})`;
|
|
1517
|
+
});
|
|
1518
|
+
this.add("DAY_OF_WEEK", ({ compiledArgs }) => {
|
|
1519
|
+
if (compiledArgs.length !== 1) throw new Error("DAY_OF_WEEK expects 1 argument");
|
|
1520
|
+
return `DATEPART(dw, ${compiledArgs[0]})`;
|
|
1521
|
+
});
|
|
1522
|
+
this.add("WEEK_OF_YEAR", ({ compiledArgs }) => {
|
|
1523
|
+
if (compiledArgs.length !== 1) throw new Error("WEEK_OF_YEAR expects 1 argument");
|
|
1524
|
+
return `DATEPART(wk, ${compiledArgs[0]})`;
|
|
1525
|
+
});
|
|
1526
|
+
this.add("DATE_TRUNC", ({ node, compiledArgs }) => {
|
|
1527
|
+
if (compiledArgs.length !== 2) throw new Error("DATE_TRUNC expects 2 arguments (part, date)");
|
|
1528
|
+
const [, date] = compiledArgs;
|
|
1529
|
+
const partArg = node.args[0];
|
|
1530
|
+
const partClean = String(partArg.value).replace(/['"]/g, "").toLowerCase();
|
|
1531
|
+
return `DATETRUNC(${partClean}, ${date})`;
|
|
1532
|
+
});
|
|
1533
|
+
}
|
|
1534
|
+
};
|
|
1535
|
+
|
|
1536
|
+
// src/core/dialect/mssql/index.ts
|
|
1537
|
+
var SqlServerDialect = class extends Dialect {
|
|
1538
|
+
/**
|
|
1539
|
+
* Creates a new SqlServerDialect instance
|
|
1540
|
+
*/
|
|
1541
|
+
constructor() {
|
|
1542
|
+
super(new MssqlFunctionStrategy());
|
|
1543
|
+
this.dialect = "mssql";
|
|
1544
|
+
}
|
|
1545
|
+
/**
|
|
1546
|
+
* Quotes an identifier using SQL Server bracket syntax
|
|
1547
|
+
* @param id - Identifier to quote
|
|
1548
|
+
* @returns Quoted identifier
|
|
1549
|
+
*/
|
|
1550
|
+
quoteIdentifier(id) {
|
|
1551
|
+
return `[${id}]`;
|
|
1552
|
+
}
|
|
1553
|
+
/**
|
|
1554
|
+
* Compiles JSON path expression using SQL Server syntax
|
|
1555
|
+
* @param node - JSON path node
|
|
1556
|
+
* @returns SQL Server JSON path expression
|
|
1557
|
+
*/
|
|
1558
|
+
compileJsonPath(node) {
|
|
1559
|
+
const col = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
|
|
1560
|
+
return `JSON_VALUE(${col}, '${node.path}')`;
|
|
1561
|
+
}
|
|
1562
|
+
/**
|
|
1563
|
+
* Formats parameter placeholders using SQL Server named parameter syntax
|
|
1564
|
+
* @param index - Parameter index
|
|
1565
|
+
* @returns Named parameter placeholder
|
|
1566
|
+
*/
|
|
1567
|
+
formatPlaceholder(index) {
|
|
1568
|
+
return `@p${index}`;
|
|
1569
|
+
}
|
|
1570
|
+
/**
|
|
1571
|
+
* Compiles SELECT query AST to SQL Server SQL
|
|
1572
|
+
* @param ast - Query AST
|
|
1573
|
+
* @param ctx - Compiler context
|
|
1574
|
+
* @returns SQL Server SQL string
|
|
1575
|
+
*/
|
|
1576
|
+
compileSelectAst(ast, ctx) {
|
|
1577
|
+
const hasSetOps = !!(ast.setOps && ast.setOps.length);
|
|
1578
|
+
const ctes = this.compileCtes(ast, ctx);
|
|
1579
|
+
const baseAst = hasSetOps ? { ...ast, setOps: void 0, orderBy: void 0, limit: void 0, offset: void 0 } : ast;
|
|
1580
|
+
const baseSelect = this.compileSelectCore(baseAst, ctx);
|
|
1581
|
+
if (!hasSetOps) {
|
|
1582
|
+
return `${ctes}${baseSelect}`;
|
|
1583
|
+
}
|
|
1584
|
+
const compound = ast.setOps.map((op) => `${op.operator} ${this.wrapSetOperand(this.compileSelectAst(op.query, ctx))}`).join(" ");
|
|
1585
|
+
const orderBy = this.compileOrderBy(ast);
|
|
1586
|
+
const pagination = this.compilePagination(ast, orderBy);
|
|
1587
|
+
const combined = `${this.wrapSetOperand(baseSelect)} ${compound}`;
|
|
1588
|
+
const tail = pagination || orderBy;
|
|
1589
|
+
return `${ctes}${combined}${tail}`;
|
|
1590
|
+
}
|
|
1591
|
+
compileInsertAst(ast, ctx) {
|
|
1592
|
+
const table = this.quoteIdentifier(ast.into.name);
|
|
1593
|
+
const columnList = ast.columns.map((column) => `${this.quoteIdentifier(column.table)}.${this.quoteIdentifier(column.name)}`).join(", ");
|
|
1594
|
+
const values = ast.values.map((row) => `(${row.map((value) => this.compileOperand(value, ctx)).join(", ")})`).join(", ");
|
|
1595
|
+
return `INSERT INTO ${table} (${columnList}) VALUES ${values};`;
|
|
1596
|
+
}
|
|
1597
|
+
compileUpdateAst(ast, ctx) {
|
|
1598
|
+
const table = this.quoteIdentifier(ast.table.name);
|
|
1599
|
+
const assignments = ast.set.map((assignment) => {
|
|
1600
|
+
const col = assignment.column;
|
|
1601
|
+
const target = `${this.quoteIdentifier(col.table)}.${this.quoteIdentifier(col.name)}`;
|
|
1602
|
+
const value = this.compileOperand(assignment.value, ctx);
|
|
1603
|
+
return `${target} = ${value}`;
|
|
1604
|
+
}).join(", ");
|
|
1605
|
+
const whereClause = this.compileWhere(ast.where, ctx);
|
|
1606
|
+
return `UPDATE ${table} SET ${assignments}${whereClause};`;
|
|
1607
|
+
}
|
|
1608
|
+
compileDeleteAst(ast, ctx) {
|
|
1609
|
+
const table = this.quoteIdentifier(ast.from.name);
|
|
1610
|
+
const whereClause = this.compileWhere(ast.where, ctx);
|
|
1611
|
+
return `DELETE FROM ${table}${whereClause};`;
|
|
1612
|
+
}
|
|
1613
|
+
compileSelectCore(ast, ctx) {
|
|
1614
|
+
const columns = ast.columns.map((c) => {
|
|
1615
|
+
let expr = "";
|
|
1616
|
+
if (c.type === "Function") {
|
|
1617
|
+
expr = this.compileOperand(c, ctx);
|
|
1618
|
+
} else if (c.type === "Column") {
|
|
1619
|
+
expr = `${this.quoteIdentifier(c.table)}.${this.quoteIdentifier(c.name)}`;
|
|
1620
|
+
} else if (c.type === "ScalarSubquery") {
|
|
1621
|
+
expr = this.compileOperand(c, ctx);
|
|
1622
|
+
} else if (c.type === "WindowFunction") {
|
|
1623
|
+
expr = this.compileOperand(c, ctx);
|
|
1624
|
+
}
|
|
1625
|
+
if (c.alias) {
|
|
1626
|
+
if (c.alias.includes("(")) return c.alias;
|
|
1627
|
+
return `${expr} AS ${this.quoteIdentifier(c.alias)}`;
|
|
1628
|
+
}
|
|
1629
|
+
return expr;
|
|
1630
|
+
}).join(", ");
|
|
1631
|
+
const distinct = ast.distinct ? "DISTINCT " : "";
|
|
1632
|
+
const from = `${this.quoteIdentifier(ast.from.name)}`;
|
|
1633
|
+
const joins = ast.joins.map((j) => {
|
|
1634
|
+
const table = this.quoteIdentifier(j.table.name);
|
|
1635
|
+
const cond = this.compileExpression(j.condition, ctx);
|
|
1636
|
+
return `${j.kind} JOIN ${table} ON ${cond}`;
|
|
1637
|
+
}).join(" ");
|
|
1638
|
+
const whereClause = this.compileWhere(ast.where, ctx);
|
|
1639
|
+
const groupBy = ast.groupBy && ast.groupBy.length > 0 ? " GROUP BY " + ast.groupBy.map((c) => `${this.quoteIdentifier(c.table)}.${this.quoteIdentifier(c.name)}`).join(", ") : "";
|
|
1640
|
+
const having = ast.having ? ` HAVING ${this.compileExpression(ast.having, ctx)}` : "";
|
|
1641
|
+
const orderBy = this.compileOrderBy(ast);
|
|
1642
|
+
const pagination = this.compilePagination(ast, orderBy);
|
|
1643
|
+
if (pagination) {
|
|
1644
|
+
return `SELECT ${distinct}${columns} FROM ${from}${joins ? " " + joins : ""}${whereClause}${groupBy}${having}${pagination}`;
|
|
1645
|
+
}
|
|
1646
|
+
return `SELECT ${distinct}${columns} FROM ${from}${joins ? " " + joins : ""}${whereClause}${groupBy}${having}${orderBy}`;
|
|
1647
|
+
}
|
|
1648
|
+
compileOrderBy(ast) {
|
|
1649
|
+
if (!ast.orderBy || ast.orderBy.length === 0) return "";
|
|
1650
|
+
return " ORDER BY " + ast.orderBy.map((o) => `${this.quoteIdentifier(o.column.table)}.${this.quoteIdentifier(o.column.name)} ${o.direction}`).join(", ");
|
|
1651
|
+
}
|
|
1652
|
+
compilePagination(ast, orderBy) {
|
|
1653
|
+
const hasLimit = ast.limit !== void 0;
|
|
1654
|
+
const hasOffset = ast.offset !== void 0;
|
|
1655
|
+
if (!hasLimit && !hasOffset) return "";
|
|
1656
|
+
const off = ast.offset ?? 0;
|
|
1657
|
+
const orderClause = orderBy || " ORDER BY (SELECT NULL)";
|
|
1658
|
+
let pagination = `${orderClause} OFFSET ${off} ROWS`;
|
|
1659
|
+
if (hasLimit) {
|
|
1660
|
+
pagination += ` FETCH NEXT ${ast.limit} ROWS ONLY`;
|
|
1661
|
+
}
|
|
1662
|
+
return pagination;
|
|
1663
|
+
}
|
|
1664
|
+
compileCtes(ast, ctx) {
|
|
1665
|
+
if (!ast.ctes || ast.ctes.length === 0) return "";
|
|
1666
|
+
const defs = ast.ctes.map((cte) => {
|
|
1667
|
+
const name = this.quoteIdentifier(cte.name);
|
|
1668
|
+
const cols = cte.columns ? `(${cte.columns.map((c) => this.quoteIdentifier(c)).join(", ")})` : "";
|
|
1669
|
+
const query = this.compileSelectAst(this.normalizeSelectAst(cte.query), ctx).trim().replace(/;$/, "");
|
|
1670
|
+
return `${name}${cols} AS (${query})`;
|
|
1671
|
+
}).join(", ");
|
|
1672
|
+
return `WITH ${defs} `;
|
|
1673
|
+
}
|
|
1674
|
+
wrapSetOperand(sql) {
|
|
1675
|
+
const trimmed = sql.trim().replace(/;$/, "");
|
|
1676
|
+
return `(${trimmed})`;
|
|
1677
|
+
}
|
|
1678
|
+
};
|
|
1679
|
+
|
|
1680
|
+
// src/core/dialect/dialect-factory.ts
|
|
1681
|
+
var DialectFactory = class {
|
|
1682
|
+
static {
|
|
1683
|
+
this.registry = /* @__PURE__ */ new Map();
|
|
1684
|
+
}
|
|
1685
|
+
static {
|
|
1686
|
+
this.defaultsInitialized = false;
|
|
1687
|
+
}
|
|
1688
|
+
static ensureDefaults() {
|
|
1689
|
+
if (this.defaultsInitialized) return;
|
|
1690
|
+
this.defaultsInitialized = true;
|
|
1691
|
+
if (!this.registry.has("postgres")) {
|
|
1692
|
+
this.registry.set("postgres", () => new PostgresDialect());
|
|
1693
|
+
}
|
|
1694
|
+
if (!this.registry.has("mysql")) {
|
|
1695
|
+
this.registry.set("mysql", () => new MySqlDialect());
|
|
1696
|
+
}
|
|
1697
|
+
if (!this.registry.has("sqlite")) {
|
|
1698
|
+
this.registry.set("sqlite", () => new SqliteDialect());
|
|
1699
|
+
}
|
|
1700
|
+
if (!this.registry.has("mssql")) {
|
|
1701
|
+
this.registry.set("mssql", () => new SqlServerDialect());
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
/**
|
|
1705
|
+
* Register (or override) a dialect factory for a key.
|
|
1706
|
+
*
|
|
1707
|
+
* Examples:
|
|
1708
|
+
* DialectFactory.register('sqlite', () => new SqliteDialect());
|
|
1709
|
+
* DialectFactory.register('my-tenant-dialect', () => new CustomDialect());
|
|
1710
|
+
*/
|
|
1711
|
+
static register(key, factory) {
|
|
1712
|
+
this.registry.set(key, factory);
|
|
1713
|
+
}
|
|
1714
|
+
/**
|
|
1715
|
+
* Resolve a key into a Dialect instance.
|
|
1716
|
+
* Throws if the key is not registered.
|
|
1717
|
+
*/
|
|
1718
|
+
static create(key) {
|
|
1719
|
+
this.ensureDefaults();
|
|
1720
|
+
const factory = this.registry.get(key);
|
|
1721
|
+
if (!factory) {
|
|
1722
|
+
throw new Error(
|
|
1723
|
+
`Dialect "${String(
|
|
1724
|
+
key
|
|
1725
|
+
)}" is not registered. Use DialectFactory.register(...) to register it.`
|
|
1726
|
+
);
|
|
1727
|
+
}
|
|
1728
|
+
return factory();
|
|
1729
|
+
}
|
|
1730
|
+
/**
|
|
1731
|
+
* Clear all registrations (mainly for tests).
|
|
1732
|
+
* Built-ins will be re-registered lazily on the next create().
|
|
1733
|
+
*/
|
|
1734
|
+
static clear() {
|
|
1735
|
+
this.registry.clear();
|
|
1736
|
+
this.defaultsInitialized = false;
|
|
1737
|
+
}
|
|
288
1738
|
};
|
|
289
|
-
var
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
type: "BinaryExpression",
|
|
293
|
-
left: toNode(left),
|
|
294
|
-
operator,
|
|
295
|
-
right: toOperand(right)
|
|
296
|
-
};
|
|
297
|
-
if (escape !== void 0) {
|
|
298
|
-
node.escape = toLiteralNode(escape);
|
|
1739
|
+
var resolveDialectInput = (dialect) => {
|
|
1740
|
+
if (typeof dialect === "string") {
|
|
1741
|
+
return DialectFactory.create(dialect);
|
|
299
1742
|
}
|
|
300
|
-
return
|
|
1743
|
+
return dialect;
|
|
301
1744
|
};
|
|
302
|
-
var eq = (left, right) => createBinaryExpression("=", left, right);
|
|
303
|
-
var and = (...operands) => ({
|
|
304
|
-
type: "LogicalExpression",
|
|
305
|
-
operator: "AND",
|
|
306
|
-
operands
|
|
307
|
-
});
|
|
308
|
-
var createInExpression = (operator, left, values) => ({
|
|
309
|
-
type: "InExpression",
|
|
310
|
-
left: toNode(left),
|
|
311
|
-
operator,
|
|
312
|
-
right: values.map((v) => toOperand(v))
|
|
313
|
-
});
|
|
314
|
-
var inList = (left, values) => createInExpression("IN", left, values);
|
|
315
|
-
var exists = (subquery) => ({
|
|
316
|
-
type: "ExistsExpression",
|
|
317
|
-
operator: "EXISTS",
|
|
318
|
-
subquery
|
|
319
|
-
});
|
|
320
|
-
var notExists = (subquery) => ({
|
|
321
|
-
type: "ExistsExpression",
|
|
322
|
-
operator: "NOT EXISTS",
|
|
323
|
-
subquery
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
// src/core/ast/aggregate-functions.ts
|
|
327
|
-
var buildAggregate = (name) => (col) => ({
|
|
328
|
-
type: "Function",
|
|
329
|
-
name,
|
|
330
|
-
args: [columnOperand(col)]
|
|
331
|
-
});
|
|
332
|
-
var count = buildAggregate("COUNT");
|
|
333
|
-
var sum = buildAggregate("SUM");
|
|
334
|
-
var avg = buildAggregate("AVG");
|
|
335
1745
|
|
|
336
1746
|
// src/query-builder/select-query-state.ts
|
|
337
1747
|
var SelectQueryState = class _SelectQueryState {
|
|
@@ -484,9 +1894,9 @@ var SelectQueryState = class _SelectQueryState {
|
|
|
484
1894
|
var createJoinNode = (kind, tableName, condition, relationName) => ({
|
|
485
1895
|
type: "Join",
|
|
486
1896
|
kind,
|
|
487
|
-
table: { type: "Table", name: tableName },
|
|
1897
|
+
table: typeof tableName === "string" ? { type: "Table", name: tableName } : tableName,
|
|
488
1898
|
condition,
|
|
489
|
-
relationName
|
|
1899
|
+
meta: relationName ? { relationName } : void 0
|
|
490
1900
|
});
|
|
491
1901
|
|
|
492
1902
|
// src/core/sql/sql.ts
|
|
@@ -828,7 +2238,8 @@ var HydrationPlanner = class _HydrationPlanner {
|
|
|
828
2238
|
*/
|
|
829
2239
|
buildRelationPlan(rel, relationName, aliasPrefix, columns, pivot) {
|
|
830
2240
|
switch (rel.type) {
|
|
831
|
-
case RelationKinds.HasMany:
|
|
2241
|
+
case RelationKinds.HasMany:
|
|
2242
|
+
case RelationKinds.HasOne: {
|
|
832
2243
|
const localKey = rel.localKey || findPrimaryKey(this.table);
|
|
833
2244
|
return {
|
|
834
2245
|
name: relationName,
|
|
@@ -1157,10 +2568,11 @@ var assertNever = (value) => {
|
|
|
1157
2568
|
throw new Error(`Unhandled relation type: ${JSON.stringify(value)}`);
|
|
1158
2569
|
};
|
|
1159
2570
|
var baseRelationCondition = (root, relation) => {
|
|
1160
|
-
const defaultLocalKey = relation.type === RelationKinds.HasMany ? findPrimaryKey(root) : findPrimaryKey(relation.target);
|
|
2571
|
+
const defaultLocalKey = relation.type === RelationKinds.HasMany || relation.type === RelationKinds.HasOne ? findPrimaryKey(root) : findPrimaryKey(relation.target);
|
|
1161
2572
|
const localKey = relation.localKey || defaultLocalKey;
|
|
1162
2573
|
switch (relation.type) {
|
|
1163
2574
|
case RelationKinds.HasMany:
|
|
2575
|
+
case RelationKinds.HasOne:
|
|
1164
2576
|
return eq(
|
|
1165
2577
|
{ type: "Column", table: relation.target.name, name: relation.foreignKey },
|
|
1166
2578
|
{ type: "Column", table: root.name, name: localKey }
|
|
@@ -1207,6 +2619,9 @@ var buildRelationCorrelation = (root, relation) => {
|
|
|
1207
2619
|
return baseRelationCondition(root, relation);
|
|
1208
2620
|
};
|
|
1209
2621
|
|
|
2622
|
+
// src/core/ast/join-metadata.ts
|
|
2623
|
+
var getJoinRelationName = (join) => join.meta?.relationName;
|
|
2624
|
+
|
|
1210
2625
|
// src/query-builder/relation-service.ts
|
|
1211
2626
|
var RelationService = class {
|
|
1212
2627
|
/**
|
|
@@ -1261,7 +2676,7 @@ var RelationService = class {
|
|
|
1261
2676
|
let hydration = this.hydration;
|
|
1262
2677
|
const relation = this.getRelation(relationName);
|
|
1263
2678
|
const aliasPrefix = options?.aliasPrefix ?? relationName;
|
|
1264
|
-
const alreadyJoined = state.ast.joins.some((j) => j
|
|
2679
|
+
const alreadyJoined = state.ast.joins.some((j) => getJoinRelationName(j) === relationName);
|
|
1265
2680
|
if (!alreadyJoined) {
|
|
1266
2681
|
const joined = this.joinRelation(relationName, options?.joinKind ?? JOIN_KINDS.LEFT, options?.filter);
|
|
1267
2682
|
state = joined.state;
|
|
@@ -1582,6 +2997,12 @@ var hydrateRows = (rows, plan) => {
|
|
|
1582
2997
|
const seen = getRelationSeenSet(rootId, rel.name);
|
|
1583
2998
|
if (seen.has(childPk)) continue;
|
|
1584
2999
|
seen.add(childPk);
|
|
3000
|
+
if (rel.type === RelationKinds.HasOne) {
|
|
3001
|
+
if (!parent[rel.name]) {
|
|
3002
|
+
parent[rel.name] = buildChild(row, rel);
|
|
3003
|
+
}
|
|
3004
|
+
continue;
|
|
3005
|
+
}
|
|
1585
3006
|
const bucket = parent[rel.name];
|
|
1586
3007
|
bucket.push(buildChild(row, rel));
|
|
1587
3008
|
}
|
|
@@ -1595,7 +3016,7 @@ var createBaseRow = (row, plan) => {
|
|
|
1595
3016
|
base[key] = row[key];
|
|
1596
3017
|
}
|
|
1597
3018
|
for (const rel of plan.relations) {
|
|
1598
|
-
base[rel.name] = [];
|
|
3019
|
+
base[rel.name] = rel.type === RelationKinds.HasOne ? null : [];
|
|
1599
3020
|
}
|
|
1600
3021
|
return base;
|
|
1601
3022
|
};
|
|
@@ -1758,7 +3179,7 @@ var DefaultHasManyCollection = class {
|
|
|
1758
3179
|
}
|
|
1759
3180
|
};
|
|
1760
3181
|
|
|
1761
|
-
// src/orm/relations/
|
|
3182
|
+
// src/orm/relations/has-one.ts
|
|
1762
3183
|
var toKey3 = (value) => value === null || value === void 0 ? "" : String(value);
|
|
1763
3184
|
var hideInternal2 = (obj, keys) => {
|
|
1764
3185
|
for (const key of keys) {
|
|
@@ -1770,6 +3191,123 @@ var hideInternal2 = (obj, keys) => {
|
|
|
1770
3191
|
});
|
|
1771
3192
|
}
|
|
1772
3193
|
};
|
|
3194
|
+
var DefaultHasOneReference = class {
|
|
3195
|
+
constructor(ctx, meta, root, relationName, relation, rootTable, loader, createEntity, localKey) {
|
|
3196
|
+
this.ctx = ctx;
|
|
3197
|
+
this.meta = meta;
|
|
3198
|
+
this.root = root;
|
|
3199
|
+
this.relationName = relationName;
|
|
3200
|
+
this.relation = relation;
|
|
3201
|
+
this.rootTable = rootTable;
|
|
3202
|
+
this.loader = loader;
|
|
3203
|
+
this.createEntity = createEntity;
|
|
3204
|
+
this.localKey = localKey;
|
|
3205
|
+
this.loaded = false;
|
|
3206
|
+
this.current = null;
|
|
3207
|
+
hideInternal2(this, [
|
|
3208
|
+
"ctx",
|
|
3209
|
+
"meta",
|
|
3210
|
+
"root",
|
|
3211
|
+
"relationName",
|
|
3212
|
+
"relation",
|
|
3213
|
+
"rootTable",
|
|
3214
|
+
"loader",
|
|
3215
|
+
"createEntity",
|
|
3216
|
+
"localKey"
|
|
3217
|
+
]);
|
|
3218
|
+
this.populateFromHydrationCache();
|
|
3219
|
+
}
|
|
3220
|
+
async load() {
|
|
3221
|
+
if (this.loaded) return this.current;
|
|
3222
|
+
const map = await this.loader();
|
|
3223
|
+
const keyValue = this.root[this.localKey];
|
|
3224
|
+
if (keyValue === void 0 || keyValue === null) {
|
|
3225
|
+
this.loaded = true;
|
|
3226
|
+
return this.current;
|
|
3227
|
+
}
|
|
3228
|
+
const row = map.get(toKey3(keyValue));
|
|
3229
|
+
this.current = row ? this.createEntity(row) : null;
|
|
3230
|
+
this.loaded = true;
|
|
3231
|
+
return this.current;
|
|
3232
|
+
}
|
|
3233
|
+
get() {
|
|
3234
|
+
return this.current;
|
|
3235
|
+
}
|
|
3236
|
+
set(data) {
|
|
3237
|
+
if (data === null) {
|
|
3238
|
+
return this.detachCurrent();
|
|
3239
|
+
}
|
|
3240
|
+
const entity = hasEntityMeta(data) ? data : this.createEntity(data);
|
|
3241
|
+
if (this.current && this.current !== entity) {
|
|
3242
|
+
this.ctx.registerRelationChange(
|
|
3243
|
+
this.root,
|
|
3244
|
+
this.relationKey,
|
|
3245
|
+
this.rootTable,
|
|
3246
|
+
this.relationName,
|
|
3247
|
+
this.relation,
|
|
3248
|
+
{ kind: "remove", entity: this.current }
|
|
3249
|
+
);
|
|
3250
|
+
}
|
|
3251
|
+
this.assignForeignKey(entity);
|
|
3252
|
+
this.current = entity;
|
|
3253
|
+
this.loaded = true;
|
|
3254
|
+
this.ctx.registerRelationChange(
|
|
3255
|
+
this.root,
|
|
3256
|
+
this.relationKey,
|
|
3257
|
+
this.rootTable,
|
|
3258
|
+
this.relationName,
|
|
3259
|
+
this.relation,
|
|
3260
|
+
{ kind: "attach", entity }
|
|
3261
|
+
);
|
|
3262
|
+
return entity;
|
|
3263
|
+
}
|
|
3264
|
+
toJSON() {
|
|
3265
|
+
return this.current;
|
|
3266
|
+
}
|
|
3267
|
+
detachCurrent() {
|
|
3268
|
+
const previous = this.current;
|
|
3269
|
+
if (!previous) return null;
|
|
3270
|
+
this.current = null;
|
|
3271
|
+
this.loaded = true;
|
|
3272
|
+
this.ctx.registerRelationChange(
|
|
3273
|
+
this.root,
|
|
3274
|
+
this.relationKey,
|
|
3275
|
+
this.rootTable,
|
|
3276
|
+
this.relationName,
|
|
3277
|
+
this.relation,
|
|
3278
|
+
{ kind: "remove", entity: previous }
|
|
3279
|
+
);
|
|
3280
|
+
return null;
|
|
3281
|
+
}
|
|
3282
|
+
assignForeignKey(entity) {
|
|
3283
|
+
const keyValue = this.root[this.localKey];
|
|
3284
|
+
entity[this.relation.foreignKey] = keyValue;
|
|
3285
|
+
}
|
|
3286
|
+
get relationKey() {
|
|
3287
|
+
return `${this.rootTable.name}.${this.relationName}`;
|
|
3288
|
+
}
|
|
3289
|
+
populateFromHydrationCache() {
|
|
3290
|
+
const keyValue = this.root[this.localKey];
|
|
3291
|
+
if (keyValue === void 0 || keyValue === null) return;
|
|
3292
|
+
const row = getHydrationRecord(this.meta, this.relationName, keyValue);
|
|
3293
|
+
if (!row) return;
|
|
3294
|
+
this.current = this.createEntity(row);
|
|
3295
|
+
this.loaded = true;
|
|
3296
|
+
}
|
|
3297
|
+
};
|
|
3298
|
+
|
|
3299
|
+
// src/orm/relations/belongs-to.ts
|
|
3300
|
+
var toKey4 = (value) => value === null || value === void 0 ? "" : String(value);
|
|
3301
|
+
var hideInternal3 = (obj, keys) => {
|
|
3302
|
+
for (const key of keys) {
|
|
3303
|
+
Object.defineProperty(obj, key, {
|
|
3304
|
+
value: obj[key],
|
|
3305
|
+
writable: false,
|
|
3306
|
+
configurable: false,
|
|
3307
|
+
enumerable: false
|
|
3308
|
+
});
|
|
3309
|
+
}
|
|
3310
|
+
};
|
|
1773
3311
|
var DefaultBelongsToReference = class {
|
|
1774
3312
|
constructor(ctx, meta, root, relationName, relation, rootTable, loader, createEntity, targetKey) {
|
|
1775
3313
|
this.ctx = ctx;
|
|
@@ -1783,7 +3321,7 @@ var DefaultBelongsToReference = class {
|
|
|
1783
3321
|
this.targetKey = targetKey;
|
|
1784
3322
|
this.loaded = false;
|
|
1785
3323
|
this.current = null;
|
|
1786
|
-
|
|
3324
|
+
hideInternal3(this, ["ctx", "meta", "root", "relationName", "relation", "rootTable", "loader", "createEntity", "targetKey"]);
|
|
1787
3325
|
this.populateFromHydrationCache();
|
|
1788
3326
|
}
|
|
1789
3327
|
async load() {
|
|
@@ -1793,7 +3331,7 @@ var DefaultBelongsToReference = class {
|
|
|
1793
3331
|
if (fkValue === null || fkValue === void 0) {
|
|
1794
3332
|
this.current = null;
|
|
1795
3333
|
} else {
|
|
1796
|
-
const row = map.get(
|
|
3334
|
+
const row = map.get(toKey4(fkValue));
|
|
1797
3335
|
this.current = row ? this.createEntity(row) : null;
|
|
1798
3336
|
}
|
|
1799
3337
|
this.loaded = true;
|
|
@@ -1850,8 +3388,8 @@ var DefaultBelongsToReference = class {
|
|
|
1850
3388
|
};
|
|
1851
3389
|
|
|
1852
3390
|
// src/orm/relations/many-to-many.ts
|
|
1853
|
-
var
|
|
1854
|
-
var
|
|
3391
|
+
var toKey5 = (value) => value === null || value === void 0 ? "" : String(value);
|
|
3392
|
+
var hideInternal4 = (obj, keys) => {
|
|
1855
3393
|
for (const key of keys) {
|
|
1856
3394
|
Object.defineProperty(obj, key, {
|
|
1857
3395
|
value: obj[key],
|
|
@@ -1874,13 +3412,13 @@ var DefaultManyToManyCollection = class {
|
|
|
1874
3412
|
this.localKey = localKey;
|
|
1875
3413
|
this.loaded = false;
|
|
1876
3414
|
this.items = [];
|
|
1877
|
-
|
|
3415
|
+
hideInternal4(this, ["ctx", "meta", "root", "relationName", "relation", "rootTable", "loader", "createEntity", "localKey"]);
|
|
1878
3416
|
this.hydrateFromCache();
|
|
1879
3417
|
}
|
|
1880
3418
|
async load() {
|
|
1881
3419
|
if (this.loaded) return this.items;
|
|
1882
3420
|
const map = await this.loader();
|
|
1883
|
-
const key =
|
|
3421
|
+
const key = toKey5(this.root[this.localKey]);
|
|
1884
3422
|
const rows = map.get(key) ?? [];
|
|
1885
3423
|
this.items = rows.map((row) => {
|
|
1886
3424
|
const entity = this.createEntity(row);
|
|
@@ -1930,15 +3468,15 @@ var DefaultManyToManyCollection = class {
|
|
|
1930
3468
|
async syncByIds(ids) {
|
|
1931
3469
|
await this.load();
|
|
1932
3470
|
const targetKey = this.relation.targetKey || findPrimaryKey(this.relation.target);
|
|
1933
|
-
const normalized = new Set(ids.map((id) =>
|
|
1934
|
-
const currentIds = new Set(this.items.map((item) =>
|
|
3471
|
+
const normalized = new Set(ids.map((id) => toKey5(id)));
|
|
3472
|
+
const currentIds = new Set(this.items.map((item) => toKey5(this.extractId(item))));
|
|
1935
3473
|
for (const id of normalized) {
|
|
1936
3474
|
if (!currentIds.has(id)) {
|
|
1937
3475
|
this.attach(id);
|
|
1938
3476
|
}
|
|
1939
3477
|
}
|
|
1940
3478
|
for (const item of [...this.items]) {
|
|
1941
|
-
const itemId =
|
|
3479
|
+
const itemId = toKey5(this.extractId(item));
|
|
1942
3480
|
if (!normalized.has(itemId)) {
|
|
1943
3481
|
this.detach(item);
|
|
1944
3482
|
}
|
|
@@ -2009,7 +3547,7 @@ var executeQuery = async (ctx, qb) => {
|
|
|
2009
3547
|
const results = await ctx.executor.executeSql(compiled.sql, compiled.params);
|
|
2010
3548
|
return rowsFromResults(results);
|
|
2011
3549
|
};
|
|
2012
|
-
var
|
|
3550
|
+
var toKey6 = (value) => value === null || value === void 0 ? "" : String(value);
|
|
2013
3551
|
var loadHasManyRelation = async (ctx, rootTable, _relationName, relation) => {
|
|
2014
3552
|
const localKey = relation.localKey || findPrimaryKey(rootTable);
|
|
2015
3553
|
const roots = ctx.getEntitiesForTable(rootTable);
|
|
@@ -2033,13 +3571,43 @@ var loadHasManyRelation = async (ctx, rootTable, _relationName, relation) => {
|
|
|
2033
3571
|
for (const row of rows) {
|
|
2034
3572
|
const fkValue = row[relation.foreignKey];
|
|
2035
3573
|
if (fkValue === null || fkValue === void 0) continue;
|
|
2036
|
-
const key =
|
|
3574
|
+
const key = toKey6(fkValue);
|
|
2037
3575
|
const bucket = grouped.get(key) ?? [];
|
|
2038
3576
|
bucket.push(row);
|
|
2039
3577
|
grouped.set(key, bucket);
|
|
2040
3578
|
}
|
|
2041
3579
|
return grouped;
|
|
2042
3580
|
};
|
|
3581
|
+
var loadHasOneRelation = async (ctx, rootTable, _relationName, relation) => {
|
|
3582
|
+
const localKey = relation.localKey || findPrimaryKey(rootTable);
|
|
3583
|
+
const roots = ctx.getEntitiesForTable(rootTable);
|
|
3584
|
+
const keys = /* @__PURE__ */ new Set();
|
|
3585
|
+
for (const tracked of roots) {
|
|
3586
|
+
const value = tracked.entity[localKey];
|
|
3587
|
+
if (value !== null && value !== void 0) {
|
|
3588
|
+
keys.add(value);
|
|
3589
|
+
}
|
|
3590
|
+
}
|
|
3591
|
+
if (!keys.size) {
|
|
3592
|
+
return /* @__PURE__ */ new Map();
|
|
3593
|
+
}
|
|
3594
|
+
const selectMap = selectAllColumns(relation.target);
|
|
3595
|
+
const qb = new SelectQueryBuilder(relation.target).select(selectMap);
|
|
3596
|
+
const fkColumn = relation.target.columns[relation.foreignKey];
|
|
3597
|
+
if (!fkColumn) return /* @__PURE__ */ new Map();
|
|
3598
|
+
qb.where(inList(fkColumn, Array.from(keys)));
|
|
3599
|
+
const rows = await executeQuery(ctx, qb);
|
|
3600
|
+
const lookup = /* @__PURE__ */ new Map();
|
|
3601
|
+
for (const row of rows) {
|
|
3602
|
+
const fkValue = row[relation.foreignKey];
|
|
3603
|
+
if (fkValue === null || fkValue === void 0) continue;
|
|
3604
|
+
const key = toKey6(fkValue);
|
|
3605
|
+
if (!lookup.has(key)) {
|
|
3606
|
+
lookup.set(key, row);
|
|
3607
|
+
}
|
|
3608
|
+
}
|
|
3609
|
+
return lookup;
|
|
3610
|
+
};
|
|
2043
3611
|
var loadBelongsToRelation = async (ctx, rootTable, _relationName, relation) => {
|
|
2044
3612
|
const roots = ctx.getEntitiesForTable(rootTable);
|
|
2045
3613
|
const foreignKeys = /* @__PURE__ */ new Set();
|
|
@@ -2063,7 +3631,7 @@ var loadBelongsToRelation = async (ctx, rootTable, _relationName, relation) => {
|
|
|
2063
3631
|
for (const row of rows) {
|
|
2064
3632
|
const keyValue = row[targetKey];
|
|
2065
3633
|
if (keyValue === null || keyValue === void 0) continue;
|
|
2066
|
-
map.set(
|
|
3634
|
+
map.set(toKey6(keyValue), row);
|
|
2067
3635
|
}
|
|
2068
3636
|
return map;
|
|
2069
3637
|
};
|
|
@@ -2094,12 +3662,12 @@ var loadBelongsToManyRelation = async (ctx, rootTable, _relationName, relation)
|
|
|
2094
3662
|
if (rootValue === null || rootValue === void 0 || targetValue === null || targetValue === void 0) {
|
|
2095
3663
|
continue;
|
|
2096
3664
|
}
|
|
2097
|
-
const bucket = rootLookup.get(
|
|
3665
|
+
const bucket = rootLookup.get(toKey6(rootValue)) ?? [];
|
|
2098
3666
|
bucket.push({
|
|
2099
3667
|
targetId: targetValue,
|
|
2100
3668
|
pivot: { ...pivot }
|
|
2101
3669
|
});
|
|
2102
|
-
rootLookup.set(
|
|
3670
|
+
rootLookup.set(toKey6(rootValue), bucket);
|
|
2103
3671
|
targetIds.add(targetValue);
|
|
2104
3672
|
}
|
|
2105
3673
|
if (!targetIds.size) {
|
|
@@ -2116,13 +3684,13 @@ var loadBelongsToManyRelation = async (ctx, rootTable, _relationName, relation)
|
|
|
2116
3684
|
for (const row of targetRows) {
|
|
2117
3685
|
const pkValue = row[targetKey];
|
|
2118
3686
|
if (pkValue === null || pkValue === void 0) continue;
|
|
2119
|
-
targetMap.set(
|
|
3687
|
+
targetMap.set(toKey6(pkValue), row);
|
|
2120
3688
|
}
|
|
2121
3689
|
const result = /* @__PURE__ */ new Map();
|
|
2122
3690
|
for (const [rootId, entries] of rootLookup.entries()) {
|
|
2123
3691
|
const bucket = [];
|
|
2124
3692
|
for (const entry of entries) {
|
|
2125
|
-
const targetRow = targetMap.get(
|
|
3693
|
+
const targetRow = targetMap.get(toKey6(entry.targetId));
|
|
2126
3694
|
if (!targetRow) continue;
|
|
2127
3695
|
bucket.push({
|
|
2128
3696
|
...targetRow,
|
|
@@ -2217,18 +3785,29 @@ var createEntityFromRow = (ctx, table, row, lazyRelations = []) => {
|
|
|
2217
3785
|
}
|
|
2218
3786
|
return entity;
|
|
2219
3787
|
};
|
|
2220
|
-
var
|
|
3788
|
+
var toKey7 = (value) => value === null || value === void 0 ? "" : String(value);
|
|
2221
3789
|
var populateHydrationCache = (entity, row, meta) => {
|
|
2222
3790
|
for (const relationName of Object.keys(meta.table.relations)) {
|
|
2223
3791
|
const relation = meta.table.relations[relationName];
|
|
2224
3792
|
const data = row[relationName];
|
|
3793
|
+
if (relation.type === RelationKinds.HasOne) {
|
|
3794
|
+
const localKey = relation.localKey || findPrimaryKey(meta.table);
|
|
3795
|
+
const rootValue = entity[localKey];
|
|
3796
|
+
if (rootValue === void 0 || rootValue === null) continue;
|
|
3797
|
+
if (!data || typeof data !== "object") continue;
|
|
3798
|
+
const cache = /* @__PURE__ */ new Map();
|
|
3799
|
+
cache.set(toKey7(rootValue), data);
|
|
3800
|
+
meta.relationHydration.set(relationName, cache);
|
|
3801
|
+
meta.relationCache.set(relationName, Promise.resolve(cache));
|
|
3802
|
+
continue;
|
|
3803
|
+
}
|
|
2225
3804
|
if (!Array.isArray(data)) continue;
|
|
2226
3805
|
if (relation.type === RelationKinds.HasMany || relation.type === RelationKinds.BelongsToMany) {
|
|
2227
3806
|
const localKey = relation.localKey || findPrimaryKey(meta.table);
|
|
2228
3807
|
const rootValue = entity[localKey];
|
|
2229
3808
|
if (rootValue === void 0 || rootValue === null) continue;
|
|
2230
3809
|
const cache = /* @__PURE__ */ new Map();
|
|
2231
|
-
cache.set(
|
|
3810
|
+
cache.set(toKey7(rootValue), data);
|
|
2232
3811
|
meta.relationHydration.set(relationName, cache);
|
|
2233
3812
|
meta.relationCache.set(relationName, Promise.resolve(cache));
|
|
2234
3813
|
continue;
|
|
@@ -2239,7 +3818,7 @@ var populateHydrationCache = (entity, row, meta) => {
|
|
|
2239
3818
|
for (const item of data) {
|
|
2240
3819
|
const pkValue = item[targetKey];
|
|
2241
3820
|
if (pkValue === void 0 || pkValue === null) continue;
|
|
2242
|
-
cache.set(
|
|
3821
|
+
cache.set(toKey7(pkValue), item);
|
|
2243
3822
|
}
|
|
2244
3823
|
if (cache.size) {
|
|
2245
3824
|
meta.relationHydration.set(relationName, cache);
|
|
@@ -2262,6 +3841,26 @@ var getRelationWrapper = (meta, relationName, owner) => {
|
|
|
2262
3841
|
};
|
|
2263
3842
|
var instantiateWrapper = (meta, relationName, relation, owner) => {
|
|
2264
3843
|
switch (relation.type) {
|
|
3844
|
+
case RelationKinds.HasOne: {
|
|
3845
|
+
const hasOne2 = relation;
|
|
3846
|
+
const localKey = hasOne2.localKey || findPrimaryKey(meta.table);
|
|
3847
|
+
const loader = () => relationLoaderCache(
|
|
3848
|
+
meta,
|
|
3849
|
+
relationName,
|
|
3850
|
+
() => loadHasOneRelation(meta.ctx, meta.table, relationName, hasOne2)
|
|
3851
|
+
);
|
|
3852
|
+
return new DefaultHasOneReference(
|
|
3853
|
+
meta.ctx,
|
|
3854
|
+
meta,
|
|
3855
|
+
owner,
|
|
3856
|
+
relationName,
|
|
3857
|
+
hasOne2,
|
|
3858
|
+
meta.table,
|
|
3859
|
+
loader,
|
|
3860
|
+
(row) => createEntityFromRow(meta.ctx, hasOne2.target, row),
|
|
3861
|
+
localKey
|
|
3862
|
+
);
|
|
3863
|
+
}
|
|
2265
3864
|
case RelationKinds.HasMany: {
|
|
2266
3865
|
const hasMany2 = relation;
|
|
2267
3866
|
const localKey = hasMany2.localKey || findPrimaryKey(meta.table);
|
|
@@ -2342,31 +3941,43 @@ var flattenResults = (results) => {
|
|
|
2342
3941
|
}
|
|
2343
3942
|
return rows;
|
|
2344
3943
|
};
|
|
2345
|
-
async
|
|
3944
|
+
var executeWithEntityContext = async (entityCtx, qb) => {
|
|
2346
3945
|
const ast = qb.getAST();
|
|
2347
|
-
const compiled =
|
|
2348
|
-
const executed = await
|
|
3946
|
+
const compiled = entityCtx.dialect.compileSelect(ast);
|
|
3947
|
+
const executed = await entityCtx.executor.executeSql(compiled.sql, compiled.params);
|
|
2349
3948
|
const rows = flattenResults(executed);
|
|
2350
3949
|
if (ast.setOps && ast.setOps.length > 0) {
|
|
2351
|
-
return rows.map(
|
|
2352
|
-
(row) => createEntityProxy(ctx, qb.getTable(), row, qb.getLazyRelations())
|
|
2353
|
-
);
|
|
3950
|
+
return rows.map((row) => createEntityProxy(entityCtx, qb.getTable(), row, qb.getLazyRelations()));
|
|
2354
3951
|
}
|
|
2355
3952
|
const hydrated = hydrateRows(rows, qb.getHydrationPlan());
|
|
2356
|
-
return hydrated.map(
|
|
2357
|
-
|
|
2358
|
-
|
|
3953
|
+
return hydrated.map((row) => createEntityFromRow(entityCtx, qb.getTable(), row, qb.getLazyRelations()));
|
|
3954
|
+
};
|
|
3955
|
+
async function executeHydrated(session, qb) {
|
|
3956
|
+
return executeWithEntityContext(session, qb);
|
|
3957
|
+
}
|
|
3958
|
+
async function executeHydratedWithContexts(_execCtx, hydCtx, qb) {
|
|
3959
|
+
const entityCtx = hydCtx.entityContext;
|
|
3960
|
+
if (!entityCtx) {
|
|
3961
|
+
throw new Error("Hydration context is missing an EntityContext");
|
|
3962
|
+
}
|
|
3963
|
+
return executeWithEntityContext(entityCtx, qb);
|
|
2359
3964
|
}
|
|
2360
3965
|
|
|
2361
3966
|
// src/query-builder/select.ts
|
|
2362
3967
|
var SelectQueryBuilder = class _SelectQueryBuilder {
|
|
2363
3968
|
/**
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
3969
|
+
|
|
3970
|
+
* Creates a new SelectQueryBuilder instance
|
|
3971
|
+
|
|
3972
|
+
* @param table - Table definition to query
|
|
3973
|
+
|
|
3974
|
+
* @param state - Optional initial query state
|
|
3975
|
+
|
|
3976
|
+
* @param hydration - Optional hydration manager
|
|
3977
|
+
|
|
3978
|
+
* @param dependencies - Optional query builder dependencies
|
|
3979
|
+
|
|
3980
|
+
*/
|
|
2370
3981
|
constructor(table, state, hydration, dependencies, lazyRelations) {
|
|
2371
3982
|
const deps = resolveSelectQueryBuilderDependencies(dependencies);
|
|
2372
3983
|
this.env = { table, deps };
|
|
@@ -2403,112 +4014,168 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
|
|
|
2403
4014
|
return this.applyAst(this.context, (service) => service.withSetOperation(operator, subAst));
|
|
2404
4015
|
}
|
|
2405
4016
|
/**
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
4017
|
+
|
|
4018
|
+
* Selects specific columns for the query
|
|
4019
|
+
|
|
4020
|
+
* @param columns - Record of column definitions, function nodes, case expressions, or window functions
|
|
4021
|
+
|
|
4022
|
+
* @returns New query builder instance with selected columns
|
|
4023
|
+
|
|
4024
|
+
*/
|
|
2410
4025
|
select(columns) {
|
|
2411
4026
|
return this.clone(this.columnSelector.select(this.context, columns));
|
|
2412
4027
|
}
|
|
2413
4028
|
/**
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
4029
|
+
|
|
4030
|
+
* Selects raw column expressions
|
|
4031
|
+
|
|
4032
|
+
* @param cols - Column expressions as strings
|
|
4033
|
+
|
|
4034
|
+
* @returns New query builder instance with raw column selections
|
|
4035
|
+
|
|
4036
|
+
*/
|
|
2418
4037
|
selectRaw(...cols) {
|
|
2419
4038
|
return this.clone(this.columnSelector.selectRaw(this.context, cols));
|
|
2420
4039
|
}
|
|
2421
4040
|
/**
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
4041
|
+
|
|
4042
|
+
* Adds a Common Table Expression (CTE) to the query
|
|
4043
|
+
|
|
4044
|
+
* @param name - Name of the CTE
|
|
4045
|
+
|
|
4046
|
+
* @param query - Query builder or query node for the CTE
|
|
4047
|
+
|
|
4048
|
+
* @param columns - Optional column names for the CTE
|
|
4049
|
+
|
|
4050
|
+
* @returns New query builder instance with the CTE
|
|
4051
|
+
|
|
4052
|
+
*/
|
|
2428
4053
|
with(name, query, columns) {
|
|
2429
4054
|
const subAst = this.resolveQueryNode(query);
|
|
2430
4055
|
const nextContext = this.applyAst(this.context, (service) => service.withCte(name, subAst, columns, false));
|
|
2431
4056
|
return this.clone(nextContext);
|
|
2432
4057
|
}
|
|
2433
4058
|
/**
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
4059
|
+
|
|
4060
|
+
* Adds a recursive Common Table Expression (CTE) to the query
|
|
4061
|
+
|
|
4062
|
+
* @param name - Name of the CTE
|
|
4063
|
+
|
|
4064
|
+
* @param query - Query builder or query node for the CTE
|
|
4065
|
+
|
|
4066
|
+
* @param columns - Optional column names for the CTE
|
|
4067
|
+
|
|
4068
|
+
* @returns New query builder instance with the recursive CTE
|
|
4069
|
+
|
|
4070
|
+
*/
|
|
2440
4071
|
withRecursive(name, query, columns) {
|
|
2441
4072
|
const subAst = this.resolveQueryNode(query);
|
|
2442
4073
|
const nextContext = this.applyAst(this.context, (service) => service.withCte(name, subAst, columns, true));
|
|
2443
4074
|
return this.clone(nextContext);
|
|
2444
4075
|
}
|
|
2445
4076
|
/**
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
4077
|
+
|
|
4078
|
+
* Selects a subquery as a column
|
|
4079
|
+
|
|
4080
|
+
* @param alias - Alias for the subquery column
|
|
4081
|
+
|
|
4082
|
+
* @param sub - Query builder or query node for the subquery
|
|
4083
|
+
|
|
4084
|
+
* @returns New query builder instance with the subquery selection
|
|
4085
|
+
|
|
4086
|
+
*/
|
|
2451
4087
|
selectSubquery(alias, sub) {
|
|
2452
4088
|
const query = this.resolveQueryNode(sub);
|
|
2453
4089
|
return this.clone(this.columnSelector.selectSubquery(this.context, alias, query));
|
|
2454
4090
|
}
|
|
2455
4091
|
/**
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
4092
|
+
|
|
4093
|
+
* Adds an INNER JOIN to the query
|
|
4094
|
+
|
|
4095
|
+
* @param table - Table to join
|
|
4096
|
+
|
|
4097
|
+
* @param condition - Join condition expression
|
|
4098
|
+
|
|
4099
|
+
* @returns New query builder instance with the INNER JOIN
|
|
4100
|
+
|
|
4101
|
+
*/
|
|
2461
4102
|
innerJoin(table, condition) {
|
|
2462
4103
|
const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.INNER);
|
|
2463
4104
|
return this.clone(nextContext);
|
|
2464
4105
|
}
|
|
2465
4106
|
/**
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
4107
|
+
|
|
4108
|
+
* Adds a LEFT JOIN to the query
|
|
4109
|
+
|
|
4110
|
+
* @param table - Table to join
|
|
4111
|
+
|
|
4112
|
+
* @param condition - Join condition expression
|
|
4113
|
+
|
|
4114
|
+
* @returns New query builder instance with the LEFT JOIN
|
|
4115
|
+
|
|
4116
|
+
*/
|
|
2471
4117
|
leftJoin(table, condition) {
|
|
2472
4118
|
const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.LEFT);
|
|
2473
4119
|
return this.clone(nextContext);
|
|
2474
4120
|
}
|
|
2475
4121
|
/**
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
4122
|
+
|
|
4123
|
+
* Adds a RIGHT JOIN to the query
|
|
4124
|
+
|
|
4125
|
+
* @param table - Table to join
|
|
4126
|
+
|
|
4127
|
+
* @param condition - Join condition expression
|
|
4128
|
+
|
|
4129
|
+
* @returns New query builder instance with the RIGHT JOIN
|
|
4130
|
+
|
|
4131
|
+
*/
|
|
2481
4132
|
rightJoin(table, condition) {
|
|
2482
4133
|
const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.RIGHT);
|
|
2483
4134
|
return this.clone(nextContext);
|
|
2484
4135
|
}
|
|
2485
4136
|
/**
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
4137
|
+
|
|
4138
|
+
* Matches records based on a relationship
|
|
4139
|
+
|
|
4140
|
+
* @param relationName - Name of the relationship to match
|
|
4141
|
+
|
|
4142
|
+
* @param predicate - Optional predicate expression
|
|
4143
|
+
|
|
4144
|
+
* @returns New query builder instance with the relationship match
|
|
4145
|
+
|
|
4146
|
+
*/
|
|
2491
4147
|
match(relationName, predicate) {
|
|
2492
4148
|
const nextContext = this.relationManager.match(this.context, relationName, predicate);
|
|
2493
4149
|
return this.clone(nextContext);
|
|
2494
4150
|
}
|
|
2495
4151
|
/**
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
4152
|
+
|
|
4153
|
+
* Joins a related table
|
|
4154
|
+
|
|
4155
|
+
* @param relationName - Name of the relationship to join
|
|
4156
|
+
|
|
4157
|
+
* @param joinKind - Type of join (defaults to INNER)
|
|
4158
|
+
|
|
4159
|
+
* @param extraCondition - Optional additional join condition
|
|
4160
|
+
|
|
4161
|
+
* @returns New query builder instance with the relationship join
|
|
4162
|
+
|
|
4163
|
+
*/
|
|
2502
4164
|
joinRelation(relationName, joinKind = JOIN_KINDS.INNER, extraCondition) {
|
|
2503
4165
|
const nextContext = this.relationManager.joinRelation(this.context, relationName, joinKind, extraCondition);
|
|
2504
4166
|
return this.clone(nextContext);
|
|
2505
4167
|
}
|
|
2506
4168
|
/**
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
4169
|
+
|
|
4170
|
+
* Includes related data in the query results
|
|
4171
|
+
|
|
4172
|
+
* @param relationName - Name of the relationship to include
|
|
4173
|
+
|
|
4174
|
+
* @param options - Optional include options
|
|
4175
|
+
|
|
4176
|
+
* @returns New query builder instance with the relationship inclusion
|
|
4177
|
+
|
|
4178
|
+
*/
|
|
2512
4179
|
include(relationName, options) {
|
|
2513
4180
|
const nextContext = this.relationManager.include(this.context, relationName, options);
|
|
2514
4181
|
return this.clone(nextContext);
|
|
@@ -2527,125 +4194,186 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
|
|
|
2527
4194
|
async execute(ctx) {
|
|
2528
4195
|
return executeHydrated(ctx, this);
|
|
2529
4196
|
}
|
|
4197
|
+
async executeWithContexts(execCtx, hydCtx) {
|
|
4198
|
+
return executeHydratedWithContexts(execCtx, hydCtx, this);
|
|
4199
|
+
}
|
|
2530
4200
|
/**
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
4201
|
+
|
|
4202
|
+
* Adds a WHERE condition to the query
|
|
4203
|
+
|
|
4204
|
+
* @param expr - Expression for the WHERE clause
|
|
4205
|
+
|
|
4206
|
+
* @returns New query builder instance with the WHERE condition
|
|
4207
|
+
|
|
4208
|
+
*/
|
|
2535
4209
|
where(expr) {
|
|
2536
4210
|
const nextContext = this.applyAst(this.context, (service) => service.withWhere(expr));
|
|
2537
4211
|
return this.clone(nextContext);
|
|
2538
4212
|
}
|
|
2539
4213
|
/**
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
4214
|
+
|
|
4215
|
+
* Adds a GROUP BY clause to the query
|
|
4216
|
+
|
|
4217
|
+
* @param col - Column definition or column node to group by
|
|
4218
|
+
|
|
4219
|
+
* @returns New query builder instance with the GROUP BY clause
|
|
4220
|
+
|
|
4221
|
+
*/
|
|
2544
4222
|
groupBy(col) {
|
|
2545
4223
|
const nextContext = this.applyAst(this.context, (service) => service.withGroupBy(col));
|
|
2546
4224
|
return this.clone(nextContext);
|
|
2547
4225
|
}
|
|
2548
4226
|
/**
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
4227
|
+
|
|
4228
|
+
* Adds a HAVING condition to the query
|
|
4229
|
+
|
|
4230
|
+
* @param expr - Expression for the HAVING clause
|
|
4231
|
+
|
|
4232
|
+
* @returns New query builder instance with the HAVING condition
|
|
4233
|
+
|
|
4234
|
+
*/
|
|
2553
4235
|
having(expr) {
|
|
2554
4236
|
const nextContext = this.applyAst(this.context, (service) => service.withHaving(expr));
|
|
2555
4237
|
return this.clone(nextContext);
|
|
2556
4238
|
}
|
|
2557
4239
|
/**
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
4240
|
+
|
|
4241
|
+
* Adds an ORDER BY clause to the query
|
|
4242
|
+
|
|
4243
|
+
* @param col - Column definition or column node to order by
|
|
4244
|
+
|
|
4245
|
+
* @param direction - Order direction (defaults to ASC)
|
|
4246
|
+
|
|
4247
|
+
* @returns New query builder instance with the ORDER BY clause
|
|
4248
|
+
|
|
4249
|
+
*/
|
|
2563
4250
|
orderBy(col, direction = ORDER_DIRECTIONS.ASC) {
|
|
2564
4251
|
const nextContext = this.applyAst(this.context, (service) => service.withOrderBy(col, direction));
|
|
2565
4252
|
return this.clone(nextContext);
|
|
2566
4253
|
}
|
|
2567
4254
|
/**
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
4255
|
+
|
|
4256
|
+
* Adds a DISTINCT clause to the query
|
|
4257
|
+
|
|
4258
|
+
* @param cols - Columns to make distinct
|
|
4259
|
+
|
|
4260
|
+
* @returns New query builder instance with the DISTINCT clause
|
|
4261
|
+
|
|
4262
|
+
*/
|
|
2572
4263
|
distinct(...cols) {
|
|
2573
4264
|
return this.clone(this.columnSelector.distinct(this.context, cols));
|
|
2574
4265
|
}
|
|
2575
4266
|
/**
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
4267
|
+
|
|
4268
|
+
* Adds a LIMIT clause to the query
|
|
4269
|
+
|
|
4270
|
+
* @param n - Maximum number of rows to return
|
|
4271
|
+
|
|
4272
|
+
* @returns New query builder instance with the LIMIT clause
|
|
4273
|
+
|
|
4274
|
+
*/
|
|
2580
4275
|
limit(n) {
|
|
2581
4276
|
const nextContext = this.applyAst(this.context, (service) => service.withLimit(n));
|
|
2582
4277
|
return this.clone(nextContext);
|
|
2583
4278
|
}
|
|
2584
4279
|
/**
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
4280
|
+
|
|
4281
|
+
* Adds an OFFSET clause to the query
|
|
4282
|
+
|
|
4283
|
+
* @param n - Number of rows to skip
|
|
4284
|
+
|
|
4285
|
+
* @returns New query builder instance with the OFFSET clause
|
|
4286
|
+
|
|
4287
|
+
*/
|
|
2589
4288
|
offset(n) {
|
|
2590
4289
|
const nextContext = this.applyAst(this.context, (service) => service.withOffset(n));
|
|
2591
4290
|
return this.clone(nextContext);
|
|
2592
4291
|
}
|
|
2593
4292
|
/**
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
4293
|
+
|
|
4294
|
+
* Combines this query with another using UNION
|
|
4295
|
+
|
|
4296
|
+
* @param query - Query to union with
|
|
4297
|
+
|
|
4298
|
+
* @returns New query builder instance with the set operation
|
|
4299
|
+
|
|
4300
|
+
*/
|
|
2598
4301
|
union(query) {
|
|
2599
4302
|
return this.clone(this.applySetOperation("UNION", query));
|
|
2600
4303
|
}
|
|
2601
4304
|
/**
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
4305
|
+
|
|
4306
|
+
* Combines this query with another using UNION ALL
|
|
4307
|
+
|
|
4308
|
+
* @param query - Query to union with
|
|
4309
|
+
|
|
4310
|
+
* @returns New query builder instance with the set operation
|
|
4311
|
+
|
|
4312
|
+
*/
|
|
2606
4313
|
unionAll(query) {
|
|
2607
4314
|
return this.clone(this.applySetOperation("UNION ALL", query));
|
|
2608
4315
|
}
|
|
2609
4316
|
/**
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
4317
|
+
|
|
4318
|
+
* Combines this query with another using INTERSECT
|
|
4319
|
+
|
|
4320
|
+
* @param query - Query to intersect with
|
|
4321
|
+
|
|
4322
|
+
* @returns New query builder instance with the set operation
|
|
4323
|
+
|
|
4324
|
+
*/
|
|
2614
4325
|
intersect(query) {
|
|
2615
4326
|
return this.clone(this.applySetOperation("INTERSECT", query));
|
|
2616
4327
|
}
|
|
2617
4328
|
/**
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
4329
|
+
|
|
4330
|
+
* Combines this query with another using EXCEPT
|
|
4331
|
+
|
|
4332
|
+
* @param query - Query to subtract
|
|
4333
|
+
|
|
4334
|
+
* @returns New query builder instance with the set operation
|
|
4335
|
+
|
|
4336
|
+
*/
|
|
2622
4337
|
except(query) {
|
|
2623
4338
|
return this.clone(this.applySetOperation("EXCEPT", query));
|
|
2624
4339
|
}
|
|
2625
4340
|
/**
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
4341
|
+
|
|
4342
|
+
* Adds a WHERE EXISTS condition to the query
|
|
4343
|
+
|
|
4344
|
+
* @param subquery - Subquery to check for existence
|
|
4345
|
+
|
|
4346
|
+
* @returns New query builder instance with the WHERE EXISTS condition
|
|
4347
|
+
|
|
4348
|
+
*/
|
|
2630
4349
|
whereExists(subquery) {
|
|
2631
4350
|
const subAst = this.resolveQueryNode(subquery);
|
|
2632
4351
|
return this.where(exists(subAst));
|
|
2633
4352
|
}
|
|
2634
4353
|
/**
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
4354
|
+
|
|
4355
|
+
* Adds a WHERE NOT EXISTS condition to the query
|
|
4356
|
+
|
|
4357
|
+
* @param subquery - Subquery to check for non-existence
|
|
4358
|
+
|
|
4359
|
+
* @returns New query builder instance with the WHERE NOT EXISTS condition
|
|
4360
|
+
|
|
4361
|
+
*/
|
|
2639
4362
|
whereNotExists(subquery) {
|
|
2640
4363
|
const subAst = this.resolveQueryNode(subquery);
|
|
2641
4364
|
return this.where(notExists(subAst));
|
|
2642
4365
|
}
|
|
2643
4366
|
/**
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
4367
|
+
|
|
4368
|
+
* Adds a WHERE EXISTS condition based on a relationship
|
|
4369
|
+
|
|
4370
|
+
* @param relationName - Name of the relationship to check
|
|
4371
|
+
|
|
4372
|
+
* @param callback - Optional callback to modify the relationship query
|
|
4373
|
+
|
|
4374
|
+
* @returns New query builder instance with the relationship existence check
|
|
4375
|
+
|
|
4376
|
+
*/
|
|
2649
4377
|
whereHas(relationName, callback) {
|
|
2650
4378
|
const relation = this.env.table.relations[relationName];
|
|
2651
4379
|
if (!relation) {
|
|
@@ -2660,11 +4388,16 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
|
|
|
2660
4388
|
return this.where(exists(finalSubAst));
|
|
2661
4389
|
}
|
|
2662
4390
|
/**
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
4391
|
+
|
|
4392
|
+
* Adds a WHERE NOT EXISTS condition based on a relationship
|
|
4393
|
+
|
|
4394
|
+
* @param relationName - Name of the relationship to check
|
|
4395
|
+
|
|
4396
|
+
* @param callback - Optional callback to modify the relationship query
|
|
4397
|
+
|
|
4398
|
+
* @returns New query builder instance with the relationship non-existence check
|
|
4399
|
+
|
|
4400
|
+
*/
|
|
2668
4401
|
whereHasNot(relationName, callback) {
|
|
2669
4402
|
const relation = this.env.table.relations[relationName];
|
|
2670
4403
|
if (!relation) {
|
|
@@ -2679,32 +4412,47 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
|
|
|
2679
4412
|
return this.where(notExists(finalSubAst));
|
|
2680
4413
|
}
|
|
2681
4414
|
/**
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
4415
|
+
|
|
4416
|
+
* Compiles the query to SQL for a specific dialect
|
|
4417
|
+
|
|
4418
|
+
* @param dialect - Database dialect to compile for
|
|
4419
|
+
|
|
4420
|
+
* @returns Compiled query with SQL and parameters
|
|
4421
|
+
|
|
4422
|
+
*/
|
|
2686
4423
|
compile(dialect) {
|
|
2687
|
-
|
|
4424
|
+
const resolved = resolveDialectInput(dialect);
|
|
4425
|
+
return resolved.compileSelect(this.context.state.ast);
|
|
2688
4426
|
}
|
|
2689
4427
|
/**
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
4428
|
+
|
|
4429
|
+
* Converts the query to SQL string for a specific dialect
|
|
4430
|
+
|
|
4431
|
+
* @param dialect - Database dialect to generate SQL for
|
|
4432
|
+
|
|
4433
|
+
* @returns SQL string representation of the query
|
|
4434
|
+
|
|
4435
|
+
*/
|
|
2694
4436
|
toSql(dialect) {
|
|
2695
4437
|
return this.compile(dialect).sql;
|
|
2696
4438
|
}
|
|
2697
4439
|
/**
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
4440
|
+
|
|
4441
|
+
* Gets the hydration plan for the query
|
|
4442
|
+
|
|
4443
|
+
* @returns Hydration plan or undefined if none exists
|
|
4444
|
+
|
|
4445
|
+
*/
|
|
2701
4446
|
getHydrationPlan() {
|
|
2702
4447
|
return this.context.hydration.getPlan();
|
|
2703
4448
|
}
|
|
2704
4449
|
/**
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
4450
|
+
|
|
4451
|
+
* Gets the Abstract Syntax Tree (AST) representation of the query
|
|
4452
|
+
|
|
4453
|
+
* @returns Query AST with hydration applied
|
|
4454
|
+
|
|
4455
|
+
*/
|
|
2708
4456
|
getAST() {
|
|
2709
4457
|
return this.context.hydration.applyToAst(this.context.state.ast);
|
|
2710
4458
|
}
|
|
@@ -2735,6 +4483,15 @@ var buildRelationDefinitions = (meta, tableMap) => {
|
|
|
2735
4483
|
const relations = {};
|
|
2736
4484
|
for (const [name, relation] of Object.entries(meta.relations)) {
|
|
2737
4485
|
switch (relation.kind) {
|
|
4486
|
+
case RelationKinds.HasOne: {
|
|
4487
|
+
relations[name] = hasOne(
|
|
4488
|
+
resolveTableTarget(relation.target, tableMap),
|
|
4489
|
+
relation.foreignKey,
|
|
4490
|
+
relation.localKey,
|
|
4491
|
+
relation.cascade
|
|
4492
|
+
);
|
|
4493
|
+
break;
|
|
4494
|
+
}
|
|
2738
4495
|
case RelationKinds.HasMany: {
|
|
2739
4496
|
relations[name] = hasMany(
|
|
2740
4497
|
resolveTableTarget(relation.target, tableMap),
|
|
@@ -2805,6 +4562,7 @@ export {
|
|
|
2805
4562
|
Column,
|
|
2806
4563
|
Entity,
|
|
2807
4564
|
HasMany,
|
|
4565
|
+
HasOne,
|
|
2808
4566
|
PrimaryKey,
|
|
2809
4567
|
bootstrapEntities,
|
|
2810
4568
|
getTableDefFromEntity,
|