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
|
@@ -24,6 +24,7 @@ __export(decorators_exports, {
|
|
|
24
24
|
Column: () => Column,
|
|
25
25
|
Entity: () => Entity,
|
|
26
26
|
HasMany: () => HasMany,
|
|
27
|
+
HasOne: () => HasOne,
|
|
27
28
|
PrimaryKey: () => PrimaryKey,
|
|
28
29
|
bootstrapEntities: () => bootstrapEntities,
|
|
29
30
|
getTableDefFromEntity: () => getTableDefFromEntity,
|
|
@@ -133,11 +134,20 @@ function Entity(options = {}) {
|
|
|
133
134
|
|
|
134
135
|
// src/decorators/column.ts
|
|
135
136
|
var normalizeColumnInput = (input) => {
|
|
137
|
+
const asOptions = input;
|
|
138
|
+
const asDefinition = input;
|
|
136
139
|
const column = {
|
|
137
|
-
type:
|
|
138
|
-
args:
|
|
139
|
-
notNull:
|
|
140
|
-
primary:
|
|
140
|
+
type: asOptions.type ?? asDefinition.type,
|
|
141
|
+
args: asOptions.args ?? asDefinition.args,
|
|
142
|
+
notNull: asOptions.notNull ?? asDefinition.notNull,
|
|
143
|
+
primary: asOptions.primary ?? asDefinition.primary,
|
|
144
|
+
unique: asDefinition.unique,
|
|
145
|
+
default: asDefinition.default,
|
|
146
|
+
autoIncrement: asDefinition.autoIncrement,
|
|
147
|
+
generated: asDefinition.generated,
|
|
148
|
+
check: asDefinition.check,
|
|
149
|
+
references: asDefinition.references,
|
|
150
|
+
comment: asDefinition.comment
|
|
141
151
|
};
|
|
142
152
|
if (!column.type) {
|
|
143
153
|
throw new Error("Column decorator requires a column type");
|
|
@@ -186,6 +196,8 @@ function PrimaryKey(definition) {
|
|
|
186
196
|
|
|
187
197
|
// src/schema/relation.ts
|
|
188
198
|
var RelationKinds = {
|
|
199
|
+
/** One-to-one relationship */
|
|
200
|
+
HasOne: "HAS_ONE",
|
|
189
201
|
/** One-to-many relationship */
|
|
190
202
|
HasMany: "HAS_MANY",
|
|
191
203
|
/** Many-to-one relationship */
|
|
@@ -200,6 +212,13 @@ var hasMany = (target, foreignKey, localKey, cascade) => ({
|
|
|
200
212
|
localKey,
|
|
201
213
|
cascade
|
|
202
214
|
});
|
|
215
|
+
var hasOne = (target, foreignKey, localKey, cascade) => ({
|
|
216
|
+
type: RelationKinds.HasOne,
|
|
217
|
+
target,
|
|
218
|
+
foreignKey,
|
|
219
|
+
localKey,
|
|
220
|
+
cascade
|
|
221
|
+
});
|
|
203
222
|
var belongsTo = (target, foreignKey, localKey, cascade) => ({
|
|
204
223
|
type: RelationKinds.BelongsTo,
|
|
205
224
|
target,
|
|
@@ -260,6 +279,16 @@ function HasMany(options) {
|
|
|
260
279
|
cascade: options.cascade
|
|
261
280
|
}));
|
|
262
281
|
}
|
|
282
|
+
function HasOne(options) {
|
|
283
|
+
return createFieldDecorator((propertyName) => ({
|
|
284
|
+
kind: RelationKinds.HasOne,
|
|
285
|
+
propertyKey: propertyName,
|
|
286
|
+
target: options.target,
|
|
287
|
+
foreignKey: options.foreignKey,
|
|
288
|
+
localKey: options.localKey,
|
|
289
|
+
cascade: options.cascade
|
|
290
|
+
}));
|
|
291
|
+
}
|
|
263
292
|
function BelongsTo(options) {
|
|
264
293
|
return createFieldDecorator((propertyName) => ({
|
|
265
294
|
kind: RelationKinds.BelongsTo,
|
|
@@ -317,54 +346,1436 @@ var toOperand = (val) => {
|
|
|
317
346
|
if (typeof val === "string" || typeof val === "number" || typeof val === "boolean") {
|
|
318
347
|
return { type: "Literal", value: val };
|
|
319
348
|
}
|
|
320
|
-
return toNode(val);
|
|
349
|
+
return toNode(val);
|
|
350
|
+
};
|
|
351
|
+
var columnOperand = (col) => toNode(col);
|
|
352
|
+
var createBinaryExpression = (operator, left, right, escape) => {
|
|
353
|
+
const node = {
|
|
354
|
+
type: "BinaryExpression",
|
|
355
|
+
left: toNode(left),
|
|
356
|
+
operator,
|
|
357
|
+
right: toOperand(right)
|
|
358
|
+
};
|
|
359
|
+
if (escape !== void 0) {
|
|
360
|
+
node.escape = toLiteralNode(escape);
|
|
361
|
+
}
|
|
362
|
+
return node;
|
|
363
|
+
};
|
|
364
|
+
var eq = (left, right) => createBinaryExpression("=", left, right);
|
|
365
|
+
var and = (...operands) => ({
|
|
366
|
+
type: "LogicalExpression",
|
|
367
|
+
operator: "AND",
|
|
368
|
+
operands
|
|
369
|
+
});
|
|
370
|
+
var createInExpression = (operator, left, values) => ({
|
|
371
|
+
type: "InExpression",
|
|
372
|
+
left: toNode(left),
|
|
373
|
+
operator,
|
|
374
|
+
right: values.map((v) => toOperand(v))
|
|
375
|
+
});
|
|
376
|
+
var inList = (left, values) => createInExpression("IN", left, values);
|
|
377
|
+
var exists = (subquery) => ({
|
|
378
|
+
type: "ExistsExpression",
|
|
379
|
+
operator: "EXISTS",
|
|
380
|
+
subquery
|
|
381
|
+
});
|
|
382
|
+
var notExists = (subquery) => ({
|
|
383
|
+
type: "ExistsExpression",
|
|
384
|
+
operator: "NOT EXISTS",
|
|
385
|
+
subquery
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
// src/core/ast/aggregate-functions.ts
|
|
389
|
+
var buildAggregate = (name) => (col) => ({
|
|
390
|
+
type: "Function",
|
|
391
|
+
name,
|
|
392
|
+
args: [columnOperand(col)]
|
|
393
|
+
});
|
|
394
|
+
var count = buildAggregate("COUNT");
|
|
395
|
+
var sum = buildAggregate("SUM");
|
|
396
|
+
var avg = buildAggregate("AVG");
|
|
397
|
+
|
|
398
|
+
// src/core/functions/standard-strategy.ts
|
|
399
|
+
var StandardFunctionStrategy = class {
|
|
400
|
+
constructor() {
|
|
401
|
+
this.renderers = /* @__PURE__ */ new Map();
|
|
402
|
+
this.registerStandard();
|
|
403
|
+
}
|
|
404
|
+
registerStandard() {
|
|
405
|
+
this.add("ABS", ({ compiledArgs }) => `ABS(${compiledArgs[0]})`);
|
|
406
|
+
this.add("UPPER", ({ compiledArgs }) => `UPPER(${compiledArgs[0]})`);
|
|
407
|
+
this.add("LOWER", ({ compiledArgs }) => `LOWER(${compiledArgs[0]})`);
|
|
408
|
+
this.add("LENGTH", ({ compiledArgs }) => `LENGTH(${compiledArgs[0]})`);
|
|
409
|
+
this.add("TRIM", ({ compiledArgs }) => `TRIM(${compiledArgs[0]})`);
|
|
410
|
+
this.add("LTRIM", ({ compiledArgs }) => `LTRIM(${compiledArgs[0]})`);
|
|
411
|
+
this.add("RTRIM", ({ compiledArgs }) => `RTRIM(${compiledArgs[0]})`);
|
|
412
|
+
this.add("SUBSTRING", ({ compiledArgs }) => `SUBSTRING(${compiledArgs.join(", ")})`);
|
|
413
|
+
this.add("CONCAT", ({ compiledArgs }) => `CONCAT(${compiledArgs.join(", ")})`);
|
|
414
|
+
this.add("NOW", () => `NOW()`);
|
|
415
|
+
this.add("CURRENT_DATE", () => `CURRENT_DATE`);
|
|
416
|
+
this.add("CURRENT_TIME", () => `CURRENT_TIME`);
|
|
417
|
+
this.add("EXTRACT", ({ compiledArgs }) => `EXTRACT(${compiledArgs[0]} FROM ${compiledArgs[1]})`);
|
|
418
|
+
this.add("YEAR", ({ compiledArgs }) => `EXTRACT(YEAR FROM ${compiledArgs[0]})`);
|
|
419
|
+
this.add("MONTH", ({ compiledArgs }) => `EXTRACT(MONTH FROM ${compiledArgs[0]})`);
|
|
420
|
+
this.add("DAY", ({ compiledArgs }) => `EXTRACT(DAY FROM ${compiledArgs[0]})`);
|
|
421
|
+
this.add("DATE_ADD", ({ compiledArgs }) => `(${compiledArgs[0]} + INTERVAL ${compiledArgs[1]} ${compiledArgs[2]})`);
|
|
422
|
+
this.add("DATE_SUB", ({ compiledArgs }) => `(${compiledArgs[0]} - INTERVAL ${compiledArgs[1]} ${compiledArgs[2]})`);
|
|
423
|
+
this.add("DATE_DIFF", ({ compiledArgs }) => `DATEDIFF(${compiledArgs[0]}, ${compiledArgs[1]})`);
|
|
424
|
+
this.add("DATE_FORMAT", ({ compiledArgs }) => `DATE_FORMAT(${compiledArgs[0]}, ${compiledArgs[1]})`);
|
|
425
|
+
this.add("UNIX_TIMESTAMP", () => `UNIX_TIMESTAMP()`);
|
|
426
|
+
this.add("FROM_UNIXTIME", ({ compiledArgs }) => `FROM_UNIXTIME(${compiledArgs[0]})`);
|
|
427
|
+
this.add("END_OF_MONTH", ({ compiledArgs }) => `LAST_DAY(${compiledArgs[0]})`);
|
|
428
|
+
this.add("DAY_OF_WEEK", ({ compiledArgs }) => `DAYOFWEEK(${compiledArgs[0]})`);
|
|
429
|
+
this.add("WEEK_OF_YEAR", ({ compiledArgs }) => `WEEKOFYEAR(${compiledArgs[0]})`);
|
|
430
|
+
this.add("DATE_TRUNC", ({ compiledArgs }) => `DATE_TRUNC(${compiledArgs[0]}, ${compiledArgs[1]})`);
|
|
431
|
+
}
|
|
432
|
+
add(name, renderer) {
|
|
433
|
+
this.renderers.set(name, renderer);
|
|
434
|
+
}
|
|
435
|
+
getRenderer(name) {
|
|
436
|
+
return this.renderers.get(name);
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
// src/core/dialect/abstract.ts
|
|
441
|
+
var Dialect = class _Dialect {
|
|
442
|
+
/**
|
|
443
|
+
* Compiles a SELECT query AST to SQL
|
|
444
|
+
* @param ast - Query AST to compile
|
|
445
|
+
* @returns Compiled query with SQL and parameters
|
|
446
|
+
*/
|
|
447
|
+
compileSelect(ast) {
|
|
448
|
+
const ctx = this.createCompilerContext();
|
|
449
|
+
const normalized = this.normalizeSelectAst(ast);
|
|
450
|
+
const rawSql = this.compileSelectAst(normalized, ctx).trim();
|
|
451
|
+
const sql = rawSql.endsWith(";") ? rawSql : `${rawSql};`;
|
|
452
|
+
return {
|
|
453
|
+
sql,
|
|
454
|
+
params: [...ctx.params]
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
compileInsert(ast) {
|
|
458
|
+
const ctx = this.createCompilerContext();
|
|
459
|
+
const rawSql = this.compileInsertAst(ast, ctx).trim();
|
|
460
|
+
const sql = rawSql.endsWith(";") ? rawSql : `${rawSql};`;
|
|
461
|
+
return {
|
|
462
|
+
sql,
|
|
463
|
+
params: [...ctx.params]
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
compileUpdate(ast) {
|
|
467
|
+
const ctx = this.createCompilerContext();
|
|
468
|
+
const rawSql = this.compileUpdateAst(ast, ctx).trim();
|
|
469
|
+
const sql = rawSql.endsWith(";") ? rawSql : `${rawSql};`;
|
|
470
|
+
return {
|
|
471
|
+
sql,
|
|
472
|
+
params: [...ctx.params]
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
compileDelete(ast) {
|
|
476
|
+
const ctx = this.createCompilerContext();
|
|
477
|
+
const rawSql = this.compileDeleteAst(ast, ctx).trim();
|
|
478
|
+
const sql = rawSql.endsWith(";") ? rawSql : `${rawSql};`;
|
|
479
|
+
return {
|
|
480
|
+
sql,
|
|
481
|
+
params: [...ctx.params]
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
supportsReturning() {
|
|
485
|
+
return false;
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Compiles a WHERE clause
|
|
489
|
+
* @param where - WHERE expression
|
|
490
|
+
* @param ctx - Compiler context
|
|
491
|
+
* @returns SQL WHERE clause or empty string
|
|
492
|
+
*/
|
|
493
|
+
compileWhere(where, ctx) {
|
|
494
|
+
if (!where) return "";
|
|
495
|
+
return ` WHERE ${this.compileExpression(where, ctx)}`;
|
|
496
|
+
}
|
|
497
|
+
compileReturning(returning, ctx) {
|
|
498
|
+
if (!returning || returning.length === 0) return "";
|
|
499
|
+
throw new Error("RETURNING is not supported by this dialect.");
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Generates subquery for EXISTS expressions
|
|
503
|
+
* Rule: Always forces SELECT 1, ignoring column list
|
|
504
|
+
* Maintains FROM, JOINs, WHERE, GROUP BY, ORDER BY, LIMIT/OFFSET
|
|
505
|
+
* Does not add ';' at the end
|
|
506
|
+
* @param ast - Query AST
|
|
507
|
+
* @param ctx - Compiler context
|
|
508
|
+
* @returns SQL for EXISTS subquery
|
|
509
|
+
*/
|
|
510
|
+
compileSelectForExists(ast, ctx) {
|
|
511
|
+
const normalized = this.normalizeSelectAst(ast);
|
|
512
|
+
const full = this.compileSelectAst(normalized, ctx).trim().replace(/;$/, "");
|
|
513
|
+
if (normalized.setOps && normalized.setOps.length > 0) {
|
|
514
|
+
return `SELECT 1 FROM (${full}) AS _exists`;
|
|
515
|
+
}
|
|
516
|
+
const upper = full.toUpperCase();
|
|
517
|
+
const fromIndex = upper.indexOf(" FROM ");
|
|
518
|
+
if (fromIndex === -1) {
|
|
519
|
+
return full;
|
|
520
|
+
}
|
|
521
|
+
const tail = full.slice(fromIndex);
|
|
522
|
+
return `SELECT 1${tail}`;
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Creates a new compiler context
|
|
526
|
+
* @returns Compiler context with parameter management
|
|
527
|
+
*/
|
|
528
|
+
createCompilerContext() {
|
|
529
|
+
const params = [];
|
|
530
|
+
let counter = 0;
|
|
531
|
+
return {
|
|
532
|
+
params,
|
|
533
|
+
addParameter: (value) => {
|
|
534
|
+
counter += 1;
|
|
535
|
+
params.push(value);
|
|
536
|
+
return this.formatPlaceholder(counter);
|
|
537
|
+
}
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Formats a parameter placeholder
|
|
542
|
+
* @param index - Parameter index
|
|
543
|
+
* @returns Formatted placeholder string
|
|
544
|
+
*/
|
|
545
|
+
formatPlaceholder(index) {
|
|
546
|
+
return "?";
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Whether the current dialect supports a given set operation.
|
|
550
|
+
* Override in concrete dialects to restrict support.
|
|
551
|
+
*/
|
|
552
|
+
supportsSetOperation(kind) {
|
|
553
|
+
return true;
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Validates set-operation semantics:
|
|
557
|
+
* - Ensures the dialect supports requested operators.
|
|
558
|
+
* - Enforces that only the outermost compound query may have ORDER/LIMIT/OFFSET.
|
|
559
|
+
* @param ast - Query to validate
|
|
560
|
+
* @param isOutermost - Whether this node is the outermost compound query
|
|
561
|
+
*/
|
|
562
|
+
validateSetOperations(ast, isOutermost = true) {
|
|
563
|
+
const hasSetOps = !!(ast.setOps && ast.setOps.length);
|
|
564
|
+
if (!isOutermost && (ast.orderBy || ast.limit !== void 0 || ast.offset !== void 0)) {
|
|
565
|
+
throw new Error("ORDER BY / LIMIT / OFFSET are only allowed on the outermost compound query.");
|
|
566
|
+
}
|
|
567
|
+
if (hasSetOps) {
|
|
568
|
+
for (const op of ast.setOps) {
|
|
569
|
+
if (!this.supportsSetOperation(op.operator)) {
|
|
570
|
+
throw new Error(`Set operation ${op.operator} is not supported by this dialect.`);
|
|
571
|
+
}
|
|
572
|
+
this.validateSetOperations(op.query, false);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Hoists CTEs from set-operation operands to the outermost query so WITH appears once.
|
|
578
|
+
* @param ast - Query AST
|
|
579
|
+
* @returns Normalized AST without inner CTEs and a list of hoisted CTEs
|
|
580
|
+
*/
|
|
581
|
+
hoistCtes(ast) {
|
|
582
|
+
let hoisted = [];
|
|
583
|
+
const normalizedSetOps = ast.setOps?.map((op) => {
|
|
584
|
+
const { normalized: child, hoistedCtes: childHoisted } = this.hoistCtes(op.query);
|
|
585
|
+
const childCtes = child.ctes ?? [];
|
|
586
|
+
if (childCtes.length) {
|
|
587
|
+
hoisted = hoisted.concat(childCtes);
|
|
588
|
+
}
|
|
589
|
+
hoisted = hoisted.concat(childHoisted);
|
|
590
|
+
const queryWithoutCtes = childCtes.length ? { ...child, ctes: void 0 } : child;
|
|
591
|
+
return { ...op, query: queryWithoutCtes };
|
|
592
|
+
});
|
|
593
|
+
const normalized = normalizedSetOps ? { ...ast, setOps: normalizedSetOps } : ast;
|
|
594
|
+
return { normalized, hoistedCtes: hoisted };
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Normalizes a SELECT AST before compilation (validation + CTE hoisting).
|
|
598
|
+
* @param ast - Query AST
|
|
599
|
+
* @returns Normalized query AST
|
|
600
|
+
*/
|
|
601
|
+
normalizeSelectAst(ast) {
|
|
602
|
+
this.validateSetOperations(ast, true);
|
|
603
|
+
const { normalized, hoistedCtes } = this.hoistCtes(ast);
|
|
604
|
+
const combinedCtes = [...normalized.ctes ?? [], ...hoistedCtes];
|
|
605
|
+
return combinedCtes.length ? { ...normalized, ctes: combinedCtes } : normalized;
|
|
606
|
+
}
|
|
607
|
+
constructor(functionStrategy) {
|
|
608
|
+
this.expressionCompilers = /* @__PURE__ */ new Map();
|
|
609
|
+
this.operandCompilers = /* @__PURE__ */ new Map();
|
|
610
|
+
this.functionStrategy = functionStrategy || new StandardFunctionStrategy();
|
|
611
|
+
this.registerDefaultOperandCompilers();
|
|
612
|
+
this.registerDefaultExpressionCompilers();
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Creates a new Dialect instance (for testing purposes)
|
|
616
|
+
* @param functionStrategy - Optional function strategy
|
|
617
|
+
* @returns New Dialect instance
|
|
618
|
+
*/
|
|
619
|
+
static create(functionStrategy) {
|
|
620
|
+
class TestDialect extends _Dialect {
|
|
621
|
+
constructor() {
|
|
622
|
+
super(...arguments);
|
|
623
|
+
this.dialect = "sqlite";
|
|
624
|
+
}
|
|
625
|
+
quoteIdentifier(id) {
|
|
626
|
+
return `"${id}"`;
|
|
627
|
+
}
|
|
628
|
+
compileSelectAst() {
|
|
629
|
+
throw new Error("Not implemented");
|
|
630
|
+
}
|
|
631
|
+
compileInsertAst() {
|
|
632
|
+
throw new Error("Not implemented");
|
|
633
|
+
}
|
|
634
|
+
compileUpdateAst() {
|
|
635
|
+
throw new Error("Not implemented");
|
|
636
|
+
}
|
|
637
|
+
compileDeleteAst() {
|
|
638
|
+
throw new Error("Not implemented");
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
return new TestDialect(functionStrategy);
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Registers an expression compiler for a specific node type
|
|
645
|
+
* @param type - Expression node type
|
|
646
|
+
* @param compiler - Compiler function
|
|
647
|
+
*/
|
|
648
|
+
registerExpressionCompiler(type, compiler) {
|
|
649
|
+
this.expressionCompilers.set(type, compiler);
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Registers an operand compiler for a specific node type
|
|
653
|
+
* @param type - Operand node type
|
|
654
|
+
* @param compiler - Compiler function
|
|
655
|
+
*/
|
|
656
|
+
registerOperandCompiler(type, compiler) {
|
|
657
|
+
this.operandCompilers.set(type, compiler);
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Compiles an expression node
|
|
661
|
+
* @param node - Expression node to compile
|
|
662
|
+
* @param ctx - Compiler context
|
|
663
|
+
* @returns Compiled SQL expression
|
|
664
|
+
*/
|
|
665
|
+
compileExpression(node, ctx) {
|
|
666
|
+
const compiler = this.expressionCompilers.get(node.type);
|
|
667
|
+
if (!compiler) {
|
|
668
|
+
throw new Error(`Unsupported expression node type "${node.type}" for ${this.constructor.name}`);
|
|
669
|
+
}
|
|
670
|
+
return compiler(node, ctx);
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Compiles an operand node
|
|
674
|
+
* @param node - Operand node to compile
|
|
675
|
+
* @param ctx - Compiler context
|
|
676
|
+
* @returns Compiled SQL operand
|
|
677
|
+
*/
|
|
678
|
+
compileOperand(node, ctx) {
|
|
679
|
+
const compiler = this.operandCompilers.get(node.type);
|
|
680
|
+
if (!compiler) {
|
|
681
|
+
throw new Error(`Unsupported operand node type "${node.type}" for ${this.constructor.name}`);
|
|
682
|
+
}
|
|
683
|
+
return compiler(node, ctx);
|
|
684
|
+
}
|
|
685
|
+
registerDefaultExpressionCompilers() {
|
|
686
|
+
this.registerExpressionCompiler("BinaryExpression", (binary, ctx) => {
|
|
687
|
+
const left = this.compileOperand(binary.left, ctx);
|
|
688
|
+
const right = this.compileOperand(binary.right, ctx);
|
|
689
|
+
const base = `${left} ${binary.operator} ${right}`;
|
|
690
|
+
if (binary.escape) {
|
|
691
|
+
const escapeOperand = this.compileOperand(binary.escape, ctx);
|
|
692
|
+
return `${base} ESCAPE ${escapeOperand}`;
|
|
693
|
+
}
|
|
694
|
+
return base;
|
|
695
|
+
});
|
|
696
|
+
this.registerExpressionCompiler("LogicalExpression", (logical, ctx) => {
|
|
697
|
+
if (logical.operands.length === 0) return "";
|
|
698
|
+
const parts = logical.operands.map((op) => {
|
|
699
|
+
const compiled = this.compileExpression(op, ctx);
|
|
700
|
+
return op.type === "LogicalExpression" ? `(${compiled})` : compiled;
|
|
701
|
+
});
|
|
702
|
+
return parts.join(` ${logical.operator} `);
|
|
703
|
+
});
|
|
704
|
+
this.registerExpressionCompiler("NullExpression", (nullExpr, ctx) => {
|
|
705
|
+
const left = this.compileOperand(nullExpr.left, ctx);
|
|
706
|
+
return `${left} ${nullExpr.operator}`;
|
|
707
|
+
});
|
|
708
|
+
this.registerExpressionCompiler("InExpression", (inExpr, ctx) => {
|
|
709
|
+
const left = this.compileOperand(inExpr.left, ctx);
|
|
710
|
+
const values = inExpr.right.map((v) => this.compileOperand(v, ctx)).join(", ");
|
|
711
|
+
return `${left} ${inExpr.operator} (${values})`;
|
|
712
|
+
});
|
|
713
|
+
this.registerExpressionCompiler("ExistsExpression", (existsExpr, ctx) => {
|
|
714
|
+
const subquerySql = this.compileSelectForExists(existsExpr.subquery, ctx);
|
|
715
|
+
return `${existsExpr.operator} (${subquerySql})`;
|
|
716
|
+
});
|
|
717
|
+
this.registerExpressionCompiler("BetweenExpression", (betweenExpr, ctx) => {
|
|
718
|
+
const left = this.compileOperand(betweenExpr.left, ctx);
|
|
719
|
+
const lower = this.compileOperand(betweenExpr.lower, ctx);
|
|
720
|
+
const upper = this.compileOperand(betweenExpr.upper, ctx);
|
|
721
|
+
return `${left} ${betweenExpr.operator} ${lower} AND ${upper}`;
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
registerDefaultOperandCompilers() {
|
|
725
|
+
this.registerOperandCompiler("Literal", (literal, ctx) => ctx.addParameter(literal.value));
|
|
726
|
+
this.registerOperandCompiler("Column", (column, _ctx) => {
|
|
727
|
+
return `${this.quoteIdentifier(column.table)}.${this.quoteIdentifier(column.name)}`;
|
|
728
|
+
});
|
|
729
|
+
this.registerOperandCompiler(
|
|
730
|
+
"Function",
|
|
731
|
+
(fnNode, ctx) => this.compileFunctionOperand(fnNode, ctx)
|
|
732
|
+
);
|
|
733
|
+
this.registerOperandCompiler("JsonPath", (path, _ctx) => this.compileJsonPath(path));
|
|
734
|
+
this.registerOperandCompiler("ScalarSubquery", (node, ctx) => {
|
|
735
|
+
const sql = this.compileSelectAst(node.query, ctx).trim().replace(/;$/, "");
|
|
736
|
+
return `(${sql})`;
|
|
737
|
+
});
|
|
738
|
+
this.registerOperandCompiler("CaseExpression", (node, ctx) => {
|
|
739
|
+
const parts = ["CASE"];
|
|
740
|
+
for (const { when, then } of node.conditions) {
|
|
741
|
+
parts.push(`WHEN ${this.compileExpression(when, ctx)} THEN ${this.compileOperand(then, ctx)}`);
|
|
742
|
+
}
|
|
743
|
+
if (node.else) {
|
|
744
|
+
parts.push(`ELSE ${this.compileOperand(node.else, ctx)}`);
|
|
745
|
+
}
|
|
746
|
+
parts.push("END");
|
|
747
|
+
return parts.join(" ");
|
|
748
|
+
});
|
|
749
|
+
this.registerOperandCompiler("WindowFunction", (node, ctx) => {
|
|
750
|
+
let result = `${node.name}(`;
|
|
751
|
+
if (node.args.length > 0) {
|
|
752
|
+
result += node.args.map((arg) => this.compileOperand(arg, ctx)).join(", ");
|
|
753
|
+
}
|
|
754
|
+
result += ") OVER (";
|
|
755
|
+
const parts = [];
|
|
756
|
+
if (node.partitionBy && node.partitionBy.length > 0) {
|
|
757
|
+
const partitionClause = "PARTITION BY " + node.partitionBy.map(
|
|
758
|
+
(col) => `${this.quoteIdentifier(col.table)}.${this.quoteIdentifier(col.name)}`
|
|
759
|
+
).join(", ");
|
|
760
|
+
parts.push(partitionClause);
|
|
761
|
+
}
|
|
762
|
+
if (node.orderBy && node.orderBy.length > 0) {
|
|
763
|
+
const orderClause = "ORDER BY " + node.orderBy.map(
|
|
764
|
+
(o) => `${this.quoteIdentifier(o.column.table)}.${this.quoteIdentifier(o.column.name)} ${o.direction}`
|
|
765
|
+
).join(", ");
|
|
766
|
+
parts.push(orderClause);
|
|
767
|
+
}
|
|
768
|
+
result += parts.join(" ");
|
|
769
|
+
result += ")";
|
|
770
|
+
return result;
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
// Default fallback, should be overridden by dialects if supported
|
|
774
|
+
compileJsonPath(node) {
|
|
775
|
+
throw new Error("JSON Path not supported by this dialect");
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* Compiles a function operand, using the dialect's function strategy.
|
|
779
|
+
*/
|
|
780
|
+
compileFunctionOperand(fnNode, ctx) {
|
|
781
|
+
const compiledArgs = fnNode.args.map((arg) => this.compileOperand(arg, ctx));
|
|
782
|
+
const renderer = this.functionStrategy.getRenderer(fnNode.name);
|
|
783
|
+
if (renderer) {
|
|
784
|
+
return renderer({ node: fnNode, compiledArgs });
|
|
785
|
+
}
|
|
786
|
+
return `${fnNode.name}(${compiledArgs.join(", ")})`;
|
|
787
|
+
}
|
|
788
|
+
};
|
|
789
|
+
|
|
790
|
+
// src/core/dialect/base/function-table-formatter.ts
|
|
791
|
+
var FunctionTableFormatter = class {
|
|
792
|
+
/**
|
|
793
|
+
* Formats a function table node into SQL syntax.
|
|
794
|
+
* @param fn - The function table node containing schema, name, args, and aliases.
|
|
795
|
+
* @param ctx - Optional compiler context for operand compilation.
|
|
796
|
+
* @param dialect - The dialect instance for compiling operands.
|
|
797
|
+
* @returns SQL function table expression (e.g., "LATERAL schema.func(args) WITH ORDINALITY AS alias(col1, col2)").
|
|
798
|
+
*/
|
|
799
|
+
static format(fn, ctx, dialect) {
|
|
800
|
+
const schemaPart = this.formatSchema(fn, dialect);
|
|
801
|
+
const args = this.formatArgs(fn, ctx, dialect);
|
|
802
|
+
const base = this.formatBase(fn, schemaPart, args, dialect);
|
|
803
|
+
const lateral = this.formatLateral(fn);
|
|
804
|
+
const alias = this.formatAlias(fn, dialect);
|
|
805
|
+
const colAliases = this.formatColumnAliases(fn, dialect);
|
|
806
|
+
return `${lateral}${base}${alias}${colAliases}`;
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* Formats the schema prefix for the function name.
|
|
810
|
+
* @param fn - The function table node.
|
|
811
|
+
* @param dialect - The dialect instance for quoting identifiers.
|
|
812
|
+
* @returns Schema prefix (e.g., "schema.") or empty string.
|
|
813
|
+
* @internal
|
|
814
|
+
*/
|
|
815
|
+
static formatSchema(fn, dialect) {
|
|
816
|
+
if (!fn.schema) return "";
|
|
817
|
+
const quoted = dialect ? dialect.quoteIdentifier(fn.schema) : fn.schema;
|
|
818
|
+
return `${quoted}.`;
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* Formats function arguments into SQL syntax.
|
|
822
|
+
* @param fn - The function table node containing arguments.
|
|
823
|
+
* @param ctx - Optional compiler context for operand compilation.
|
|
824
|
+
* @param dialect - The dialect instance for compiling operands.
|
|
825
|
+
* @returns Comma-separated function arguments.
|
|
826
|
+
* @internal
|
|
827
|
+
*/
|
|
828
|
+
static formatArgs(fn, ctx, dialect) {
|
|
829
|
+
return (fn.args || []).map((a) => {
|
|
830
|
+
if (ctx && dialect) {
|
|
831
|
+
return dialect.compileOperand(a, ctx);
|
|
832
|
+
}
|
|
833
|
+
return String(a);
|
|
834
|
+
}).join(", ");
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Formats the base function call with WITH ORDINALITY if present.
|
|
838
|
+
* @param fn - The function table node.
|
|
839
|
+
* @param schemaPart - Formatted schema prefix.
|
|
840
|
+
* @param args - Formatted function arguments.
|
|
841
|
+
* @param dialect - The dialect instance for quoting identifiers.
|
|
842
|
+
* @returns Base function call expression (e.g., "schema.func(args) WITH ORDINALITY").
|
|
843
|
+
* @internal
|
|
844
|
+
*/
|
|
845
|
+
static formatBase(fn, schemaPart, args, dialect) {
|
|
846
|
+
const ordinality = fn.withOrdinality ? " WITH ORDINALITY" : "";
|
|
847
|
+
const quoted = dialect ? dialect.quoteIdentifier(fn.name) : fn.name;
|
|
848
|
+
return `${schemaPart}${quoted}(${args})${ordinality}`;
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* Formats the LATERAL keyword if present.
|
|
852
|
+
* @param fn - The function table node.
|
|
853
|
+
* @returns "LATERAL " or empty string.
|
|
854
|
+
* @internal
|
|
855
|
+
*/
|
|
856
|
+
static formatLateral(fn) {
|
|
857
|
+
return fn.lateral ? "LATERAL " : "";
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* Formats the table alias for the function table.
|
|
861
|
+
* @param fn - The function table node.
|
|
862
|
+
* @param dialect - The dialect instance for quoting identifiers.
|
|
863
|
+
* @returns " AS alias" or empty string.
|
|
864
|
+
* @internal
|
|
865
|
+
*/
|
|
866
|
+
static formatAlias(fn, dialect) {
|
|
867
|
+
if (!fn.alias) return "";
|
|
868
|
+
const quoted = dialect ? dialect.quoteIdentifier(fn.alias) : fn.alias;
|
|
869
|
+
return ` AS ${quoted}`;
|
|
870
|
+
}
|
|
871
|
+
/**
|
|
872
|
+
* Formats column aliases for the function table result columns.
|
|
873
|
+
* @param fn - The function table node containing column aliases.
|
|
874
|
+
* @param dialect - The dialect instance for quoting identifiers.
|
|
875
|
+
* @returns "(col1, col2, ...)" or empty string.
|
|
876
|
+
* @internal
|
|
877
|
+
*/
|
|
878
|
+
static formatColumnAliases(fn, dialect) {
|
|
879
|
+
if (!fn.columnAliases || !fn.columnAliases.length) return "";
|
|
880
|
+
const aliases = fn.columnAliases.map((col) => dialect ? dialect.quoteIdentifier(col) : col).join(", ");
|
|
881
|
+
return `(${aliases})`;
|
|
882
|
+
}
|
|
883
|
+
};
|
|
884
|
+
|
|
885
|
+
// src/core/dialect/base/pagination-strategy.ts
|
|
886
|
+
var StandardLimitOffsetPagination = class {
|
|
887
|
+
/**
|
|
888
|
+
* Compiles LIMIT/OFFSET pagination clause.
|
|
889
|
+
* @param limit - The maximum number of rows to return.
|
|
890
|
+
* @param offset - The number of rows to skip.
|
|
891
|
+
* @returns SQL pagination clause with LIMIT and/or OFFSET.
|
|
892
|
+
*/
|
|
893
|
+
compilePagination(limit, offset) {
|
|
894
|
+
const parts = [];
|
|
895
|
+
if (limit !== void 0) parts.push(`LIMIT ${limit}`);
|
|
896
|
+
if (offset !== void 0) parts.push(`OFFSET ${offset}`);
|
|
897
|
+
return parts.length ? ` ${parts.join(" ")}` : "";
|
|
898
|
+
}
|
|
899
|
+
};
|
|
900
|
+
|
|
901
|
+
// src/core/dialect/base/cte-compiler.ts
|
|
902
|
+
var CteCompiler = class {
|
|
903
|
+
/**
|
|
904
|
+
* Compiles CTEs (WITH clauses) including recursive CTEs.
|
|
905
|
+
* @param ast - The SELECT query AST containing CTE definitions.
|
|
906
|
+
* @param ctx - The compiler context for expression compilation.
|
|
907
|
+
* @param quoteIdentifier - Function to quote identifiers according to dialect rules.
|
|
908
|
+
* @param compileSelectAst - Function to recursively compile SELECT query ASTs.
|
|
909
|
+
* @param normalizeSelectAst - Function to normalize SELECT query ASTs before compilation.
|
|
910
|
+
* @param stripTrailingSemicolon - Function to remove trailing semicolons from SQL.
|
|
911
|
+
* @returns SQL WITH clause string (e.g., "WITH cte_name AS (...) ") or empty string if no CTEs.
|
|
912
|
+
*/
|
|
913
|
+
static compileCtes(ast, ctx, quoteIdentifier, compileSelectAst, normalizeSelectAst, stripTrailingSemicolon) {
|
|
914
|
+
if (!ast.ctes || ast.ctes.length === 0) return "";
|
|
915
|
+
const hasRecursive = ast.ctes.some((cte) => cte.recursive);
|
|
916
|
+
const prefix = hasRecursive ? "WITH RECURSIVE " : "WITH ";
|
|
917
|
+
const cteDefs = ast.ctes.map((cte) => {
|
|
918
|
+
const name = quoteIdentifier(cte.name);
|
|
919
|
+
const cols = cte.columns && cte.columns.length ? `(${cte.columns.map((c) => quoteIdentifier(c)).join(", ")})` : "";
|
|
920
|
+
const query = stripTrailingSemicolon(compileSelectAst(normalizeSelectAst(cte.query), ctx));
|
|
921
|
+
return `${name}${cols} AS (${query})`;
|
|
922
|
+
}).join(", ");
|
|
923
|
+
return `${prefix}${cteDefs} `;
|
|
924
|
+
}
|
|
925
|
+
};
|
|
926
|
+
|
|
927
|
+
// src/core/dialect/base/returning-strategy.ts
|
|
928
|
+
var NoReturningStrategy = class {
|
|
929
|
+
/**
|
|
930
|
+
* Throws an error as RETURNING is not supported.
|
|
931
|
+
* @param returning - Columns to return (causes error if non-empty).
|
|
932
|
+
* @param _ctx - Compiler context (unused).
|
|
933
|
+
* @throws Error indicating RETURNING is not supported.
|
|
934
|
+
*/
|
|
935
|
+
compileReturning(returning, _ctx) {
|
|
936
|
+
if (!returning || returning.length === 0) return "";
|
|
937
|
+
throw new Error("RETURNING is not supported by this dialect.");
|
|
938
|
+
}
|
|
939
|
+
/**
|
|
940
|
+
* Formats column names for RETURNING clause.
|
|
941
|
+
* @param returning - Columns to format.
|
|
942
|
+
* @param quoteIdentifier - Function to quote identifiers according to dialect rules.
|
|
943
|
+
* @returns Simple comma-separated column names.
|
|
944
|
+
*/
|
|
945
|
+
formatReturningColumns(returning, quoteIdentifier) {
|
|
946
|
+
return returning.map((column) => {
|
|
947
|
+
const tablePart = column.table ? `${quoteIdentifier(column.table)}.` : "";
|
|
948
|
+
const aliasPart = column.alias ? ` AS ${quoteIdentifier(column.alias)}` : "";
|
|
949
|
+
return `${tablePart}${quoteIdentifier(column.name)}${aliasPart}`;
|
|
950
|
+
}).join(", ");
|
|
951
|
+
}
|
|
952
|
+
};
|
|
953
|
+
|
|
954
|
+
// src/core/dialect/base/join-compiler.ts
|
|
955
|
+
var JoinCompiler = class {
|
|
956
|
+
/**
|
|
957
|
+
* Compiles all JOIN clauses from a SELECT query AST.
|
|
958
|
+
* @param ast - The SELECT query AST containing join definitions.
|
|
959
|
+
* @param ctx - The compiler context for expression compilation.
|
|
960
|
+
* @param compileFrom - Function to compile table sources (tables or subqueries).
|
|
961
|
+
* @param compileExpression - Function to compile join condition expressions.
|
|
962
|
+
* @returns SQL JOIN clauses (e.g., " LEFT JOIN table ON condition") or empty string if no joins.
|
|
963
|
+
*/
|
|
964
|
+
static compileJoins(ast, ctx, compileFrom, compileExpression) {
|
|
965
|
+
if (!ast.joins || ast.joins.length === 0) return "";
|
|
966
|
+
const parts = ast.joins.map((j) => {
|
|
967
|
+
const table = compileFrom(j.table, ctx);
|
|
968
|
+
const cond = compileExpression(j.condition, ctx);
|
|
969
|
+
return `${j.kind} JOIN ${table} ON ${cond}`;
|
|
970
|
+
});
|
|
971
|
+
return ` ${parts.join(" ")}`;
|
|
972
|
+
}
|
|
973
|
+
};
|
|
974
|
+
|
|
975
|
+
// src/core/dialect/base/groupby-compiler.ts
|
|
976
|
+
var GroupByCompiler = class {
|
|
977
|
+
/**
|
|
978
|
+
* Compiles GROUP BY clause from a SELECT query AST.
|
|
979
|
+
* @param ast - The SELECT query AST containing grouping columns.
|
|
980
|
+
* @param quoteIdentifier - Function to quote identifiers according to dialect rules.
|
|
981
|
+
* @returns SQL GROUP BY clause (e.g., " GROUP BY table.col1, table.col2") or empty string if no grouping.
|
|
982
|
+
*/
|
|
983
|
+
static compileGroupBy(ast, quoteIdentifier) {
|
|
984
|
+
if (!ast.groupBy || ast.groupBy.length === 0) return "";
|
|
985
|
+
const cols = ast.groupBy.map((c) => `${quoteIdentifier(c.table)}.${quoteIdentifier(c.name)}`).join(", ");
|
|
986
|
+
return ` GROUP BY ${cols}`;
|
|
987
|
+
}
|
|
988
|
+
};
|
|
989
|
+
|
|
990
|
+
// src/core/dialect/base/orderby-compiler.ts
|
|
991
|
+
var OrderByCompiler = class {
|
|
992
|
+
/**
|
|
993
|
+
* Compiles ORDER BY clause from a SELECT query AST.
|
|
994
|
+
* @param ast - The SELECT query AST containing sort specifications.
|
|
995
|
+
* @param quoteIdentifier - Function to quote identifiers according to dialect rules.
|
|
996
|
+
* @returns SQL ORDER BY clause (e.g., " ORDER BY table.col1 ASC, table.col2 DESC") or empty string if no ordering.
|
|
997
|
+
*/
|
|
998
|
+
static compileOrderBy(ast, quoteIdentifier) {
|
|
999
|
+
if (!ast.orderBy || ast.orderBy.length === 0) return "";
|
|
1000
|
+
const parts = ast.orderBy.map((o) => `${quoteIdentifier(o.column.table)}.${quoteIdentifier(o.column.name)} ${o.direction}`).join(", ");
|
|
1001
|
+
return ` ORDER BY ${parts}`;
|
|
1002
|
+
}
|
|
1003
|
+
};
|
|
1004
|
+
|
|
1005
|
+
// src/core/dialect/base/sql-dialect.ts
|
|
1006
|
+
var SqlDialectBase = class extends Dialect {
|
|
1007
|
+
constructor() {
|
|
1008
|
+
super(...arguments);
|
|
1009
|
+
this.paginationStrategy = new StandardLimitOffsetPagination();
|
|
1010
|
+
this.returningStrategy = new NoReturningStrategy();
|
|
1011
|
+
}
|
|
1012
|
+
compileSelectAst(ast, ctx) {
|
|
1013
|
+
const hasSetOps = !!(ast.setOps && ast.setOps.length);
|
|
1014
|
+
const ctes = CteCompiler.compileCtes(
|
|
1015
|
+
ast,
|
|
1016
|
+
ctx,
|
|
1017
|
+
this.quoteIdentifier.bind(this),
|
|
1018
|
+
this.compileSelectAst.bind(this),
|
|
1019
|
+
this.normalizeSelectAst?.bind(this) ?? ((a) => a),
|
|
1020
|
+
this.stripTrailingSemicolon.bind(this)
|
|
1021
|
+
);
|
|
1022
|
+
const baseAst = hasSetOps ? { ...ast, setOps: void 0, orderBy: void 0, limit: void 0, offset: void 0 } : ast;
|
|
1023
|
+
const baseSelect = this.compileSelectCore(baseAst, ctx);
|
|
1024
|
+
if (!hasSetOps) {
|
|
1025
|
+
return `${ctes}${baseSelect}`;
|
|
1026
|
+
}
|
|
1027
|
+
return this.compileSelectWithSetOps(ast, baseSelect, ctes, ctx);
|
|
1028
|
+
}
|
|
1029
|
+
compileSelectWithSetOps(ast, baseSelect, ctes, ctx) {
|
|
1030
|
+
const compound = ast.setOps.map((op) => `${op.operator} ${this.wrapSetOperand(this.compileSelectAst(op.query, ctx))}`).join(" ");
|
|
1031
|
+
const orderBy = OrderByCompiler.compileOrderBy(ast, this.quoteIdentifier.bind(this));
|
|
1032
|
+
const pagination = this.paginationStrategy.compilePagination(ast.limit, ast.offset);
|
|
1033
|
+
const combined = `${this.wrapSetOperand(baseSelect)} ${compound}`;
|
|
1034
|
+
return `${ctes}${combined}${orderBy}${pagination}`;
|
|
1035
|
+
}
|
|
1036
|
+
compileInsertAst(ast, ctx) {
|
|
1037
|
+
const table = this.compileTableName(ast.into);
|
|
1038
|
+
const columnList = this.compileInsertColumnList(ast.columns);
|
|
1039
|
+
const values = this.compileInsertValues(ast.values, ctx);
|
|
1040
|
+
const returning = this.compileReturning(ast.returning, ctx);
|
|
1041
|
+
return `INSERT INTO ${table} (${columnList}) VALUES ${values}${returning}`;
|
|
1042
|
+
}
|
|
1043
|
+
compileReturning(returning, ctx) {
|
|
1044
|
+
return this.returningStrategy.compileReturning(returning, ctx);
|
|
1045
|
+
}
|
|
1046
|
+
compileInsertColumnList(columns) {
|
|
1047
|
+
return columns.map((column) => `${this.quoteIdentifier(column.table)}.${this.quoteIdentifier(column.name)}`).join(", ");
|
|
1048
|
+
}
|
|
1049
|
+
compileInsertValues(values, ctx) {
|
|
1050
|
+
return values.map((row) => `(${row.map((value) => this.compileOperand(value, ctx)).join(", ")})`).join(", ");
|
|
1051
|
+
}
|
|
1052
|
+
compileSelectCore(ast, ctx) {
|
|
1053
|
+
const columns = this.compileSelectColumns(ast, ctx);
|
|
1054
|
+
const from = this.compileFrom(ast.from, ctx);
|
|
1055
|
+
const joins = JoinCompiler.compileJoins(ast, ctx, this.compileFrom.bind(this), this.compileExpression.bind(this));
|
|
1056
|
+
const whereClause = this.compileWhere(ast.where, ctx);
|
|
1057
|
+
const groupBy = GroupByCompiler.compileGroupBy(ast, this.quoteIdentifier.bind(this));
|
|
1058
|
+
const having = this.compileHaving(ast, ctx);
|
|
1059
|
+
const orderBy = OrderByCompiler.compileOrderBy(ast, this.quoteIdentifier.bind(this));
|
|
1060
|
+
const pagination = this.paginationStrategy.compilePagination(ast.limit, ast.offset);
|
|
1061
|
+
return `SELECT ${this.compileDistinct(ast)}${columns} FROM ${from}${joins}${whereClause}${groupBy}${having}${orderBy}${pagination}`;
|
|
1062
|
+
}
|
|
1063
|
+
compileUpdateAst(ast, ctx) {
|
|
1064
|
+
const table = this.compileTableName(ast.table);
|
|
1065
|
+
const assignments = this.compileUpdateAssignments(ast.set, ctx);
|
|
1066
|
+
const whereClause = this.compileWhere(ast.where, ctx);
|
|
1067
|
+
const returning = this.compileReturning(ast.returning, ctx);
|
|
1068
|
+
return `UPDATE ${table} SET ${assignments}${whereClause}${returning}`;
|
|
1069
|
+
}
|
|
1070
|
+
compileUpdateAssignments(assignments, ctx) {
|
|
1071
|
+
return assignments.map((assignment) => {
|
|
1072
|
+
const col = assignment.column;
|
|
1073
|
+
const target = `${this.quoteIdentifier(col.table)}.${this.quoteIdentifier(col.name)}`;
|
|
1074
|
+
const value = this.compileOperand(assignment.value, ctx);
|
|
1075
|
+
return `${target} = ${value}`;
|
|
1076
|
+
}).join(", ");
|
|
1077
|
+
}
|
|
1078
|
+
compileDeleteAst(ast, ctx) {
|
|
1079
|
+
const table = this.compileTableName(ast.from);
|
|
1080
|
+
const whereClause = this.compileWhere(ast.where, ctx);
|
|
1081
|
+
const returning = this.compileReturning(ast.returning, ctx);
|
|
1082
|
+
return `DELETE FROM ${table}${whereClause}${returning}`;
|
|
1083
|
+
}
|
|
1084
|
+
formatReturningColumns(returning) {
|
|
1085
|
+
return this.returningStrategy.formatReturningColumns(returning, this.quoteIdentifier.bind(this));
|
|
1086
|
+
}
|
|
1087
|
+
compileDistinct(ast) {
|
|
1088
|
+
return ast.distinct ? "DISTINCT " : "";
|
|
1089
|
+
}
|
|
1090
|
+
compileSelectColumns(ast, ctx) {
|
|
1091
|
+
return ast.columns.map((c) => {
|
|
1092
|
+
const expr = this.compileOperand(c, ctx);
|
|
1093
|
+
if (c.alias) {
|
|
1094
|
+
if (c.alias.includes("(")) return c.alias;
|
|
1095
|
+
return `${expr} AS ${this.quoteIdentifier(c.alias)}`;
|
|
1096
|
+
}
|
|
1097
|
+
return expr;
|
|
1098
|
+
}).join(", ");
|
|
1099
|
+
}
|
|
1100
|
+
compileFrom(ast, ctx) {
|
|
1101
|
+
const tableSource = ast;
|
|
1102
|
+
if (tableSource.type === "FunctionTable") {
|
|
1103
|
+
return this.compileFunctionTable(tableSource, ctx);
|
|
1104
|
+
}
|
|
1105
|
+
return this.compileTableSource(tableSource);
|
|
1106
|
+
}
|
|
1107
|
+
compileFunctionTable(fn, ctx) {
|
|
1108
|
+
return FunctionTableFormatter.format(fn, ctx, this);
|
|
1109
|
+
}
|
|
1110
|
+
compileTableSource(table) {
|
|
1111
|
+
const base = this.compileTableName(table);
|
|
1112
|
+
return table.alias ? `${base} AS ${this.quoteIdentifier(table.alias)}` : base;
|
|
1113
|
+
}
|
|
1114
|
+
compileTableName(table) {
|
|
1115
|
+
if (table.schema) {
|
|
1116
|
+
return `${this.quoteIdentifier(table.schema)}.${this.quoteIdentifier(table.name)}`;
|
|
1117
|
+
}
|
|
1118
|
+
return this.quoteIdentifier(table.name);
|
|
1119
|
+
}
|
|
1120
|
+
compileHaving(ast, ctx) {
|
|
1121
|
+
if (!ast.having) return "";
|
|
1122
|
+
return ` HAVING ${this.compileExpression(ast.having, ctx)}`;
|
|
1123
|
+
}
|
|
1124
|
+
stripTrailingSemicolon(sql) {
|
|
1125
|
+
return sql.trim().replace(/;$/, "");
|
|
1126
|
+
}
|
|
1127
|
+
wrapSetOperand(sql) {
|
|
1128
|
+
const trimmed = this.stripTrailingSemicolon(sql);
|
|
1129
|
+
return `(${trimmed})`;
|
|
1130
|
+
}
|
|
1131
|
+
};
|
|
1132
|
+
|
|
1133
|
+
// src/core/dialect/postgres/functions.ts
|
|
1134
|
+
var PostgresFunctionStrategy = class extends StandardFunctionStrategy {
|
|
1135
|
+
constructor() {
|
|
1136
|
+
super();
|
|
1137
|
+
this.registerOverrides();
|
|
1138
|
+
}
|
|
1139
|
+
registerOverrides() {
|
|
1140
|
+
this.add("UTC_NOW", () => `(NOW() AT TIME ZONE 'UTC')`);
|
|
1141
|
+
this.add("UNIX_TIMESTAMP", () => `EXTRACT(EPOCH FROM NOW())::INTEGER`);
|
|
1142
|
+
this.add("FROM_UNIXTIME", ({ compiledArgs }) => {
|
|
1143
|
+
if (compiledArgs.length !== 1) throw new Error("FROM_UNIXTIME expects 1 argument");
|
|
1144
|
+
return `to_timestamp(${compiledArgs[0]})`;
|
|
1145
|
+
});
|
|
1146
|
+
this.add("EXTRACT", ({ compiledArgs }) => {
|
|
1147
|
+
if (compiledArgs.length !== 2) throw new Error("EXTRACT expects 2 arguments (part, date)");
|
|
1148
|
+
const [part, date] = compiledArgs;
|
|
1149
|
+
const partClean = part.replace(/['"]/g, "");
|
|
1150
|
+
return `EXTRACT(${partClean} FROM ${date})`;
|
|
1151
|
+
});
|
|
1152
|
+
this.add("YEAR", ({ compiledArgs }) => {
|
|
1153
|
+
if (compiledArgs.length !== 1) throw new Error("YEAR expects 1 argument");
|
|
1154
|
+
return `EXTRACT(YEAR FROM ${compiledArgs[0]})`;
|
|
1155
|
+
});
|
|
1156
|
+
this.add("MONTH", ({ compiledArgs }) => {
|
|
1157
|
+
if (compiledArgs.length !== 1) throw new Error("MONTH expects 1 argument");
|
|
1158
|
+
return `EXTRACT(MONTH FROM ${compiledArgs[0]})`;
|
|
1159
|
+
});
|
|
1160
|
+
this.add("DAY", ({ compiledArgs }) => {
|
|
1161
|
+
if (compiledArgs.length !== 1) throw new Error("DAY expects 1 argument");
|
|
1162
|
+
return `EXTRACT(DAY FROM ${compiledArgs[0]})`;
|
|
1163
|
+
});
|
|
1164
|
+
this.add("DATE_ADD", ({ node, compiledArgs }) => {
|
|
1165
|
+
if (compiledArgs.length !== 3) throw new Error("DATE_ADD expects 3 arguments (date, interval, unit)");
|
|
1166
|
+
const [date, interval] = compiledArgs;
|
|
1167
|
+
const unitArg = node.args[2];
|
|
1168
|
+
const unitClean = String(unitArg.value).replace(/['"]/g, "").toLowerCase();
|
|
1169
|
+
return `(${date} + (${interval} || ' ${unitClean}')::INTERVAL)`;
|
|
1170
|
+
});
|
|
1171
|
+
this.add("DATE_SUB", ({ node, compiledArgs }) => {
|
|
1172
|
+
if (compiledArgs.length !== 3) throw new Error("DATE_SUB expects 3 arguments (date, interval, unit)");
|
|
1173
|
+
const [date, interval] = compiledArgs;
|
|
1174
|
+
const unitArg = node.args[2];
|
|
1175
|
+
const unitClean = String(unitArg.value).replace(/['"]/g, "").toLowerCase();
|
|
1176
|
+
return `(${date} - (${interval} || ' ${unitClean}')::INTERVAL)`;
|
|
1177
|
+
});
|
|
1178
|
+
this.add("DATE_DIFF", ({ compiledArgs }) => {
|
|
1179
|
+
if (compiledArgs.length !== 2) throw new Error("DATE_DIFF expects 2 arguments");
|
|
1180
|
+
const [date1, date2] = compiledArgs;
|
|
1181
|
+
return `(${date1}::DATE - ${date2}::DATE)`;
|
|
1182
|
+
});
|
|
1183
|
+
this.add("DATE_FORMAT", ({ compiledArgs }) => {
|
|
1184
|
+
if (compiledArgs.length !== 2) throw new Error("DATE_FORMAT expects 2 arguments");
|
|
1185
|
+
const [date, format] = compiledArgs;
|
|
1186
|
+
return `TO_CHAR(${date}, ${format})`;
|
|
1187
|
+
});
|
|
1188
|
+
this.add("END_OF_MONTH", ({ compiledArgs }) => {
|
|
1189
|
+
if (compiledArgs.length !== 1) throw new Error("END_OF_MONTH expects 1 argument");
|
|
1190
|
+
return `(date_trunc('month', ${compiledArgs[0]}) + interval '1 month' - interval '1 day')::DATE`;
|
|
1191
|
+
});
|
|
1192
|
+
this.add("DAY_OF_WEEK", ({ compiledArgs }) => {
|
|
1193
|
+
if (compiledArgs.length !== 1) throw new Error("DAY_OF_WEEK expects 1 argument");
|
|
1194
|
+
return `EXTRACT(DOW FROM ${compiledArgs[0]})`;
|
|
1195
|
+
});
|
|
1196
|
+
this.add("WEEK_OF_YEAR", ({ compiledArgs }) => {
|
|
1197
|
+
if (compiledArgs.length !== 1) throw new Error("WEEK_OF_YEAR expects 1 argument");
|
|
1198
|
+
return `EXTRACT(WEEK FROM ${compiledArgs[0]})`;
|
|
1199
|
+
});
|
|
1200
|
+
this.add("DATE_TRUNC", ({ node, compiledArgs }) => {
|
|
1201
|
+
if (compiledArgs.length !== 2) throw new Error("DATE_TRUNC expects 2 arguments (part, date)");
|
|
1202
|
+
const [, date] = compiledArgs;
|
|
1203
|
+
const partArg = node.args[0];
|
|
1204
|
+
const partClean = String(partArg.value).replace(/['"]/g, "").toLowerCase();
|
|
1205
|
+
return `DATE_TRUNC('${partClean}', ${date})`;
|
|
1206
|
+
});
|
|
1207
|
+
}
|
|
1208
|
+
};
|
|
1209
|
+
|
|
1210
|
+
// src/core/dialect/postgres/index.ts
|
|
1211
|
+
var PostgresDialect = class extends SqlDialectBase {
|
|
1212
|
+
/**
|
|
1213
|
+
* Creates a new PostgresDialect instance
|
|
1214
|
+
*/
|
|
1215
|
+
constructor() {
|
|
1216
|
+
super(new PostgresFunctionStrategy());
|
|
1217
|
+
this.dialect = "postgres";
|
|
1218
|
+
}
|
|
1219
|
+
/**
|
|
1220
|
+
* Quotes an identifier using PostgreSQL double-quote syntax
|
|
1221
|
+
* @param id - Identifier to quote
|
|
1222
|
+
* @returns Quoted identifier
|
|
1223
|
+
*/
|
|
1224
|
+
quoteIdentifier(id) {
|
|
1225
|
+
return `"${id}"`;
|
|
1226
|
+
}
|
|
1227
|
+
/**
|
|
1228
|
+
* Compiles JSON path expression using PostgreSQL syntax
|
|
1229
|
+
* @param node - JSON path node
|
|
1230
|
+
* @returns PostgreSQL JSON path expression
|
|
1231
|
+
*/
|
|
1232
|
+
compileJsonPath(node) {
|
|
1233
|
+
const col = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
|
|
1234
|
+
return `${col}->>'${node.path}'`;
|
|
1235
|
+
}
|
|
1236
|
+
compileReturning(returning, ctx) {
|
|
1237
|
+
if (!returning || returning.length === 0) return "";
|
|
1238
|
+
const columns = this.formatReturningColumns(returning);
|
|
1239
|
+
return ` RETURNING ${columns}`;
|
|
1240
|
+
}
|
|
1241
|
+
supportsReturning() {
|
|
1242
|
+
return true;
|
|
1243
|
+
}
|
|
1244
|
+
};
|
|
1245
|
+
|
|
1246
|
+
// src/core/dialect/mysql/functions.ts
|
|
1247
|
+
var MysqlFunctionStrategy = class extends StandardFunctionStrategy {
|
|
1248
|
+
constructor() {
|
|
1249
|
+
super();
|
|
1250
|
+
this.registerOverrides();
|
|
1251
|
+
}
|
|
1252
|
+
registerOverrides() {
|
|
1253
|
+
this.add("NOW", () => `NOW()`);
|
|
1254
|
+
this.add("CURRENT_DATE", () => `CURDATE()`);
|
|
1255
|
+
this.add("CURRENT_TIME", () => `CURTIME()`);
|
|
1256
|
+
this.add("UTC_NOW", () => `UTC_TIMESTAMP()`);
|
|
1257
|
+
this.add("EXTRACT", ({ compiledArgs }) => {
|
|
1258
|
+
if (compiledArgs.length !== 2) throw new Error("EXTRACT expects 2 arguments (part, date)");
|
|
1259
|
+
const [part, date] = compiledArgs;
|
|
1260
|
+
const partClean = part.replace(/['"]/g, "");
|
|
1261
|
+
return `EXTRACT(${partClean} FROM ${date})`;
|
|
1262
|
+
});
|
|
1263
|
+
this.add("YEAR", ({ compiledArgs }) => {
|
|
1264
|
+
if (compiledArgs.length !== 1) throw new Error("YEAR expects 1 argument");
|
|
1265
|
+
return `YEAR(${compiledArgs[0]})`;
|
|
1266
|
+
});
|
|
1267
|
+
this.add("MONTH", ({ compiledArgs }) => {
|
|
1268
|
+
if (compiledArgs.length !== 1) throw new Error("MONTH expects 1 argument");
|
|
1269
|
+
return `MONTH(${compiledArgs[0]})`;
|
|
1270
|
+
});
|
|
1271
|
+
this.add("DAY", ({ compiledArgs }) => {
|
|
1272
|
+
if (compiledArgs.length !== 1) throw new Error("DAY expects 1 argument");
|
|
1273
|
+
return `DAY(${compiledArgs[0]})`;
|
|
1274
|
+
});
|
|
1275
|
+
this.add("DATE_ADD", ({ node, compiledArgs }) => {
|
|
1276
|
+
if (compiledArgs.length !== 3) throw new Error("DATE_ADD expects 3 arguments (date, interval, unit)");
|
|
1277
|
+
const [date, interval] = compiledArgs;
|
|
1278
|
+
const unitArg = node.args[2];
|
|
1279
|
+
const unitClean = String(unitArg.value).replace(/['"]/g, "").toUpperCase();
|
|
1280
|
+
return `DATE_ADD(${date}, INTERVAL ${interval} ${unitClean})`;
|
|
1281
|
+
});
|
|
1282
|
+
this.add("DATE_SUB", ({ node, compiledArgs }) => {
|
|
1283
|
+
if (compiledArgs.length !== 3) throw new Error("DATE_SUB expects 3 arguments (date, interval, unit)");
|
|
1284
|
+
const [date, interval] = compiledArgs;
|
|
1285
|
+
const unitArg = node.args[2];
|
|
1286
|
+
const unitClean = String(unitArg.value).replace(/['"]/g, "").toUpperCase();
|
|
1287
|
+
return `DATE_SUB(${date}, INTERVAL ${interval} ${unitClean})`;
|
|
1288
|
+
});
|
|
1289
|
+
this.add("DATE_DIFF", ({ compiledArgs }) => {
|
|
1290
|
+
if (compiledArgs.length !== 2) throw new Error("DATE_DIFF expects 2 arguments");
|
|
1291
|
+
const [date1, date2] = compiledArgs;
|
|
1292
|
+
return `DATEDIFF(${date1}, ${date2})`;
|
|
1293
|
+
});
|
|
1294
|
+
this.add("DATE_FORMAT", ({ compiledArgs }) => {
|
|
1295
|
+
if (compiledArgs.length !== 2) throw new Error("DATE_FORMAT expects 2 arguments");
|
|
1296
|
+
const [date, format] = compiledArgs;
|
|
1297
|
+
return `DATE_FORMAT(${date}, ${format})`;
|
|
1298
|
+
});
|
|
1299
|
+
this.add("END_OF_MONTH", ({ compiledArgs }) => {
|
|
1300
|
+
if (compiledArgs.length !== 1) throw new Error("END_OF_MONTH expects 1 argument");
|
|
1301
|
+
return `LAST_DAY(${compiledArgs[0]})`;
|
|
1302
|
+
});
|
|
1303
|
+
this.add("DAY_OF_WEEK", ({ compiledArgs }) => {
|
|
1304
|
+
if (compiledArgs.length !== 1) throw new Error("DAY_OF_WEEK expects 1 argument");
|
|
1305
|
+
return `DAYOFWEEK(${compiledArgs[0]})`;
|
|
1306
|
+
});
|
|
1307
|
+
this.add("WEEK_OF_YEAR", ({ compiledArgs }) => {
|
|
1308
|
+
if (compiledArgs.length !== 1) throw new Error("WEEK_OF_YEAR expects 1 argument");
|
|
1309
|
+
return `WEEKOFYEAR(${compiledArgs[0]})`;
|
|
1310
|
+
});
|
|
1311
|
+
this.add("DATE_TRUNC", ({ node, compiledArgs }) => {
|
|
1312
|
+
if (compiledArgs.length !== 2) throw new Error("DATE_TRUNC expects 2 arguments (part, date)");
|
|
1313
|
+
const [, date] = compiledArgs;
|
|
1314
|
+
const partArg = node.args[0];
|
|
1315
|
+
const partClean = String(partArg.value).replace(/['"]/g, "").toLowerCase();
|
|
1316
|
+
if (partClean === "year") {
|
|
1317
|
+
return `DATE_FORMAT(${date}, '%Y-01-01')`;
|
|
1318
|
+
} else if (partClean === "month") {
|
|
1319
|
+
return `DATE_FORMAT(${date}, '%Y-%m-01')`;
|
|
1320
|
+
} else if (partClean === "day") {
|
|
1321
|
+
return `DATE(${date})`;
|
|
1322
|
+
}
|
|
1323
|
+
return `DATE(${date})`;
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1326
|
+
};
|
|
1327
|
+
|
|
1328
|
+
// src/core/dialect/mysql/index.ts
|
|
1329
|
+
var MySqlDialect = class extends SqlDialectBase {
|
|
1330
|
+
/**
|
|
1331
|
+
* Creates a new MySqlDialect instance
|
|
1332
|
+
*/
|
|
1333
|
+
constructor() {
|
|
1334
|
+
super(new MysqlFunctionStrategy());
|
|
1335
|
+
this.dialect = "mysql";
|
|
1336
|
+
}
|
|
1337
|
+
/**
|
|
1338
|
+
* Quotes an identifier using MySQL backtick syntax
|
|
1339
|
+
* @param id - Identifier to quote
|
|
1340
|
+
* @returns Quoted identifier
|
|
1341
|
+
*/
|
|
1342
|
+
quoteIdentifier(id) {
|
|
1343
|
+
return `\`${id}\``;
|
|
1344
|
+
}
|
|
1345
|
+
/**
|
|
1346
|
+
* Compiles JSON path expression using MySQL syntax
|
|
1347
|
+
* @param node - JSON path node
|
|
1348
|
+
* @returns MySQL JSON path expression
|
|
1349
|
+
*/
|
|
1350
|
+
compileJsonPath(node) {
|
|
1351
|
+
const col = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
|
|
1352
|
+
return `${col}->'${node.path}'`;
|
|
1353
|
+
}
|
|
1354
|
+
};
|
|
1355
|
+
|
|
1356
|
+
// src/core/dialect/sqlite/functions.ts
|
|
1357
|
+
var SqliteFunctionStrategy = class extends StandardFunctionStrategy {
|
|
1358
|
+
constructor() {
|
|
1359
|
+
super();
|
|
1360
|
+
this.registerOverrides();
|
|
1361
|
+
}
|
|
1362
|
+
registerOverrides() {
|
|
1363
|
+
this.add("NOW", () => `datetime('now', 'localtime')`);
|
|
1364
|
+
this.add("CURRENT_DATE", () => `date('now', 'localtime')`);
|
|
1365
|
+
this.add("CURRENT_TIME", () => `time('now', 'localtime')`);
|
|
1366
|
+
this.add("UTC_NOW", () => `datetime('now')`);
|
|
1367
|
+
this.add("EXTRACT", ({ compiledArgs }) => {
|
|
1368
|
+
if (compiledArgs.length !== 2) throw new Error("EXTRACT expects 2 arguments (part, date)");
|
|
1369
|
+
const [part, date] = compiledArgs;
|
|
1370
|
+
const partUpper = part.replace(/['"]/g, "").toUpperCase();
|
|
1371
|
+
const formatMap = {
|
|
1372
|
+
"YEAR": "%Y",
|
|
1373
|
+
"MONTH": "%m",
|
|
1374
|
+
"DAY": "%d",
|
|
1375
|
+
"HOUR": "%H",
|
|
1376
|
+
"MINUTE": "%M",
|
|
1377
|
+
"SECOND": "%S",
|
|
1378
|
+
"DOW": "%w",
|
|
1379
|
+
"WEEK": "%W"
|
|
1380
|
+
};
|
|
1381
|
+
const format = formatMap[partUpper] || "%Y";
|
|
1382
|
+
return `CAST(strftime('${format}', ${date}) AS INTEGER)`;
|
|
1383
|
+
});
|
|
1384
|
+
this.add("YEAR", ({ compiledArgs }) => {
|
|
1385
|
+
if (compiledArgs.length !== 1) throw new Error("YEAR expects 1 argument");
|
|
1386
|
+
return `CAST(strftime('%Y', ${compiledArgs[0]}) AS INTEGER)`;
|
|
1387
|
+
});
|
|
1388
|
+
this.add("MONTH", ({ compiledArgs }) => {
|
|
1389
|
+
if (compiledArgs.length !== 1) throw new Error("MONTH expects 1 argument");
|
|
1390
|
+
return `CAST(strftime('%m', ${compiledArgs[0]}) AS INTEGER)`;
|
|
1391
|
+
});
|
|
1392
|
+
this.add("DAY", ({ compiledArgs }) => {
|
|
1393
|
+
if (compiledArgs.length !== 1) throw new Error("DAY expects 1 argument");
|
|
1394
|
+
return `CAST(strftime('%d', ${compiledArgs[0]}) AS INTEGER)`;
|
|
1395
|
+
});
|
|
1396
|
+
this.add("DATE_ADD", ({ node, compiledArgs }) => {
|
|
1397
|
+
if (compiledArgs.length !== 3) throw new Error("DATE_ADD expects 3 arguments (date, interval, unit)");
|
|
1398
|
+
const [date, interval] = compiledArgs;
|
|
1399
|
+
const unitArg = node.args[2];
|
|
1400
|
+
const unitClean = String(unitArg.value).replace(/['"]/g, "").toLowerCase();
|
|
1401
|
+
return `datetime(${date}, '+' || ${interval} || ' ${unitClean}')`;
|
|
1402
|
+
});
|
|
1403
|
+
this.add("DATE_SUB", ({ node, compiledArgs }) => {
|
|
1404
|
+
if (compiledArgs.length !== 3) throw new Error("DATE_SUB expects 3 arguments (date, interval, unit)");
|
|
1405
|
+
const [date, interval] = compiledArgs;
|
|
1406
|
+
const unitArg = node.args[2];
|
|
1407
|
+
const unitClean = String(unitArg.value).replace(/['"]/g, "").toLowerCase();
|
|
1408
|
+
return `datetime(${date}, '-' || ${interval} || ' ${unitClean}')`;
|
|
1409
|
+
});
|
|
1410
|
+
this.add("DATE_DIFF", ({ compiledArgs }) => {
|
|
1411
|
+
if (compiledArgs.length !== 2) throw new Error("DATE_DIFF expects 2 arguments");
|
|
1412
|
+
const [date1, date2] = compiledArgs;
|
|
1413
|
+
return `CAST(julianday(${date1}) - julianday(${date2}) AS INTEGER)`;
|
|
1414
|
+
});
|
|
1415
|
+
this.add("DATE_FORMAT", ({ compiledArgs }) => {
|
|
1416
|
+
if (compiledArgs.length !== 2) throw new Error("DATE_FORMAT expects 2 arguments");
|
|
1417
|
+
const [date, format] = compiledArgs;
|
|
1418
|
+
return `strftime(${format}, ${date})`;
|
|
1419
|
+
});
|
|
1420
|
+
this.add("UNIX_TIMESTAMP", () => `CAST(strftime('%s', 'now') AS INTEGER)`);
|
|
1421
|
+
this.add("FROM_UNIXTIME", ({ compiledArgs }) => {
|
|
1422
|
+
if (compiledArgs.length !== 1) throw new Error("FROM_UNIXTIME expects 1 argument");
|
|
1423
|
+
return `datetime(${compiledArgs[0]}, 'unixepoch')`;
|
|
1424
|
+
});
|
|
1425
|
+
this.add("END_OF_MONTH", ({ compiledArgs }) => {
|
|
1426
|
+
if (compiledArgs.length !== 1) throw new Error("END_OF_MONTH expects 1 argument");
|
|
1427
|
+
return `date(${compiledArgs[0]}, 'start of month', '+1 month', '-1 day')`;
|
|
1428
|
+
});
|
|
1429
|
+
this.add("DAY_OF_WEEK", ({ compiledArgs }) => {
|
|
1430
|
+
if (compiledArgs.length !== 1) throw new Error("DAY_OF_WEEK expects 1 argument");
|
|
1431
|
+
return `CAST(strftime('%w', ${compiledArgs[0]}) AS INTEGER)`;
|
|
1432
|
+
});
|
|
1433
|
+
this.add("WEEK_OF_YEAR", ({ compiledArgs }) => {
|
|
1434
|
+
if (compiledArgs.length !== 1) throw new Error("WEEK_OF_YEAR expects 1 argument");
|
|
1435
|
+
return `CAST(strftime('%W', ${compiledArgs[0]}) AS INTEGER)`;
|
|
1436
|
+
});
|
|
1437
|
+
this.add("DATE_TRUNC", ({ node, compiledArgs }) => {
|
|
1438
|
+
if (compiledArgs.length !== 2) throw new Error("DATE_TRUNC expects 2 arguments (part, date)");
|
|
1439
|
+
const [, date] = compiledArgs;
|
|
1440
|
+
const partArg = node.args[0];
|
|
1441
|
+
const partClean = String(partArg.value).replace(/['"]/g, "").toLowerCase();
|
|
1442
|
+
if (partClean === "year") {
|
|
1443
|
+
return `date(${date}, 'start of year')`;
|
|
1444
|
+
} else if (partClean === "month") {
|
|
1445
|
+
return `date(${date}, 'start of month')`;
|
|
1446
|
+
} else if (partClean === "day") {
|
|
1447
|
+
return `date(${date})`;
|
|
1448
|
+
}
|
|
1449
|
+
return `date(${date}, 'start of ${partClean}')`;
|
|
1450
|
+
});
|
|
1451
|
+
}
|
|
1452
|
+
};
|
|
1453
|
+
|
|
1454
|
+
// src/core/dialect/sqlite/index.ts
|
|
1455
|
+
var SqliteDialect = class extends SqlDialectBase {
|
|
1456
|
+
/**
|
|
1457
|
+
* Creates a new SqliteDialect instance
|
|
1458
|
+
*/
|
|
1459
|
+
constructor() {
|
|
1460
|
+
super(new SqliteFunctionStrategy());
|
|
1461
|
+
this.dialect = "sqlite";
|
|
1462
|
+
}
|
|
1463
|
+
/**
|
|
1464
|
+
* Quotes an identifier using SQLite double-quote syntax
|
|
1465
|
+
* @param id - Identifier to quote
|
|
1466
|
+
* @returns Quoted identifier
|
|
1467
|
+
*/
|
|
1468
|
+
quoteIdentifier(id) {
|
|
1469
|
+
return `"${id}"`;
|
|
1470
|
+
}
|
|
1471
|
+
/**
|
|
1472
|
+
* Compiles JSON path expression using SQLite syntax
|
|
1473
|
+
* @param node - JSON path node
|
|
1474
|
+
* @returns SQLite JSON path expression
|
|
1475
|
+
*/
|
|
1476
|
+
compileJsonPath(node) {
|
|
1477
|
+
const col = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
|
|
1478
|
+
return `json_extract(${col}, '${node.path}')`;
|
|
1479
|
+
}
|
|
1480
|
+
compileReturning(returning, ctx) {
|
|
1481
|
+
if (!returning || returning.length === 0) return "";
|
|
1482
|
+
const columns = this.formatReturningColumns(returning);
|
|
1483
|
+
return ` RETURNING ${columns}`;
|
|
1484
|
+
}
|
|
1485
|
+
supportsReturning() {
|
|
1486
|
+
return true;
|
|
1487
|
+
}
|
|
1488
|
+
};
|
|
1489
|
+
|
|
1490
|
+
// src/core/dialect/mssql/functions.ts
|
|
1491
|
+
var MssqlFunctionStrategy = class extends StandardFunctionStrategy {
|
|
1492
|
+
constructor() {
|
|
1493
|
+
super();
|
|
1494
|
+
this.registerOverrides();
|
|
1495
|
+
}
|
|
1496
|
+
registerOverrides() {
|
|
1497
|
+
this.add("NOW", () => `GETDATE()`);
|
|
1498
|
+
this.add("CURRENT_DATE", () => `CAST(GETDATE() AS DATE)`);
|
|
1499
|
+
this.add("CURRENT_TIME", () => `CAST(GETDATE() AS TIME)`);
|
|
1500
|
+
this.add("UTC_NOW", () => `GETUTCDATE()`);
|
|
1501
|
+
this.add("EXTRACT", ({ compiledArgs }) => {
|
|
1502
|
+
if (compiledArgs.length !== 2) throw new Error("EXTRACT expects 2 arguments (part, date)");
|
|
1503
|
+
const [part, date] = compiledArgs;
|
|
1504
|
+
const partClean = part.replace(/['"]/g, "").toLowerCase();
|
|
1505
|
+
return `DATEPART(${partClean}, ${date})`;
|
|
1506
|
+
});
|
|
1507
|
+
this.add("YEAR", ({ compiledArgs }) => {
|
|
1508
|
+
if (compiledArgs.length !== 1) throw new Error("YEAR expects 1 argument");
|
|
1509
|
+
return `YEAR(${compiledArgs[0]})`;
|
|
1510
|
+
});
|
|
1511
|
+
this.add("MONTH", ({ compiledArgs }) => {
|
|
1512
|
+
if (compiledArgs.length !== 1) throw new Error("MONTH expects 1 argument");
|
|
1513
|
+
return `MONTH(${compiledArgs[0]})`;
|
|
1514
|
+
});
|
|
1515
|
+
this.add("DAY", ({ compiledArgs }) => {
|
|
1516
|
+
if (compiledArgs.length !== 1) throw new Error("DAY expects 1 argument");
|
|
1517
|
+
return `DAY(${compiledArgs[0]})`;
|
|
1518
|
+
});
|
|
1519
|
+
this.add("DATE_ADD", ({ node, compiledArgs }) => {
|
|
1520
|
+
if (compiledArgs.length !== 3) throw new Error("DATE_ADD expects 3 arguments (date, interval, unit)");
|
|
1521
|
+
const [date, interval] = compiledArgs;
|
|
1522
|
+
const unitArg = node.args[2];
|
|
1523
|
+
const unitClean = String(unitArg.value).replace(/['"]/g, "").toLowerCase();
|
|
1524
|
+
return `DATEADD(${unitClean}, ${interval}, ${date})`;
|
|
1525
|
+
});
|
|
1526
|
+
this.add("DATE_SUB", ({ node, compiledArgs }) => {
|
|
1527
|
+
if (compiledArgs.length !== 3) throw new Error("DATE_SUB expects 3 arguments (date, interval, unit)");
|
|
1528
|
+
const [date, interval] = compiledArgs;
|
|
1529
|
+
const unitArg = node.args[2];
|
|
1530
|
+
const unitClean = String(unitArg.value).replace(/['"]/g, "").toLowerCase();
|
|
1531
|
+
return `DATEADD(${unitClean}, -${interval}, ${date})`;
|
|
1532
|
+
});
|
|
1533
|
+
this.add("DATE_DIFF", ({ compiledArgs }) => {
|
|
1534
|
+
if (compiledArgs.length !== 2) throw new Error("DATE_DIFF expects 2 arguments");
|
|
1535
|
+
const [date1, date2] = compiledArgs;
|
|
1536
|
+
return `DATEDIFF(day, ${date2}, ${date1})`;
|
|
1537
|
+
});
|
|
1538
|
+
this.add("DATE_FORMAT", ({ compiledArgs }) => {
|
|
1539
|
+
if (compiledArgs.length !== 2) throw new Error("DATE_FORMAT expects 2 arguments");
|
|
1540
|
+
const [date, format] = compiledArgs;
|
|
1541
|
+
return `FORMAT(${date}, ${format})`;
|
|
1542
|
+
});
|
|
1543
|
+
this.add("UNIX_TIMESTAMP", () => `DATEDIFF(SECOND, '1970-01-01', GETUTCDATE())`);
|
|
1544
|
+
this.add("FROM_UNIXTIME", ({ compiledArgs }) => {
|
|
1545
|
+
if (compiledArgs.length !== 1) throw new Error("FROM_UNIXTIME expects 1 argument");
|
|
1546
|
+
return `DATEADD(SECOND, ${compiledArgs[0]}, '1970-01-01')`;
|
|
1547
|
+
});
|
|
1548
|
+
this.add("END_OF_MONTH", ({ compiledArgs }) => {
|
|
1549
|
+
if (compiledArgs.length !== 1) throw new Error("END_OF_MONTH expects 1 argument");
|
|
1550
|
+
return `EOMONTH(${compiledArgs[0]})`;
|
|
1551
|
+
});
|
|
1552
|
+
this.add("DAY_OF_WEEK", ({ compiledArgs }) => {
|
|
1553
|
+
if (compiledArgs.length !== 1) throw new Error("DAY_OF_WEEK expects 1 argument");
|
|
1554
|
+
return `DATEPART(dw, ${compiledArgs[0]})`;
|
|
1555
|
+
});
|
|
1556
|
+
this.add("WEEK_OF_YEAR", ({ compiledArgs }) => {
|
|
1557
|
+
if (compiledArgs.length !== 1) throw new Error("WEEK_OF_YEAR expects 1 argument");
|
|
1558
|
+
return `DATEPART(wk, ${compiledArgs[0]})`;
|
|
1559
|
+
});
|
|
1560
|
+
this.add("DATE_TRUNC", ({ node, compiledArgs }) => {
|
|
1561
|
+
if (compiledArgs.length !== 2) throw new Error("DATE_TRUNC expects 2 arguments (part, date)");
|
|
1562
|
+
const [, date] = compiledArgs;
|
|
1563
|
+
const partArg = node.args[0];
|
|
1564
|
+
const partClean = String(partArg.value).replace(/['"]/g, "").toLowerCase();
|
|
1565
|
+
return `DATETRUNC(${partClean}, ${date})`;
|
|
1566
|
+
});
|
|
1567
|
+
}
|
|
1568
|
+
};
|
|
1569
|
+
|
|
1570
|
+
// src/core/dialect/mssql/index.ts
|
|
1571
|
+
var SqlServerDialect = class extends Dialect {
|
|
1572
|
+
/**
|
|
1573
|
+
* Creates a new SqlServerDialect instance
|
|
1574
|
+
*/
|
|
1575
|
+
constructor() {
|
|
1576
|
+
super(new MssqlFunctionStrategy());
|
|
1577
|
+
this.dialect = "mssql";
|
|
1578
|
+
}
|
|
1579
|
+
/**
|
|
1580
|
+
* Quotes an identifier using SQL Server bracket syntax
|
|
1581
|
+
* @param id - Identifier to quote
|
|
1582
|
+
* @returns Quoted identifier
|
|
1583
|
+
*/
|
|
1584
|
+
quoteIdentifier(id) {
|
|
1585
|
+
return `[${id}]`;
|
|
1586
|
+
}
|
|
1587
|
+
/**
|
|
1588
|
+
* Compiles JSON path expression using SQL Server syntax
|
|
1589
|
+
* @param node - JSON path node
|
|
1590
|
+
* @returns SQL Server JSON path expression
|
|
1591
|
+
*/
|
|
1592
|
+
compileJsonPath(node) {
|
|
1593
|
+
const col = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
|
|
1594
|
+
return `JSON_VALUE(${col}, '${node.path}')`;
|
|
1595
|
+
}
|
|
1596
|
+
/**
|
|
1597
|
+
* Formats parameter placeholders using SQL Server named parameter syntax
|
|
1598
|
+
* @param index - Parameter index
|
|
1599
|
+
* @returns Named parameter placeholder
|
|
1600
|
+
*/
|
|
1601
|
+
formatPlaceholder(index) {
|
|
1602
|
+
return `@p${index}`;
|
|
1603
|
+
}
|
|
1604
|
+
/**
|
|
1605
|
+
* Compiles SELECT query AST to SQL Server SQL
|
|
1606
|
+
* @param ast - Query AST
|
|
1607
|
+
* @param ctx - Compiler context
|
|
1608
|
+
* @returns SQL Server SQL string
|
|
1609
|
+
*/
|
|
1610
|
+
compileSelectAst(ast, ctx) {
|
|
1611
|
+
const hasSetOps = !!(ast.setOps && ast.setOps.length);
|
|
1612
|
+
const ctes = this.compileCtes(ast, ctx);
|
|
1613
|
+
const baseAst = hasSetOps ? { ...ast, setOps: void 0, orderBy: void 0, limit: void 0, offset: void 0 } : ast;
|
|
1614
|
+
const baseSelect = this.compileSelectCore(baseAst, ctx);
|
|
1615
|
+
if (!hasSetOps) {
|
|
1616
|
+
return `${ctes}${baseSelect}`;
|
|
1617
|
+
}
|
|
1618
|
+
const compound = ast.setOps.map((op) => `${op.operator} ${this.wrapSetOperand(this.compileSelectAst(op.query, ctx))}`).join(" ");
|
|
1619
|
+
const orderBy = this.compileOrderBy(ast);
|
|
1620
|
+
const pagination = this.compilePagination(ast, orderBy);
|
|
1621
|
+
const combined = `${this.wrapSetOperand(baseSelect)} ${compound}`;
|
|
1622
|
+
const tail = pagination || orderBy;
|
|
1623
|
+
return `${ctes}${combined}${tail}`;
|
|
1624
|
+
}
|
|
1625
|
+
compileInsertAst(ast, ctx) {
|
|
1626
|
+
const table = this.quoteIdentifier(ast.into.name);
|
|
1627
|
+
const columnList = ast.columns.map((column) => `${this.quoteIdentifier(column.table)}.${this.quoteIdentifier(column.name)}`).join(", ");
|
|
1628
|
+
const values = ast.values.map((row) => `(${row.map((value) => this.compileOperand(value, ctx)).join(", ")})`).join(", ");
|
|
1629
|
+
return `INSERT INTO ${table} (${columnList}) VALUES ${values};`;
|
|
1630
|
+
}
|
|
1631
|
+
compileUpdateAst(ast, ctx) {
|
|
1632
|
+
const table = this.quoteIdentifier(ast.table.name);
|
|
1633
|
+
const assignments = ast.set.map((assignment) => {
|
|
1634
|
+
const col = assignment.column;
|
|
1635
|
+
const target = `${this.quoteIdentifier(col.table)}.${this.quoteIdentifier(col.name)}`;
|
|
1636
|
+
const value = this.compileOperand(assignment.value, ctx);
|
|
1637
|
+
return `${target} = ${value}`;
|
|
1638
|
+
}).join(", ");
|
|
1639
|
+
const whereClause = this.compileWhere(ast.where, ctx);
|
|
1640
|
+
return `UPDATE ${table} SET ${assignments}${whereClause};`;
|
|
1641
|
+
}
|
|
1642
|
+
compileDeleteAst(ast, ctx) {
|
|
1643
|
+
const table = this.quoteIdentifier(ast.from.name);
|
|
1644
|
+
const whereClause = this.compileWhere(ast.where, ctx);
|
|
1645
|
+
return `DELETE FROM ${table}${whereClause};`;
|
|
1646
|
+
}
|
|
1647
|
+
compileSelectCore(ast, ctx) {
|
|
1648
|
+
const columns = ast.columns.map((c) => {
|
|
1649
|
+
let expr = "";
|
|
1650
|
+
if (c.type === "Function") {
|
|
1651
|
+
expr = this.compileOperand(c, ctx);
|
|
1652
|
+
} else if (c.type === "Column") {
|
|
1653
|
+
expr = `${this.quoteIdentifier(c.table)}.${this.quoteIdentifier(c.name)}`;
|
|
1654
|
+
} else if (c.type === "ScalarSubquery") {
|
|
1655
|
+
expr = this.compileOperand(c, ctx);
|
|
1656
|
+
} else if (c.type === "WindowFunction") {
|
|
1657
|
+
expr = this.compileOperand(c, ctx);
|
|
1658
|
+
}
|
|
1659
|
+
if (c.alias) {
|
|
1660
|
+
if (c.alias.includes("(")) return c.alias;
|
|
1661
|
+
return `${expr} AS ${this.quoteIdentifier(c.alias)}`;
|
|
1662
|
+
}
|
|
1663
|
+
return expr;
|
|
1664
|
+
}).join(", ");
|
|
1665
|
+
const distinct = ast.distinct ? "DISTINCT " : "";
|
|
1666
|
+
const from = `${this.quoteIdentifier(ast.from.name)}`;
|
|
1667
|
+
const joins = ast.joins.map((j) => {
|
|
1668
|
+
const table = this.quoteIdentifier(j.table.name);
|
|
1669
|
+
const cond = this.compileExpression(j.condition, ctx);
|
|
1670
|
+
return `${j.kind} JOIN ${table} ON ${cond}`;
|
|
1671
|
+
}).join(" ");
|
|
1672
|
+
const whereClause = this.compileWhere(ast.where, ctx);
|
|
1673
|
+
const groupBy = ast.groupBy && ast.groupBy.length > 0 ? " GROUP BY " + ast.groupBy.map((c) => `${this.quoteIdentifier(c.table)}.${this.quoteIdentifier(c.name)}`).join(", ") : "";
|
|
1674
|
+
const having = ast.having ? ` HAVING ${this.compileExpression(ast.having, ctx)}` : "";
|
|
1675
|
+
const orderBy = this.compileOrderBy(ast);
|
|
1676
|
+
const pagination = this.compilePagination(ast, orderBy);
|
|
1677
|
+
if (pagination) {
|
|
1678
|
+
return `SELECT ${distinct}${columns} FROM ${from}${joins ? " " + joins : ""}${whereClause}${groupBy}${having}${pagination}`;
|
|
1679
|
+
}
|
|
1680
|
+
return `SELECT ${distinct}${columns} FROM ${from}${joins ? " " + joins : ""}${whereClause}${groupBy}${having}${orderBy}`;
|
|
1681
|
+
}
|
|
1682
|
+
compileOrderBy(ast) {
|
|
1683
|
+
if (!ast.orderBy || ast.orderBy.length === 0) return "";
|
|
1684
|
+
return " ORDER BY " + ast.orderBy.map((o) => `${this.quoteIdentifier(o.column.table)}.${this.quoteIdentifier(o.column.name)} ${o.direction}`).join(", ");
|
|
1685
|
+
}
|
|
1686
|
+
compilePagination(ast, orderBy) {
|
|
1687
|
+
const hasLimit = ast.limit !== void 0;
|
|
1688
|
+
const hasOffset = ast.offset !== void 0;
|
|
1689
|
+
if (!hasLimit && !hasOffset) return "";
|
|
1690
|
+
const off = ast.offset ?? 0;
|
|
1691
|
+
const orderClause = orderBy || " ORDER BY (SELECT NULL)";
|
|
1692
|
+
let pagination = `${orderClause} OFFSET ${off} ROWS`;
|
|
1693
|
+
if (hasLimit) {
|
|
1694
|
+
pagination += ` FETCH NEXT ${ast.limit} ROWS ONLY`;
|
|
1695
|
+
}
|
|
1696
|
+
return pagination;
|
|
1697
|
+
}
|
|
1698
|
+
compileCtes(ast, ctx) {
|
|
1699
|
+
if (!ast.ctes || ast.ctes.length === 0) return "";
|
|
1700
|
+
const defs = ast.ctes.map((cte) => {
|
|
1701
|
+
const name = this.quoteIdentifier(cte.name);
|
|
1702
|
+
const cols = cte.columns ? `(${cte.columns.map((c) => this.quoteIdentifier(c)).join(", ")})` : "";
|
|
1703
|
+
const query = this.compileSelectAst(this.normalizeSelectAst(cte.query), ctx).trim().replace(/;$/, "");
|
|
1704
|
+
return `${name}${cols} AS (${query})`;
|
|
1705
|
+
}).join(", ");
|
|
1706
|
+
return `WITH ${defs} `;
|
|
1707
|
+
}
|
|
1708
|
+
wrapSetOperand(sql) {
|
|
1709
|
+
const trimmed = sql.trim().replace(/;$/, "");
|
|
1710
|
+
return `(${trimmed})`;
|
|
1711
|
+
}
|
|
1712
|
+
};
|
|
1713
|
+
|
|
1714
|
+
// src/core/dialect/dialect-factory.ts
|
|
1715
|
+
var DialectFactory = class {
|
|
1716
|
+
static {
|
|
1717
|
+
this.registry = /* @__PURE__ */ new Map();
|
|
1718
|
+
}
|
|
1719
|
+
static {
|
|
1720
|
+
this.defaultsInitialized = false;
|
|
1721
|
+
}
|
|
1722
|
+
static ensureDefaults() {
|
|
1723
|
+
if (this.defaultsInitialized) return;
|
|
1724
|
+
this.defaultsInitialized = true;
|
|
1725
|
+
if (!this.registry.has("postgres")) {
|
|
1726
|
+
this.registry.set("postgres", () => new PostgresDialect());
|
|
1727
|
+
}
|
|
1728
|
+
if (!this.registry.has("mysql")) {
|
|
1729
|
+
this.registry.set("mysql", () => new MySqlDialect());
|
|
1730
|
+
}
|
|
1731
|
+
if (!this.registry.has("sqlite")) {
|
|
1732
|
+
this.registry.set("sqlite", () => new SqliteDialect());
|
|
1733
|
+
}
|
|
1734
|
+
if (!this.registry.has("mssql")) {
|
|
1735
|
+
this.registry.set("mssql", () => new SqlServerDialect());
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
/**
|
|
1739
|
+
* Register (or override) a dialect factory for a key.
|
|
1740
|
+
*
|
|
1741
|
+
* Examples:
|
|
1742
|
+
* DialectFactory.register('sqlite', () => new SqliteDialect());
|
|
1743
|
+
* DialectFactory.register('my-tenant-dialect', () => new CustomDialect());
|
|
1744
|
+
*/
|
|
1745
|
+
static register(key, factory) {
|
|
1746
|
+
this.registry.set(key, factory);
|
|
1747
|
+
}
|
|
1748
|
+
/**
|
|
1749
|
+
* Resolve a key into a Dialect instance.
|
|
1750
|
+
* Throws if the key is not registered.
|
|
1751
|
+
*/
|
|
1752
|
+
static create(key) {
|
|
1753
|
+
this.ensureDefaults();
|
|
1754
|
+
const factory = this.registry.get(key);
|
|
1755
|
+
if (!factory) {
|
|
1756
|
+
throw new Error(
|
|
1757
|
+
`Dialect "${String(
|
|
1758
|
+
key
|
|
1759
|
+
)}" is not registered. Use DialectFactory.register(...) to register it.`
|
|
1760
|
+
);
|
|
1761
|
+
}
|
|
1762
|
+
return factory();
|
|
1763
|
+
}
|
|
1764
|
+
/**
|
|
1765
|
+
* Clear all registrations (mainly for tests).
|
|
1766
|
+
* Built-ins will be re-registered lazily on the next create().
|
|
1767
|
+
*/
|
|
1768
|
+
static clear() {
|
|
1769
|
+
this.registry.clear();
|
|
1770
|
+
this.defaultsInitialized = false;
|
|
1771
|
+
}
|
|
321
1772
|
};
|
|
322
|
-
var
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
type: "BinaryExpression",
|
|
326
|
-
left: toNode(left),
|
|
327
|
-
operator,
|
|
328
|
-
right: toOperand(right)
|
|
329
|
-
};
|
|
330
|
-
if (escape !== void 0) {
|
|
331
|
-
node.escape = toLiteralNode(escape);
|
|
1773
|
+
var resolveDialectInput = (dialect) => {
|
|
1774
|
+
if (typeof dialect === "string") {
|
|
1775
|
+
return DialectFactory.create(dialect);
|
|
332
1776
|
}
|
|
333
|
-
return
|
|
1777
|
+
return dialect;
|
|
334
1778
|
};
|
|
335
|
-
var eq = (left, right) => createBinaryExpression("=", left, right);
|
|
336
|
-
var and = (...operands) => ({
|
|
337
|
-
type: "LogicalExpression",
|
|
338
|
-
operator: "AND",
|
|
339
|
-
operands
|
|
340
|
-
});
|
|
341
|
-
var createInExpression = (operator, left, values) => ({
|
|
342
|
-
type: "InExpression",
|
|
343
|
-
left: toNode(left),
|
|
344
|
-
operator,
|
|
345
|
-
right: values.map((v) => toOperand(v))
|
|
346
|
-
});
|
|
347
|
-
var inList = (left, values) => createInExpression("IN", left, values);
|
|
348
|
-
var exists = (subquery) => ({
|
|
349
|
-
type: "ExistsExpression",
|
|
350
|
-
operator: "EXISTS",
|
|
351
|
-
subquery
|
|
352
|
-
});
|
|
353
|
-
var notExists = (subquery) => ({
|
|
354
|
-
type: "ExistsExpression",
|
|
355
|
-
operator: "NOT EXISTS",
|
|
356
|
-
subquery
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
// src/core/ast/aggregate-functions.ts
|
|
360
|
-
var buildAggregate = (name) => (col) => ({
|
|
361
|
-
type: "Function",
|
|
362
|
-
name,
|
|
363
|
-
args: [columnOperand(col)]
|
|
364
|
-
});
|
|
365
|
-
var count = buildAggregate("COUNT");
|
|
366
|
-
var sum = buildAggregate("SUM");
|
|
367
|
-
var avg = buildAggregate("AVG");
|
|
368
1779
|
|
|
369
1780
|
// src/query-builder/select-query-state.ts
|
|
370
1781
|
var SelectQueryState = class _SelectQueryState {
|
|
@@ -517,9 +1928,9 @@ var SelectQueryState = class _SelectQueryState {
|
|
|
517
1928
|
var createJoinNode = (kind, tableName, condition, relationName) => ({
|
|
518
1929
|
type: "Join",
|
|
519
1930
|
kind,
|
|
520
|
-
table: { type: "Table", name: tableName },
|
|
1931
|
+
table: typeof tableName === "string" ? { type: "Table", name: tableName } : tableName,
|
|
521
1932
|
condition,
|
|
522
|
-
relationName
|
|
1933
|
+
meta: relationName ? { relationName } : void 0
|
|
523
1934
|
});
|
|
524
1935
|
|
|
525
1936
|
// src/core/sql/sql.ts
|
|
@@ -861,7 +2272,8 @@ var HydrationPlanner = class _HydrationPlanner {
|
|
|
861
2272
|
*/
|
|
862
2273
|
buildRelationPlan(rel, relationName, aliasPrefix, columns, pivot) {
|
|
863
2274
|
switch (rel.type) {
|
|
864
|
-
case RelationKinds.HasMany:
|
|
2275
|
+
case RelationKinds.HasMany:
|
|
2276
|
+
case RelationKinds.HasOne: {
|
|
865
2277
|
const localKey = rel.localKey || findPrimaryKey(this.table);
|
|
866
2278
|
return {
|
|
867
2279
|
name: relationName,
|
|
@@ -1190,10 +2602,11 @@ var assertNever = (value) => {
|
|
|
1190
2602
|
throw new Error(`Unhandled relation type: ${JSON.stringify(value)}`);
|
|
1191
2603
|
};
|
|
1192
2604
|
var baseRelationCondition = (root, relation) => {
|
|
1193
|
-
const defaultLocalKey = relation.type === RelationKinds.HasMany ? findPrimaryKey(root) : findPrimaryKey(relation.target);
|
|
2605
|
+
const defaultLocalKey = relation.type === RelationKinds.HasMany || relation.type === RelationKinds.HasOne ? findPrimaryKey(root) : findPrimaryKey(relation.target);
|
|
1194
2606
|
const localKey = relation.localKey || defaultLocalKey;
|
|
1195
2607
|
switch (relation.type) {
|
|
1196
2608
|
case RelationKinds.HasMany:
|
|
2609
|
+
case RelationKinds.HasOne:
|
|
1197
2610
|
return eq(
|
|
1198
2611
|
{ type: "Column", table: relation.target.name, name: relation.foreignKey },
|
|
1199
2612
|
{ type: "Column", table: root.name, name: localKey }
|
|
@@ -1240,6 +2653,9 @@ var buildRelationCorrelation = (root, relation) => {
|
|
|
1240
2653
|
return baseRelationCondition(root, relation);
|
|
1241
2654
|
};
|
|
1242
2655
|
|
|
2656
|
+
// src/core/ast/join-metadata.ts
|
|
2657
|
+
var getJoinRelationName = (join) => join.meta?.relationName;
|
|
2658
|
+
|
|
1243
2659
|
// src/query-builder/relation-service.ts
|
|
1244
2660
|
var RelationService = class {
|
|
1245
2661
|
/**
|
|
@@ -1294,7 +2710,7 @@ var RelationService = class {
|
|
|
1294
2710
|
let hydration = this.hydration;
|
|
1295
2711
|
const relation = this.getRelation(relationName);
|
|
1296
2712
|
const aliasPrefix = options?.aliasPrefix ?? relationName;
|
|
1297
|
-
const alreadyJoined = state.ast.joins.some((j) => j
|
|
2713
|
+
const alreadyJoined = state.ast.joins.some((j) => getJoinRelationName(j) === relationName);
|
|
1298
2714
|
if (!alreadyJoined) {
|
|
1299
2715
|
const joined = this.joinRelation(relationName, options?.joinKind ?? JOIN_KINDS.LEFT, options?.filter);
|
|
1300
2716
|
state = joined.state;
|
|
@@ -1615,6 +3031,12 @@ var hydrateRows = (rows, plan) => {
|
|
|
1615
3031
|
const seen = getRelationSeenSet(rootId, rel.name);
|
|
1616
3032
|
if (seen.has(childPk)) continue;
|
|
1617
3033
|
seen.add(childPk);
|
|
3034
|
+
if (rel.type === RelationKinds.HasOne) {
|
|
3035
|
+
if (!parent[rel.name]) {
|
|
3036
|
+
parent[rel.name] = buildChild(row, rel);
|
|
3037
|
+
}
|
|
3038
|
+
continue;
|
|
3039
|
+
}
|
|
1618
3040
|
const bucket = parent[rel.name];
|
|
1619
3041
|
bucket.push(buildChild(row, rel));
|
|
1620
3042
|
}
|
|
@@ -1628,7 +3050,7 @@ var createBaseRow = (row, plan) => {
|
|
|
1628
3050
|
base[key] = row[key];
|
|
1629
3051
|
}
|
|
1630
3052
|
for (const rel of plan.relations) {
|
|
1631
|
-
base[rel.name] = [];
|
|
3053
|
+
base[rel.name] = rel.type === RelationKinds.HasOne ? null : [];
|
|
1632
3054
|
}
|
|
1633
3055
|
return base;
|
|
1634
3056
|
};
|
|
@@ -1791,7 +3213,7 @@ var DefaultHasManyCollection = class {
|
|
|
1791
3213
|
}
|
|
1792
3214
|
};
|
|
1793
3215
|
|
|
1794
|
-
// src/orm/relations/
|
|
3216
|
+
// src/orm/relations/has-one.ts
|
|
1795
3217
|
var toKey3 = (value) => value === null || value === void 0 ? "" : String(value);
|
|
1796
3218
|
var hideInternal2 = (obj, keys) => {
|
|
1797
3219
|
for (const key of keys) {
|
|
@@ -1803,6 +3225,123 @@ var hideInternal2 = (obj, keys) => {
|
|
|
1803
3225
|
});
|
|
1804
3226
|
}
|
|
1805
3227
|
};
|
|
3228
|
+
var DefaultHasOneReference = class {
|
|
3229
|
+
constructor(ctx, meta, root, relationName, relation, rootTable, loader, createEntity, localKey) {
|
|
3230
|
+
this.ctx = ctx;
|
|
3231
|
+
this.meta = meta;
|
|
3232
|
+
this.root = root;
|
|
3233
|
+
this.relationName = relationName;
|
|
3234
|
+
this.relation = relation;
|
|
3235
|
+
this.rootTable = rootTable;
|
|
3236
|
+
this.loader = loader;
|
|
3237
|
+
this.createEntity = createEntity;
|
|
3238
|
+
this.localKey = localKey;
|
|
3239
|
+
this.loaded = false;
|
|
3240
|
+
this.current = null;
|
|
3241
|
+
hideInternal2(this, [
|
|
3242
|
+
"ctx",
|
|
3243
|
+
"meta",
|
|
3244
|
+
"root",
|
|
3245
|
+
"relationName",
|
|
3246
|
+
"relation",
|
|
3247
|
+
"rootTable",
|
|
3248
|
+
"loader",
|
|
3249
|
+
"createEntity",
|
|
3250
|
+
"localKey"
|
|
3251
|
+
]);
|
|
3252
|
+
this.populateFromHydrationCache();
|
|
3253
|
+
}
|
|
3254
|
+
async load() {
|
|
3255
|
+
if (this.loaded) return this.current;
|
|
3256
|
+
const map = await this.loader();
|
|
3257
|
+
const keyValue = this.root[this.localKey];
|
|
3258
|
+
if (keyValue === void 0 || keyValue === null) {
|
|
3259
|
+
this.loaded = true;
|
|
3260
|
+
return this.current;
|
|
3261
|
+
}
|
|
3262
|
+
const row = map.get(toKey3(keyValue));
|
|
3263
|
+
this.current = row ? this.createEntity(row) : null;
|
|
3264
|
+
this.loaded = true;
|
|
3265
|
+
return this.current;
|
|
3266
|
+
}
|
|
3267
|
+
get() {
|
|
3268
|
+
return this.current;
|
|
3269
|
+
}
|
|
3270
|
+
set(data) {
|
|
3271
|
+
if (data === null) {
|
|
3272
|
+
return this.detachCurrent();
|
|
3273
|
+
}
|
|
3274
|
+
const entity = hasEntityMeta(data) ? data : this.createEntity(data);
|
|
3275
|
+
if (this.current && this.current !== entity) {
|
|
3276
|
+
this.ctx.registerRelationChange(
|
|
3277
|
+
this.root,
|
|
3278
|
+
this.relationKey,
|
|
3279
|
+
this.rootTable,
|
|
3280
|
+
this.relationName,
|
|
3281
|
+
this.relation,
|
|
3282
|
+
{ kind: "remove", entity: this.current }
|
|
3283
|
+
);
|
|
3284
|
+
}
|
|
3285
|
+
this.assignForeignKey(entity);
|
|
3286
|
+
this.current = entity;
|
|
3287
|
+
this.loaded = true;
|
|
3288
|
+
this.ctx.registerRelationChange(
|
|
3289
|
+
this.root,
|
|
3290
|
+
this.relationKey,
|
|
3291
|
+
this.rootTable,
|
|
3292
|
+
this.relationName,
|
|
3293
|
+
this.relation,
|
|
3294
|
+
{ kind: "attach", entity }
|
|
3295
|
+
);
|
|
3296
|
+
return entity;
|
|
3297
|
+
}
|
|
3298
|
+
toJSON() {
|
|
3299
|
+
return this.current;
|
|
3300
|
+
}
|
|
3301
|
+
detachCurrent() {
|
|
3302
|
+
const previous = this.current;
|
|
3303
|
+
if (!previous) return null;
|
|
3304
|
+
this.current = null;
|
|
3305
|
+
this.loaded = true;
|
|
3306
|
+
this.ctx.registerRelationChange(
|
|
3307
|
+
this.root,
|
|
3308
|
+
this.relationKey,
|
|
3309
|
+
this.rootTable,
|
|
3310
|
+
this.relationName,
|
|
3311
|
+
this.relation,
|
|
3312
|
+
{ kind: "remove", entity: previous }
|
|
3313
|
+
);
|
|
3314
|
+
return null;
|
|
3315
|
+
}
|
|
3316
|
+
assignForeignKey(entity) {
|
|
3317
|
+
const keyValue = this.root[this.localKey];
|
|
3318
|
+
entity[this.relation.foreignKey] = keyValue;
|
|
3319
|
+
}
|
|
3320
|
+
get relationKey() {
|
|
3321
|
+
return `${this.rootTable.name}.${this.relationName}`;
|
|
3322
|
+
}
|
|
3323
|
+
populateFromHydrationCache() {
|
|
3324
|
+
const keyValue = this.root[this.localKey];
|
|
3325
|
+
if (keyValue === void 0 || keyValue === null) return;
|
|
3326
|
+
const row = getHydrationRecord(this.meta, this.relationName, keyValue);
|
|
3327
|
+
if (!row) return;
|
|
3328
|
+
this.current = this.createEntity(row);
|
|
3329
|
+
this.loaded = true;
|
|
3330
|
+
}
|
|
3331
|
+
};
|
|
3332
|
+
|
|
3333
|
+
// src/orm/relations/belongs-to.ts
|
|
3334
|
+
var toKey4 = (value) => value === null || value === void 0 ? "" : String(value);
|
|
3335
|
+
var hideInternal3 = (obj, keys) => {
|
|
3336
|
+
for (const key of keys) {
|
|
3337
|
+
Object.defineProperty(obj, key, {
|
|
3338
|
+
value: obj[key],
|
|
3339
|
+
writable: false,
|
|
3340
|
+
configurable: false,
|
|
3341
|
+
enumerable: false
|
|
3342
|
+
});
|
|
3343
|
+
}
|
|
3344
|
+
};
|
|
1806
3345
|
var DefaultBelongsToReference = class {
|
|
1807
3346
|
constructor(ctx, meta, root, relationName, relation, rootTable, loader, createEntity, targetKey) {
|
|
1808
3347
|
this.ctx = ctx;
|
|
@@ -1816,7 +3355,7 @@ var DefaultBelongsToReference = class {
|
|
|
1816
3355
|
this.targetKey = targetKey;
|
|
1817
3356
|
this.loaded = false;
|
|
1818
3357
|
this.current = null;
|
|
1819
|
-
|
|
3358
|
+
hideInternal3(this, ["ctx", "meta", "root", "relationName", "relation", "rootTable", "loader", "createEntity", "targetKey"]);
|
|
1820
3359
|
this.populateFromHydrationCache();
|
|
1821
3360
|
}
|
|
1822
3361
|
async load() {
|
|
@@ -1826,7 +3365,7 @@ var DefaultBelongsToReference = class {
|
|
|
1826
3365
|
if (fkValue === null || fkValue === void 0) {
|
|
1827
3366
|
this.current = null;
|
|
1828
3367
|
} else {
|
|
1829
|
-
const row = map.get(
|
|
3368
|
+
const row = map.get(toKey4(fkValue));
|
|
1830
3369
|
this.current = row ? this.createEntity(row) : null;
|
|
1831
3370
|
}
|
|
1832
3371
|
this.loaded = true;
|
|
@@ -1883,8 +3422,8 @@ var DefaultBelongsToReference = class {
|
|
|
1883
3422
|
};
|
|
1884
3423
|
|
|
1885
3424
|
// src/orm/relations/many-to-many.ts
|
|
1886
|
-
var
|
|
1887
|
-
var
|
|
3425
|
+
var toKey5 = (value) => value === null || value === void 0 ? "" : String(value);
|
|
3426
|
+
var hideInternal4 = (obj, keys) => {
|
|
1888
3427
|
for (const key of keys) {
|
|
1889
3428
|
Object.defineProperty(obj, key, {
|
|
1890
3429
|
value: obj[key],
|
|
@@ -1907,13 +3446,13 @@ var DefaultManyToManyCollection = class {
|
|
|
1907
3446
|
this.localKey = localKey;
|
|
1908
3447
|
this.loaded = false;
|
|
1909
3448
|
this.items = [];
|
|
1910
|
-
|
|
3449
|
+
hideInternal4(this, ["ctx", "meta", "root", "relationName", "relation", "rootTable", "loader", "createEntity", "localKey"]);
|
|
1911
3450
|
this.hydrateFromCache();
|
|
1912
3451
|
}
|
|
1913
3452
|
async load() {
|
|
1914
3453
|
if (this.loaded) return this.items;
|
|
1915
3454
|
const map = await this.loader();
|
|
1916
|
-
const key =
|
|
3455
|
+
const key = toKey5(this.root[this.localKey]);
|
|
1917
3456
|
const rows = map.get(key) ?? [];
|
|
1918
3457
|
this.items = rows.map((row) => {
|
|
1919
3458
|
const entity = this.createEntity(row);
|
|
@@ -1963,15 +3502,15 @@ var DefaultManyToManyCollection = class {
|
|
|
1963
3502
|
async syncByIds(ids) {
|
|
1964
3503
|
await this.load();
|
|
1965
3504
|
const targetKey = this.relation.targetKey || findPrimaryKey(this.relation.target);
|
|
1966
|
-
const normalized = new Set(ids.map((id) =>
|
|
1967
|
-
const currentIds = new Set(this.items.map((item) =>
|
|
3505
|
+
const normalized = new Set(ids.map((id) => toKey5(id)));
|
|
3506
|
+
const currentIds = new Set(this.items.map((item) => toKey5(this.extractId(item))));
|
|
1968
3507
|
for (const id of normalized) {
|
|
1969
3508
|
if (!currentIds.has(id)) {
|
|
1970
3509
|
this.attach(id);
|
|
1971
3510
|
}
|
|
1972
3511
|
}
|
|
1973
3512
|
for (const item of [...this.items]) {
|
|
1974
|
-
const itemId =
|
|
3513
|
+
const itemId = toKey5(this.extractId(item));
|
|
1975
3514
|
if (!normalized.has(itemId)) {
|
|
1976
3515
|
this.detach(item);
|
|
1977
3516
|
}
|
|
@@ -2042,7 +3581,7 @@ var executeQuery = async (ctx, qb) => {
|
|
|
2042
3581
|
const results = await ctx.executor.executeSql(compiled.sql, compiled.params);
|
|
2043
3582
|
return rowsFromResults(results);
|
|
2044
3583
|
};
|
|
2045
|
-
var
|
|
3584
|
+
var toKey6 = (value) => value === null || value === void 0 ? "" : String(value);
|
|
2046
3585
|
var loadHasManyRelation = async (ctx, rootTable, _relationName, relation) => {
|
|
2047
3586
|
const localKey = relation.localKey || findPrimaryKey(rootTable);
|
|
2048
3587
|
const roots = ctx.getEntitiesForTable(rootTable);
|
|
@@ -2066,13 +3605,43 @@ var loadHasManyRelation = async (ctx, rootTable, _relationName, relation) => {
|
|
|
2066
3605
|
for (const row of rows) {
|
|
2067
3606
|
const fkValue = row[relation.foreignKey];
|
|
2068
3607
|
if (fkValue === null || fkValue === void 0) continue;
|
|
2069
|
-
const key =
|
|
3608
|
+
const key = toKey6(fkValue);
|
|
2070
3609
|
const bucket = grouped.get(key) ?? [];
|
|
2071
3610
|
bucket.push(row);
|
|
2072
3611
|
grouped.set(key, bucket);
|
|
2073
3612
|
}
|
|
2074
3613
|
return grouped;
|
|
2075
3614
|
};
|
|
3615
|
+
var loadHasOneRelation = async (ctx, rootTable, _relationName, relation) => {
|
|
3616
|
+
const localKey = relation.localKey || findPrimaryKey(rootTable);
|
|
3617
|
+
const roots = ctx.getEntitiesForTable(rootTable);
|
|
3618
|
+
const keys = /* @__PURE__ */ new Set();
|
|
3619
|
+
for (const tracked of roots) {
|
|
3620
|
+
const value = tracked.entity[localKey];
|
|
3621
|
+
if (value !== null && value !== void 0) {
|
|
3622
|
+
keys.add(value);
|
|
3623
|
+
}
|
|
3624
|
+
}
|
|
3625
|
+
if (!keys.size) {
|
|
3626
|
+
return /* @__PURE__ */ new Map();
|
|
3627
|
+
}
|
|
3628
|
+
const selectMap = selectAllColumns(relation.target);
|
|
3629
|
+
const qb = new SelectQueryBuilder(relation.target).select(selectMap);
|
|
3630
|
+
const fkColumn = relation.target.columns[relation.foreignKey];
|
|
3631
|
+
if (!fkColumn) return /* @__PURE__ */ new Map();
|
|
3632
|
+
qb.where(inList(fkColumn, Array.from(keys)));
|
|
3633
|
+
const rows = await executeQuery(ctx, qb);
|
|
3634
|
+
const lookup = /* @__PURE__ */ new Map();
|
|
3635
|
+
for (const row of rows) {
|
|
3636
|
+
const fkValue = row[relation.foreignKey];
|
|
3637
|
+
if (fkValue === null || fkValue === void 0) continue;
|
|
3638
|
+
const key = toKey6(fkValue);
|
|
3639
|
+
if (!lookup.has(key)) {
|
|
3640
|
+
lookup.set(key, row);
|
|
3641
|
+
}
|
|
3642
|
+
}
|
|
3643
|
+
return lookup;
|
|
3644
|
+
};
|
|
2076
3645
|
var loadBelongsToRelation = async (ctx, rootTable, _relationName, relation) => {
|
|
2077
3646
|
const roots = ctx.getEntitiesForTable(rootTable);
|
|
2078
3647
|
const foreignKeys = /* @__PURE__ */ new Set();
|
|
@@ -2096,7 +3665,7 @@ var loadBelongsToRelation = async (ctx, rootTable, _relationName, relation) => {
|
|
|
2096
3665
|
for (const row of rows) {
|
|
2097
3666
|
const keyValue = row[targetKey];
|
|
2098
3667
|
if (keyValue === null || keyValue === void 0) continue;
|
|
2099
|
-
map.set(
|
|
3668
|
+
map.set(toKey6(keyValue), row);
|
|
2100
3669
|
}
|
|
2101
3670
|
return map;
|
|
2102
3671
|
};
|
|
@@ -2127,12 +3696,12 @@ var loadBelongsToManyRelation = async (ctx, rootTable, _relationName, relation)
|
|
|
2127
3696
|
if (rootValue === null || rootValue === void 0 || targetValue === null || targetValue === void 0) {
|
|
2128
3697
|
continue;
|
|
2129
3698
|
}
|
|
2130
|
-
const bucket = rootLookup.get(
|
|
3699
|
+
const bucket = rootLookup.get(toKey6(rootValue)) ?? [];
|
|
2131
3700
|
bucket.push({
|
|
2132
3701
|
targetId: targetValue,
|
|
2133
3702
|
pivot: { ...pivot }
|
|
2134
3703
|
});
|
|
2135
|
-
rootLookup.set(
|
|
3704
|
+
rootLookup.set(toKey6(rootValue), bucket);
|
|
2136
3705
|
targetIds.add(targetValue);
|
|
2137
3706
|
}
|
|
2138
3707
|
if (!targetIds.size) {
|
|
@@ -2149,13 +3718,13 @@ var loadBelongsToManyRelation = async (ctx, rootTable, _relationName, relation)
|
|
|
2149
3718
|
for (const row of targetRows) {
|
|
2150
3719
|
const pkValue = row[targetKey];
|
|
2151
3720
|
if (pkValue === null || pkValue === void 0) continue;
|
|
2152
|
-
targetMap.set(
|
|
3721
|
+
targetMap.set(toKey6(pkValue), row);
|
|
2153
3722
|
}
|
|
2154
3723
|
const result = /* @__PURE__ */ new Map();
|
|
2155
3724
|
for (const [rootId, entries] of rootLookup.entries()) {
|
|
2156
3725
|
const bucket = [];
|
|
2157
3726
|
for (const entry of entries) {
|
|
2158
|
-
const targetRow = targetMap.get(
|
|
3727
|
+
const targetRow = targetMap.get(toKey6(entry.targetId));
|
|
2159
3728
|
if (!targetRow) continue;
|
|
2160
3729
|
bucket.push({
|
|
2161
3730
|
...targetRow,
|
|
@@ -2250,18 +3819,29 @@ var createEntityFromRow = (ctx, table, row, lazyRelations = []) => {
|
|
|
2250
3819
|
}
|
|
2251
3820
|
return entity;
|
|
2252
3821
|
};
|
|
2253
|
-
var
|
|
3822
|
+
var toKey7 = (value) => value === null || value === void 0 ? "" : String(value);
|
|
2254
3823
|
var populateHydrationCache = (entity, row, meta) => {
|
|
2255
3824
|
for (const relationName of Object.keys(meta.table.relations)) {
|
|
2256
3825
|
const relation = meta.table.relations[relationName];
|
|
2257
3826
|
const data = row[relationName];
|
|
3827
|
+
if (relation.type === RelationKinds.HasOne) {
|
|
3828
|
+
const localKey = relation.localKey || findPrimaryKey(meta.table);
|
|
3829
|
+
const rootValue = entity[localKey];
|
|
3830
|
+
if (rootValue === void 0 || rootValue === null) continue;
|
|
3831
|
+
if (!data || typeof data !== "object") continue;
|
|
3832
|
+
const cache = /* @__PURE__ */ new Map();
|
|
3833
|
+
cache.set(toKey7(rootValue), data);
|
|
3834
|
+
meta.relationHydration.set(relationName, cache);
|
|
3835
|
+
meta.relationCache.set(relationName, Promise.resolve(cache));
|
|
3836
|
+
continue;
|
|
3837
|
+
}
|
|
2258
3838
|
if (!Array.isArray(data)) continue;
|
|
2259
3839
|
if (relation.type === RelationKinds.HasMany || relation.type === RelationKinds.BelongsToMany) {
|
|
2260
3840
|
const localKey = relation.localKey || findPrimaryKey(meta.table);
|
|
2261
3841
|
const rootValue = entity[localKey];
|
|
2262
3842
|
if (rootValue === void 0 || rootValue === null) continue;
|
|
2263
3843
|
const cache = /* @__PURE__ */ new Map();
|
|
2264
|
-
cache.set(
|
|
3844
|
+
cache.set(toKey7(rootValue), data);
|
|
2265
3845
|
meta.relationHydration.set(relationName, cache);
|
|
2266
3846
|
meta.relationCache.set(relationName, Promise.resolve(cache));
|
|
2267
3847
|
continue;
|
|
@@ -2272,7 +3852,7 @@ var populateHydrationCache = (entity, row, meta) => {
|
|
|
2272
3852
|
for (const item of data) {
|
|
2273
3853
|
const pkValue = item[targetKey];
|
|
2274
3854
|
if (pkValue === void 0 || pkValue === null) continue;
|
|
2275
|
-
cache.set(
|
|
3855
|
+
cache.set(toKey7(pkValue), item);
|
|
2276
3856
|
}
|
|
2277
3857
|
if (cache.size) {
|
|
2278
3858
|
meta.relationHydration.set(relationName, cache);
|
|
@@ -2295,6 +3875,26 @@ var getRelationWrapper = (meta, relationName, owner) => {
|
|
|
2295
3875
|
};
|
|
2296
3876
|
var instantiateWrapper = (meta, relationName, relation, owner) => {
|
|
2297
3877
|
switch (relation.type) {
|
|
3878
|
+
case RelationKinds.HasOne: {
|
|
3879
|
+
const hasOne2 = relation;
|
|
3880
|
+
const localKey = hasOne2.localKey || findPrimaryKey(meta.table);
|
|
3881
|
+
const loader = () => relationLoaderCache(
|
|
3882
|
+
meta,
|
|
3883
|
+
relationName,
|
|
3884
|
+
() => loadHasOneRelation(meta.ctx, meta.table, relationName, hasOne2)
|
|
3885
|
+
);
|
|
3886
|
+
return new DefaultHasOneReference(
|
|
3887
|
+
meta.ctx,
|
|
3888
|
+
meta,
|
|
3889
|
+
owner,
|
|
3890
|
+
relationName,
|
|
3891
|
+
hasOne2,
|
|
3892
|
+
meta.table,
|
|
3893
|
+
loader,
|
|
3894
|
+
(row) => createEntityFromRow(meta.ctx, hasOne2.target, row),
|
|
3895
|
+
localKey
|
|
3896
|
+
);
|
|
3897
|
+
}
|
|
2298
3898
|
case RelationKinds.HasMany: {
|
|
2299
3899
|
const hasMany2 = relation;
|
|
2300
3900
|
const localKey = hasMany2.localKey || findPrimaryKey(meta.table);
|
|
@@ -2375,31 +3975,43 @@ var flattenResults = (results) => {
|
|
|
2375
3975
|
}
|
|
2376
3976
|
return rows;
|
|
2377
3977
|
};
|
|
2378
|
-
async
|
|
3978
|
+
var executeWithEntityContext = async (entityCtx, qb) => {
|
|
2379
3979
|
const ast = qb.getAST();
|
|
2380
|
-
const compiled =
|
|
2381
|
-
const executed = await
|
|
3980
|
+
const compiled = entityCtx.dialect.compileSelect(ast);
|
|
3981
|
+
const executed = await entityCtx.executor.executeSql(compiled.sql, compiled.params);
|
|
2382
3982
|
const rows = flattenResults(executed);
|
|
2383
3983
|
if (ast.setOps && ast.setOps.length > 0) {
|
|
2384
|
-
return rows.map(
|
|
2385
|
-
(row) => createEntityProxy(ctx, qb.getTable(), row, qb.getLazyRelations())
|
|
2386
|
-
);
|
|
3984
|
+
return rows.map((row) => createEntityProxy(entityCtx, qb.getTable(), row, qb.getLazyRelations()));
|
|
2387
3985
|
}
|
|
2388
3986
|
const hydrated = hydrateRows(rows, qb.getHydrationPlan());
|
|
2389
|
-
return hydrated.map(
|
|
2390
|
-
|
|
2391
|
-
|
|
3987
|
+
return hydrated.map((row) => createEntityFromRow(entityCtx, qb.getTable(), row, qb.getLazyRelations()));
|
|
3988
|
+
};
|
|
3989
|
+
async function executeHydrated(session, qb) {
|
|
3990
|
+
return executeWithEntityContext(session, qb);
|
|
3991
|
+
}
|
|
3992
|
+
async function executeHydratedWithContexts(_execCtx, hydCtx, qb) {
|
|
3993
|
+
const entityCtx = hydCtx.entityContext;
|
|
3994
|
+
if (!entityCtx) {
|
|
3995
|
+
throw new Error("Hydration context is missing an EntityContext");
|
|
3996
|
+
}
|
|
3997
|
+
return executeWithEntityContext(entityCtx, qb);
|
|
2392
3998
|
}
|
|
2393
3999
|
|
|
2394
4000
|
// src/query-builder/select.ts
|
|
2395
4001
|
var SelectQueryBuilder = class _SelectQueryBuilder {
|
|
2396
4002
|
/**
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
4003
|
+
|
|
4004
|
+
* Creates a new SelectQueryBuilder instance
|
|
4005
|
+
|
|
4006
|
+
* @param table - Table definition to query
|
|
4007
|
+
|
|
4008
|
+
* @param state - Optional initial query state
|
|
4009
|
+
|
|
4010
|
+
* @param hydration - Optional hydration manager
|
|
4011
|
+
|
|
4012
|
+
* @param dependencies - Optional query builder dependencies
|
|
4013
|
+
|
|
4014
|
+
*/
|
|
2403
4015
|
constructor(table, state, hydration, dependencies, lazyRelations) {
|
|
2404
4016
|
const deps = resolveSelectQueryBuilderDependencies(dependencies);
|
|
2405
4017
|
this.env = { table, deps };
|
|
@@ -2436,112 +4048,168 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
|
|
|
2436
4048
|
return this.applyAst(this.context, (service) => service.withSetOperation(operator, subAst));
|
|
2437
4049
|
}
|
|
2438
4050
|
/**
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
4051
|
+
|
|
4052
|
+
* Selects specific columns for the query
|
|
4053
|
+
|
|
4054
|
+
* @param columns - Record of column definitions, function nodes, case expressions, or window functions
|
|
4055
|
+
|
|
4056
|
+
* @returns New query builder instance with selected columns
|
|
4057
|
+
|
|
4058
|
+
*/
|
|
2443
4059
|
select(columns) {
|
|
2444
4060
|
return this.clone(this.columnSelector.select(this.context, columns));
|
|
2445
4061
|
}
|
|
2446
4062
|
/**
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
4063
|
+
|
|
4064
|
+
* Selects raw column expressions
|
|
4065
|
+
|
|
4066
|
+
* @param cols - Column expressions as strings
|
|
4067
|
+
|
|
4068
|
+
* @returns New query builder instance with raw column selections
|
|
4069
|
+
|
|
4070
|
+
*/
|
|
2451
4071
|
selectRaw(...cols) {
|
|
2452
4072
|
return this.clone(this.columnSelector.selectRaw(this.context, cols));
|
|
2453
4073
|
}
|
|
2454
4074
|
/**
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
4075
|
+
|
|
4076
|
+
* Adds a Common Table Expression (CTE) to the query
|
|
4077
|
+
|
|
4078
|
+
* @param name - Name of the CTE
|
|
4079
|
+
|
|
4080
|
+
* @param query - Query builder or query node for the CTE
|
|
4081
|
+
|
|
4082
|
+
* @param columns - Optional column names for the CTE
|
|
4083
|
+
|
|
4084
|
+
* @returns New query builder instance with the CTE
|
|
4085
|
+
|
|
4086
|
+
*/
|
|
2461
4087
|
with(name, query, columns) {
|
|
2462
4088
|
const subAst = this.resolveQueryNode(query);
|
|
2463
4089
|
const nextContext = this.applyAst(this.context, (service) => service.withCte(name, subAst, columns, false));
|
|
2464
4090
|
return this.clone(nextContext);
|
|
2465
4091
|
}
|
|
2466
4092
|
/**
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
4093
|
+
|
|
4094
|
+
* Adds a recursive Common Table Expression (CTE) to the query
|
|
4095
|
+
|
|
4096
|
+
* @param name - Name of the CTE
|
|
4097
|
+
|
|
4098
|
+
* @param query - Query builder or query node for the CTE
|
|
4099
|
+
|
|
4100
|
+
* @param columns - Optional column names for the CTE
|
|
4101
|
+
|
|
4102
|
+
* @returns New query builder instance with the recursive CTE
|
|
4103
|
+
|
|
4104
|
+
*/
|
|
2473
4105
|
withRecursive(name, query, columns) {
|
|
2474
4106
|
const subAst = this.resolveQueryNode(query);
|
|
2475
4107
|
const nextContext = this.applyAst(this.context, (service) => service.withCte(name, subAst, columns, true));
|
|
2476
4108
|
return this.clone(nextContext);
|
|
2477
4109
|
}
|
|
2478
4110
|
/**
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
4111
|
+
|
|
4112
|
+
* Selects a subquery as a column
|
|
4113
|
+
|
|
4114
|
+
* @param alias - Alias for the subquery column
|
|
4115
|
+
|
|
4116
|
+
* @param sub - Query builder or query node for the subquery
|
|
4117
|
+
|
|
4118
|
+
* @returns New query builder instance with the subquery selection
|
|
4119
|
+
|
|
4120
|
+
*/
|
|
2484
4121
|
selectSubquery(alias, sub) {
|
|
2485
4122
|
const query = this.resolveQueryNode(sub);
|
|
2486
4123
|
return this.clone(this.columnSelector.selectSubquery(this.context, alias, query));
|
|
2487
4124
|
}
|
|
2488
4125
|
/**
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
4126
|
+
|
|
4127
|
+
* Adds an INNER JOIN to the query
|
|
4128
|
+
|
|
4129
|
+
* @param table - Table to join
|
|
4130
|
+
|
|
4131
|
+
* @param condition - Join condition expression
|
|
4132
|
+
|
|
4133
|
+
* @returns New query builder instance with the INNER JOIN
|
|
4134
|
+
|
|
4135
|
+
*/
|
|
2494
4136
|
innerJoin(table, condition) {
|
|
2495
4137
|
const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.INNER);
|
|
2496
4138
|
return this.clone(nextContext);
|
|
2497
4139
|
}
|
|
2498
4140
|
/**
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
4141
|
+
|
|
4142
|
+
* Adds a LEFT JOIN to the query
|
|
4143
|
+
|
|
4144
|
+
* @param table - Table to join
|
|
4145
|
+
|
|
4146
|
+
* @param condition - Join condition expression
|
|
4147
|
+
|
|
4148
|
+
* @returns New query builder instance with the LEFT JOIN
|
|
4149
|
+
|
|
4150
|
+
*/
|
|
2504
4151
|
leftJoin(table, condition) {
|
|
2505
4152
|
const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.LEFT);
|
|
2506
4153
|
return this.clone(nextContext);
|
|
2507
4154
|
}
|
|
2508
4155
|
/**
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
4156
|
+
|
|
4157
|
+
* Adds a RIGHT JOIN to the query
|
|
4158
|
+
|
|
4159
|
+
* @param table - Table to join
|
|
4160
|
+
|
|
4161
|
+
* @param condition - Join condition expression
|
|
4162
|
+
|
|
4163
|
+
* @returns New query builder instance with the RIGHT JOIN
|
|
4164
|
+
|
|
4165
|
+
*/
|
|
2514
4166
|
rightJoin(table, condition) {
|
|
2515
4167
|
const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.RIGHT);
|
|
2516
4168
|
return this.clone(nextContext);
|
|
2517
4169
|
}
|
|
2518
4170
|
/**
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
4171
|
+
|
|
4172
|
+
* Matches records based on a relationship
|
|
4173
|
+
|
|
4174
|
+
* @param relationName - Name of the relationship to match
|
|
4175
|
+
|
|
4176
|
+
* @param predicate - Optional predicate expression
|
|
4177
|
+
|
|
4178
|
+
* @returns New query builder instance with the relationship match
|
|
4179
|
+
|
|
4180
|
+
*/
|
|
2524
4181
|
match(relationName, predicate) {
|
|
2525
4182
|
const nextContext = this.relationManager.match(this.context, relationName, predicate);
|
|
2526
4183
|
return this.clone(nextContext);
|
|
2527
4184
|
}
|
|
2528
4185
|
/**
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
4186
|
+
|
|
4187
|
+
* Joins a related table
|
|
4188
|
+
|
|
4189
|
+
* @param relationName - Name of the relationship to join
|
|
4190
|
+
|
|
4191
|
+
* @param joinKind - Type of join (defaults to INNER)
|
|
4192
|
+
|
|
4193
|
+
* @param extraCondition - Optional additional join condition
|
|
4194
|
+
|
|
4195
|
+
* @returns New query builder instance with the relationship join
|
|
4196
|
+
|
|
4197
|
+
*/
|
|
2535
4198
|
joinRelation(relationName, joinKind = JOIN_KINDS.INNER, extraCondition) {
|
|
2536
4199
|
const nextContext = this.relationManager.joinRelation(this.context, relationName, joinKind, extraCondition);
|
|
2537
4200
|
return this.clone(nextContext);
|
|
2538
4201
|
}
|
|
2539
4202
|
/**
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
4203
|
+
|
|
4204
|
+
* Includes related data in the query results
|
|
4205
|
+
|
|
4206
|
+
* @param relationName - Name of the relationship to include
|
|
4207
|
+
|
|
4208
|
+
* @param options - Optional include options
|
|
4209
|
+
|
|
4210
|
+
* @returns New query builder instance with the relationship inclusion
|
|
4211
|
+
|
|
4212
|
+
*/
|
|
2545
4213
|
include(relationName, options) {
|
|
2546
4214
|
const nextContext = this.relationManager.include(this.context, relationName, options);
|
|
2547
4215
|
return this.clone(nextContext);
|
|
@@ -2560,125 +4228,186 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
|
|
|
2560
4228
|
async execute(ctx) {
|
|
2561
4229
|
return executeHydrated(ctx, this);
|
|
2562
4230
|
}
|
|
4231
|
+
async executeWithContexts(execCtx, hydCtx) {
|
|
4232
|
+
return executeHydratedWithContexts(execCtx, hydCtx, this);
|
|
4233
|
+
}
|
|
2563
4234
|
/**
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
4235
|
+
|
|
4236
|
+
* Adds a WHERE condition to the query
|
|
4237
|
+
|
|
4238
|
+
* @param expr - Expression for the WHERE clause
|
|
4239
|
+
|
|
4240
|
+
* @returns New query builder instance with the WHERE condition
|
|
4241
|
+
|
|
4242
|
+
*/
|
|
2568
4243
|
where(expr) {
|
|
2569
4244
|
const nextContext = this.applyAst(this.context, (service) => service.withWhere(expr));
|
|
2570
4245
|
return this.clone(nextContext);
|
|
2571
4246
|
}
|
|
2572
4247
|
/**
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
4248
|
+
|
|
4249
|
+
* Adds a GROUP BY clause to the query
|
|
4250
|
+
|
|
4251
|
+
* @param col - Column definition or column node to group by
|
|
4252
|
+
|
|
4253
|
+
* @returns New query builder instance with the GROUP BY clause
|
|
4254
|
+
|
|
4255
|
+
*/
|
|
2577
4256
|
groupBy(col) {
|
|
2578
4257
|
const nextContext = this.applyAst(this.context, (service) => service.withGroupBy(col));
|
|
2579
4258
|
return this.clone(nextContext);
|
|
2580
4259
|
}
|
|
2581
4260
|
/**
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
4261
|
+
|
|
4262
|
+
* Adds a HAVING condition to the query
|
|
4263
|
+
|
|
4264
|
+
* @param expr - Expression for the HAVING clause
|
|
4265
|
+
|
|
4266
|
+
* @returns New query builder instance with the HAVING condition
|
|
4267
|
+
|
|
4268
|
+
*/
|
|
2586
4269
|
having(expr) {
|
|
2587
4270
|
const nextContext = this.applyAst(this.context, (service) => service.withHaving(expr));
|
|
2588
4271
|
return this.clone(nextContext);
|
|
2589
4272
|
}
|
|
2590
4273
|
/**
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
4274
|
+
|
|
4275
|
+
* Adds an ORDER BY clause to the query
|
|
4276
|
+
|
|
4277
|
+
* @param col - Column definition or column node to order by
|
|
4278
|
+
|
|
4279
|
+
* @param direction - Order direction (defaults to ASC)
|
|
4280
|
+
|
|
4281
|
+
* @returns New query builder instance with the ORDER BY clause
|
|
4282
|
+
|
|
4283
|
+
*/
|
|
2596
4284
|
orderBy(col, direction = ORDER_DIRECTIONS.ASC) {
|
|
2597
4285
|
const nextContext = this.applyAst(this.context, (service) => service.withOrderBy(col, direction));
|
|
2598
4286
|
return this.clone(nextContext);
|
|
2599
4287
|
}
|
|
2600
4288
|
/**
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
4289
|
+
|
|
4290
|
+
* Adds a DISTINCT clause to the query
|
|
4291
|
+
|
|
4292
|
+
* @param cols - Columns to make distinct
|
|
4293
|
+
|
|
4294
|
+
* @returns New query builder instance with the DISTINCT clause
|
|
4295
|
+
|
|
4296
|
+
*/
|
|
2605
4297
|
distinct(...cols) {
|
|
2606
4298
|
return this.clone(this.columnSelector.distinct(this.context, cols));
|
|
2607
4299
|
}
|
|
2608
4300
|
/**
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
4301
|
+
|
|
4302
|
+
* Adds a LIMIT clause to the query
|
|
4303
|
+
|
|
4304
|
+
* @param n - Maximum number of rows to return
|
|
4305
|
+
|
|
4306
|
+
* @returns New query builder instance with the LIMIT clause
|
|
4307
|
+
|
|
4308
|
+
*/
|
|
2613
4309
|
limit(n) {
|
|
2614
4310
|
const nextContext = this.applyAst(this.context, (service) => service.withLimit(n));
|
|
2615
4311
|
return this.clone(nextContext);
|
|
2616
4312
|
}
|
|
2617
4313
|
/**
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
4314
|
+
|
|
4315
|
+
* Adds an OFFSET clause to the query
|
|
4316
|
+
|
|
4317
|
+
* @param n - Number of rows to skip
|
|
4318
|
+
|
|
4319
|
+
* @returns New query builder instance with the OFFSET clause
|
|
4320
|
+
|
|
4321
|
+
*/
|
|
2622
4322
|
offset(n) {
|
|
2623
4323
|
const nextContext = this.applyAst(this.context, (service) => service.withOffset(n));
|
|
2624
4324
|
return this.clone(nextContext);
|
|
2625
4325
|
}
|
|
2626
4326
|
/**
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
4327
|
+
|
|
4328
|
+
* Combines this query with another using UNION
|
|
4329
|
+
|
|
4330
|
+
* @param query - Query to union with
|
|
4331
|
+
|
|
4332
|
+
* @returns New query builder instance with the set operation
|
|
4333
|
+
|
|
4334
|
+
*/
|
|
2631
4335
|
union(query) {
|
|
2632
4336
|
return this.clone(this.applySetOperation("UNION", query));
|
|
2633
4337
|
}
|
|
2634
4338
|
/**
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
4339
|
+
|
|
4340
|
+
* Combines this query with another using UNION ALL
|
|
4341
|
+
|
|
4342
|
+
* @param query - Query to union with
|
|
4343
|
+
|
|
4344
|
+
* @returns New query builder instance with the set operation
|
|
4345
|
+
|
|
4346
|
+
*/
|
|
2639
4347
|
unionAll(query) {
|
|
2640
4348
|
return this.clone(this.applySetOperation("UNION ALL", query));
|
|
2641
4349
|
}
|
|
2642
4350
|
/**
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
4351
|
+
|
|
4352
|
+
* Combines this query with another using INTERSECT
|
|
4353
|
+
|
|
4354
|
+
* @param query - Query to intersect with
|
|
4355
|
+
|
|
4356
|
+
* @returns New query builder instance with the set operation
|
|
4357
|
+
|
|
4358
|
+
*/
|
|
2647
4359
|
intersect(query) {
|
|
2648
4360
|
return this.clone(this.applySetOperation("INTERSECT", query));
|
|
2649
4361
|
}
|
|
2650
4362
|
/**
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
4363
|
+
|
|
4364
|
+
* Combines this query with another using EXCEPT
|
|
4365
|
+
|
|
4366
|
+
* @param query - Query to subtract
|
|
4367
|
+
|
|
4368
|
+
* @returns New query builder instance with the set operation
|
|
4369
|
+
|
|
4370
|
+
*/
|
|
2655
4371
|
except(query) {
|
|
2656
4372
|
return this.clone(this.applySetOperation("EXCEPT", query));
|
|
2657
4373
|
}
|
|
2658
4374
|
/**
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
4375
|
+
|
|
4376
|
+
* Adds a WHERE EXISTS condition to the query
|
|
4377
|
+
|
|
4378
|
+
* @param subquery - Subquery to check for existence
|
|
4379
|
+
|
|
4380
|
+
* @returns New query builder instance with the WHERE EXISTS condition
|
|
4381
|
+
|
|
4382
|
+
*/
|
|
2663
4383
|
whereExists(subquery) {
|
|
2664
4384
|
const subAst = this.resolveQueryNode(subquery);
|
|
2665
4385
|
return this.where(exists(subAst));
|
|
2666
4386
|
}
|
|
2667
4387
|
/**
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
4388
|
+
|
|
4389
|
+
* Adds a WHERE NOT EXISTS condition to the query
|
|
4390
|
+
|
|
4391
|
+
* @param subquery - Subquery to check for non-existence
|
|
4392
|
+
|
|
4393
|
+
* @returns New query builder instance with the WHERE NOT EXISTS condition
|
|
4394
|
+
|
|
4395
|
+
*/
|
|
2672
4396
|
whereNotExists(subquery) {
|
|
2673
4397
|
const subAst = this.resolveQueryNode(subquery);
|
|
2674
4398
|
return this.where(notExists(subAst));
|
|
2675
4399
|
}
|
|
2676
4400
|
/**
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
4401
|
+
|
|
4402
|
+
* Adds a WHERE EXISTS condition based on a relationship
|
|
4403
|
+
|
|
4404
|
+
* @param relationName - Name of the relationship to check
|
|
4405
|
+
|
|
4406
|
+
* @param callback - Optional callback to modify the relationship query
|
|
4407
|
+
|
|
4408
|
+
* @returns New query builder instance with the relationship existence check
|
|
4409
|
+
|
|
4410
|
+
*/
|
|
2682
4411
|
whereHas(relationName, callback) {
|
|
2683
4412
|
const relation = this.env.table.relations[relationName];
|
|
2684
4413
|
if (!relation) {
|
|
@@ -2693,11 +4422,16 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
|
|
|
2693
4422
|
return this.where(exists(finalSubAst));
|
|
2694
4423
|
}
|
|
2695
4424
|
/**
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
4425
|
+
|
|
4426
|
+
* Adds a WHERE NOT EXISTS condition based on a relationship
|
|
4427
|
+
|
|
4428
|
+
* @param relationName - Name of the relationship to check
|
|
4429
|
+
|
|
4430
|
+
* @param callback - Optional callback to modify the relationship query
|
|
4431
|
+
|
|
4432
|
+
* @returns New query builder instance with the relationship non-existence check
|
|
4433
|
+
|
|
4434
|
+
*/
|
|
2701
4435
|
whereHasNot(relationName, callback) {
|
|
2702
4436
|
const relation = this.env.table.relations[relationName];
|
|
2703
4437
|
if (!relation) {
|
|
@@ -2712,32 +4446,47 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
|
|
|
2712
4446
|
return this.where(notExists(finalSubAst));
|
|
2713
4447
|
}
|
|
2714
4448
|
/**
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
4449
|
+
|
|
4450
|
+
* Compiles the query to SQL for a specific dialect
|
|
4451
|
+
|
|
4452
|
+
* @param dialect - Database dialect to compile for
|
|
4453
|
+
|
|
4454
|
+
* @returns Compiled query with SQL and parameters
|
|
4455
|
+
|
|
4456
|
+
*/
|
|
2719
4457
|
compile(dialect) {
|
|
2720
|
-
|
|
4458
|
+
const resolved = resolveDialectInput(dialect);
|
|
4459
|
+
return resolved.compileSelect(this.context.state.ast);
|
|
2721
4460
|
}
|
|
2722
4461
|
/**
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
4462
|
+
|
|
4463
|
+
* Converts the query to SQL string for a specific dialect
|
|
4464
|
+
|
|
4465
|
+
* @param dialect - Database dialect to generate SQL for
|
|
4466
|
+
|
|
4467
|
+
* @returns SQL string representation of the query
|
|
4468
|
+
|
|
4469
|
+
*/
|
|
2727
4470
|
toSql(dialect) {
|
|
2728
4471
|
return this.compile(dialect).sql;
|
|
2729
4472
|
}
|
|
2730
4473
|
/**
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
4474
|
+
|
|
4475
|
+
* Gets the hydration plan for the query
|
|
4476
|
+
|
|
4477
|
+
* @returns Hydration plan or undefined if none exists
|
|
4478
|
+
|
|
4479
|
+
*/
|
|
2734
4480
|
getHydrationPlan() {
|
|
2735
4481
|
return this.context.hydration.getPlan();
|
|
2736
4482
|
}
|
|
2737
4483
|
/**
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
4484
|
+
|
|
4485
|
+
* Gets the Abstract Syntax Tree (AST) representation of the query
|
|
4486
|
+
|
|
4487
|
+
* @returns Query AST with hydration applied
|
|
4488
|
+
|
|
4489
|
+
*/
|
|
2741
4490
|
getAST() {
|
|
2742
4491
|
return this.context.hydration.applyToAst(this.context.state.ast);
|
|
2743
4492
|
}
|
|
@@ -2768,6 +4517,15 @@ var buildRelationDefinitions = (meta, tableMap) => {
|
|
|
2768
4517
|
const relations = {};
|
|
2769
4518
|
for (const [name, relation] of Object.entries(meta.relations)) {
|
|
2770
4519
|
switch (relation.kind) {
|
|
4520
|
+
case RelationKinds.HasOne: {
|
|
4521
|
+
relations[name] = hasOne(
|
|
4522
|
+
resolveTableTarget(relation.target, tableMap),
|
|
4523
|
+
relation.foreignKey,
|
|
4524
|
+
relation.localKey,
|
|
4525
|
+
relation.cascade
|
|
4526
|
+
);
|
|
4527
|
+
break;
|
|
4528
|
+
}
|
|
2771
4529
|
case RelationKinds.HasMany: {
|
|
2772
4530
|
relations[name] = hasMany(
|
|
2773
4531
|
resolveTableTarget(relation.target, tableMap),
|
|
@@ -2839,6 +4597,7 @@ var selectFromEntity = (ctor) => {
|
|
|
2839
4597
|
Column,
|
|
2840
4598
|
Entity,
|
|
2841
4599
|
HasMany,
|
|
4600
|
+
HasOne,
|
|
2842
4601
|
PrimaryKey,
|
|
2843
4602
|
bootstrapEntities,
|
|
2844
4603
|
getTableDefFromEntity,
|