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.
Files changed (129) hide show
  1. package/README.md +69 -67
  2. package/dist/decorators/index.cjs +1983 -224
  3. package/dist/decorators/index.cjs.map +1 -1
  4. package/dist/decorators/index.d.cts +6 -6
  5. package/dist/decorators/index.d.ts +6 -6
  6. package/dist/decorators/index.js +1982 -224
  7. package/dist/decorators/index.js.map +1 -1
  8. package/dist/index.cjs +5284 -3751
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.cts +524 -169
  11. package/dist/index.d.ts +524 -169
  12. package/dist/index.js +5197 -3736
  13. package/dist/index.js.map +1 -1
  14. package/dist/{select-CCp1oz9p.d.cts → select-BKZrMRCQ.d.cts} +555 -94
  15. package/dist/{select-CCp1oz9p.d.ts → select-BKZrMRCQ.d.ts} +555 -94
  16. package/package.json +1 -1
  17. package/src/codegen/naming-strategy.ts +64 -0
  18. package/src/codegen/typescript.ts +19 -21
  19. package/src/core/ast/adapters.ts +21 -0
  20. package/src/core/ast/aggregate-functions.ts +13 -13
  21. package/src/core/ast/builders.ts +56 -43
  22. package/src/core/ast/expression-builders.ts +34 -34
  23. package/src/core/ast/expression-nodes.ts +18 -16
  24. package/src/core/ast/expression-visitor.ts +122 -69
  25. package/src/core/ast/expression.ts +6 -4
  26. package/src/core/ast/join-metadata.ts +15 -0
  27. package/src/core/ast/join-node.ts +22 -20
  28. package/src/core/ast/join.ts +5 -5
  29. package/src/core/ast/query.ts +52 -88
  30. package/src/core/ast/types.ts +20 -0
  31. package/src/core/ast/window-functions.ts +55 -55
  32. package/src/core/ddl/dialects/base-schema-dialect.ts +20 -6
  33. package/src/core/ddl/dialects/mssql-schema-dialect.ts +32 -8
  34. package/src/core/ddl/dialects/mysql-schema-dialect.ts +21 -10
  35. package/src/core/ddl/dialects/postgres-schema-dialect.ts +52 -7
  36. package/src/core/ddl/dialects/sqlite-schema-dialect.ts +23 -9
  37. package/src/core/ddl/introspect/catalogs/index.ts +1 -0
  38. package/src/core/ddl/introspect/catalogs/postgres.ts +143 -0
  39. package/src/core/ddl/introspect/context.ts +9 -0
  40. package/src/core/ddl/introspect/functions/postgres.ts +26 -0
  41. package/src/core/ddl/introspect/mssql.ts +149 -149
  42. package/src/core/ddl/introspect/mysql.ts +99 -99
  43. package/src/core/ddl/introspect/postgres.ts +245 -154
  44. package/src/core/ddl/introspect/registry.ts +26 -0
  45. package/src/core/ddl/introspect/run-select.ts +25 -0
  46. package/src/core/ddl/introspect/sqlite.ts +7 -7
  47. package/src/core/ddl/introspect/types.ts +23 -19
  48. package/src/core/ddl/introspect/utils.ts +1 -1
  49. package/src/core/ddl/naming-strategy.ts +10 -0
  50. package/src/core/ddl/schema-dialect.ts +41 -0
  51. package/src/core/ddl/schema-diff.ts +211 -179
  52. package/src/core/ddl/schema-generator.ts +17 -90
  53. package/src/core/ddl/schema-introspect.ts +25 -32
  54. package/src/core/ddl/schema-plan-executor.ts +17 -0
  55. package/src/core/ddl/schema-types.ts +46 -39
  56. package/src/core/ddl/sql-writing.ts +170 -0
  57. package/src/core/dialect/abstract.ts +172 -126
  58. package/src/core/dialect/base/cte-compiler.ts +33 -0
  59. package/src/core/dialect/base/function-table-formatter.ts +132 -0
  60. package/src/core/dialect/base/groupby-compiler.ts +21 -0
  61. package/src/core/dialect/base/join-compiler.ts +26 -0
  62. package/src/core/dialect/base/orderby-compiler.ts +21 -0
  63. package/src/core/dialect/base/pagination-strategy.ts +32 -0
  64. package/src/core/dialect/base/returning-strategy.ts +56 -0
  65. package/src/core/dialect/base/sql-dialect.ts +181 -204
  66. package/src/core/dialect/dialect-factory.ts +91 -0
  67. package/src/core/dialect/mssql/functions.ts +101 -0
  68. package/src/core/dialect/mssql/index.ts +128 -126
  69. package/src/core/dialect/mysql/functions.ts +101 -0
  70. package/src/core/dialect/mysql/index.ts +20 -18
  71. package/src/core/dialect/postgres/functions.ts +95 -0
  72. package/src/core/dialect/postgres/index.ts +30 -28
  73. package/src/core/dialect/sqlite/functions.ts +115 -0
  74. package/src/core/dialect/sqlite/index.ts +30 -28
  75. package/src/core/driver/database-driver.ts +11 -0
  76. package/src/core/driver/mssql-driver.ts +20 -0
  77. package/src/core/driver/mysql-driver.ts +20 -0
  78. package/src/core/driver/postgres-driver.ts +20 -0
  79. package/src/core/driver/sqlite-driver.ts +20 -0
  80. package/src/core/execution/db-executor.ts +63 -0
  81. package/src/core/execution/executors/mssql-executor.ts +39 -0
  82. package/src/core/execution/executors/mysql-executor.ts +47 -0
  83. package/src/core/execution/executors/postgres-executor.ts +32 -0
  84. package/src/core/execution/executors/sqlite-executor.ts +31 -0
  85. package/src/core/functions/datetime.ts +132 -0
  86. package/src/core/functions/numeric.ts +179 -0
  87. package/src/core/functions/standard-strategy.ts +47 -0
  88. package/src/core/functions/text.ts +147 -0
  89. package/src/core/functions/types.ts +18 -0
  90. package/src/core/hydration/types.ts +57 -0
  91. package/src/decorators/bootstrap.ts +10 -0
  92. package/src/decorators/column.ts +13 -4
  93. package/src/decorators/relations.ts +15 -0
  94. package/src/index.ts +37 -19
  95. package/src/orm/entity-context.ts +30 -0
  96. package/src/orm/entity-meta.ts +2 -2
  97. package/src/orm/entity-metadata.ts +8 -6
  98. package/src/orm/entity.ts +72 -41
  99. package/src/orm/execute.ts +42 -25
  100. package/src/orm/execution-context.ts +12 -0
  101. package/src/orm/hydration-context.ts +14 -0
  102. package/src/orm/hydration.ts +25 -17
  103. package/src/orm/identity-map.ts +4 -0
  104. package/src/orm/interceptor-pipeline.ts +29 -0
  105. package/src/orm/lazy-batch.ts +50 -6
  106. package/src/orm/orm-session.ts +234 -0
  107. package/src/orm/orm.ts +58 -0
  108. package/src/orm/query-logger.ts +1 -1
  109. package/src/orm/relation-change-processor.ts +48 -3
  110. package/src/orm/relations/belongs-to.ts +45 -44
  111. package/src/orm/relations/has-many.ts +44 -43
  112. package/src/orm/relations/has-one.ts +140 -0
  113. package/src/orm/relations/many-to-many.ts +46 -45
  114. package/src/orm/transaction-runner.ts +1 -1
  115. package/src/orm/unit-of-work.ts +66 -61
  116. package/src/query-builder/delete.ts +22 -5
  117. package/src/query-builder/hydration-manager.ts +2 -1
  118. package/src/query-builder/hydration-planner.ts +8 -7
  119. package/src/query-builder/insert.ts +22 -5
  120. package/src/query-builder/relation-conditions.ts +9 -8
  121. package/src/query-builder/relation-service.ts +3 -2
  122. package/src/query-builder/select.ts +575 -64
  123. package/src/query-builder/update.ts +22 -5
  124. package/src/schema/column.ts +246 -246
  125. package/src/schema/relation.ts +35 -1
  126. package/src/schema/table.ts +28 -28
  127. package/src/schema/types.ts +41 -31
  128. package/src/orm/db-executor.ts +0 -11
  129. 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: input.type ?? input.type,
138
- args: input.args ?? input.args,
139
- notNull: input.notNull ?? input.notNull,
140
- primary: input.primary ?? input.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 columnOperand = (col) => toNode(col);
323
- var createBinaryExpression = (operator, left, right, escape) => {
324
- const node = {
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 node;
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.relationName === relationName);
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/belongs-to.ts
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
- hideInternal2(this, ["ctx", "meta", "root", "relationName", "relation", "rootTable", "loader", "createEntity", "targetKey"]);
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(toKey3(fkValue));
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 toKey4 = (value) => value === null || value === void 0 ? "" : String(value);
1887
- var hideInternal3 = (obj, keys) => {
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
- hideInternal3(this, ["ctx", "meta", "root", "relationName", "relation", "rootTable", "loader", "createEntity", "localKey"]);
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 = toKey4(this.root[this.localKey]);
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) => toKey4(id)));
1967
- const currentIds = new Set(this.items.map((item) => toKey4(this.extractId(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 = toKey4(this.extractId(item));
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 toKey5 = (value) => value === null || value === void 0 ? "" : String(value);
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 = toKey5(fkValue);
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(toKey5(keyValue), row);
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(toKey5(rootValue)) ?? [];
3699
+ const bucket = rootLookup.get(toKey6(rootValue)) ?? [];
2131
3700
  bucket.push({
2132
3701
  targetId: targetValue,
2133
3702
  pivot: { ...pivot }
2134
3703
  });
2135
- rootLookup.set(toKey5(rootValue), bucket);
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(toKey5(pkValue), row);
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(toKey5(entry.targetId));
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 toKey6 = (value) => value === null || value === void 0 ? "" : String(value);
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(toKey6(rootValue), data);
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(toKey6(pkValue), item);
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 function executeHydrated(ctx, qb) {
3978
+ var executeWithEntityContext = async (entityCtx, qb) => {
2379
3979
  const ast = qb.getAST();
2380
- const compiled = ctx.dialect.compileSelect(ast);
2381
- const executed = await ctx.executor.executeSql(compiled.sql, compiled.params);
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
- (row) => createEntityFromRow(ctx, qb.getTable(), row, qb.getLazyRelations())
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
- * Creates a new SelectQueryBuilder instance
2398
- * @param table - Table definition to query
2399
- * @param state - Optional initial query state
2400
- * @param hydration - Optional hydration manager
2401
- * @param dependencies - Optional query builder dependencies
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
- * Selects specific columns for the query
2440
- * @param columns - Record of column definitions, function nodes, case expressions, or window functions
2441
- * @returns New query builder instance with selected columns
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
- * Selects raw column expressions
2448
- * @param cols - Column expressions as strings
2449
- * @returns New query builder instance with raw column selections
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
- * Adds a Common Table Expression (CTE) to the query
2456
- * @param name - Name of the CTE
2457
- * @param query - Query builder or query node for the CTE
2458
- * @param columns - Optional column names for the CTE
2459
- * @returns New query builder instance with the CTE
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
- * Adds a recursive Common Table Expression (CTE) to the query
2468
- * @param name - Name of the CTE
2469
- * @param query - Query builder or query node for the CTE
2470
- * @param columns - Optional column names for the CTE
2471
- * @returns New query builder instance with the recursive CTE
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
- * Selects a subquery as a column
2480
- * @param alias - Alias for the subquery column
2481
- * @param sub - Query builder or query node for the subquery
2482
- * @returns New query builder instance with the subquery selection
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
- * Adds an INNER JOIN to the query
2490
- * @param table - Table to join
2491
- * @param condition - Join condition expression
2492
- * @returns New query builder instance with the INNER JOIN
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
- * Adds a LEFT JOIN to the query
2500
- * @param table - Table to join
2501
- * @param condition - Join condition expression
2502
- * @returns New query builder instance with the LEFT JOIN
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
- * Adds a RIGHT JOIN to the query
2510
- * @param table - Table to join
2511
- * @param condition - Join condition expression
2512
- * @returns New query builder instance with the RIGHT JOIN
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
- * Matches records based on a relationship
2520
- * @param relationName - Name of the relationship to match
2521
- * @param predicate - Optional predicate expression
2522
- * @returns New query builder instance with the relationship match
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
- * Joins a related table
2530
- * @param relationName - Name of the relationship to join
2531
- * @param joinKind - Type of join (defaults to INNER)
2532
- * @param extraCondition - Optional additional join condition
2533
- * @returns New query builder instance with the relationship join
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
- * Includes related data in the query results
2541
- * @param relationName - Name of the relationship to include
2542
- * @param options - Optional include options
2543
- * @returns New query builder instance with the relationship inclusion
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
- * Adds a WHERE condition to the query
2565
- * @param expr - Expression for the WHERE clause
2566
- * @returns New query builder instance with the WHERE condition
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
- * Adds a GROUP BY clause to the query
2574
- * @param col - Column definition or column node to group by
2575
- * @returns New query builder instance with the GROUP BY clause
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
- * Adds a HAVING condition to the query
2583
- * @param expr - Expression for the HAVING clause
2584
- * @returns New query builder instance with the HAVING condition
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
- * Adds an ORDER BY clause to the query
2592
- * @param col - Column definition or column node to order by
2593
- * @param direction - Order direction (defaults to ASC)
2594
- * @returns New query builder instance with the ORDER BY clause
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
- * Adds a DISTINCT clause to the query
2602
- * @param cols - Columns to make distinct
2603
- * @returns New query builder instance with the DISTINCT clause
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
- * Adds a LIMIT clause to the query
2610
- * @param n - Maximum number of rows to return
2611
- * @returns New query builder instance with the LIMIT clause
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
- * Adds an OFFSET clause to the query
2619
- * @param n - Number of rows to skip
2620
- * @returns New query builder instance with the OFFSET clause
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
- * Combines this query with another using UNION
2628
- * @param query - Query to union with
2629
- * @returns New query builder instance with the set operation
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
- * Combines this query with another using UNION ALL
2636
- * @param query - Query to union with
2637
- * @returns New query builder instance with the set operation
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
- * Combines this query with another using INTERSECT
2644
- * @param query - Query to intersect with
2645
- * @returns New query builder instance with the set operation
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
- * Combines this query with another using EXCEPT
2652
- * @param query - Query to subtract
2653
- * @returns New query builder instance with the set operation
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
- * Adds a WHERE EXISTS condition to the query
2660
- * @param subquery - Subquery to check for existence
2661
- * @returns New query builder instance with the WHERE EXISTS condition
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
- * Adds a WHERE NOT EXISTS condition to the query
2669
- * @param subquery - Subquery to check for non-existence
2670
- * @returns New query builder instance with the WHERE NOT EXISTS condition
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
- * Adds a WHERE EXISTS condition based on a relationship
2678
- * @param relationName - Name of the relationship to check
2679
- * @param callback - Optional callback to modify the relationship query
2680
- * @returns New query builder instance with the relationship existence check
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
- * Adds a WHERE NOT EXISTS condition based on a relationship
2697
- * @param relationName - Name of the relationship to check
2698
- * @param callback - Optional callback to modify the relationship query
2699
- * @returns New query builder instance with the relationship non-existence check
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
- * Compiles the query to SQL for a specific dialect
2716
- * @param dialect - Database dialect to compile for
2717
- * @returns Compiled query with SQL and parameters
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
- return dialect.compileSelect(this.context.state.ast);
4458
+ const resolved = resolveDialectInput(dialect);
4459
+ return resolved.compileSelect(this.context.state.ast);
2721
4460
  }
2722
4461
  /**
2723
- * Converts the query to SQL string for a specific dialect
2724
- * @param dialect - Database dialect to generate SQL for
2725
- * @returns SQL string representation of the query
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
- * Gets the hydration plan for the query
2732
- * @returns Hydration plan or undefined if none exists
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
- * Gets the Abstract Syntax Tree (AST) representation of the query
2739
- * @returns Query AST with hydration applied
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,