metal-orm 1.0.12 → 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.
- package/README.md +17 -15
- package/dist/decorators/index.cjs +302 -32
- package/dist/decorators/index.cjs.map +1 -1
- package/dist/decorators/index.d.cts +1 -1
- package/dist/decorators/index.d.ts +1 -1
- package/dist/decorators/index.js +302 -32
- package/dist/decorators/index.js.map +1 -1
- package/dist/index.cjs +583 -130
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -2
- package/dist/index.d.ts +18 -2
- package/dist/index.js +583 -130
- package/dist/index.js.map +1 -1
- package/dist/{select-BKlr2ivY.d.cts → select-CCp1oz9p.d.cts} +114 -1
- package/dist/{select-BKlr2ivY.d.ts → select-CCp1oz9p.d.ts} +114 -1
- package/package.json +3 -2
- package/src/core/ast/query.ts +40 -22
- package/src/core/dialect/abstract.ts +122 -37
- package/src/core/dialect/base/sql-dialect.ts +51 -8
- package/src/core/dialect/mssql/index.ts +125 -80
- package/src/core/dialect/postgres/index.ts +5 -6
- package/src/core/dialect/sqlite/index.ts +5 -6
- package/src/orm/execute.ts +25 -16
- package/src/orm/orm-context.ts +60 -55
- package/src/orm/query-logger.ts +38 -0
- package/src/orm/relations/belongs-to.ts +42 -26
- package/src/orm/relations/has-many.ts +41 -25
- package/src/orm/relations/many-to-many.ts +43 -27
- package/src/orm/unit-of-work.ts +60 -23
- package/src/query-builder/hydration-manager.ts +229 -25
- package/src/query-builder/query-ast-service.ts +27 -12
- package/src/query-builder/select-query-state.ts +24 -12
- package/src/query-builder/select.ts +58 -14
package/dist/index.js
CHANGED
|
@@ -550,6 +550,82 @@ var SelectQueryState = class _SelectQueryState {
|
|
|
550
550
|
ctes: [...this.ast.ctes ?? [], cte]
|
|
551
551
|
});
|
|
552
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"
|
|
553
629
|
};
|
|
554
630
|
|
|
555
631
|
// src/query-builder/hydration-manager.ts
|
|
@@ -601,8 +677,26 @@ var HydrationManager = class _HydrationManager {
|
|
|
601
677
|
* @returns AST with hydration metadata
|
|
602
678
|
*/
|
|
603
679
|
applyToAst(ast) {
|
|
680
|
+
if (ast.setOps && ast.setOps.length > 0) {
|
|
681
|
+
return ast;
|
|
682
|
+
}
|
|
604
683
|
const plan = this.planner.getPlan();
|
|
605
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) {
|
|
606
700
|
return {
|
|
607
701
|
...ast,
|
|
608
702
|
meta: {
|
|
@@ -612,11 +706,154 @@ var HydrationManager = class _HydrationManager {
|
|
|
612
706
|
};
|
|
613
707
|
}
|
|
614
708
|
/**
|
|
615
|
-
*
|
|
616
|
-
*
|
|
709
|
+
* Determines whether the query needs pagination rewriting to keep LIMIT/OFFSET
|
|
710
|
+
* applied to parent rows when eager-loading multiplicative relations.
|
|
617
711
|
*/
|
|
618
|
-
|
|
619
|
-
|
|
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;
|
|
620
857
|
}
|
|
621
858
|
};
|
|
622
859
|
|
|
@@ -879,6 +1116,20 @@ var QueryAstService = class {
|
|
|
879
1116
|
};
|
|
880
1117
|
return this.state.withCte(cte);
|
|
881
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
|
+
}
|
|
882
1133
|
/**
|
|
883
1134
|
* Selects a subquery as a column
|
|
884
1135
|
* @param alias - Alias for the subquery
|
|
@@ -1031,15 +1282,6 @@ var RelationProjectionHelper = class {
|
|
|
1031
1282
|
}
|
|
1032
1283
|
};
|
|
1033
1284
|
|
|
1034
|
-
// src/core/ast/join-node.ts
|
|
1035
|
-
var createJoinNode = (kind, tableName, condition, relationName) => ({
|
|
1036
|
-
type: "Join",
|
|
1037
|
-
kind,
|
|
1038
|
-
table: { type: "Table", name: tableName },
|
|
1039
|
-
condition,
|
|
1040
|
-
relationName
|
|
1041
|
-
});
|
|
1042
|
-
|
|
1043
1285
|
// src/query-builder/relation-conditions.ts
|
|
1044
1286
|
var assertNever = (value) => {
|
|
1045
1287
|
throw new Error(`Unhandled relation type: ${JSON.stringify(value)}`);
|
|
@@ -1095,62 +1337,6 @@ var buildRelationCorrelation = (root, relation) => {
|
|
|
1095
1337
|
return baseRelationCondition(root, relation);
|
|
1096
1338
|
};
|
|
1097
1339
|
|
|
1098
|
-
// src/core/sql/sql.ts
|
|
1099
|
-
var SQL_OPERATORS = {
|
|
1100
|
-
/** Equality operator */
|
|
1101
|
-
EQUALS: "=",
|
|
1102
|
-
/** Not equals operator */
|
|
1103
|
-
NOT_EQUALS: "!=",
|
|
1104
|
-
/** Greater than operator */
|
|
1105
|
-
GREATER_THAN: ">",
|
|
1106
|
-
/** Greater than or equal operator */
|
|
1107
|
-
GREATER_OR_EQUAL: ">=",
|
|
1108
|
-
/** Less than operator */
|
|
1109
|
-
LESS_THAN: "<",
|
|
1110
|
-
/** Less than or equal operator */
|
|
1111
|
-
LESS_OR_EQUAL: "<=",
|
|
1112
|
-
/** LIKE pattern matching operator */
|
|
1113
|
-
LIKE: "LIKE",
|
|
1114
|
-
/** NOT LIKE pattern matching operator */
|
|
1115
|
-
NOT_LIKE: "NOT LIKE",
|
|
1116
|
-
/** IN membership operator */
|
|
1117
|
-
IN: "IN",
|
|
1118
|
-
/** NOT IN membership operator */
|
|
1119
|
-
NOT_IN: "NOT IN",
|
|
1120
|
-
/** BETWEEN range operator */
|
|
1121
|
-
BETWEEN: "BETWEEN",
|
|
1122
|
-
/** NOT BETWEEN range operator */
|
|
1123
|
-
NOT_BETWEEN: "NOT BETWEEN",
|
|
1124
|
-
/** IS NULL null check operator */
|
|
1125
|
-
IS_NULL: "IS NULL",
|
|
1126
|
-
/** IS NOT NULL null check operator */
|
|
1127
|
-
IS_NOT_NULL: "IS NOT NULL",
|
|
1128
|
-
/** Logical AND operator */
|
|
1129
|
-
AND: "AND",
|
|
1130
|
-
/** Logical OR operator */
|
|
1131
|
-
OR: "OR",
|
|
1132
|
-
/** EXISTS operator */
|
|
1133
|
-
EXISTS: "EXISTS",
|
|
1134
|
-
/** NOT EXISTS operator */
|
|
1135
|
-
NOT_EXISTS: "NOT EXISTS"
|
|
1136
|
-
};
|
|
1137
|
-
var JOIN_KINDS = {
|
|
1138
|
-
/** INNER JOIN type */
|
|
1139
|
-
INNER: "INNER",
|
|
1140
|
-
/** LEFT JOIN type */
|
|
1141
|
-
LEFT: "LEFT",
|
|
1142
|
-
/** RIGHT JOIN type */
|
|
1143
|
-
RIGHT: "RIGHT",
|
|
1144
|
-
/** CROSS JOIN type */
|
|
1145
|
-
CROSS: "CROSS"
|
|
1146
|
-
};
|
|
1147
|
-
var ORDER_DIRECTIONS = {
|
|
1148
|
-
/** Ascending order */
|
|
1149
|
-
ASC: "ASC",
|
|
1150
|
-
/** Descending order */
|
|
1151
|
-
DESC: "DESC"
|
|
1152
|
-
};
|
|
1153
|
-
|
|
1154
1340
|
// src/query-builder/relation-service.ts
|
|
1155
1341
|
var RelationService = class {
|
|
1156
1342
|
/**
|
|
@@ -1596,6 +1782,16 @@ var hasEntityMeta = (entity) => {
|
|
|
1596
1782
|
|
|
1597
1783
|
// src/orm/relations/has-many.ts
|
|
1598
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
|
+
};
|
|
1599
1795
|
var DefaultHasManyCollection = class {
|
|
1600
1796
|
constructor(ctx, meta, root, relationName, relation, rootTable, loader, createEntity, localKey) {
|
|
1601
1797
|
this.ctx = ctx;
|
|
@@ -1611,6 +1807,7 @@ var DefaultHasManyCollection = class {
|
|
|
1611
1807
|
this.items = [];
|
|
1612
1808
|
this.added = /* @__PURE__ */ new Set();
|
|
1613
1809
|
this.removed = /* @__PURE__ */ new Set();
|
|
1810
|
+
hideInternal(this, ["ctx", "meta", "root", "relationName", "relation", "rootTable", "loader", "createEntity", "localKey"]);
|
|
1614
1811
|
this.hydrateFromCache();
|
|
1615
1812
|
}
|
|
1616
1813
|
async load() {
|
|
@@ -1686,10 +1883,23 @@ var DefaultHasManyCollection = class {
|
|
|
1686
1883
|
this.items = rows.map((row) => this.createEntity(row));
|
|
1687
1884
|
this.loaded = true;
|
|
1688
1885
|
}
|
|
1886
|
+
toJSON() {
|
|
1887
|
+
return this.items;
|
|
1888
|
+
}
|
|
1689
1889
|
};
|
|
1690
1890
|
|
|
1691
1891
|
// src/orm/relations/belongs-to.ts
|
|
1692
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
|
+
};
|
|
1693
1903
|
var DefaultBelongsToReference = class {
|
|
1694
1904
|
constructor(ctx, meta, root, relationName, relation, rootTable, loader, createEntity, targetKey) {
|
|
1695
1905
|
this.ctx = ctx;
|
|
@@ -1703,6 +1913,7 @@ var DefaultBelongsToReference = class {
|
|
|
1703
1913
|
this.targetKey = targetKey;
|
|
1704
1914
|
this.loaded = false;
|
|
1705
1915
|
this.current = null;
|
|
1916
|
+
hideInternal2(this, ["ctx", "meta", "root", "relationName", "relation", "rootTable", "loader", "createEntity", "targetKey"]);
|
|
1706
1917
|
this.populateFromHydrationCache();
|
|
1707
1918
|
}
|
|
1708
1919
|
async load() {
|
|
@@ -1763,10 +1974,23 @@ var DefaultBelongsToReference = class {
|
|
|
1763
1974
|
this.current = this.createEntity(row);
|
|
1764
1975
|
this.loaded = true;
|
|
1765
1976
|
}
|
|
1977
|
+
toJSON() {
|
|
1978
|
+
return this.current;
|
|
1979
|
+
}
|
|
1766
1980
|
};
|
|
1767
1981
|
|
|
1768
1982
|
// src/orm/relations/many-to-many.ts
|
|
1769
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
|
+
};
|
|
1770
1994
|
var DefaultManyToManyCollection = class {
|
|
1771
1995
|
constructor(ctx, meta, root, relationName, relation, rootTable, loader, createEntity, localKey) {
|
|
1772
1996
|
this.ctx = ctx;
|
|
@@ -1780,6 +2004,7 @@ var DefaultManyToManyCollection = class {
|
|
|
1780
2004
|
this.localKey = localKey;
|
|
1781
2005
|
this.loaded = false;
|
|
1782
2006
|
this.items = [];
|
|
2007
|
+
hideInternal3(this, ["ctx", "meta", "root", "relationName", "relation", "rootTable", "loader", "createEntity", "localKey"]);
|
|
1783
2008
|
this.hydrateFromCache();
|
|
1784
2009
|
}
|
|
1785
2010
|
async load() {
|
|
@@ -1885,6 +2110,9 @@ var DefaultManyToManyCollection = class {
|
|
|
1885
2110
|
});
|
|
1886
2111
|
this.loaded = true;
|
|
1887
2112
|
}
|
|
2113
|
+
toJSON() {
|
|
2114
|
+
return this.items;
|
|
2115
|
+
}
|
|
1888
2116
|
};
|
|
1889
2117
|
|
|
1890
2118
|
// src/orm/lazy-batch.ts
|
|
@@ -2245,9 +2473,15 @@ var flattenResults = (results) => {
|
|
|
2245
2473
|
return rows;
|
|
2246
2474
|
};
|
|
2247
2475
|
async function executeHydrated(ctx, qb) {
|
|
2248
|
-
const
|
|
2476
|
+
const ast = qb.getAST();
|
|
2477
|
+
const compiled = ctx.dialect.compileSelect(ast);
|
|
2249
2478
|
const executed = await ctx.executor.executeSql(compiled.sql, compiled.params);
|
|
2250
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
|
+
}
|
|
2251
2485
|
const hydrated = hydrateRows(rows, qb.getHydrationPlan());
|
|
2252
2486
|
return hydrated.map(
|
|
2253
2487
|
(row) => createEntityFromRow(ctx, qb.getTable(), row, qb.getLazyRelations())
|
|
@@ -2294,6 +2528,10 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
|
|
|
2294
2528
|
const joinNode = createJoinNode(kind, table.name, condition);
|
|
2295
2529
|
return this.applyAst(context, (service) => service.withJoin(joinNode));
|
|
2296
2530
|
}
|
|
2531
|
+
applySetOperation(operator, query) {
|
|
2532
|
+
const subAst = this.resolveQueryNode(query);
|
|
2533
|
+
return this.applyAst(this.context, (service) => service.withSetOperation(operator, subAst));
|
|
2534
|
+
}
|
|
2297
2535
|
/**
|
|
2298
2536
|
* Selects specific columns for the query
|
|
2299
2537
|
* @param columns - Record of column definitions, function nodes, case expressions, or window functions
|
|
@@ -2482,6 +2720,38 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
|
|
|
2482
2720
|
const nextContext = this.applyAst(this.context, (service) => service.withOffset(n));
|
|
2483
2721
|
return this.clone(nextContext);
|
|
2484
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
|
+
}
|
|
2485
2755
|
/**
|
|
2486
2756
|
* Adds a WHERE EXISTS condition to the query
|
|
2487
2757
|
* @param subquery - Subquery to check for existence
|
|
@@ -2771,7 +3041,8 @@ var Dialect = class {
|
|
|
2771
3041
|
*/
|
|
2772
3042
|
compileSelect(ast) {
|
|
2773
3043
|
const ctx = this.createCompilerContext();
|
|
2774
|
-
const
|
|
3044
|
+
const normalized = this.normalizeSelectAst(ast);
|
|
3045
|
+
const rawSql = this.compileSelectAst(normalized, ctx).trim();
|
|
2775
3046
|
const sql = rawSql.endsWith(";") ? rawSql : `${rawSql};`;
|
|
2776
3047
|
return {
|
|
2777
3048
|
sql,
|
|
@@ -2805,6 +3076,9 @@ var Dialect = class {
|
|
|
2805
3076
|
params: [...ctx.params]
|
|
2806
3077
|
};
|
|
2807
3078
|
}
|
|
3079
|
+
supportsReturning() {
|
|
3080
|
+
return false;
|
|
3081
|
+
}
|
|
2808
3082
|
/**
|
|
2809
3083
|
* Compiles a WHERE clause
|
|
2810
3084
|
* @param where - WHERE expression
|
|
@@ -2829,7 +3103,11 @@ var Dialect = class {
|
|
|
2829
3103
|
* @returns SQL for EXISTS subquery
|
|
2830
3104
|
*/
|
|
2831
3105
|
compileSelectForExists(ast, ctx) {
|
|
2832
|
-
const
|
|
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
|
+
}
|
|
2833
3111
|
const upper = full.toUpperCase();
|
|
2834
3112
|
const fromIndex = upper.indexOf(" FROM ");
|
|
2835
3113
|
if (fromIndex === -1) {
|
|
@@ -2862,6 +3140,65 @@ var Dialect = class {
|
|
|
2862
3140
|
formatPlaceholder(index) {
|
|
2863
3141
|
return "?";
|
|
2864
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
|
+
}
|
|
2865
3202
|
constructor() {
|
|
2866
3203
|
this.expressionCompilers = /* @__PURE__ */ new Map();
|
|
2867
3204
|
this.operandCompilers = /* @__PURE__ */ new Map();
|
|
@@ -3010,16 +3347,18 @@ var SqlDialectBase = class extends Dialect {
|
|
|
3010
3347
|
* Compiles SELECT query AST to SQL using common rules.
|
|
3011
3348
|
*/
|
|
3012
3349
|
compileSelectAst(ast, ctx) {
|
|
3350
|
+
const hasSetOps = !!(ast.setOps && ast.setOps.length);
|
|
3013
3351
|
const ctes = this.compileCtes(ast, ctx);
|
|
3014
|
-
const
|
|
3015
|
-
const
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
const
|
|
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(" ");
|
|
3020
3358
|
const orderBy = this.compileOrderBy(ast);
|
|
3021
3359
|
const pagination = this.compilePagination(ast, orderBy);
|
|
3022
|
-
|
|
3360
|
+
const combined = `${this.wrapSetOperand(baseSelect)} ${compound}`;
|
|
3361
|
+
return `${ctes}${combined}${orderBy}${pagination}`;
|
|
3023
3362
|
}
|
|
3024
3363
|
compileInsertAst(ast, ctx) {
|
|
3025
3364
|
const table = this.compileTableName(ast.into);
|
|
@@ -3028,6 +3367,20 @@ var SqlDialectBase = class extends Dialect {
|
|
|
3028
3367
|
const returning = this.compileReturning(ast.returning, ctx);
|
|
3029
3368
|
return `INSERT INTO ${table} (${columnList}) VALUES ${values}${returning}`;
|
|
3030
3369
|
}
|
|
3370
|
+
/**
|
|
3371
|
+
* Compiles a single SELECT (no set operations, no CTE prefix).
|
|
3372
|
+
*/
|
|
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
|
+
}
|
|
3031
3384
|
compileUpdateAst(ast, ctx) {
|
|
3032
3385
|
const table = this.compileTableName(ast.table);
|
|
3033
3386
|
const assignments = ast.set.map((assignment) => {
|
|
@@ -3053,6 +3406,13 @@ var SqlDialectBase = class extends Dialect {
|
|
|
3053
3406
|
if (!returning || returning.length === 0) return "";
|
|
3054
3407
|
throw new Error("RETURNING is not supported by this dialect.");
|
|
3055
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(", ");
|
|
3415
|
+
}
|
|
3056
3416
|
/**
|
|
3057
3417
|
* DISTINCT clause. Override for DISTINCT ON support.
|
|
3058
3418
|
*/
|
|
@@ -3118,7 +3478,7 @@ var SqlDialectBase = class extends Dialect {
|
|
|
3118
3478
|
const cteDefs = ast.ctes.map((cte) => {
|
|
3119
3479
|
const name = this.quoteIdentifier(cte.name);
|
|
3120
3480
|
const cols = cte.columns && cte.columns.length ? `(${cte.columns.map((c) => this.quoteIdentifier(c)).join(", ")})` : "";
|
|
3121
|
-
const query = this.stripTrailingSemicolon(this.compileSelectAst(cte.query, ctx));
|
|
3481
|
+
const query = this.stripTrailingSemicolon(this.compileSelectAst(this.normalizeSelectAst(cte.query), ctx));
|
|
3122
3482
|
return `${name}${cols} AS (${query})`;
|
|
3123
3483
|
}).join(", ");
|
|
3124
3484
|
return `${prefix}${cteDefs} `;
|
|
@@ -3126,6 +3486,10 @@ var SqlDialectBase = class extends Dialect {
|
|
|
3126
3486
|
stripTrailingSemicolon(sql) {
|
|
3127
3487
|
return sql.trim().replace(/;$/, "");
|
|
3128
3488
|
}
|
|
3489
|
+
wrapSetOperand(sql) {
|
|
3490
|
+
const trimmed = this.stripTrailingSemicolon(sql);
|
|
3491
|
+
return `(${trimmed})`;
|
|
3492
|
+
}
|
|
3129
3493
|
};
|
|
3130
3494
|
|
|
3131
3495
|
// src/core/dialect/mysql/index.ts
|
|
@@ -3195,6 +3559,43 @@ var SqlServerDialect = class extends Dialect {
|
|
|
3195
3559
|
* @returns SQL Server SQL string
|
|
3196
3560
|
*/
|
|
3197
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) {
|
|
3198
3599
|
const columns = ast.columns.map((c) => {
|
|
3199
3600
|
let expr = "";
|
|
3200
3601
|
if (c.type === "Function") {
|
|
@@ -3222,46 +3623,42 @@ var SqlServerDialect = class extends Dialect {
|
|
|
3222
3623
|
const whereClause = this.compileWhere(ast.where, ctx);
|
|
3223
3624
|
const groupBy = ast.groupBy && ast.groupBy.length > 0 ? " GROUP BY " + ast.groupBy.map((c) => `${this.quoteIdentifier(c.table)}.${this.quoteIdentifier(c.name)}`).join(", ") : "";
|
|
3224
3625
|
const having = ast.having ? ` HAVING ${this.compileExpression(ast.having, ctx)}` : "";
|
|
3225
|
-
const orderBy =
|
|
3226
|
-
|
|
3227
|
-
if (
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
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`;
|
|
3235
3646
|
}
|
|
3236
|
-
|
|
3647
|
+
return pagination;
|
|
3648
|
+
}
|
|
3649
|
+
compileCtes(ast, ctx) {
|
|
3650
|
+
if (!ast.ctes || ast.ctes.length === 0) return "";
|
|
3651
|
+
const defs = ast.ctes.map((cte) => {
|
|
3237
3652
|
const name = this.quoteIdentifier(cte.name);
|
|
3238
3653
|
const cols = cte.columns ? `(${cte.columns.map((c) => this.quoteIdentifier(c)).join(", ")})` : "";
|
|
3239
|
-
const query = this.compileSelectAst(cte.query, ctx).trim().replace(/;$/, "");
|
|
3654
|
+
const query = this.compileSelectAst(this.normalizeSelectAst(cte.query), ctx).trim().replace(/;$/, "");
|
|
3240
3655
|
return `${name}${cols} AS (${query})`;
|
|
3241
|
-
}).join(", ") + " " : "";
|
|
3242
|
-
return `${ctes}SELECT ${distinct}${columns} FROM ${from}${joins ? " " + joins : ""}${whereClause}${groupBy}${having}${orderBy};`;
|
|
3243
|
-
}
|
|
3244
|
-
compileInsertAst(ast, ctx) {
|
|
3245
|
-
const table = this.quoteIdentifier(ast.into.name);
|
|
3246
|
-
const columnList = ast.columns.map((column) => `${this.quoteIdentifier(column.table)}.${this.quoteIdentifier(column.name)}`).join(", ");
|
|
3247
|
-
const values = ast.values.map((row) => `(${row.map((value) => this.compileOperand(value, ctx)).join(", ")})`).join(", ");
|
|
3248
|
-
return `INSERT INTO ${table} (${columnList}) VALUES ${values};`;
|
|
3249
|
-
}
|
|
3250
|
-
compileUpdateAst(ast, ctx) {
|
|
3251
|
-
const table = this.quoteIdentifier(ast.table.name);
|
|
3252
|
-
const assignments = ast.set.map((assignment) => {
|
|
3253
|
-
const col2 = assignment.column;
|
|
3254
|
-
const target = `${this.quoteIdentifier(col2.table)}.${this.quoteIdentifier(col2.name)}`;
|
|
3255
|
-
const value = this.compileOperand(assignment.value, ctx);
|
|
3256
|
-
return `${target} = ${value}`;
|
|
3257
3656
|
}).join(", ");
|
|
3258
|
-
|
|
3259
|
-
return `UPDATE ${table} SET ${assignments}${whereClause};`;
|
|
3657
|
+
return `WITH ${defs} `;
|
|
3260
3658
|
}
|
|
3261
|
-
|
|
3262
|
-
const
|
|
3263
|
-
|
|
3264
|
-
return `DELETE FROM ${table}${whereClause};`;
|
|
3659
|
+
wrapSetOperand(sql) {
|
|
3660
|
+
const trimmed = sql.trim().replace(/;$/, "");
|
|
3661
|
+
return `(${trimmed})`;
|
|
3265
3662
|
}
|
|
3266
3663
|
};
|
|
3267
3664
|
|
|
@@ -3292,12 +3689,12 @@ var SqliteDialect = class extends SqlDialectBase {
|
|
|
3292
3689
|
}
|
|
3293
3690
|
compileReturning(returning, ctx) {
|
|
3294
3691
|
if (!returning || returning.length === 0) return "";
|
|
3295
|
-
const columns =
|
|
3296
|
-
const tablePart = column.table ? `${this.quoteIdentifier(column.table)}.` : "";
|
|
3297
|
-
return `${tablePart}${this.quoteIdentifier(column.name)}`;
|
|
3298
|
-
}).join(", ");
|
|
3692
|
+
const columns = this.formatReturningColumns(returning);
|
|
3299
3693
|
return ` RETURNING ${columns}`;
|
|
3300
3694
|
}
|
|
3695
|
+
supportsReturning() {
|
|
3696
|
+
return true;
|
|
3697
|
+
}
|
|
3301
3698
|
};
|
|
3302
3699
|
|
|
3303
3700
|
// src/core/dialect/postgres/index.ts
|
|
@@ -3327,12 +3724,12 @@ var PostgresDialect = class extends SqlDialectBase {
|
|
|
3327
3724
|
}
|
|
3328
3725
|
compileReturning(returning, ctx) {
|
|
3329
3726
|
if (!returning || returning.length === 0) return "";
|
|
3330
|
-
const columns =
|
|
3331
|
-
const tablePart = column.table ? `${this.quoteIdentifier(column.table)}.` : "";
|
|
3332
|
-
return `${tablePart}${this.quoteIdentifier(column.name)}`;
|
|
3333
|
-
}).join(", ");
|
|
3727
|
+
const columns = this.formatReturningColumns(returning);
|
|
3334
3728
|
return ` RETURNING ${columns}`;
|
|
3335
3729
|
}
|
|
3730
|
+
supportsReturning() {
|
|
3731
|
+
return true;
|
|
3732
|
+
}
|
|
3336
3733
|
};
|
|
3337
3734
|
|
|
3338
3735
|
// src/core/ddl/dialects/base-schema-dialect.ts
|
|
@@ -5096,9 +5493,13 @@ var UnitOfWork = class {
|
|
|
5096
5493
|
async flushInsert(tracked) {
|
|
5097
5494
|
await this.runHook(tracked.table.hooks?.beforeInsert, tracked);
|
|
5098
5495
|
const payload = this.extractColumns(tracked.table, tracked.entity);
|
|
5099
|
-
|
|
5496
|
+
let builder = new InsertQueryBuilder(tracked.table).values(payload);
|
|
5497
|
+
if (this.dialect.supportsReturning()) {
|
|
5498
|
+
builder = builder.returning(...this.getReturningColumns(tracked.table));
|
|
5499
|
+
}
|
|
5100
5500
|
const compiled = builder.compile(this.dialect);
|
|
5101
|
-
await this.executeCompiled(compiled);
|
|
5501
|
+
const results = await this.executeCompiled(compiled);
|
|
5502
|
+
this.applyReturningResults(tracked, results);
|
|
5102
5503
|
tracked.status = "managed" /* Managed */;
|
|
5103
5504
|
tracked.original = this.createSnapshot(tracked.table, tracked.entity);
|
|
5104
5505
|
tracked.pk = this.getPrimaryKeyValue(tracked);
|
|
@@ -5115,9 +5516,13 @@ var UnitOfWork = class {
|
|
|
5115
5516
|
await this.runHook(tracked.table.hooks?.beforeUpdate, tracked);
|
|
5116
5517
|
const pkColumn = tracked.table.columns[findPrimaryKey(tracked.table)];
|
|
5117
5518
|
if (!pkColumn) return;
|
|
5118
|
-
|
|
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
|
+
}
|
|
5119
5523
|
const compiled = builder.compile(this.dialect);
|
|
5120
|
-
await this.executeCompiled(compiled);
|
|
5524
|
+
const results = await this.executeCompiled(compiled);
|
|
5525
|
+
this.applyReturningResults(tracked, results);
|
|
5121
5526
|
tracked.status = "managed" /* Managed */;
|
|
5122
5527
|
tracked.original = this.createSnapshot(tracked.table, tracked.entity);
|
|
5123
5528
|
this.registerIdentity(tracked);
|
|
@@ -5159,7 +5564,31 @@ var UnitOfWork = class {
|
|
|
5159
5564
|
return payload;
|
|
5160
5565
|
}
|
|
5161
5566
|
async executeCompiled(compiled) {
|
|
5162
|
-
|
|
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, "");
|
|
5163
5592
|
}
|
|
5164
5593
|
registerIdentity(tracked) {
|
|
5165
5594
|
if (tracked.pk == null) return;
|
|
@@ -5180,22 +5609,46 @@ var UnitOfWork = class {
|
|
|
5180
5609
|
}
|
|
5181
5610
|
};
|
|
5182
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
|
+
|
|
5183
5635
|
// src/orm/orm-context.ts
|
|
5184
5636
|
var OrmContext = class {
|
|
5185
5637
|
constructor(options) {
|
|
5186
5638
|
this.options = options;
|
|
5187
5639
|
this.identityMap = new IdentityMap();
|
|
5188
5640
|
this.interceptors = [...options.interceptors ?? []];
|
|
5641
|
+
this.executorWithLogging = createQueryLoggingExecutor(options.executor, options.queryLogger);
|
|
5189
5642
|
this.unitOfWork = new UnitOfWork(
|
|
5190
5643
|
options.dialect,
|
|
5191
|
-
|
|
5644
|
+
this.executorWithLogging,
|
|
5192
5645
|
this.identityMap,
|
|
5193
5646
|
() => this
|
|
5194
5647
|
);
|
|
5195
5648
|
this.relationChanges = new RelationChangeProcessor(
|
|
5196
5649
|
this.unitOfWork,
|
|
5197
5650
|
options.dialect,
|
|
5198
|
-
|
|
5651
|
+
this.executorWithLogging
|
|
5199
5652
|
);
|
|
5200
5653
|
this.domainEvents = new DomainEventBus(options.domainEventHandlers);
|
|
5201
5654
|
}
|
|
@@ -5203,7 +5656,7 @@ var OrmContext = class {
|
|
|
5203
5656
|
return this.options.dialect;
|
|
5204
5657
|
}
|
|
5205
5658
|
get executor() {
|
|
5206
|
-
return this.
|
|
5659
|
+
return this.executorWithLogging;
|
|
5207
5660
|
}
|
|
5208
5661
|
get identityBuckets() {
|
|
5209
5662
|
return this.unitOfWork.identityBuckets;
|