metal-orm 1.0.11 → 1.0.13

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 (54) hide show
  1. package/README.md +21 -18
  2. package/dist/decorators/index.cjs +317 -34
  3. package/dist/decorators/index.cjs.map +1 -1
  4. package/dist/decorators/index.d.cts +1 -1
  5. package/dist/decorators/index.d.ts +1 -1
  6. package/dist/decorators/index.js +317 -34
  7. package/dist/decorators/index.js.map +1 -1
  8. package/dist/index.cjs +1965 -267
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.cts +273 -23
  11. package/dist/index.d.ts +273 -23
  12. package/dist/index.js +1947 -267
  13. package/dist/index.js.map +1 -1
  14. package/dist/{select-654m4qy8.d.cts → select-CCp1oz9p.d.cts} +254 -4
  15. package/dist/{select-654m4qy8.d.ts → select-CCp1oz9p.d.ts} +254 -4
  16. package/package.json +3 -2
  17. package/src/core/ast/query.ts +40 -22
  18. package/src/core/ddl/dialects/base-schema-dialect.ts +48 -0
  19. package/src/core/ddl/dialects/index.ts +5 -0
  20. package/src/core/ddl/dialects/mssql-schema-dialect.ts +97 -0
  21. package/src/core/ddl/dialects/mysql-schema-dialect.ts +109 -0
  22. package/src/core/ddl/dialects/postgres-schema-dialect.ts +99 -0
  23. package/src/core/ddl/dialects/sqlite-schema-dialect.ts +103 -0
  24. package/src/core/ddl/introspect/mssql.ts +149 -0
  25. package/src/core/ddl/introspect/mysql.ts +99 -0
  26. package/src/core/ddl/introspect/postgres.ts +154 -0
  27. package/src/core/ddl/introspect/sqlite.ts +66 -0
  28. package/src/core/ddl/introspect/types.ts +19 -0
  29. package/src/core/ddl/introspect/utils.ts +27 -0
  30. package/src/core/ddl/schema-diff.ts +179 -0
  31. package/src/core/ddl/schema-generator.ts +229 -0
  32. package/src/core/ddl/schema-introspect.ts +32 -0
  33. package/src/core/ddl/schema-types.ts +39 -0
  34. package/src/core/dialect/abstract.ts +122 -37
  35. package/src/core/dialect/base/sql-dialect.ts +204 -0
  36. package/src/core/dialect/mssql/index.ts +125 -80
  37. package/src/core/dialect/mysql/index.ts +18 -112
  38. package/src/core/dialect/postgres/index.ts +29 -126
  39. package/src/core/dialect/sqlite/index.ts +28 -129
  40. package/src/index.ts +4 -0
  41. package/src/orm/execute.ts +25 -16
  42. package/src/orm/orm-context.ts +60 -55
  43. package/src/orm/query-logger.ts +38 -0
  44. package/src/orm/relations/belongs-to.ts +42 -26
  45. package/src/orm/relations/has-many.ts +41 -25
  46. package/src/orm/relations/many-to-many.ts +43 -27
  47. package/src/orm/unit-of-work.ts +60 -23
  48. package/src/query-builder/hydration-manager.ts +229 -25
  49. package/src/query-builder/query-ast-service.ts +27 -12
  50. package/src/query-builder/select-query-state.ts +24 -12
  51. package/src/query-builder/select.ts +58 -14
  52. package/src/schema/column.ts +206 -27
  53. package/src/schema/table.ts +89 -32
  54. package/src/schema/types.ts +8 -5
package/dist/index.js CHANGED
@@ -1,10 +1,23 @@
1
1
  // src/schema/table.ts
2
- var defineTable = (name, columns, relations = {}, hooks) => {
2
+ var defineTable = (name, columns, relations = {}, hooks, options = {}) => {
3
3
  const colsWithNames = Object.entries(columns).reduce((acc, [key, def]) => {
4
4
  acc[key] = { ...def, name: key, table: name };
5
5
  return acc;
6
6
  }, {});
7
- return { name, columns: colsWithNames, relations, hooks };
7
+ return {
8
+ name,
9
+ schema: options.schema,
10
+ columns: colsWithNames,
11
+ relations,
12
+ hooks,
13
+ primaryKey: options.primaryKey,
14
+ indexes: options.indexes,
15
+ checks: options.checks,
16
+ comment: options.comment,
17
+ engine: options.engine,
18
+ charset: options.charset,
19
+ collation: options.collation
20
+ };
8
21
  };
9
22
 
10
23
  // src/schema/column.ts
@@ -14,12 +27,52 @@ var col = {
14
27
  * @returns ColumnDef with INT type
15
28
  */
16
29
  int: () => ({ name: "", type: "INT" }),
30
+ /**
31
+ * Creates a big integer column definition
32
+ */
33
+ bigint: () => ({ name: "", type: "BIGINT" }),
17
34
  /**
18
35
  * Creates a variable character column definition
19
36
  * @param length - Maximum length of the string
20
37
  * @returns ColumnDef with VARCHAR type
21
38
  */
22
39
  varchar: (length) => ({ name: "", type: "VARCHAR", args: [length] }),
40
+ /**
41
+ * Creates a fixed precision decimal column definition
42
+ */
43
+ decimal: (precision, scale = 0) => ({
44
+ name: "",
45
+ type: "DECIMAL",
46
+ args: [precision, scale]
47
+ }),
48
+ /**
49
+ * Creates a floating point column definition
50
+ */
51
+ float: (precision) => ({
52
+ name: "",
53
+ type: "FLOAT",
54
+ args: precision !== void 0 ? [precision] : void 0
55
+ }),
56
+ /**
57
+ * Creates a UUID column definition
58
+ */
59
+ uuid: () => ({ name: "", type: "UUID" }),
60
+ /**
61
+ * Creates a timestamp column definition
62
+ */
63
+ timestamp: () => ({ name: "", type: "TIMESTAMP" }),
64
+ /**
65
+ * Creates a timestamptz column definition
66
+ */
67
+ timestamptz: () => ({ name: "", type: "TIMESTAMPTZ" }),
68
+ /**
69
+ * Creates a date column definition
70
+ */
71
+ date: () => ({ name: "", type: "DATE" }),
72
+ /**
73
+ * Creates a datetime column definition
74
+ */
75
+ datetime: () => ({ name: "", type: "DATETIME" }),
23
76
  /**
24
77
  * Creates a JSON column definition
25
78
  * @returns ColumnDef with JSON type
@@ -30,12 +83,64 @@ var col = {
30
83
  * @returns ColumnDef with BOOLEAN type
31
84
  */
32
85
  boolean: () => ({ name: "", type: "BOOLEAN" }),
86
+ /**
87
+ * Creates an enum column definition
88
+ * @param values - Enum values
89
+ */
90
+ enum: (values) => ({ name: "", type: "ENUM", args: values }),
33
91
  /**
34
92
  * Marks a column definition as a primary key
35
93
  * @param def - Column definition to modify
36
94
  * @returns Modified ColumnDef with primary: true
37
95
  */
38
- primaryKey: (def) => ({ ...def, primary: true })
96
+ primaryKey: (def) => ({ ...def, primary: true }),
97
+ /**
98
+ * Marks a column as NOT NULL
99
+ */
100
+ notNull: (def) => ({ ...def, notNull: true }),
101
+ /**
102
+ * Marks a column as UNIQUE
103
+ */
104
+ unique: (def, name) => ({
105
+ ...def,
106
+ unique: name ?? true
107
+ }),
108
+ /**
109
+ * Sets a default value for the column
110
+ */
111
+ default: (def, value) => ({
112
+ ...def,
113
+ default: value
114
+ }),
115
+ /**
116
+ * Sets a raw SQL default value for the column
117
+ */
118
+ defaultRaw: (def, expression) => ({
119
+ ...def,
120
+ default: { raw: expression }
121
+ }),
122
+ /**
123
+ * Marks a column as auto-increment / identity
124
+ */
125
+ autoIncrement: (def, strategy = "byDefault") => ({
126
+ ...def,
127
+ autoIncrement: true,
128
+ generated: strategy
129
+ }),
130
+ /**
131
+ * Adds a foreign key reference
132
+ */
133
+ references: (def, ref) => ({
134
+ ...def,
135
+ references: ref
136
+ }),
137
+ /**
138
+ * Adds a check constraint to the column
139
+ */
140
+ check: (def, expression) => ({
141
+ ...def,
142
+ check: expression
143
+ })
39
144
  };
40
145
 
41
146
  // src/schema/relation.ts
@@ -445,6 +550,82 @@ var SelectQueryState = class _SelectQueryState {
445
550
  ctes: [...this.ast.ctes ?? [], cte]
446
551
  });
447
552
  }
553
+ /**
554
+ * Adds a set operation (UNION/INTERSECT/EXCEPT) to the query
555
+ * @param op - Set operation node to add
556
+ * @returns New SelectQueryState with set operation
557
+ */
558
+ withSetOperation(op) {
559
+ return this.clone({
560
+ ...this.ast,
561
+ setOps: [...this.ast.setOps ?? [], op]
562
+ });
563
+ }
564
+ };
565
+
566
+ // src/core/ast/join-node.ts
567
+ var createJoinNode = (kind, tableName, condition, relationName) => ({
568
+ type: "Join",
569
+ kind,
570
+ table: { type: "Table", name: tableName },
571
+ condition,
572
+ relationName
573
+ });
574
+
575
+ // src/core/sql/sql.ts
576
+ var SQL_OPERATORS = {
577
+ /** Equality operator */
578
+ EQUALS: "=",
579
+ /** Not equals operator */
580
+ NOT_EQUALS: "!=",
581
+ /** Greater than operator */
582
+ GREATER_THAN: ">",
583
+ /** Greater than or equal operator */
584
+ GREATER_OR_EQUAL: ">=",
585
+ /** Less than operator */
586
+ LESS_THAN: "<",
587
+ /** Less than or equal operator */
588
+ LESS_OR_EQUAL: "<=",
589
+ /** LIKE pattern matching operator */
590
+ LIKE: "LIKE",
591
+ /** NOT LIKE pattern matching operator */
592
+ NOT_LIKE: "NOT LIKE",
593
+ /** IN membership operator */
594
+ IN: "IN",
595
+ /** NOT IN membership operator */
596
+ NOT_IN: "NOT IN",
597
+ /** BETWEEN range operator */
598
+ BETWEEN: "BETWEEN",
599
+ /** NOT BETWEEN range operator */
600
+ NOT_BETWEEN: "NOT BETWEEN",
601
+ /** IS NULL null check operator */
602
+ IS_NULL: "IS NULL",
603
+ /** IS NOT NULL null check operator */
604
+ IS_NOT_NULL: "IS NOT NULL",
605
+ /** Logical AND operator */
606
+ AND: "AND",
607
+ /** Logical OR operator */
608
+ OR: "OR",
609
+ /** EXISTS operator */
610
+ EXISTS: "EXISTS",
611
+ /** NOT EXISTS operator */
612
+ NOT_EXISTS: "NOT EXISTS"
613
+ };
614
+ var JOIN_KINDS = {
615
+ /** INNER JOIN type */
616
+ INNER: "INNER",
617
+ /** LEFT JOIN type */
618
+ LEFT: "LEFT",
619
+ /** RIGHT JOIN type */
620
+ RIGHT: "RIGHT",
621
+ /** CROSS JOIN type */
622
+ CROSS: "CROSS"
623
+ };
624
+ var ORDER_DIRECTIONS = {
625
+ /** Ascending order */
626
+ ASC: "ASC",
627
+ /** Descending order */
628
+ DESC: "DESC"
448
629
  };
449
630
 
450
631
  // src/query-builder/hydration-manager.ts
@@ -496,8 +677,26 @@ var HydrationManager = class _HydrationManager {
496
677
  * @returns AST with hydration metadata
497
678
  */
498
679
  applyToAst(ast) {
680
+ if (ast.setOps && ast.setOps.length > 0) {
681
+ return ast;
682
+ }
499
683
  const plan = this.planner.getPlan();
500
684
  if (!plan) return ast;
685
+ const needsPaginationGuard = this.requiresParentPagination(ast, plan);
686
+ const rewritten = needsPaginationGuard ? this.wrapForParentPagination(ast, plan) : ast;
687
+ return this.attachHydrationMeta(rewritten, plan);
688
+ }
689
+ /**
690
+ * Gets the current hydration plan
691
+ * @returns Hydration plan or undefined if none exists
692
+ */
693
+ getPlan() {
694
+ return this.planner.getPlan();
695
+ }
696
+ /**
697
+ * Attaches hydration metadata to a query AST node.
698
+ */
699
+ attachHydrationMeta(ast, plan) {
501
700
  return {
502
701
  ...ast,
503
702
  meta: {
@@ -507,11 +706,154 @@ var HydrationManager = class _HydrationManager {
507
706
  };
508
707
  }
509
708
  /**
510
- * Gets the current hydration plan
511
- * @returns Hydration plan or undefined if none exists
709
+ * Determines whether the query needs pagination rewriting to keep LIMIT/OFFSET
710
+ * applied to parent rows when eager-loading multiplicative relations.
512
711
  */
513
- getPlan() {
514
- return this.planner.getPlan();
712
+ requiresParentPagination(ast, plan) {
713
+ const hasPagination = ast.limit !== void 0 || ast.offset !== void 0;
714
+ return hasPagination && this.hasMultiplyingRelations(plan);
715
+ }
716
+ hasMultiplyingRelations(plan) {
717
+ return plan.relations.some(
718
+ (rel) => rel.type === RelationKinds.HasMany || rel.type === RelationKinds.BelongsToMany
719
+ );
720
+ }
721
+ /**
722
+ * Rewrites the query using CTEs so LIMIT/OFFSET target distinct parent rows
723
+ * instead of the joined result set.
724
+ *
725
+ * The strategy:
726
+ * - Hoist the original query (minus limit/offset) into a base CTE.
727
+ * - Select distinct parent ids from that base CTE with the original ordering and pagination.
728
+ * - Join the base CTE against the paged ids to retrieve the joined rows for just that page.
729
+ */
730
+ wrapForParentPagination(ast, plan) {
731
+ const projectionNames = this.getProjectionNames(ast.columns);
732
+ if (!projectionNames) {
733
+ return ast;
734
+ }
735
+ const projectionAliases = this.buildProjectionAliasMap(ast.columns);
736
+ const projectionSet = new Set(projectionNames);
737
+ const rootPkAlias = projectionAliases.get(`${plan.rootTable}.${plan.rootPrimaryKey}`) ?? plan.rootPrimaryKey;
738
+ const baseCteName = this.nextCteName(ast.ctes, "__metal_pagination_base");
739
+ const baseQuery = {
740
+ ...ast,
741
+ ctes: void 0,
742
+ limit: void 0,
743
+ offset: void 0,
744
+ orderBy: void 0,
745
+ meta: void 0
746
+ };
747
+ const baseCte = {
748
+ type: "CommonTableExpression",
749
+ name: baseCteName,
750
+ query: baseQuery,
751
+ recursive: false
752
+ };
753
+ const orderBy = this.mapOrderBy(ast.orderBy, plan, projectionAliases, baseCteName, projectionSet);
754
+ if (orderBy === null) {
755
+ return ast;
756
+ }
757
+ const pageCteName = this.nextCteName([...ast.ctes ?? [], baseCte], "__metal_pagination_page");
758
+ const pagingColumns = this.buildPagingColumns(rootPkAlias, orderBy, baseCteName);
759
+ const pageCte = {
760
+ type: "CommonTableExpression",
761
+ name: pageCteName,
762
+ query: {
763
+ type: "SelectQuery",
764
+ from: { type: "Table", name: baseCteName },
765
+ columns: pagingColumns,
766
+ joins: [],
767
+ distinct: [{ type: "Column", table: baseCteName, name: rootPkAlias }],
768
+ orderBy,
769
+ limit: ast.limit,
770
+ offset: ast.offset
771
+ },
772
+ recursive: false
773
+ };
774
+ const joinCondition = eq(
775
+ { type: "Column", table: baseCteName, name: rootPkAlias },
776
+ { type: "Column", table: pageCteName, name: rootPkAlias }
777
+ );
778
+ const outerColumns = projectionNames.map((name) => ({
779
+ type: "Column",
780
+ table: baseCteName,
781
+ name,
782
+ alias: name
783
+ }));
784
+ return {
785
+ type: "SelectQuery",
786
+ from: { type: "Table", name: baseCteName },
787
+ columns: outerColumns,
788
+ joins: [createJoinNode(JOIN_KINDS.INNER, pageCteName, joinCondition)],
789
+ orderBy,
790
+ ctes: [...ast.ctes ?? [], baseCte, pageCte]
791
+ };
792
+ }
793
+ nextCteName(existing, baseName) {
794
+ const names = new Set((existing ?? []).map((cte) => cte.name));
795
+ let candidate = baseName;
796
+ let suffix = 1;
797
+ while (names.has(candidate)) {
798
+ suffix += 1;
799
+ candidate = `${baseName}_${suffix}`;
800
+ }
801
+ return candidate;
802
+ }
803
+ getProjectionNames(columns) {
804
+ const names = [];
805
+ for (const col2 of columns) {
806
+ const alias = col2.alias ?? col2.name;
807
+ if (!alias) return void 0;
808
+ names.push(alias);
809
+ }
810
+ return names;
811
+ }
812
+ buildProjectionAliasMap(columns) {
813
+ const map = /* @__PURE__ */ new Map();
814
+ for (const col2 of columns) {
815
+ if (col2.type !== "Column") continue;
816
+ const node = col2;
817
+ const key = `${node.table}.${node.name}`;
818
+ map.set(key, node.alias ?? node.name);
819
+ }
820
+ return map;
821
+ }
822
+ mapOrderBy(orderBy, plan, projectionAliases, baseAlias, availableColumns) {
823
+ if (!orderBy || orderBy.length === 0) {
824
+ return void 0;
825
+ }
826
+ const mapped = [];
827
+ for (const ob of orderBy) {
828
+ if (ob.column.table !== plan.rootTable) {
829
+ return null;
830
+ }
831
+ const alias = projectionAliases.get(`${ob.column.table}.${ob.column.name}`) ?? ob.column.name;
832
+ if (!availableColumns.has(alias)) {
833
+ return null;
834
+ }
835
+ mapped.push({
836
+ type: "OrderBy",
837
+ column: { type: "Column", table: baseAlias, name: alias },
838
+ direction: ob.direction
839
+ });
840
+ }
841
+ return mapped;
842
+ }
843
+ buildPagingColumns(primaryKey, orderBy, tableAlias) {
844
+ const columns = [{ type: "Column", table: tableAlias, name: primaryKey, alias: primaryKey }];
845
+ if (!orderBy) return columns;
846
+ for (const ob of orderBy) {
847
+ if (!columns.some((col2) => col2.name === ob.column.name)) {
848
+ columns.push({
849
+ type: "Column",
850
+ table: tableAlias,
851
+ name: ob.column.name,
852
+ alias: ob.column.name
853
+ });
854
+ }
855
+ }
856
+ return columns;
515
857
  }
516
858
  };
517
859
 
@@ -774,6 +1116,20 @@ var QueryAstService = class {
774
1116
  };
775
1117
  return this.state.withCte(cte);
776
1118
  }
1119
+ /**
1120
+ * Adds a set operation (UNION/UNION ALL/INTERSECT/EXCEPT) to the query
1121
+ * @param operator - Set operator
1122
+ * @param query - Right-hand side query
1123
+ * @returns Updated query state with set operation
1124
+ */
1125
+ withSetOperation(operator, query) {
1126
+ const op = {
1127
+ type: "SetOperation",
1128
+ operator,
1129
+ query
1130
+ };
1131
+ return this.state.withSetOperation(op);
1132
+ }
777
1133
  /**
778
1134
  * Selects a subquery as a column
779
1135
  * @param alias - Alias for the subquery
@@ -926,15 +1282,6 @@ var RelationProjectionHelper = class {
926
1282
  }
927
1283
  };
928
1284
 
929
- // src/core/ast/join-node.ts
930
- var createJoinNode = (kind, tableName, condition, relationName) => ({
931
- type: "Join",
932
- kind,
933
- table: { type: "Table", name: tableName },
934
- condition,
935
- relationName
936
- });
937
-
938
1285
  // src/query-builder/relation-conditions.ts
939
1286
  var assertNever = (value) => {
940
1287
  throw new Error(`Unhandled relation type: ${JSON.stringify(value)}`);
@@ -990,62 +1337,6 @@ var buildRelationCorrelation = (root, relation) => {
990
1337
  return baseRelationCondition(root, relation);
991
1338
  };
992
1339
 
993
- // src/core/sql/sql.ts
994
- var SQL_OPERATORS = {
995
- /** Equality operator */
996
- EQUALS: "=",
997
- /** Not equals operator */
998
- NOT_EQUALS: "!=",
999
- /** Greater than operator */
1000
- GREATER_THAN: ">",
1001
- /** Greater than or equal operator */
1002
- GREATER_OR_EQUAL: ">=",
1003
- /** Less than operator */
1004
- LESS_THAN: "<",
1005
- /** Less than or equal operator */
1006
- LESS_OR_EQUAL: "<=",
1007
- /** LIKE pattern matching operator */
1008
- LIKE: "LIKE",
1009
- /** NOT LIKE pattern matching operator */
1010
- NOT_LIKE: "NOT LIKE",
1011
- /** IN membership operator */
1012
- IN: "IN",
1013
- /** NOT IN membership operator */
1014
- NOT_IN: "NOT IN",
1015
- /** BETWEEN range operator */
1016
- BETWEEN: "BETWEEN",
1017
- /** NOT BETWEEN range operator */
1018
- NOT_BETWEEN: "NOT BETWEEN",
1019
- /** IS NULL null check operator */
1020
- IS_NULL: "IS NULL",
1021
- /** IS NOT NULL null check operator */
1022
- IS_NOT_NULL: "IS NOT NULL",
1023
- /** Logical AND operator */
1024
- AND: "AND",
1025
- /** Logical OR operator */
1026
- OR: "OR",
1027
- /** EXISTS operator */
1028
- EXISTS: "EXISTS",
1029
- /** NOT EXISTS operator */
1030
- NOT_EXISTS: "NOT EXISTS"
1031
- };
1032
- var JOIN_KINDS = {
1033
- /** INNER JOIN type */
1034
- INNER: "INNER",
1035
- /** LEFT JOIN type */
1036
- LEFT: "LEFT",
1037
- /** RIGHT JOIN type */
1038
- RIGHT: "RIGHT",
1039
- /** CROSS JOIN type */
1040
- CROSS: "CROSS"
1041
- };
1042
- var ORDER_DIRECTIONS = {
1043
- /** Ascending order */
1044
- ASC: "ASC",
1045
- /** Descending order */
1046
- DESC: "DESC"
1047
- };
1048
-
1049
1340
  // src/query-builder/relation-service.ts
1050
1341
  var RelationService = class {
1051
1342
  /**
@@ -1491,6 +1782,16 @@ var hasEntityMeta = (entity) => {
1491
1782
 
1492
1783
  // src/orm/relations/has-many.ts
1493
1784
  var toKey2 = (value) => value === null || value === void 0 ? "" : String(value);
1785
+ var hideInternal = (obj, keys) => {
1786
+ for (const key of keys) {
1787
+ Object.defineProperty(obj, key, {
1788
+ value: obj[key],
1789
+ writable: false,
1790
+ configurable: false,
1791
+ enumerable: false
1792
+ });
1793
+ }
1794
+ };
1494
1795
  var DefaultHasManyCollection = class {
1495
1796
  constructor(ctx, meta, root, relationName, relation, rootTable, loader, createEntity, localKey) {
1496
1797
  this.ctx = ctx;
@@ -1506,6 +1807,7 @@ var DefaultHasManyCollection = class {
1506
1807
  this.items = [];
1507
1808
  this.added = /* @__PURE__ */ new Set();
1508
1809
  this.removed = /* @__PURE__ */ new Set();
1810
+ hideInternal(this, ["ctx", "meta", "root", "relationName", "relation", "rootTable", "loader", "createEntity", "localKey"]);
1509
1811
  this.hydrateFromCache();
1510
1812
  }
1511
1813
  async load() {
@@ -1581,10 +1883,23 @@ var DefaultHasManyCollection = class {
1581
1883
  this.items = rows.map((row) => this.createEntity(row));
1582
1884
  this.loaded = true;
1583
1885
  }
1886
+ toJSON() {
1887
+ return this.items;
1888
+ }
1584
1889
  };
1585
1890
 
1586
1891
  // src/orm/relations/belongs-to.ts
1587
1892
  var toKey3 = (value) => value === null || value === void 0 ? "" : String(value);
1893
+ var hideInternal2 = (obj, keys) => {
1894
+ for (const key of keys) {
1895
+ Object.defineProperty(obj, key, {
1896
+ value: obj[key],
1897
+ writable: false,
1898
+ configurable: false,
1899
+ enumerable: false
1900
+ });
1901
+ }
1902
+ };
1588
1903
  var DefaultBelongsToReference = class {
1589
1904
  constructor(ctx, meta, root, relationName, relation, rootTable, loader, createEntity, targetKey) {
1590
1905
  this.ctx = ctx;
@@ -1598,6 +1913,7 @@ var DefaultBelongsToReference = class {
1598
1913
  this.targetKey = targetKey;
1599
1914
  this.loaded = false;
1600
1915
  this.current = null;
1916
+ hideInternal2(this, ["ctx", "meta", "root", "relationName", "relation", "rootTable", "loader", "createEntity", "targetKey"]);
1601
1917
  this.populateFromHydrationCache();
1602
1918
  }
1603
1919
  async load() {
@@ -1658,10 +1974,23 @@ var DefaultBelongsToReference = class {
1658
1974
  this.current = this.createEntity(row);
1659
1975
  this.loaded = true;
1660
1976
  }
1977
+ toJSON() {
1978
+ return this.current;
1979
+ }
1661
1980
  };
1662
1981
 
1663
1982
  // src/orm/relations/many-to-many.ts
1664
1983
  var toKey4 = (value) => value === null || value === void 0 ? "" : String(value);
1984
+ var hideInternal3 = (obj, keys) => {
1985
+ for (const key of keys) {
1986
+ Object.defineProperty(obj, key, {
1987
+ value: obj[key],
1988
+ writable: false,
1989
+ configurable: false,
1990
+ enumerable: false
1991
+ });
1992
+ }
1993
+ };
1665
1994
  var DefaultManyToManyCollection = class {
1666
1995
  constructor(ctx, meta, root, relationName, relation, rootTable, loader, createEntity, localKey) {
1667
1996
  this.ctx = ctx;
@@ -1675,6 +2004,7 @@ var DefaultManyToManyCollection = class {
1675
2004
  this.localKey = localKey;
1676
2005
  this.loaded = false;
1677
2006
  this.items = [];
2007
+ hideInternal3(this, ["ctx", "meta", "root", "relationName", "relation", "rootTable", "loader", "createEntity", "localKey"]);
1678
2008
  this.hydrateFromCache();
1679
2009
  }
1680
2010
  async load() {
@@ -1780,6 +2110,9 @@ var DefaultManyToManyCollection = class {
1780
2110
  });
1781
2111
  this.loaded = true;
1782
2112
  }
2113
+ toJSON() {
2114
+ return this.items;
2115
+ }
1783
2116
  };
1784
2117
 
1785
2118
  // src/orm/lazy-batch.ts
@@ -2140,9 +2473,15 @@ var flattenResults = (results) => {
2140
2473
  return rows;
2141
2474
  };
2142
2475
  async function executeHydrated(ctx, qb) {
2143
- const compiled = ctx.dialect.compileSelect(qb.getAST());
2476
+ const ast = qb.getAST();
2477
+ const compiled = ctx.dialect.compileSelect(ast);
2144
2478
  const executed = await ctx.executor.executeSql(compiled.sql, compiled.params);
2145
2479
  const rows = flattenResults(executed);
2480
+ if (ast.setOps && ast.setOps.length > 0) {
2481
+ return rows.map(
2482
+ (row) => createEntityProxy(ctx, qb.getTable(), row, qb.getLazyRelations())
2483
+ );
2484
+ }
2146
2485
  const hydrated = hydrateRows(rows, qb.getHydrationPlan());
2147
2486
  return hydrated.map(
2148
2487
  (row) => createEntityFromRow(ctx, qb.getTable(), row, qb.getLazyRelations())
@@ -2189,6 +2528,10 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
2189
2528
  const joinNode = createJoinNode(kind, table.name, condition);
2190
2529
  return this.applyAst(context, (service) => service.withJoin(joinNode));
2191
2530
  }
2531
+ applySetOperation(operator, query) {
2532
+ const subAst = this.resolveQueryNode(query);
2533
+ return this.applyAst(this.context, (service) => service.withSetOperation(operator, subAst));
2534
+ }
2192
2535
  /**
2193
2536
  * Selects specific columns for the query
2194
2537
  * @param columns - Record of column definitions, function nodes, case expressions, or window functions
@@ -2377,6 +2720,38 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
2377
2720
  const nextContext = this.applyAst(this.context, (service) => service.withOffset(n));
2378
2721
  return this.clone(nextContext);
2379
2722
  }
2723
+ /**
2724
+ * Combines this query with another using UNION
2725
+ * @param query - Query to union with
2726
+ * @returns New query builder instance with the set operation
2727
+ */
2728
+ union(query) {
2729
+ return this.clone(this.applySetOperation("UNION", query));
2730
+ }
2731
+ /**
2732
+ * Combines this query with another using UNION ALL
2733
+ * @param query - Query to union with
2734
+ * @returns New query builder instance with the set operation
2735
+ */
2736
+ unionAll(query) {
2737
+ return this.clone(this.applySetOperation("UNION ALL", query));
2738
+ }
2739
+ /**
2740
+ * Combines this query with another using INTERSECT
2741
+ * @param query - Query to intersect with
2742
+ * @returns New query builder instance with the set operation
2743
+ */
2744
+ intersect(query) {
2745
+ return this.clone(this.applySetOperation("INTERSECT", query));
2746
+ }
2747
+ /**
2748
+ * Combines this query with another using EXCEPT
2749
+ * @param query - Query to subtract
2750
+ * @returns New query builder instance with the set operation
2751
+ */
2752
+ except(query) {
2753
+ return this.clone(this.applySetOperation("EXCEPT", query));
2754
+ }
2380
2755
  /**
2381
2756
  * Adds a WHERE EXISTS condition to the query
2382
2757
  * @param subquery - Subquery to check for existence
@@ -2666,7 +3041,8 @@ var Dialect = class {
2666
3041
  */
2667
3042
  compileSelect(ast) {
2668
3043
  const ctx = this.createCompilerContext();
2669
- const rawSql = this.compileSelectAst(ast, ctx).trim();
3044
+ const normalized = this.normalizeSelectAst(ast);
3045
+ const rawSql = this.compileSelectAst(normalized, ctx).trim();
2670
3046
  const sql = rawSql.endsWith(";") ? rawSql : `${rawSql};`;
2671
3047
  return {
2672
3048
  sql,
@@ -2700,6 +3076,9 @@ var Dialect = class {
2700
3076
  params: [...ctx.params]
2701
3077
  };
2702
3078
  }
3079
+ supportsReturning() {
3080
+ return false;
3081
+ }
2703
3082
  /**
2704
3083
  * Compiles a WHERE clause
2705
3084
  * @param where - WHERE expression
@@ -2724,7 +3103,11 @@ var Dialect = class {
2724
3103
  * @returns SQL for EXISTS subquery
2725
3104
  */
2726
3105
  compileSelectForExists(ast, ctx) {
2727
- const full = this.compileSelectAst(ast, ctx).trim().replace(/;$/, "");
3106
+ const normalized = this.normalizeSelectAst(ast);
3107
+ const full = this.compileSelectAst(normalized, ctx).trim().replace(/;$/, "");
3108
+ if (normalized.setOps && normalized.setOps.length > 0) {
3109
+ return `SELECT 1 FROM (${full}) AS _exists`;
3110
+ }
2728
3111
  const upper = full.toUpperCase();
2729
3112
  const fromIndex = upper.indexOf(" FROM ");
2730
3113
  if (fromIndex === -1) {
@@ -2757,6 +3140,65 @@ var Dialect = class {
2757
3140
  formatPlaceholder(index) {
2758
3141
  return "?";
2759
3142
  }
3143
+ /**
3144
+ * Whether the current dialect supports a given set operation.
3145
+ * Override in concrete dialects to restrict support.
3146
+ */
3147
+ supportsSetOperation(kind) {
3148
+ return true;
3149
+ }
3150
+ /**
3151
+ * Validates set-operation semantics:
3152
+ * - Ensures the dialect supports requested operators.
3153
+ * - Enforces that only the outermost compound query may have ORDER/LIMIT/OFFSET.
3154
+ * @param ast - Query to validate
3155
+ * @param isOutermost - Whether this node is the outermost compound query
3156
+ */
3157
+ validateSetOperations(ast, isOutermost = true) {
3158
+ const hasSetOps = !!(ast.setOps && ast.setOps.length);
3159
+ if (!isOutermost && (ast.orderBy || ast.limit !== void 0 || ast.offset !== void 0)) {
3160
+ throw new Error("ORDER BY / LIMIT / OFFSET are only allowed on the outermost compound query.");
3161
+ }
3162
+ if (hasSetOps) {
3163
+ for (const op of ast.setOps) {
3164
+ if (!this.supportsSetOperation(op.operator)) {
3165
+ throw new Error(`Set operation ${op.operator} is not supported by this dialect.`);
3166
+ }
3167
+ this.validateSetOperations(op.query, false);
3168
+ }
3169
+ }
3170
+ }
3171
+ /**
3172
+ * Hoists CTEs from set-operation operands to the outermost query so WITH appears once.
3173
+ * @param ast - Query AST
3174
+ * @returns Normalized AST without inner CTEs and a list of hoisted CTEs
3175
+ */
3176
+ hoistCtes(ast) {
3177
+ let hoisted = [];
3178
+ const normalizedSetOps = ast.setOps?.map((op) => {
3179
+ const { normalized: child, hoistedCtes: childHoisted } = this.hoistCtes(op.query);
3180
+ const childCtes = child.ctes ?? [];
3181
+ if (childCtes.length) {
3182
+ hoisted = hoisted.concat(childCtes);
3183
+ }
3184
+ hoisted = hoisted.concat(childHoisted);
3185
+ const queryWithoutCtes = childCtes.length ? { ...child, ctes: void 0 } : child;
3186
+ return { ...op, query: queryWithoutCtes };
3187
+ });
3188
+ const normalized = normalizedSetOps ? { ...ast, setOps: normalizedSetOps } : ast;
3189
+ return { normalized, hoistedCtes: hoisted };
3190
+ }
3191
+ /**
3192
+ * Normalizes a SELECT AST before compilation (validation + CTE hoisting).
3193
+ * @param ast - Query AST
3194
+ * @returns Normalized query AST
3195
+ */
3196
+ normalizeSelectAst(ast) {
3197
+ this.validateSetOperations(ast, true);
3198
+ const { normalized, hoistedCtes } = this.hoistCtes(ast);
3199
+ const combinedCtes = [...normalized.ctes ?? [], ...hoistedCtes];
3200
+ return combinedCtes.length ? { ...normalized, ctes: combinedCtes } : normalized;
3201
+ }
2760
3202
  constructor() {
2761
3203
  this.expressionCompilers = /* @__PURE__ */ new Map();
2762
3204
  this.operandCompilers = /* @__PURE__ */ new Map();
@@ -2899,102 +3341,181 @@ var Dialect = class {
2899
3341
  }
2900
3342
  };
2901
3343
 
2902
- // src/core/dialect/mysql/index.ts
2903
- var MySqlDialect = class extends Dialect {
3344
+ // src/core/dialect/base/sql-dialect.ts
3345
+ var SqlDialectBase = class extends Dialect {
2904
3346
  /**
2905
- * Creates a new MySqlDialect instance
3347
+ * Compiles SELECT query AST to SQL using common rules.
2906
3348
  */
2907
- constructor() {
2908
- super();
3349
+ compileSelectAst(ast, ctx) {
3350
+ const hasSetOps = !!(ast.setOps && ast.setOps.length);
3351
+ const ctes = this.compileCtes(ast, ctx);
3352
+ const baseAst = hasSetOps ? { ...ast, setOps: void 0, orderBy: void 0, limit: void 0, offset: void 0 } : ast;
3353
+ const baseSelect = this.compileSelectCore(baseAst, ctx);
3354
+ if (!hasSetOps) {
3355
+ return `${ctes}${baseSelect}`;
3356
+ }
3357
+ const compound = ast.setOps.map((op) => `${op.operator} ${this.wrapSetOperand(this.compileSelectAst(op.query, ctx))}`).join(" ");
3358
+ const orderBy = this.compileOrderBy(ast);
3359
+ const pagination = this.compilePagination(ast, orderBy);
3360
+ const combined = `${this.wrapSetOperand(baseSelect)} ${compound}`;
3361
+ return `${ctes}${combined}${orderBy}${pagination}`;
3362
+ }
3363
+ compileInsertAst(ast, ctx) {
3364
+ const table = this.compileTableName(ast.into);
3365
+ const columnList = ast.columns.map((column) => `${this.quoteIdentifier(column.table)}.${this.quoteIdentifier(column.name)}`).join(", ");
3366
+ const values = ast.values.map((row) => `(${row.map((value) => this.compileOperand(value, ctx)).join(", ")})`).join(", ");
3367
+ const returning = this.compileReturning(ast.returning, ctx);
3368
+ return `INSERT INTO ${table} (${columnList}) VALUES ${values}${returning}`;
2909
3369
  }
2910
3370
  /**
2911
- * Quotes an identifier using MySQL backtick syntax
2912
- * @param id - Identifier to quote
2913
- * @returns Quoted identifier
3371
+ * Compiles a single SELECT (no set operations, no CTE prefix).
2914
3372
  */
2915
- quoteIdentifier(id) {
2916
- return `\`${id}\``;
3373
+ compileSelectCore(ast, ctx) {
3374
+ const columns = this.compileSelectColumns(ast, ctx);
3375
+ const from = this.compileFrom(ast.from);
3376
+ const joins = this.compileJoins(ast, ctx);
3377
+ const whereClause = this.compileWhere(ast.where, ctx);
3378
+ const groupBy = this.compileGroupBy(ast);
3379
+ const having = this.compileHaving(ast, ctx);
3380
+ const orderBy = this.compileOrderBy(ast);
3381
+ const pagination = this.compilePagination(ast, orderBy);
3382
+ return `SELECT ${this.compileDistinct(ast)}${columns} FROM ${from}${joins}${whereClause}${groupBy}${having}${orderBy}${pagination}`;
3383
+ }
3384
+ compileUpdateAst(ast, ctx) {
3385
+ const table = this.compileTableName(ast.table);
3386
+ const assignments = ast.set.map((assignment) => {
3387
+ const col2 = assignment.column;
3388
+ const target = `${this.quoteIdentifier(col2.table)}.${this.quoteIdentifier(col2.name)}`;
3389
+ const value = this.compileOperand(assignment.value, ctx);
3390
+ return `${target} = ${value}`;
3391
+ }).join(", ");
3392
+ const whereClause = this.compileWhere(ast.where, ctx);
3393
+ const returning = this.compileReturning(ast.returning, ctx);
3394
+ return `UPDATE ${table} SET ${assignments}${whereClause}${returning}`;
3395
+ }
3396
+ compileDeleteAst(ast, ctx) {
3397
+ const table = this.compileTableName(ast.from);
3398
+ const whereClause = this.compileWhere(ast.where, ctx);
3399
+ const returning = this.compileReturning(ast.returning, ctx);
3400
+ return `DELETE FROM ${table}${whereClause}${returning}`;
2917
3401
  }
2918
3402
  /**
2919
- * Compiles JSON path expression using MySQL syntax
2920
- * @param node - JSON path node
2921
- * @returns MySQL JSON path expression
3403
+ * Default RETURNING compilation: no support.
2922
3404
  */
2923
- compileJsonPath(node) {
2924
- const col2 = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
2925
- return `${col2}->'${node.path}'`;
3405
+ compileReturning(returning, _ctx) {
3406
+ if (!returning || returning.length === 0) return "";
3407
+ throw new Error("RETURNING is not supported by this dialect.");
3408
+ }
3409
+ formatReturningColumns(returning) {
3410
+ return returning.map((column) => {
3411
+ const tablePart = column.table ? `${this.quoteIdentifier(column.table)}.` : "";
3412
+ const aliasPart = column.alias ? ` AS ${this.quoteIdentifier(column.alias)}` : "";
3413
+ return `${tablePart}${this.quoteIdentifier(column.name)}${aliasPart}`;
3414
+ }).join(", ");
2926
3415
  }
2927
3416
  /**
2928
- * Compiles SELECT query AST to MySQL SQL
2929
- * @param ast - Query AST
2930
- * @param ctx - Compiler context
2931
- * @returns MySQL SQL string
3417
+ * DISTINCT clause. Override for DISTINCT ON support.
2932
3418
  */
2933
- compileSelectAst(ast, ctx) {
2934
- const columns = ast.columns.map((c) => {
2935
- let expr = "";
2936
- if (c.type === "Function") {
2937
- expr = this.compileOperand(c, ctx);
2938
- } else if (c.type === "Column") {
2939
- expr = `${this.quoteIdentifier(c.table)}.${this.quoteIdentifier(c.name)}`;
2940
- } else if (c.type === "ScalarSubquery") {
2941
- expr = this.compileOperand(c, ctx);
2942
- } else if (c.type === "WindowFunction") {
2943
- expr = this.compileOperand(c, ctx);
2944
- }
3419
+ compileDistinct(ast) {
3420
+ return ast.distinct ? "DISTINCT " : "";
3421
+ }
3422
+ compileSelectColumns(ast, ctx) {
3423
+ return ast.columns.map((c) => {
3424
+ const expr = this.compileOperand(c, ctx);
2945
3425
  if (c.alias) {
2946
3426
  if (c.alias.includes("(")) return c.alias;
2947
3427
  return `${expr} AS ${this.quoteIdentifier(c.alias)}`;
2948
3428
  }
2949
3429
  return expr;
2950
3430
  }).join(", ");
2951
- const distinct = ast.distinct ? "DISTINCT " : "";
2952
- const from = `${this.quoteIdentifier(ast.from.name)}`;
2953
- const joins = ast.joins.map((j) => {
2954
- const table = this.quoteIdentifier(j.table.name);
3431
+ }
3432
+ compileFrom(ast) {
3433
+ const base = this.compileTableName(ast);
3434
+ return ast.alias ? `${base} AS ${this.quoteIdentifier(ast.alias)}` : base;
3435
+ }
3436
+ compileTableName(table) {
3437
+ if (table.schema) {
3438
+ return `${this.quoteIdentifier(table.schema)}.${this.quoteIdentifier(table.name)}`;
3439
+ }
3440
+ return this.quoteIdentifier(table.name);
3441
+ }
3442
+ compileJoins(ast, ctx) {
3443
+ if (!ast.joins || ast.joins.length === 0) return "";
3444
+ const parts = ast.joins.map((j) => {
3445
+ const table = this.compileFrom(j.table);
2955
3446
  const cond = this.compileExpression(j.condition, ctx);
2956
3447
  return `${j.kind} JOIN ${table} ON ${cond}`;
2957
- }).join(" ");
2958
- const whereClause = this.compileWhere(ast.where, ctx);
2959
- const groupBy = ast.groupBy && ast.groupBy.length > 0 ? " GROUP BY " + ast.groupBy.map((c) => `${this.quoteIdentifier(c.table)}.${this.quoteIdentifier(c.name)}`).join(", ") : "";
2960
- const having = ast.having ? ` HAVING ${this.compileExpression(ast.having, ctx)}` : "";
2961
- const orderBy = ast.orderBy && ast.orderBy.length > 0 ? " ORDER BY " + ast.orderBy.map((o) => `${this.quoteIdentifier(o.column.table)}.${this.quoteIdentifier(o.column.name)} ${o.direction}`).join(", ") : "";
2962
- const limit = ast.limit ? ` LIMIT ${ast.limit}` : "";
2963
- const offset = ast.offset ? ` OFFSET ${ast.offset}` : "";
2964
- const ctes = ast.ctes && ast.ctes.length > 0 ? (() => {
2965
- const hasRecursive = ast.ctes.some((cte) => cte.recursive);
2966
- const prefix = hasRecursive ? "WITH RECURSIVE " : "WITH ";
2967
- const cteDefs = ast.ctes.map((cte) => {
2968
- const name = this.quoteIdentifier(cte.name);
2969
- const cols = cte.columns ? `(${cte.columns.map((c) => this.quoteIdentifier(c)).join(", ")})` : "";
2970
- const query = this.compileSelectAst(cte.query, ctx).trim().replace(/;$/, "");
2971
- return `${name}${cols} AS (${query})`;
2972
- }).join(", ");
2973
- return prefix + cteDefs + " ";
2974
- })() : "";
2975
- return `${ctes}SELECT ${distinct}${columns} FROM ${from}${joins ? " " + joins : ""}${whereClause}${groupBy}${having}${orderBy}${limit}${offset};`;
3448
+ });
3449
+ return ` ${parts.join(" ")}`;
2976
3450
  }
2977
- compileInsertAst(ast, ctx) {
2978
- const table = this.quoteIdentifier(ast.into.name);
2979
- const columnList = ast.columns.map((column) => `${this.quoteIdentifier(column.table)}.${this.quoteIdentifier(column.name)}`).join(", ");
2980
- const values = ast.values.map((row) => `(${row.map((value) => this.compileOperand(value, ctx)).join(", ")})`).join(", ");
2981
- return `INSERT INTO ${table} (${columnList}) VALUES ${values};`;
3451
+ compileGroupBy(ast) {
3452
+ if (!ast.groupBy || ast.groupBy.length === 0) return "";
3453
+ const cols = ast.groupBy.map((c) => `${this.quoteIdentifier(c.table)}.${this.quoteIdentifier(c.name)}`).join(", ");
3454
+ return ` GROUP BY ${cols}`;
2982
3455
  }
2983
- compileUpdateAst(ast, ctx) {
2984
- const table = this.quoteIdentifier(ast.table.name);
2985
- const assignments = ast.set.map((assignment) => {
2986
- const col2 = assignment.column;
2987
- const target = `${this.quoteIdentifier(col2.table)}.${this.quoteIdentifier(col2.name)}`;
2988
- const value = this.compileOperand(assignment.value, ctx);
2989
- return `${target} = ${value}`;
3456
+ compileHaving(ast, ctx) {
3457
+ if (!ast.having) return "";
3458
+ return ` HAVING ${this.compileExpression(ast.having, ctx)}`;
3459
+ }
3460
+ compileOrderBy(ast) {
3461
+ if (!ast.orderBy || ast.orderBy.length === 0) return "";
3462
+ const parts = ast.orderBy.map((o) => `${this.quoteIdentifier(o.column.table)}.${this.quoteIdentifier(o.column.name)} ${o.direction}`).join(", ");
3463
+ return ` ORDER BY ${parts}`;
3464
+ }
3465
+ /**
3466
+ * Default LIMIT/OFFSET pagination clause.
3467
+ */
3468
+ compilePagination(ast, _orderByClause) {
3469
+ const parts = [];
3470
+ if (ast.limit !== void 0) parts.push(`LIMIT ${ast.limit}`);
3471
+ if (ast.offset !== void 0) parts.push(`OFFSET ${ast.offset}`);
3472
+ return parts.length ? ` ${parts.join(" ")}` : "";
3473
+ }
3474
+ compileCtes(ast, ctx) {
3475
+ if (!ast.ctes || ast.ctes.length === 0) return "";
3476
+ const hasRecursive = ast.ctes.some((cte) => cte.recursive);
3477
+ const prefix = hasRecursive ? "WITH RECURSIVE " : "WITH ";
3478
+ const cteDefs = ast.ctes.map((cte) => {
3479
+ const name = this.quoteIdentifier(cte.name);
3480
+ const cols = cte.columns && cte.columns.length ? `(${cte.columns.map((c) => this.quoteIdentifier(c)).join(", ")})` : "";
3481
+ const query = this.stripTrailingSemicolon(this.compileSelectAst(this.normalizeSelectAst(cte.query), ctx));
3482
+ return `${name}${cols} AS (${query})`;
2990
3483
  }).join(", ");
2991
- const whereClause = this.compileWhere(ast.where, ctx);
2992
- return `UPDATE ${table} SET ${assignments}${whereClause};`;
3484
+ return `${prefix}${cteDefs} `;
2993
3485
  }
2994
- compileDeleteAst(ast, ctx) {
2995
- const table = this.quoteIdentifier(ast.from.name);
2996
- const whereClause = this.compileWhere(ast.where, ctx);
2997
- return `DELETE FROM ${table}${whereClause};`;
3486
+ stripTrailingSemicolon(sql) {
3487
+ return sql.trim().replace(/;$/, "");
3488
+ }
3489
+ wrapSetOperand(sql) {
3490
+ const trimmed = this.stripTrailingSemicolon(sql);
3491
+ return `(${trimmed})`;
3492
+ }
3493
+ };
3494
+
3495
+ // src/core/dialect/mysql/index.ts
3496
+ var MySqlDialect = class extends SqlDialectBase {
3497
+ /**
3498
+ * Creates a new MySqlDialect instance
3499
+ */
3500
+ constructor() {
3501
+ super();
3502
+ }
3503
+ /**
3504
+ * Quotes an identifier using MySQL backtick syntax
3505
+ * @param id - Identifier to quote
3506
+ * @returns Quoted identifier
3507
+ */
3508
+ quoteIdentifier(id) {
3509
+ return `\`${id}\``;
3510
+ }
3511
+ /**
3512
+ * Compiles JSON path expression using MySQL syntax
3513
+ * @param node - JSON path node
3514
+ * @returns MySQL JSON path expression
3515
+ */
3516
+ compileJsonPath(node) {
3517
+ const col2 = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
3518
+ return `${col2}->'${node.path}'`;
2998
3519
  }
2999
3520
  };
3000
3521
 
@@ -3038,6 +3559,43 @@ var SqlServerDialect = class extends Dialect {
3038
3559
  * @returns SQL Server SQL string
3039
3560
  */
3040
3561
  compileSelectAst(ast, ctx) {
3562
+ const hasSetOps = !!(ast.setOps && ast.setOps.length);
3563
+ const ctes = this.compileCtes(ast, ctx);
3564
+ const baseAst = hasSetOps ? { ...ast, setOps: void 0, orderBy: void 0, limit: void 0, offset: void 0 } : ast;
3565
+ const baseSelect = this.compileSelectCore(baseAst, ctx);
3566
+ if (!hasSetOps) {
3567
+ return `${ctes}${baseSelect}`;
3568
+ }
3569
+ const compound = ast.setOps.map((op) => `${op.operator} ${this.wrapSetOperand(this.compileSelectAst(op.query, ctx))}`).join(" ");
3570
+ const orderBy = this.compileOrderBy(ast);
3571
+ const pagination = this.compilePagination(ast, orderBy);
3572
+ const combined = `${this.wrapSetOperand(baseSelect)} ${compound}`;
3573
+ const tail = pagination || orderBy;
3574
+ return `${ctes}${combined}${tail}`;
3575
+ }
3576
+ compileInsertAst(ast, ctx) {
3577
+ const table = this.quoteIdentifier(ast.into.name);
3578
+ const columnList = ast.columns.map((column) => `${this.quoteIdentifier(column.table)}.${this.quoteIdentifier(column.name)}`).join(", ");
3579
+ const values = ast.values.map((row) => `(${row.map((value) => this.compileOperand(value, ctx)).join(", ")})`).join(", ");
3580
+ return `INSERT INTO ${table} (${columnList}) VALUES ${values};`;
3581
+ }
3582
+ compileUpdateAst(ast, ctx) {
3583
+ const table = this.quoteIdentifier(ast.table.name);
3584
+ const assignments = ast.set.map((assignment) => {
3585
+ const col2 = assignment.column;
3586
+ const target = `${this.quoteIdentifier(col2.table)}.${this.quoteIdentifier(col2.name)}`;
3587
+ const value = this.compileOperand(assignment.value, ctx);
3588
+ return `${target} = ${value}`;
3589
+ }).join(", ");
3590
+ const whereClause = this.compileWhere(ast.where, ctx);
3591
+ return `UPDATE ${table} SET ${assignments}${whereClause};`;
3592
+ }
3593
+ compileDeleteAst(ast, ctx) {
3594
+ const table = this.quoteIdentifier(ast.from.name);
3595
+ const whereClause = this.compileWhere(ast.where, ctx);
3596
+ return `DELETE FROM ${table}${whereClause};`;
3597
+ }
3598
+ compileSelectCore(ast, ctx) {
3041
3599
  const columns = ast.columns.map((c) => {
3042
3600
  let expr = "";
3043
3601
  if (c.type === "Function") {
@@ -3065,51 +3623,47 @@ var SqlServerDialect = class extends Dialect {
3065
3623
  const whereClause = this.compileWhere(ast.where, ctx);
3066
3624
  const groupBy = ast.groupBy && ast.groupBy.length > 0 ? " GROUP BY " + ast.groupBy.map((c) => `${this.quoteIdentifier(c.table)}.${this.quoteIdentifier(c.name)}`).join(", ") : "";
3067
3625
  const having = ast.having ? ` HAVING ${this.compileExpression(ast.having, ctx)}` : "";
3068
- const orderBy = ast.orderBy && ast.orderBy.length > 0 ? " ORDER BY " + ast.orderBy.map((o) => `${this.quoteIdentifier(o.column.table)}.${this.quoteIdentifier(o.column.name)} ${o.direction}`).join(", ") : "";
3069
- let pagination = "";
3070
- if (ast.limit || ast.offset) {
3071
- const off = ast.offset || 0;
3072
- const orderClause = orderBy || " ORDER BY (SELECT NULL)";
3073
- pagination = `${orderClause} OFFSET ${off} ROWS`;
3074
- if (ast.limit) {
3075
- pagination += ` FETCH NEXT ${ast.limit} ROWS ONLY`;
3076
- }
3077
- return `SELECT ${distinct}${columns} FROM ${from}${joins ? " " + joins : ""}${whereClause}${groupBy}${having}${pagination};`;
3626
+ const orderBy = this.compileOrderBy(ast);
3627
+ const pagination = this.compilePagination(ast, orderBy);
3628
+ if (pagination) {
3629
+ return `SELECT ${distinct}${columns} FROM ${from}${joins ? " " + joins : ""}${whereClause}${groupBy}${having}${pagination}`;
3630
+ }
3631
+ return `SELECT ${distinct}${columns} FROM ${from}${joins ? " " + joins : ""}${whereClause}${groupBy}${having}${orderBy}`;
3632
+ }
3633
+ compileOrderBy(ast) {
3634
+ if (!ast.orderBy || ast.orderBy.length === 0) return "";
3635
+ return " ORDER BY " + ast.orderBy.map((o) => `${this.quoteIdentifier(o.column.table)}.${this.quoteIdentifier(o.column.name)} ${o.direction}`).join(", ");
3636
+ }
3637
+ compilePagination(ast, orderBy) {
3638
+ const hasLimit = ast.limit !== void 0;
3639
+ const hasOffset = ast.offset !== void 0;
3640
+ if (!hasLimit && !hasOffset) return "";
3641
+ const off = ast.offset ?? 0;
3642
+ const orderClause = orderBy || " ORDER BY (SELECT NULL)";
3643
+ let pagination = `${orderClause} OFFSET ${off} ROWS`;
3644
+ if (hasLimit) {
3645
+ pagination += ` FETCH NEXT ${ast.limit} ROWS ONLY`;
3078
3646
  }
3079
- const ctes = ast.ctes && ast.ctes.length > 0 ? "WITH " + ast.ctes.map((cte) => {
3647
+ return pagination;
3648
+ }
3649
+ compileCtes(ast, ctx) {
3650
+ if (!ast.ctes || ast.ctes.length === 0) return "";
3651
+ const defs = ast.ctes.map((cte) => {
3080
3652
  const name = this.quoteIdentifier(cte.name);
3081
3653
  const cols = cte.columns ? `(${cte.columns.map((c) => this.quoteIdentifier(c)).join(", ")})` : "";
3082
- const query = this.compileSelectAst(cte.query, ctx).trim().replace(/;$/, "");
3654
+ const query = this.compileSelectAst(this.normalizeSelectAst(cte.query), ctx).trim().replace(/;$/, "");
3083
3655
  return `${name}${cols} AS (${query})`;
3084
- }).join(", ") + " " : "";
3085
- return `${ctes}SELECT ${distinct}${columns} FROM ${from}${joins ? " " + joins : ""}${whereClause}${groupBy}${having}${orderBy};`;
3086
- }
3087
- compileInsertAst(ast, ctx) {
3088
- const table = this.quoteIdentifier(ast.into.name);
3089
- const columnList = ast.columns.map((column) => `${this.quoteIdentifier(column.table)}.${this.quoteIdentifier(column.name)}`).join(", ");
3090
- const values = ast.values.map((row) => `(${row.map((value) => this.compileOperand(value, ctx)).join(", ")})`).join(", ");
3091
- return `INSERT INTO ${table} (${columnList}) VALUES ${values};`;
3092
- }
3093
- compileUpdateAst(ast, ctx) {
3094
- const table = this.quoteIdentifier(ast.table.name);
3095
- const assignments = ast.set.map((assignment) => {
3096
- const col2 = assignment.column;
3097
- const target = `${this.quoteIdentifier(col2.table)}.${this.quoteIdentifier(col2.name)}`;
3098
- const value = this.compileOperand(assignment.value, ctx);
3099
- return `${target} = ${value}`;
3100
3656
  }).join(", ");
3101
- const whereClause = this.compileWhere(ast.where, ctx);
3102
- return `UPDATE ${table} SET ${assignments}${whereClause};`;
3657
+ return `WITH ${defs} `;
3103
3658
  }
3104
- compileDeleteAst(ast, ctx) {
3105
- const table = this.quoteIdentifier(ast.from.name);
3106
- const whereClause = this.compileWhere(ast.where, ctx);
3107
- return `DELETE FROM ${table}${whereClause};`;
3659
+ wrapSetOperand(sql) {
3660
+ const trimmed = sql.trim().replace(/;$/, "");
3661
+ return `(${trimmed})`;
3108
3662
  }
3109
3663
  };
3110
3664
 
3111
3665
  // src/core/dialect/sqlite/index.ts
3112
- var SqliteDialect = class extends Dialect {
3666
+ var SqliteDialect = class extends SqlDialectBase {
3113
3667
  /**
3114
3668
  * Creates a new SqliteDialect instance
3115
3669
  */
@@ -3133,91 +3687,1143 @@ var SqliteDialect = class extends Dialect {
3133
3687
  const col2 = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
3134
3688
  return `json_extract(${col2}, '${node.path}')`;
3135
3689
  }
3690
+ compileReturning(returning, ctx) {
3691
+ if (!returning || returning.length === 0) return "";
3692
+ const columns = this.formatReturningColumns(returning);
3693
+ return ` RETURNING ${columns}`;
3694
+ }
3695
+ supportsReturning() {
3696
+ return true;
3697
+ }
3698
+ };
3699
+
3700
+ // src/core/dialect/postgres/index.ts
3701
+ var PostgresDialect = class extends SqlDialectBase {
3136
3702
  /**
3137
- * Compiles SELECT query AST to SQLite SQL
3138
- * @param ast - Query AST
3139
- * @param ctx - Compiler context
3140
- * @returns SQLite SQL string
3703
+ * Creates a new PostgresDialect instance
3141
3704
  */
3142
- compileSelectAst(ast, ctx) {
3143
- const columns = ast.columns.map((c) => {
3144
- let expr = "";
3145
- if (c.type === "Function") {
3146
- expr = this.compileOperand(c, ctx);
3147
- } else if (c.type === "Column") {
3148
- expr = `${this.quoteIdentifier(c.table)}.${this.quoteIdentifier(c.name)}`;
3149
- } else if (c.type === "ScalarSubquery") {
3150
- expr = this.compileOperand(c, ctx);
3151
- } else if (c.type === "CaseExpression") {
3152
- expr = this.compileOperand(c, ctx);
3153
- } else if (c.type === "WindowFunction") {
3154
- expr = this.compileOperand(c, ctx);
3155
- }
3156
- if (c.alias) {
3157
- if (c.alias.includes("(")) return c.alias;
3158
- return `${expr} AS ${this.quoteIdentifier(c.alias)}`;
3159
- }
3160
- return expr;
3161
- }).join(", ");
3162
- const distinct = ast.distinct ? "DISTINCT " : "";
3163
- const from = `${this.quoteIdentifier(ast.from.name)}`;
3164
- const joins = ast.joins.map((j) => {
3165
- const table = this.quoteIdentifier(j.table.name);
3166
- const cond = this.compileExpression(j.condition, ctx);
3167
- return `${j.kind} JOIN ${table} ON ${cond}`;
3168
- }).join(" ");
3169
- const whereClause = this.compileWhere(ast.where, ctx);
3170
- const groupBy = ast.groupBy && ast.groupBy.length > 0 ? " GROUP BY " + ast.groupBy.map((c) => `${this.quoteIdentifier(c.table)}.${this.quoteIdentifier(c.name)}`).join(", ") : "";
3171
- const having = ast.having ? ` HAVING ${this.compileExpression(ast.having, ctx)}` : "";
3172
- const orderBy = ast.orderBy && ast.orderBy.length > 0 ? " ORDER BY " + ast.orderBy.map((o) => `${this.quoteIdentifier(o.column.table)}.${this.quoteIdentifier(o.column.name)} ${o.direction}`).join(", ") : "";
3173
- const limit = ast.limit ? ` LIMIT ${ast.limit}` : "";
3174
- const offset = ast.offset ? ` OFFSET ${ast.offset}` : "";
3175
- const ctes = ast.ctes && ast.ctes.length > 0 ? (() => {
3176
- const hasRecursive = ast.ctes.some((cte) => cte.recursive);
3177
- const prefix = hasRecursive ? "WITH RECURSIVE " : "WITH ";
3178
- const cteDefs = ast.ctes.map((cte) => {
3179
- const name = this.quoteIdentifier(cte.name);
3180
- const cols = cte.columns ? `(${cte.columns.map((c) => this.quoteIdentifier(c)).join(", ")})` : "";
3181
- const query = this.compileSelectAst(cte.query, ctx).trim().replace(/;$/, "");
3182
- return `${name}${cols} AS (${query})`;
3183
- }).join(", ");
3184
- return prefix + cteDefs + " ";
3185
- })() : "";
3186
- return `${ctes}SELECT ${distinct}${columns} FROM ${from}${joins ? " " + joins : ""}${whereClause}${groupBy}${having}${orderBy}${limit}${offset};`;
3187
- }
3188
- compileInsertAst(ast, ctx) {
3189
- const table = this.quoteIdentifier(ast.into.name);
3190
- const columnList = ast.columns.map((column) => `${this.quoteIdentifier(column.table)}.${this.quoteIdentifier(column.name)}`).join(", ");
3191
- const values = ast.values.map((row) => `(${row.map((value) => this.compileOperand(value, ctx)).join(", ")})`).join(", ");
3192
- const returning = this.compileReturning(ast.returning, ctx);
3193
- return `INSERT INTO ${table} (${columnList}) VALUES ${values}${returning};`;
3705
+ constructor() {
3706
+ super();
3194
3707
  }
3195
- compileUpdateAst(ast, ctx) {
3196
- const table = this.quoteIdentifier(ast.table.name);
3197
- const assignments = ast.set.map((assignment) => {
3198
- const col2 = assignment.column;
3199
- const target = `${this.quoteIdentifier(col2.table)}.${this.quoteIdentifier(col2.name)}`;
3200
- const value = this.compileOperand(assignment.value, ctx);
3201
- return `${target} = ${value}`;
3202
- }).join(", ");
3203
- const whereClause = this.compileWhere(ast.where, ctx);
3204
- const returning = this.compileReturning(ast.returning, ctx);
3205
- return `UPDATE ${table} SET ${assignments}${whereClause}${returning};`;
3708
+ /**
3709
+ * Quotes an identifier using PostgreSQL double-quote syntax
3710
+ * @param id - Identifier to quote
3711
+ * @returns Quoted identifier
3712
+ */
3713
+ quoteIdentifier(id) {
3714
+ return `"${id}"`;
3206
3715
  }
3207
- compileDeleteAst(ast, ctx) {
3208
- const table = this.quoteIdentifier(ast.from.name);
3209
- const whereClause = this.compileWhere(ast.where, ctx);
3210
- const returning = this.compileReturning(ast.returning, ctx);
3211
- return `DELETE FROM ${table}${whereClause}${returning};`;
3716
+ /**
3717
+ * Compiles JSON path expression using PostgreSQL syntax
3718
+ * @param node - JSON path node
3719
+ * @returns PostgreSQL JSON path expression
3720
+ */
3721
+ compileJsonPath(node) {
3722
+ const col2 = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
3723
+ return `${col2}->>'${node.path}'`;
3212
3724
  }
3213
3725
  compileReturning(returning, ctx) {
3214
3726
  if (!returning || returning.length === 0) return "";
3215
- const columns = returning.map((column) => {
3216
- const tablePart = column.table ? `${this.quoteIdentifier(column.table)}.` : "";
3217
- return `${tablePart}${this.quoteIdentifier(column.name)}`;
3218
- }).join(", ");
3727
+ const columns = this.formatReturningColumns(returning);
3219
3728
  return ` RETURNING ${columns}`;
3220
3729
  }
3730
+ supportsReturning() {
3731
+ return true;
3732
+ }
3733
+ };
3734
+
3735
+ // src/core/ddl/dialects/base-schema-dialect.ts
3736
+ var BaseSchemaDialect = class {
3737
+ supportsPartialIndexes() {
3738
+ return false;
3739
+ }
3740
+ formatTableName(table) {
3741
+ if (table.schema) {
3742
+ return `${this.quoteIdentifier(table.schema)}.${this.quoteIdentifier(table.name)}`;
3743
+ }
3744
+ return this.quoteIdentifier(table.name);
3745
+ }
3746
+ renderDefault(value, _column) {
3747
+ return formatLiteral(value, this.name);
3748
+ }
3749
+ renderReference(ref, _table) {
3750
+ const parts = ["REFERENCES", quoteQualified(this, ref.table), `(${this.quoteIdentifier(ref.column)})`];
3751
+ if (ref.onDelete) parts.push("ON DELETE", ref.onDelete);
3752
+ if (ref.onUpdate) parts.push("ON UPDATE", ref.onUpdate);
3753
+ if (ref.deferrable && this.name === "postgres") parts.push("DEFERRABLE INITIALLY DEFERRED");
3754
+ return parts.join(" ");
3755
+ }
3756
+ renderTableOptions(_table) {
3757
+ return void 0;
3758
+ }
3759
+ dropTableSql(table) {
3760
+ return [`DROP TABLE IF EXISTS ${this.formatTableName(table)};`];
3761
+ }
3762
+ warnDropColumn(_table, _column) {
3763
+ return void 0;
3764
+ }
3765
+ };
3766
+
3767
+ // src/core/ddl/dialects/postgres-schema-dialect.ts
3768
+ var PostgresSchemaDialect = class extends BaseSchemaDialect {
3769
+ constructor() {
3770
+ super(...arguments);
3771
+ this.name = "postgres";
3772
+ }
3773
+ quoteIdentifier(id) {
3774
+ return `"${id}"`;
3775
+ }
3776
+ renderColumnType(column) {
3777
+ switch (column.type) {
3778
+ case "INT":
3779
+ case "INTEGER":
3780
+ case "int":
3781
+ case "integer":
3782
+ return "integer";
3783
+ case "BIGINT":
3784
+ case "bigint":
3785
+ return "bigint";
3786
+ case "UUID":
3787
+ case "uuid":
3788
+ return "uuid";
3789
+ case "BOOLEAN":
3790
+ case "boolean":
3791
+ return "boolean";
3792
+ case "JSON":
3793
+ case "json":
3794
+ return "jsonb";
3795
+ case "DECIMAL":
3796
+ case "decimal":
3797
+ return column.args?.length ? `numeric(${column.args[0]}, ${column.args[1] ?? 0})` : "numeric";
3798
+ case "FLOAT":
3799
+ case "float":
3800
+ case "DOUBLE":
3801
+ case "double":
3802
+ return "double precision";
3803
+ case "TIMESTAMPTZ":
3804
+ case "timestamptz":
3805
+ return "timestamptz";
3806
+ case "TIMESTAMP":
3807
+ case "timestamp":
3808
+ return "timestamp";
3809
+ case "DATE":
3810
+ case "date":
3811
+ return "date";
3812
+ case "DATETIME":
3813
+ case "datetime":
3814
+ return "timestamp";
3815
+ case "VARCHAR":
3816
+ case "varchar":
3817
+ return column.args?.length ? `varchar(${column.args[0]})` : "varchar";
3818
+ case "TEXT":
3819
+ case "text":
3820
+ return "text";
3821
+ case "ENUM":
3822
+ case "enum":
3823
+ return "text";
3824
+ default:
3825
+ return String(column.type).toLowerCase();
3826
+ }
3827
+ }
3828
+ renderAutoIncrement(column) {
3829
+ if (!column.autoIncrement) return void 0;
3830
+ const strategy = column.generated === "always" ? "GENERATED ALWAYS" : "GENERATED BY DEFAULT";
3831
+ return `${strategy} AS IDENTITY`;
3832
+ }
3833
+ renderIndex(table, index) {
3834
+ const name = index.name || deriveIndexName(table, index);
3835
+ const cols = renderIndexColumns(this, index.columns);
3836
+ const unique = index.unique ? "UNIQUE " : "";
3837
+ const where = index.where ? ` WHERE ${index.where}` : "";
3838
+ return `CREATE ${unique}INDEX IF NOT EXISTS ${this.quoteIdentifier(name)} ON ${this.formatTableName(table)} (${cols})${where};`;
3839
+ }
3840
+ supportsPartialIndexes() {
3841
+ return true;
3842
+ }
3843
+ dropColumnSql(table, column) {
3844
+ return [`ALTER TABLE ${this.formatTableName(table)} DROP COLUMN ${this.quoteIdentifier(column)};`];
3845
+ }
3846
+ dropIndexSql(table, index) {
3847
+ const qualified = table.schema ? `${this.quoteIdentifier(table.schema)}.${this.quoteIdentifier(index)}` : this.quoteIdentifier(index);
3848
+ return [`DROP INDEX IF EXISTS ${qualified};`];
3849
+ }
3850
+ };
3851
+
3852
+ // src/core/ddl/dialects/mysql-schema-dialect.ts
3853
+ var MySqlSchemaDialect = class extends BaseSchemaDialect {
3854
+ constructor() {
3855
+ super(...arguments);
3856
+ this.name = "mysql";
3857
+ }
3858
+ quoteIdentifier(id) {
3859
+ return `\`${id}\``;
3860
+ }
3861
+ renderColumnType(column) {
3862
+ switch (column.type) {
3863
+ case "INT":
3864
+ case "INTEGER":
3865
+ case "int":
3866
+ case "integer":
3867
+ return "INT";
3868
+ case "BIGINT":
3869
+ case "bigint":
3870
+ return "BIGINT";
3871
+ case "UUID":
3872
+ case "uuid":
3873
+ return "CHAR(36)";
3874
+ case "BOOLEAN":
3875
+ case "boolean":
3876
+ return "TINYINT(1)";
3877
+ case "JSON":
3878
+ case "json":
3879
+ return "JSON";
3880
+ case "DECIMAL":
3881
+ case "decimal":
3882
+ return column.args?.length ? `DECIMAL(${column.args[0]},${column.args[1] ?? 0})` : "DECIMAL";
3883
+ case "FLOAT":
3884
+ case "float":
3885
+ return column.args?.length ? `FLOAT(${column.args[0]})` : "FLOAT";
3886
+ case "DOUBLE":
3887
+ case "double":
3888
+ return "DOUBLE";
3889
+ case "TIMESTAMPTZ":
3890
+ case "timestamptz":
3891
+ return "TIMESTAMP";
3892
+ case "TIMESTAMP":
3893
+ case "timestamp":
3894
+ return "TIMESTAMP";
3895
+ case "DATETIME":
3896
+ case "datetime":
3897
+ return "DATETIME";
3898
+ case "DATE":
3899
+ case "date":
3900
+ return "DATE";
3901
+ case "VARCHAR":
3902
+ case "varchar":
3903
+ return column.args?.length ? `VARCHAR(${column.args[0]})` : "VARCHAR(255)";
3904
+ case "TEXT":
3905
+ case "text":
3906
+ return "TEXT";
3907
+ case "ENUM":
3908
+ case "enum":
3909
+ return column.args && Array.isArray(column.args) && column.args.length ? `ENUM(${column.args.map((v) => `'${escapeLiteral(v)}'`).join(",")})` : "ENUM";
3910
+ default:
3911
+ return String(column.type).toUpperCase();
3912
+ }
3913
+ }
3914
+ renderDefault(value) {
3915
+ return formatLiteral(value, this.name);
3916
+ }
3917
+ renderAutoIncrement(column) {
3918
+ return column.autoIncrement ? "AUTO_INCREMENT" : void 0;
3919
+ }
3920
+ renderIndex(table, index) {
3921
+ if (index.where) {
3922
+ throw new Error("MySQL does not support partial/filtered indexes");
3923
+ }
3924
+ const name = index.name || deriveIndexName(table, index);
3925
+ const cols = renderIndexColumns(this, index.columns);
3926
+ const unique = index.unique ? "UNIQUE " : "";
3927
+ return `CREATE ${unique}INDEX ${this.quoteIdentifier(name)} ON ${this.formatTableName(table)} (${cols});`;
3928
+ }
3929
+ renderTableOptions(table) {
3930
+ const parts = [];
3931
+ if (table.engine) parts.push(`ENGINE=${table.engine}`);
3932
+ if (table.charset) parts.push(`DEFAULT CHARSET=${table.charset}`);
3933
+ if (table.collation) parts.push(`COLLATE=${table.collation}`);
3934
+ return parts.length ? parts.join(" ") : void 0;
3935
+ }
3936
+ dropColumnSql(table, column) {
3937
+ return [`ALTER TABLE ${this.formatTableName(table)} DROP COLUMN ${this.quoteIdentifier(column)};`];
3938
+ }
3939
+ dropIndexSql(table, index) {
3940
+ return [`DROP INDEX ${this.quoteIdentifier(index)} ON ${this.formatTableName(table)};`];
3941
+ }
3942
+ };
3943
+
3944
+ // src/core/ddl/dialects/sqlite-schema-dialect.ts
3945
+ var SQLiteSchemaDialect = class extends BaseSchemaDialect {
3946
+ constructor() {
3947
+ super(...arguments);
3948
+ this.name = "sqlite";
3949
+ }
3950
+ quoteIdentifier(id) {
3951
+ return `"${id}"`;
3952
+ }
3953
+ renderColumnType(column) {
3954
+ switch (column.type) {
3955
+ case "INT":
3956
+ case "INTEGER":
3957
+ case "int":
3958
+ case "integer":
3959
+ case "BIGINT":
3960
+ case "bigint":
3961
+ return "INTEGER";
3962
+ case "BOOLEAN":
3963
+ case "boolean":
3964
+ return "INTEGER";
3965
+ case "DECIMAL":
3966
+ case "decimal":
3967
+ case "FLOAT":
3968
+ case "float":
3969
+ case "DOUBLE":
3970
+ case "double":
3971
+ return "REAL";
3972
+ case "DATE":
3973
+ case "date":
3974
+ case "DATETIME":
3975
+ case "datetime":
3976
+ case "TIMESTAMP":
3977
+ case "timestamp":
3978
+ case "TIMESTAMPTZ":
3979
+ case "timestamptz":
3980
+ return "TEXT";
3981
+ case "VARCHAR":
3982
+ case "varchar":
3983
+ case "TEXT":
3984
+ case "text":
3985
+ case "JSON":
3986
+ case "json":
3987
+ case "UUID":
3988
+ case "uuid":
3989
+ return "TEXT";
3990
+ case "ENUM":
3991
+ case "enum":
3992
+ return "TEXT";
3993
+ default:
3994
+ return "TEXT";
3995
+ }
3996
+ }
3997
+ renderAutoIncrement(column, table) {
3998
+ const pk = resolvePrimaryKey(table);
3999
+ if (column.autoIncrement && pk.length === 1 && pk[0] === column.name) {
4000
+ return "PRIMARY KEY AUTOINCREMENT";
4001
+ }
4002
+ return void 0;
4003
+ }
4004
+ preferInlinePkAutoincrement(column, table, pk) {
4005
+ return !!(column.autoIncrement && pk.length === 1 && pk[0] === column.name);
4006
+ }
4007
+ renderDefault(value) {
4008
+ return formatLiteral(value, this.name);
4009
+ }
4010
+ renderIndex(table, index) {
4011
+ if (index.where) {
4012
+ throw new Error("SQLite does not support partial/filtered indexes");
4013
+ }
4014
+ const name = index.name || deriveIndexName(table, index);
4015
+ const cols = renderIndexColumns(this, index.columns);
4016
+ const unique = index.unique ? "UNIQUE " : "";
4017
+ return `CREATE ${unique}INDEX IF NOT EXISTS ${this.quoteIdentifier(name)} ON ${this.formatTableName(table)} (${cols});`;
4018
+ }
4019
+ dropColumnSql(_table, _column) {
4020
+ return [];
4021
+ }
4022
+ dropIndexSql(_table, index) {
4023
+ return [`DROP INDEX IF EXISTS ${this.quoteIdentifier(index)};`];
4024
+ }
4025
+ warnDropColumn(table, column) {
4026
+ const key = table.schema ? `${table.schema}.${table.name}` : table.name;
4027
+ return `Dropping columns on SQLite requires table rebuild (column ${column} on ${key}).`;
4028
+ }
4029
+ };
4030
+
4031
+ // src/core/ddl/dialects/mssql-schema-dialect.ts
4032
+ var MSSqlSchemaDialect = class extends BaseSchemaDialect {
4033
+ constructor() {
4034
+ super(...arguments);
4035
+ this.name = "mssql";
4036
+ }
4037
+ quoteIdentifier(id) {
4038
+ return `[${id.replace(/]/g, "]]")}]`;
4039
+ }
4040
+ renderColumnType(column) {
4041
+ switch (column.type) {
4042
+ case "INT":
4043
+ case "INTEGER":
4044
+ case "int":
4045
+ case "integer":
4046
+ return "INT";
4047
+ case "BIGINT":
4048
+ case "bigint":
4049
+ return "BIGINT";
4050
+ case "UUID":
4051
+ case "uuid":
4052
+ return "UNIQUEIDENTIFIER";
4053
+ case "BOOLEAN":
4054
+ case "boolean":
4055
+ return "BIT";
4056
+ case "JSON":
4057
+ case "json":
4058
+ return "NVARCHAR(MAX)";
4059
+ case "DECIMAL":
4060
+ case "decimal":
4061
+ return column.args?.length ? `DECIMAL(${column.args[0]},${column.args[1] ?? 0})` : "DECIMAL(18,0)";
4062
+ case "FLOAT":
4063
+ case "float":
4064
+ case "DOUBLE":
4065
+ case "double":
4066
+ return "FLOAT";
4067
+ case "TIMESTAMPTZ":
4068
+ case "timestamptz":
4069
+ case "TIMESTAMP":
4070
+ case "timestamp":
4071
+ case "DATETIME":
4072
+ case "datetime":
4073
+ return "DATETIME2";
4074
+ case "DATE":
4075
+ case "date":
4076
+ return "DATE";
4077
+ case "VARCHAR":
4078
+ case "varchar":
4079
+ return column.args?.length ? `NVARCHAR(${column.args[0]})` : "NVARCHAR(255)";
4080
+ case "TEXT":
4081
+ case "text":
4082
+ return "NVARCHAR(MAX)";
4083
+ case "ENUM":
4084
+ case "enum":
4085
+ return "NVARCHAR(255)";
4086
+ default:
4087
+ return String(column.type).toUpperCase();
4088
+ }
4089
+ }
4090
+ renderDefault(value) {
4091
+ return formatLiteral(value, this.name);
4092
+ }
4093
+ renderAutoIncrement(column) {
4094
+ return column.autoIncrement ? "IDENTITY(1,1)" : void 0;
4095
+ }
4096
+ renderIndex(table, index) {
4097
+ const name = index.name || deriveIndexName(table, index);
4098
+ const cols = renderIndexColumns(this, index.columns);
4099
+ const unique = index.unique ? "UNIQUE " : "";
4100
+ const where = index.where ? ` WHERE ${index.where}` : "";
4101
+ return `CREATE ${unique}INDEX ${this.quoteIdentifier(name)} ON ${this.formatTableName(table)} (${cols})${where};`;
4102
+ }
4103
+ supportsPartialIndexes() {
4104
+ return true;
4105
+ }
4106
+ dropColumnSql(table, column) {
4107
+ return [`ALTER TABLE ${this.formatTableName(table)} DROP COLUMN ${this.quoteIdentifier(column)};`];
4108
+ }
4109
+ dropIndexSql(table, index) {
4110
+ return [`DROP INDEX ${this.quoteIdentifier(index)} ON ${this.formatTableName(table)};`];
4111
+ }
4112
+ };
4113
+
4114
+ // src/core/ddl/schema-generator.ts
4115
+ var escapeLiteral = (value) => value.replace(/'/g, "''");
4116
+ var isRawDefault = (value) => {
4117
+ return !!value && typeof value === "object" && "raw" in value && typeof value.raw === "string";
4118
+ };
4119
+ var formatLiteral = (value, dialect) => {
4120
+ if (isRawDefault(value)) return value.raw;
4121
+ if (value === null) return "NULL";
4122
+ if (typeof value === "number") return Number.isFinite(value) ? String(value) : "NULL";
4123
+ if (typeof value === "boolean") {
4124
+ if (dialect === "mysql" || dialect === "sqlite" || dialect === "mssql") {
4125
+ return value ? "1" : "0";
4126
+ }
4127
+ return value ? "TRUE" : "FALSE";
4128
+ }
4129
+ if (value instanceof Date) return `'${escapeLiteral(value.toISOString())}'`;
4130
+ if (typeof value === "string") return `'${escapeLiteral(value)}'`;
4131
+ return `'${escapeLiteral(JSON.stringify(value))}'`;
4132
+ };
4133
+ var resolvePrimaryKey = (table) => {
4134
+ if (table.primaryKey && table.primaryKey.length > 0) {
4135
+ return table.primaryKey;
4136
+ }
4137
+ const cols = Object.values(table.columns);
4138
+ return cols.filter((c) => c.primary).map((c) => c.name);
4139
+ };
4140
+ var quoteQualified = (dialect, identifier) => {
4141
+ if (identifier.includes(".")) {
4142
+ return identifier.split(".").map((part) => dialect.quoteIdentifier(part)).join(".");
4143
+ }
4144
+ return dialect.quoteIdentifier(identifier);
4145
+ };
4146
+ var renderIndexColumns = (dialect, columns) => {
4147
+ return columns.map((col2) => {
4148
+ if (typeof col2 === "string") return dialect.quoteIdentifier(col2);
4149
+ const parts = [dialect.quoteIdentifier(col2.column)];
4150
+ if (col2.order) parts.push(col2.order);
4151
+ if (col2.nulls) parts.push(`NULLS ${col2.nulls}`);
4152
+ return parts.join(" ");
4153
+ }).join(", ");
4154
+ };
4155
+ var deriveIndexName = (table, index) => {
4156
+ const base = (index.columns || []).map((col2) => typeof col2 === "string" ? col2 : col2.column).join("_");
4157
+ const suffix = index.unique ? "uniq" : "idx";
4158
+ return `${table.name}_${base}_${suffix}`;
4159
+ };
4160
+ var renderColumnDefinition = (table, col2, dialect, options = {}) => {
4161
+ const parts = [];
4162
+ parts.push(dialect.quoteIdentifier(col2.name));
4163
+ parts.push(dialect.renderColumnType(col2));
4164
+ const autoInc = dialect.renderAutoIncrement(col2, table);
4165
+ if (autoInc) parts.push(autoInc);
4166
+ if (col2.notNull) parts.push("NOT NULL");
4167
+ if (col2.unique) parts.push("UNIQUE");
4168
+ if (col2.default !== void 0) {
4169
+ parts.push(`DEFAULT ${dialect.renderDefault(col2.default, col2)}`);
4170
+ }
4171
+ if (options.includePrimary && col2.primary) {
4172
+ parts.push("PRIMARY KEY");
4173
+ }
4174
+ if (col2.check) {
4175
+ parts.push(`CHECK (${col2.check})`);
4176
+ }
4177
+ if (col2.references) {
4178
+ parts.push(dialect.renderReference(col2.references, table));
4179
+ }
4180
+ return { sql: parts.join(" "), inlinePrimary: !!(options.includePrimary && col2.primary) };
4181
+ };
4182
+ var generateCreateTableSql = (table, dialect) => {
4183
+ const pk = resolvePrimaryKey(table);
4184
+ const inlinePkColumns = /* @__PURE__ */ new Set();
4185
+ const columnLines = Object.values(table.columns).map((col2) => {
4186
+ const includePk = dialect.preferInlinePkAutoincrement?.(col2, table, pk) && pk.includes(col2.name);
4187
+ if (includePk) {
4188
+ inlinePkColumns.add(col2.name);
4189
+ }
4190
+ return renderColumnDefinition(table, col2, dialect, { includePrimary: includePk }).sql;
4191
+ });
4192
+ const constraintLines = [];
4193
+ if (pk.length > 0 && !(pk.length === 1 && inlinePkColumns.has(pk[0]))) {
4194
+ const cols = pk.map((c) => dialect.quoteIdentifier(c)).join(", ");
4195
+ constraintLines.push(`PRIMARY KEY (${cols})`);
4196
+ }
4197
+ if (table.checks) {
4198
+ table.checks.forEach((check) => {
4199
+ const name = check.name ? `${dialect.quoteIdentifier(check.name)} ` : "";
4200
+ constraintLines.push(`CONSTRAINT ${name}CHECK (${check.expression})`);
4201
+ });
4202
+ }
4203
+ const allLines = [...columnLines, ...constraintLines];
4204
+ const body = allLines.map((line) => ` ${line}`).join(",\n");
4205
+ const tableOptions = dialect.renderTableOptions(table);
4206
+ const tableSql = `CREATE TABLE ${dialect.formatTableName(table)} (
4207
+ ${body}
4208
+ )${tableOptions ? " " + tableOptions : ""};`;
4209
+ const indexSql = [];
4210
+ if (table.indexes && table.indexes.length > 0) {
4211
+ for (const idx of table.indexes) {
4212
+ if (idx.where && !dialect.supportsPartialIndexes()) {
4213
+ throw new Error(`Dialect ${dialect.name} does not support partial/filtered indexes (${idx.name || idx.columns.join("_")}).`);
4214
+ }
4215
+ indexSql.push(dialect.renderIndex(table, idx));
4216
+ }
4217
+ }
4218
+ return { tableSql, indexSql };
4219
+ };
4220
+ var generateSchemaSql = (tables, dialect) => {
4221
+ const ordered = orderTablesByDependencies(tables);
4222
+ const statements = [];
4223
+ ordered.forEach((table) => {
4224
+ const { tableSql, indexSql } = generateCreateTableSql(table, dialect);
4225
+ statements.push(tableSql, ...indexSql);
4226
+ });
4227
+ return statements;
4228
+ };
4229
+ var orderTablesByDependencies = (tables) => {
4230
+ const map = /* @__PURE__ */ new Map();
4231
+ tables.forEach((t) => map.set(t.name, t));
4232
+ const deps = /* @__PURE__ */ new Map();
4233
+ for (const table of tables) {
4234
+ const refTables = /* @__PURE__ */ new Set();
4235
+ Object.values(table.columns).forEach((col2) => {
4236
+ if (col2.references?.table) {
4237
+ refTables.add(col2.references.table);
4238
+ }
4239
+ });
4240
+ deps.set(table.name, refTables);
4241
+ }
4242
+ const visited = /* @__PURE__ */ new Set();
4243
+ const ordered = [];
4244
+ const visit = (name, stack) => {
4245
+ if (visited.has(name)) return;
4246
+ const table = map.get(name);
4247
+ if (!table) return;
4248
+ if (stack.has(name)) {
4249
+ ordered.push(table);
4250
+ visited.add(name);
4251
+ return;
4252
+ }
4253
+ stack.add(name);
4254
+ for (const dep of deps.get(name) || []) {
4255
+ visit(dep, stack);
4256
+ }
4257
+ stack.delete(name);
4258
+ visited.add(name);
4259
+ ordered.push(table);
4260
+ };
4261
+ tables.forEach((t) => visit(t.name, /* @__PURE__ */ new Set()));
4262
+ return ordered;
4263
+ };
4264
+
4265
+ // src/core/ddl/schema-diff.ts
4266
+ var tableKey = (name, schema) => schema ? `${schema}.${name}` : name;
4267
+ var mapTables = (schema) => {
4268
+ const map = /* @__PURE__ */ new Map();
4269
+ for (const table of schema.tables) {
4270
+ map.set(tableKey(table.name, table.schema), table);
4271
+ }
4272
+ return map;
4273
+ };
4274
+ var buildAddColumnSql = (table, colName, dialect) => {
4275
+ const column = table.columns[colName];
4276
+ const rendered = renderColumnDefinition(table, column, dialect);
4277
+ return `ALTER TABLE ${dialect.formatTableName(table)} ADD ${rendered.sql};`;
4278
+ };
4279
+ var diffSchema = (expectedTables, actualSchema, dialect, options = {}) => {
4280
+ const allowDestructive = options.allowDestructive ?? false;
4281
+ const plan = { changes: [], warnings: [] };
4282
+ const actualMap = mapTables(actualSchema);
4283
+ for (const table of expectedTables) {
4284
+ const key = tableKey(table.name, table.schema);
4285
+ const actual = actualMap.get(key);
4286
+ if (!actual) {
4287
+ const { tableSql, indexSql } = generateCreateTableSql(table, dialect);
4288
+ plan.changes.push({
4289
+ kind: "createTable",
4290
+ table: key,
4291
+ description: `Create table ${key}`,
4292
+ statements: [tableSql, ...indexSql],
4293
+ safe: true
4294
+ });
4295
+ continue;
4296
+ }
4297
+ const actualCols = new Map(actual.columns.map((c) => [c.name, c]));
4298
+ for (const colName of Object.keys(table.columns)) {
4299
+ if (!actualCols.has(colName)) {
4300
+ plan.changes.push({
4301
+ kind: "addColumn",
4302
+ table: key,
4303
+ description: `Add column ${colName} to ${key}`,
4304
+ statements: [buildAddColumnSql(table, colName, dialect)],
4305
+ safe: true
4306
+ });
4307
+ }
4308
+ }
4309
+ for (const colName of actualCols.keys()) {
4310
+ if (!table.columns[colName]) {
4311
+ plan.changes.push({
4312
+ kind: "dropColumn",
4313
+ table: key,
4314
+ description: `Drop column ${colName} from ${key}`,
4315
+ statements: allowDestructive ? dialect.dropColumnSql(actual, colName) : [],
4316
+ safe: false
4317
+ });
4318
+ const warning = dialect.warnDropColumn?.(actual, colName);
4319
+ if (warning) plan.warnings.push(warning);
4320
+ }
4321
+ }
4322
+ const expectedIndexes = table.indexes ?? [];
4323
+ const actualIndexes = actual.indexes ?? [];
4324
+ const actualIndexMap = new Map(actualIndexes.map((idx) => [idx.name, idx]));
4325
+ for (const idx of expectedIndexes) {
4326
+ const name = idx.name || deriveIndexName(table, idx);
4327
+ if (!actualIndexMap.has(name)) {
4328
+ plan.changes.push({
4329
+ kind: "addIndex",
4330
+ table: key,
4331
+ description: `Create index ${name} on ${key}`,
4332
+ statements: [dialect.renderIndex(table, { ...idx, name })],
4333
+ safe: true
4334
+ });
4335
+ }
4336
+ }
4337
+ for (const idx of actualIndexes) {
4338
+ if (idx.name && !expectedIndexes.find((expected) => (expected.name || deriveIndexName(table, expected)) === idx.name)) {
4339
+ plan.changes.push({
4340
+ kind: "dropIndex",
4341
+ table: key,
4342
+ description: `Drop index ${idx.name} on ${key}`,
4343
+ statements: allowDestructive ? dialect.dropIndexSql(actual, idx.name) : [],
4344
+ safe: false
4345
+ });
4346
+ }
4347
+ }
4348
+ }
4349
+ for (const actual of actualSchema.tables) {
4350
+ const key = tableKey(actual.name, actual.schema);
4351
+ if (!expectedTables.find((t) => tableKey(t.name, t.schema) === key)) {
4352
+ plan.changes.push({
4353
+ kind: "dropTable",
4354
+ table: key,
4355
+ description: `Drop table ${key}`,
4356
+ statements: allowDestructive ? dialect.dropTableSql(actual) : [],
4357
+ safe: false
4358
+ });
4359
+ }
4360
+ }
4361
+ return plan;
4362
+ };
4363
+ var synchronizeSchema = async (expectedTables, actualSchema, dialect, executor, options = {}) => {
4364
+ const plan = diffSchema(expectedTables, actualSchema, dialect, options);
4365
+ if (options.dryRun) return plan;
4366
+ for (const change of plan.changes) {
4367
+ if (!change.statements.length) continue;
4368
+ if (!change.safe && !options.allowDestructive) continue;
4369
+ for (const stmt of change.statements) {
4370
+ if (!stmt.trim()) continue;
4371
+ await executor.executeSql(stmt);
4372
+ }
4373
+ }
4374
+ return plan;
4375
+ };
4376
+
4377
+ // src/core/ddl/introspect/utils.ts
4378
+ var toRows = (result) => {
4379
+ if (!result) return [];
4380
+ return result.values.map(
4381
+ (row) => result.columns.reduce((acc, col2, idx) => {
4382
+ acc[col2] = row[idx];
4383
+ return acc;
4384
+ }, {})
4385
+ );
4386
+ };
4387
+ var queryRows = async (executor, sql, params = []) => {
4388
+ const [first] = await executor.executeSql(sql, params);
4389
+ return toRows(first);
4390
+ };
4391
+ var shouldIncludeTable = (name, options) => {
4392
+ if (options.includeTables && !options.includeTables.includes(name)) return false;
4393
+ if (options.excludeTables && options.excludeTables.includes(name)) return false;
4394
+ return true;
4395
+ };
4396
+
4397
+ // src/core/ddl/introspect/postgres.ts
4398
+ var postgresIntrospector = {
4399
+ async introspect(executor, options) {
4400
+ const schema = options.schema || "public";
4401
+ const tables = [];
4402
+ const columnRows = await queryRows(
4403
+ executor,
4404
+ `
4405
+ SELECT table_schema, table_name, column_name, data_type, is_nullable, column_default
4406
+ FROM information_schema.columns
4407
+ WHERE table_schema = $1
4408
+ ORDER BY table_name, ordinal_position
4409
+ `,
4410
+ [schema]
4411
+ );
4412
+ const pkRows = await queryRows(
4413
+ executor,
4414
+ `
4415
+ SELECT
4416
+ ns.nspname AS table_schema,
4417
+ tbl.relname AS table_name,
4418
+ array_agg(att.attname ORDER BY arr.idx) AS pk_columns
4419
+ FROM pg_index i
4420
+ JOIN pg_class tbl ON tbl.oid = i.indrelid
4421
+ JOIN pg_namespace ns ON ns.oid = tbl.relnamespace
4422
+ JOIN LATERAL unnest(i.indkey) WITH ORDINALITY AS arr(attnum, idx) ON TRUE
4423
+ LEFT JOIN pg_attribute att ON att.attrelid = tbl.oid AND att.attnum = arr.attnum
4424
+ WHERE i.indisprimary AND ns.nspname = $1
4425
+ GROUP BY ns.nspname, tbl.relname
4426
+ `,
4427
+ [schema]
4428
+ );
4429
+ const pkMap = /* @__PURE__ */ new Map();
4430
+ pkRows.forEach((r) => {
4431
+ pkMap.set(`${r.table_schema}.${r.table_name}`, r.pk_columns || []);
4432
+ });
4433
+ const fkRows = await queryRows(
4434
+ executor,
4435
+ `
4436
+ SELECT
4437
+ tc.table_schema,
4438
+ tc.table_name,
4439
+ kcu.column_name,
4440
+ ccu.table_schema AS foreign_table_schema,
4441
+ ccu.table_name AS foreign_table_name,
4442
+ ccu.column_name AS foreign_column_name,
4443
+ rc.update_rule AS on_update,
4444
+ rc.delete_rule AS on_delete
4445
+ FROM information_schema.table_constraints AS tc
4446
+ JOIN information_schema.key_column_usage AS kcu
4447
+ ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema
4448
+ JOIN information_schema.constraint_column_usage AS ccu
4449
+ ON ccu.constraint_name = tc.constraint_name AND ccu.table_schema = tc.table_schema
4450
+ JOIN information_schema.referential_constraints rc
4451
+ ON rc.constraint_name = tc.constraint_name AND rc.constraint_schema = tc.table_schema
4452
+ WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_schema = $1
4453
+ `,
4454
+ [schema]
4455
+ );
4456
+ const fkMap = /* @__PURE__ */ new Map();
4457
+ fkRows.forEach((r) => {
4458
+ const key = `${r.table_schema}.${r.table_name}.${r.column_name}`;
4459
+ fkMap.set(key, [{
4460
+ table: `${r.foreign_table_schema}.${r.foreign_table_name}`,
4461
+ column: r.foreign_column_name,
4462
+ onDelete: r.on_delete?.toUpperCase(),
4463
+ onUpdate: r.on_update?.toUpperCase()
4464
+ }]);
4465
+ });
4466
+ const indexRows = await queryRows(
4467
+ executor,
4468
+ `
4469
+ SELECT
4470
+ ns.nspname AS table_schema,
4471
+ tbl.relname AS table_name,
4472
+ idx.relname AS index_name,
4473
+ i.indisunique AS is_unique,
4474
+ pg_get_expr(i.indpred, i.indrelid) AS predicate,
4475
+ array_agg(att.attname ORDER BY arr.idx) AS column_names
4476
+ FROM pg_index i
4477
+ JOIN pg_class tbl ON tbl.oid = i.indrelid
4478
+ JOIN pg_namespace ns ON ns.oid = tbl.relnamespace
4479
+ JOIN pg_class idx ON idx.oid = i.indexrelid
4480
+ JOIN LATERAL unnest(i.indkey) WITH ORDINALITY AS arr(attnum, idx) ON TRUE
4481
+ LEFT JOIN pg_attribute att ON att.attrelid = tbl.oid AND att.attnum = arr.attnum
4482
+ WHERE ns.nspname = $1 AND NOT i.indisprimary
4483
+ GROUP BY ns.nspname, tbl.relname, idx.relname, i.indisunique, i.indpred
4484
+ `,
4485
+ [schema]
4486
+ );
4487
+ const tablesByKey = /* @__PURE__ */ new Map();
4488
+ columnRows.forEach((r) => {
4489
+ const key = `${r.table_schema}.${r.table_name}`;
4490
+ if (!shouldIncludeTable(r.table_name, options)) {
4491
+ return;
4492
+ }
4493
+ if (!tablesByKey.has(key)) {
4494
+ tablesByKey.set(key, {
4495
+ name: r.table_name,
4496
+ schema: r.table_schema,
4497
+ columns: [],
4498
+ primaryKey: pkMap.get(key) || [],
4499
+ indexes: []
4500
+ });
4501
+ }
4502
+ const cols = tablesByKey.get(key);
4503
+ const fk = fkMap.get(`${r.table_schema}.${r.table_name}.${r.column_name}`)?.[0];
4504
+ const column = {
4505
+ name: r.column_name,
4506
+ type: r.data_type,
4507
+ notNull: r.is_nullable === "NO",
4508
+ default: r.column_default ?? void 0,
4509
+ references: fk ? {
4510
+ table: fk.table,
4511
+ column: fk.column,
4512
+ onDelete: fk.onDelete,
4513
+ onUpdate: fk.onUpdate
4514
+ } : void 0
4515
+ };
4516
+ cols.columns.push(column);
4517
+ });
4518
+ indexRows.forEach((r) => {
4519
+ const key = `${r.table_schema}.${r.table_name}`;
4520
+ const table = tablesByKey.get(key);
4521
+ if (!table) return;
4522
+ const idx = {
4523
+ name: r.index_name,
4524
+ columns: (r.column_names || []).map((c) => ({ column: c })),
4525
+ unique: !!r.is_unique,
4526
+ where: r.predicate || void 0
4527
+ };
4528
+ table.indexes = table.indexes || [];
4529
+ table.indexes.push(idx);
4530
+ });
4531
+ tables.push(...tablesByKey.values());
4532
+ return { tables };
4533
+ }
4534
+ };
4535
+
4536
+ // src/core/ddl/introspect/mysql.ts
4537
+ var mysqlIntrospector = {
4538
+ async introspect(executor, options) {
4539
+ const schema = options.schema;
4540
+ const filterClause = schema ? "table_schema = ?" : "table_schema = database()";
4541
+ const params = schema ? [schema] : [];
4542
+ const columnRows = await queryRows(
4543
+ executor,
4544
+ `
4545
+ SELECT table_schema, table_name, column_name, data_type, is_nullable, column_default, extra
4546
+ FROM information_schema.columns
4547
+ WHERE ${filterClause}
4548
+ ORDER BY table_name, ordinal_position
4549
+ `,
4550
+ params
4551
+ );
4552
+ const pkRows = await queryRows(
4553
+ executor,
4554
+ `
4555
+ SELECT table_schema, table_name, column_name
4556
+ FROM information_schema.key_column_usage
4557
+ WHERE constraint_name = 'PRIMARY' AND ${filterClause}
4558
+ ORDER BY ordinal_position
4559
+ `,
4560
+ params
4561
+ );
4562
+ const pkMap = /* @__PURE__ */ new Map();
4563
+ pkRows.forEach((r) => {
4564
+ const key = `${r.table_schema}.${r.table_name}`;
4565
+ const list = pkMap.get(key) || [];
4566
+ list.push(r.column_name);
4567
+ pkMap.set(key, list);
4568
+ });
4569
+ const indexRows = await queryRows(
4570
+ executor,
4571
+ `
4572
+ SELECT
4573
+ table_schema,
4574
+ table_name,
4575
+ index_name,
4576
+ non_unique,
4577
+ GROUP_CONCAT(column_name ORDER BY seq_in_index) AS cols
4578
+ FROM information_schema.statistics
4579
+ WHERE ${filterClause} AND index_name <> 'PRIMARY'
4580
+ GROUP BY table_schema, table_name, index_name, non_unique
4581
+ `,
4582
+ params
4583
+ );
4584
+ const tablesByKey = /* @__PURE__ */ new Map();
4585
+ columnRows.forEach((r) => {
4586
+ const key = `${r.table_schema}.${r.table_name}`;
4587
+ if (!shouldIncludeTable(r.table_name, options)) return;
4588
+ if (!tablesByKey.has(key)) {
4589
+ tablesByKey.set(key, {
4590
+ name: r.table_name,
4591
+ schema: r.table_schema,
4592
+ columns: [],
4593
+ primaryKey: pkMap.get(key) || [],
4594
+ indexes: []
4595
+ });
4596
+ }
4597
+ const cols = tablesByKey.get(key);
4598
+ const column = {
4599
+ name: r.column_name,
4600
+ type: r.data_type,
4601
+ notNull: r.is_nullable === "NO",
4602
+ default: r.column_default ?? void 0,
4603
+ autoIncrement: typeof r.extra === "string" && r.extra.includes("auto_increment")
4604
+ };
4605
+ cols.columns.push(column);
4606
+ });
4607
+ indexRows.forEach((r) => {
4608
+ const key = `${r.table_schema}.${r.table_name}`;
4609
+ const table = tablesByKey.get(key);
4610
+ if (!table) return;
4611
+ const cols = (typeof r.cols === "string" ? r.cols.split(",") : []).map((c) => ({ column: c.trim() }));
4612
+ const idx = {
4613
+ name: r.index_name,
4614
+ columns: cols,
4615
+ unique: r.non_unique === 0
4616
+ };
4617
+ table.indexes = table.indexes || [];
4618
+ table.indexes.push(idx);
4619
+ });
4620
+ return { tables: Array.from(tablesByKey.values()) };
4621
+ }
4622
+ };
4623
+
4624
+ // src/core/ddl/introspect/sqlite.ts
4625
+ var escapeSingleQuotes = (name) => name.replace(/'/g, "''");
4626
+ var sqliteIntrospector = {
4627
+ async introspect(executor, options) {
4628
+ const tables = [];
4629
+ const tableRows = await queryRows(
4630
+ executor,
4631
+ `SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%';`
4632
+ );
4633
+ for (const row of tableRows) {
4634
+ const name = row.name;
4635
+ if (!shouldIncludeTable(name, options)) continue;
4636
+ const table = { name, columns: [], primaryKey: [], indexes: [] };
4637
+ const cols = await queryRows(executor, `PRAGMA table_info('${escapeSingleQuotes(name)}');`);
4638
+ cols.forEach((c) => {
4639
+ table.columns.push({
4640
+ name: c.name,
4641
+ type: c.type,
4642
+ notNull: c.notnull === 1,
4643
+ default: c.dflt_value ?? void 0,
4644
+ autoIncrement: false
4645
+ });
4646
+ if (c.pk && c.pk > 0) {
4647
+ table.primaryKey = table.primaryKey || [];
4648
+ table.primaryKey.push(c.name);
4649
+ }
4650
+ });
4651
+ const fkRows = await queryRows(executor, `PRAGMA foreign_key_list('${escapeSingleQuotes(name)}');`);
4652
+ fkRows.forEach((fk) => {
4653
+ const col2 = table.columns.find((c) => c.name === fk.from);
4654
+ if (col2) {
4655
+ col2.references = {
4656
+ table: fk.table,
4657
+ column: fk.to,
4658
+ onDelete: fk.on_delete?.toUpperCase(),
4659
+ onUpdate: fk.on_update?.toUpperCase()
4660
+ };
4661
+ }
4662
+ });
4663
+ const idxList = await queryRows(executor, `PRAGMA index_list('${escapeSingleQuotes(name)}');`);
4664
+ for (const idx of idxList) {
4665
+ const idxName = idx.name;
4666
+ const columnsInfo = await queryRows(executor, `PRAGMA index_info('${escapeSingleQuotes(idxName)}');`);
4667
+ const idxEntry = {
4668
+ name: idxName,
4669
+ columns: columnsInfo.map((ci) => ({ column: ci.name })),
4670
+ unique: idx.unique === 1
4671
+ };
4672
+ table.indexes.push(idxEntry);
4673
+ }
4674
+ tables.push(table);
4675
+ }
4676
+ return { tables };
4677
+ }
4678
+ };
4679
+
4680
+ // src/core/ddl/introspect/mssql.ts
4681
+ var mssqlIntrospector = {
4682
+ async introspect(executor, options) {
4683
+ const schema = options.schema;
4684
+ const filterSchema = schema ? "sch.name = @p1" : "1=1";
4685
+ const params = schema ? [schema] : [];
4686
+ const columnRows = await queryRows(
4687
+ executor,
4688
+ `
4689
+ SELECT
4690
+ sch.name AS table_schema,
4691
+ t.name AS table_name,
4692
+ c.name AS column_name,
4693
+ ty.name AS data_type,
4694
+ c.is_nullable,
4695
+ c.is_identity,
4696
+ object_definition(c.default_object_id) AS column_default
4697
+ FROM sys.columns c
4698
+ JOIN sys.tables t ON t.object_id = c.object_id
4699
+ JOIN sys.schemas sch ON sch.schema_id = t.schema_id
4700
+ JOIN sys.types ty ON ty.user_type_id = c.user_type_id
4701
+ WHERE t.is_ms_shipped = 0 AND ${filterSchema}
4702
+ `,
4703
+ params
4704
+ );
4705
+ const pkRows = await queryRows(
4706
+ executor,
4707
+ `
4708
+ SELECT
4709
+ sch.name AS table_schema,
4710
+ t.name AS table_name,
4711
+ c.name AS column_name,
4712
+ ic.key_ordinal
4713
+ FROM sys.indexes i
4714
+ JOIN sys.index_columns ic ON ic.object_id = i.object_id AND ic.index_id = i.index_id
4715
+ JOIN sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id
4716
+ JOIN sys.tables t ON t.object_id = i.object_id
4717
+ JOIN sys.schemas sch ON sch.schema_id = t.schema_id
4718
+ WHERE i.is_primary_key = 1 AND ${filterSchema}
4719
+ ORDER BY ic.key_ordinal
4720
+ `,
4721
+ params
4722
+ );
4723
+ const pkMap = /* @__PURE__ */ new Map();
4724
+ pkRows.forEach((r) => {
4725
+ const key = `${r.table_schema}.${r.table_name}`;
4726
+ const list = pkMap.get(key) || [];
4727
+ list.push(r.column_name);
4728
+ pkMap.set(key, list);
4729
+ });
4730
+ const indexRows = await queryRows(
4731
+ executor,
4732
+ `
4733
+ SELECT
4734
+ sch.name AS table_schema,
4735
+ t.name AS table_name,
4736
+ i.name AS index_name,
4737
+ i.is_unique,
4738
+ i.has_filter,
4739
+ i.filter_definition
4740
+ FROM sys.indexes i
4741
+ JOIN sys.tables t ON t.object_id = i.object_id
4742
+ JOIN sys.schemas sch ON sch.schema_id = t.schema_id
4743
+ WHERE i.is_primary_key = 0 AND i.is_hypothetical = 0 AND ${filterSchema}
4744
+ `,
4745
+ params
4746
+ );
4747
+ const indexColsRows = await queryRows(
4748
+ executor,
4749
+ `
4750
+ SELECT
4751
+ sch.name AS table_schema,
4752
+ t.name AS table_name,
4753
+ i.name AS index_name,
4754
+ c.name AS column_name,
4755
+ ic.key_ordinal
4756
+ FROM sys.index_columns ic
4757
+ JOIN sys.indexes i ON i.object_id = ic.object_id AND i.index_id = ic.index_id
4758
+ JOIN sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id
4759
+ JOIN sys.tables t ON t.object_id = i.object_id
4760
+ JOIN sys.schemas sch ON sch.schema_id = t.schema_id
4761
+ WHERE i.is_primary_key = 0 AND ${filterSchema}
4762
+ ORDER BY ic.key_ordinal
4763
+ `,
4764
+ params
4765
+ );
4766
+ const indexColumnsMap = /* @__PURE__ */ new Map();
4767
+ indexColsRows.forEach((r) => {
4768
+ const key = `${r.table_schema}.${r.table_name}.${r.index_name}`;
4769
+ const list = indexColumnsMap.get(key) || [];
4770
+ list.push({ column: r.column_name, order: r.key_ordinal });
4771
+ indexColumnsMap.set(key, list);
4772
+ });
4773
+ const tablesByKey = /* @__PURE__ */ new Map();
4774
+ columnRows.forEach((r) => {
4775
+ if (!shouldIncludeTable(r.table_name, options)) return;
4776
+ const key = `${r.table_schema}.${r.table_name}`;
4777
+ if (!tablesByKey.has(key)) {
4778
+ tablesByKey.set(key, {
4779
+ name: r.table_name,
4780
+ schema: r.table_schema,
4781
+ columns: [],
4782
+ primaryKey: pkMap.get(key) || [],
4783
+ indexes: []
4784
+ });
4785
+ }
4786
+ const t = tablesByKey.get(key);
4787
+ const column = {
4788
+ name: r.column_name,
4789
+ type: r.data_type,
4790
+ notNull: r.is_nullable === false || r.is_nullable === 0,
4791
+ default: r.column_default ?? void 0,
4792
+ autoIncrement: !!r.is_identity
4793
+ };
4794
+ t.columns.push(column);
4795
+ });
4796
+ indexRows.forEach((r) => {
4797
+ const key = `${r.table_schema}.${r.table_name}`;
4798
+ const table = tablesByKey.get(key);
4799
+ if (!table) return;
4800
+ const cols = (indexColumnsMap.get(`${r.table_schema}.${r.table_name}.${r.index_name}`) || []).sort((a, b) => a.order - b.order).map((c) => ({ column: c.column }));
4801
+ const idx = {
4802
+ name: r.index_name,
4803
+ columns: cols,
4804
+ unique: !!r.is_unique,
4805
+ where: r.has_filter ? r.filter_definition : void 0
4806
+ };
4807
+ table.indexes = table.indexes || [];
4808
+ table.indexes.push(idx);
4809
+ });
4810
+ return { tables: Array.from(tablesByKey.values()) };
4811
+ }
4812
+ };
4813
+
4814
+ // src/core/ddl/schema-introspect.ts
4815
+ var INTROSPECTORS = {
4816
+ postgres: postgresIntrospector,
4817
+ mysql: mysqlIntrospector,
4818
+ sqlite: sqliteIntrospector,
4819
+ mssql: mssqlIntrospector
4820
+ };
4821
+ var introspectSchema = async (executor, dialect, options = {}) => {
4822
+ const handler = INTROSPECTORS[dialect];
4823
+ if (!handler) {
4824
+ throw new Error(`Unsupported dialect for introspection: ${dialect}`);
4825
+ }
4826
+ return handler.introspect(executor, options);
3221
4827
  };
3222
4828
 
3223
4829
  // src/orm/als.ts
@@ -3887,9 +5493,13 @@ var UnitOfWork = class {
3887
5493
  async flushInsert(tracked) {
3888
5494
  await this.runHook(tracked.table.hooks?.beforeInsert, tracked);
3889
5495
  const payload = this.extractColumns(tracked.table, tracked.entity);
3890
- const builder = new InsertQueryBuilder(tracked.table).values(payload);
5496
+ let builder = new InsertQueryBuilder(tracked.table).values(payload);
5497
+ if (this.dialect.supportsReturning()) {
5498
+ builder = builder.returning(...this.getReturningColumns(tracked.table));
5499
+ }
3891
5500
  const compiled = builder.compile(this.dialect);
3892
- await this.executeCompiled(compiled);
5501
+ const results = await this.executeCompiled(compiled);
5502
+ this.applyReturningResults(tracked, results);
3893
5503
  tracked.status = "managed" /* Managed */;
3894
5504
  tracked.original = this.createSnapshot(tracked.table, tracked.entity);
3895
5505
  tracked.pk = this.getPrimaryKeyValue(tracked);
@@ -3906,9 +5516,13 @@ var UnitOfWork = class {
3906
5516
  await this.runHook(tracked.table.hooks?.beforeUpdate, tracked);
3907
5517
  const pkColumn = tracked.table.columns[findPrimaryKey(tracked.table)];
3908
5518
  if (!pkColumn) return;
3909
- const builder = new UpdateQueryBuilder(tracked.table).set(changes).where(eq(pkColumn, tracked.pk));
5519
+ let builder = new UpdateQueryBuilder(tracked.table).set(changes).where(eq(pkColumn, tracked.pk));
5520
+ if (this.dialect.supportsReturning()) {
5521
+ builder = builder.returning(...this.getReturningColumns(tracked.table));
5522
+ }
3910
5523
  const compiled = builder.compile(this.dialect);
3911
- await this.executeCompiled(compiled);
5524
+ const results = await this.executeCompiled(compiled);
5525
+ this.applyReturningResults(tracked, results);
3912
5526
  tracked.status = "managed" /* Managed */;
3913
5527
  tracked.original = this.createSnapshot(tracked.table, tracked.entity);
3914
5528
  this.registerIdentity(tracked);
@@ -3950,7 +5564,31 @@ var UnitOfWork = class {
3950
5564
  return payload;
3951
5565
  }
3952
5566
  async executeCompiled(compiled) {
3953
- await this.executor.executeSql(compiled.sql, compiled.params);
5567
+ return this.executor.executeSql(compiled.sql, compiled.params);
5568
+ }
5569
+ getReturningColumns(table) {
5570
+ return Object.values(table.columns).map((column) => ({
5571
+ type: "Column",
5572
+ table: table.name,
5573
+ name: column.name,
5574
+ alias: column.name
5575
+ }));
5576
+ }
5577
+ applyReturningResults(tracked, results) {
5578
+ if (!this.dialect.supportsReturning()) return;
5579
+ const first = results[0];
5580
+ if (!first || first.values.length === 0) return;
5581
+ const row = first.values[0];
5582
+ for (let i = 0; i < first.columns.length; i++) {
5583
+ const columnName = this.normalizeColumnName(first.columns[i]);
5584
+ if (!(columnName in tracked.table.columns)) continue;
5585
+ tracked.entity[columnName] = row[i];
5586
+ }
5587
+ }
5588
+ normalizeColumnName(column) {
5589
+ const parts = column.split(".");
5590
+ const candidate = parts[parts.length - 1];
5591
+ return candidate.replace(/^["`[\]]+|["`[\]]+$/g, "");
3954
5592
  }
3955
5593
  registerIdentity(tracked) {
3956
5594
  if (tracked.pk == null) return;
@@ -3971,22 +5609,46 @@ var UnitOfWork = class {
3971
5609
  }
3972
5610
  };
3973
5611
 
5612
+ // src/orm/query-logger.ts
5613
+ var createQueryLoggingExecutor = (executor, logger) => {
5614
+ if (!logger) {
5615
+ return executor;
5616
+ }
5617
+ const wrapped = {
5618
+ async executeSql(sql, params) {
5619
+ logger({ sql, params });
5620
+ return executor.executeSql(sql, params);
5621
+ }
5622
+ };
5623
+ if (executor.beginTransaction) {
5624
+ wrapped.beginTransaction = executor.beginTransaction.bind(executor);
5625
+ }
5626
+ if (executor.commitTransaction) {
5627
+ wrapped.commitTransaction = executor.commitTransaction.bind(executor);
5628
+ }
5629
+ if (executor.rollbackTransaction) {
5630
+ wrapped.rollbackTransaction = executor.rollbackTransaction.bind(executor);
5631
+ }
5632
+ return wrapped;
5633
+ };
5634
+
3974
5635
  // src/orm/orm-context.ts
3975
5636
  var OrmContext = class {
3976
5637
  constructor(options) {
3977
5638
  this.options = options;
3978
5639
  this.identityMap = new IdentityMap();
3979
5640
  this.interceptors = [...options.interceptors ?? []];
5641
+ this.executorWithLogging = createQueryLoggingExecutor(options.executor, options.queryLogger);
3980
5642
  this.unitOfWork = new UnitOfWork(
3981
5643
  options.dialect,
3982
- options.executor,
5644
+ this.executorWithLogging,
3983
5645
  this.identityMap,
3984
5646
  () => this
3985
5647
  );
3986
5648
  this.relationChanges = new RelationChangeProcessor(
3987
5649
  this.unitOfWork,
3988
5650
  options.dialect,
3989
- options.executor
5651
+ this.executorWithLogging
3990
5652
  );
3991
5653
  this.domainEvents = new DomainEventBus(options.domainEventHandlers);
3992
5654
  }
@@ -3994,7 +5656,7 @@ var OrmContext = class {
3994
5656
  return this.options.dialect;
3995
5657
  }
3996
5658
  get executor() {
3997
- return this.options.executor;
5659
+ return this.executorWithLogging;
3998
5660
  }
3999
5661
  get identityBuckets() {
4000
5662
  return this.unitOfWork.identityBuckets;
@@ -4057,15 +5719,21 @@ var OrmContext = class {
4057
5719
  };
4058
5720
  export {
4059
5721
  AsyncLocalStorage,
5722
+ BaseSchemaDialect,
4060
5723
  DefaultBelongsToReference,
4061
5724
  DefaultHasManyCollection,
4062
5725
  DefaultManyToManyCollection,
4063
5726
  DeleteQueryBuilder,
4064
5727
  EntityStatus,
4065
5728
  InsertQueryBuilder,
5729
+ MSSqlSchemaDialect,
4066
5730
  MySqlDialect,
5731
+ MySqlSchemaDialect,
4067
5732
  OrmContext,
5733
+ PostgresDialect,
5734
+ PostgresSchemaDialect,
4068
5735
  RelationKinds,
5736
+ SQLiteSchemaDialect,
4069
5737
  SelectQueryBuilder,
4070
5738
  SqlServerDialect,
4071
5739
  SqliteDialect,
@@ -4087,15 +5755,22 @@ export {
4087
5755
  createLiteral,
4088
5756
  defineTable,
4089
5757
  denseRank,
5758
+ deriveIndexName,
5759
+ diffSchema,
4090
5760
  eq,
5761
+ escapeLiteral,
4091
5762
  executeHydrated,
4092
5763
  exists,
4093
5764
  firstValue,
5765
+ formatLiteral,
5766
+ generateCreateTableSql,
5767
+ generateSchemaSql,
4094
5768
  gt,
4095
5769
  gte,
4096
5770
  hasMany,
4097
5771
  hydrateRows,
4098
5772
  inList,
5773
+ introspectSchema,
4099
5774
  isCaseExpressionNode,
4100
5775
  isExpressionSelectionNode,
4101
5776
  isFunctionNode,
@@ -4120,9 +5795,14 @@ export {
4120
5795
  notLike,
4121
5796
  ntile,
4122
5797
  or,
5798
+ quoteQualified,
4123
5799
  rank,
5800
+ renderColumnDefinition,
5801
+ renderIndexColumns,
5802
+ resolvePrimaryKey,
4124
5803
  rowNumber,
4125
5804
  sum,
5805
+ synchronizeSchema,
4126
5806
  valueToOperand,
4127
5807
  visitExpression,
4128
5808
  visitOperand,