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.cjs
CHANGED
|
@@ -663,6 +663,82 @@ var SelectQueryState = class _SelectQueryState {
|
|
|
663
663
|
ctes: [...this.ast.ctes ?? [], cte]
|
|
664
664
|
});
|
|
665
665
|
}
|
|
666
|
+
/**
|
|
667
|
+
* Adds a set operation (UNION/INTERSECT/EXCEPT) to the query
|
|
668
|
+
* @param op - Set operation node to add
|
|
669
|
+
* @returns New SelectQueryState with set operation
|
|
670
|
+
*/
|
|
671
|
+
withSetOperation(op) {
|
|
672
|
+
return this.clone({
|
|
673
|
+
...this.ast,
|
|
674
|
+
setOps: [...this.ast.setOps ?? [], op]
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
};
|
|
678
|
+
|
|
679
|
+
// src/core/ast/join-node.ts
|
|
680
|
+
var createJoinNode = (kind, tableName, condition, relationName) => ({
|
|
681
|
+
type: "Join",
|
|
682
|
+
kind,
|
|
683
|
+
table: { type: "Table", name: tableName },
|
|
684
|
+
condition,
|
|
685
|
+
relationName
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
// src/core/sql/sql.ts
|
|
689
|
+
var SQL_OPERATORS = {
|
|
690
|
+
/** Equality operator */
|
|
691
|
+
EQUALS: "=",
|
|
692
|
+
/** Not equals operator */
|
|
693
|
+
NOT_EQUALS: "!=",
|
|
694
|
+
/** Greater than operator */
|
|
695
|
+
GREATER_THAN: ">",
|
|
696
|
+
/** Greater than or equal operator */
|
|
697
|
+
GREATER_OR_EQUAL: ">=",
|
|
698
|
+
/** Less than operator */
|
|
699
|
+
LESS_THAN: "<",
|
|
700
|
+
/** Less than or equal operator */
|
|
701
|
+
LESS_OR_EQUAL: "<=",
|
|
702
|
+
/** LIKE pattern matching operator */
|
|
703
|
+
LIKE: "LIKE",
|
|
704
|
+
/** NOT LIKE pattern matching operator */
|
|
705
|
+
NOT_LIKE: "NOT LIKE",
|
|
706
|
+
/** IN membership operator */
|
|
707
|
+
IN: "IN",
|
|
708
|
+
/** NOT IN membership operator */
|
|
709
|
+
NOT_IN: "NOT IN",
|
|
710
|
+
/** BETWEEN range operator */
|
|
711
|
+
BETWEEN: "BETWEEN",
|
|
712
|
+
/** NOT BETWEEN range operator */
|
|
713
|
+
NOT_BETWEEN: "NOT BETWEEN",
|
|
714
|
+
/** IS NULL null check operator */
|
|
715
|
+
IS_NULL: "IS NULL",
|
|
716
|
+
/** IS NOT NULL null check operator */
|
|
717
|
+
IS_NOT_NULL: "IS NOT NULL",
|
|
718
|
+
/** Logical AND operator */
|
|
719
|
+
AND: "AND",
|
|
720
|
+
/** Logical OR operator */
|
|
721
|
+
OR: "OR",
|
|
722
|
+
/** EXISTS operator */
|
|
723
|
+
EXISTS: "EXISTS",
|
|
724
|
+
/** NOT EXISTS operator */
|
|
725
|
+
NOT_EXISTS: "NOT EXISTS"
|
|
726
|
+
};
|
|
727
|
+
var JOIN_KINDS = {
|
|
728
|
+
/** INNER JOIN type */
|
|
729
|
+
INNER: "INNER",
|
|
730
|
+
/** LEFT JOIN type */
|
|
731
|
+
LEFT: "LEFT",
|
|
732
|
+
/** RIGHT JOIN type */
|
|
733
|
+
RIGHT: "RIGHT",
|
|
734
|
+
/** CROSS JOIN type */
|
|
735
|
+
CROSS: "CROSS"
|
|
736
|
+
};
|
|
737
|
+
var ORDER_DIRECTIONS = {
|
|
738
|
+
/** Ascending order */
|
|
739
|
+
ASC: "ASC",
|
|
740
|
+
/** Descending order */
|
|
741
|
+
DESC: "DESC"
|
|
666
742
|
};
|
|
667
743
|
|
|
668
744
|
// src/query-builder/hydration-manager.ts
|
|
@@ -714,8 +790,26 @@ var HydrationManager = class _HydrationManager {
|
|
|
714
790
|
* @returns AST with hydration metadata
|
|
715
791
|
*/
|
|
716
792
|
applyToAst(ast) {
|
|
793
|
+
if (ast.setOps && ast.setOps.length > 0) {
|
|
794
|
+
return ast;
|
|
795
|
+
}
|
|
717
796
|
const plan = this.planner.getPlan();
|
|
718
797
|
if (!plan) return ast;
|
|
798
|
+
const needsPaginationGuard = this.requiresParentPagination(ast, plan);
|
|
799
|
+
const rewritten = needsPaginationGuard ? this.wrapForParentPagination(ast, plan) : ast;
|
|
800
|
+
return this.attachHydrationMeta(rewritten, plan);
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* Gets the current hydration plan
|
|
804
|
+
* @returns Hydration plan or undefined if none exists
|
|
805
|
+
*/
|
|
806
|
+
getPlan() {
|
|
807
|
+
return this.planner.getPlan();
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* Attaches hydration metadata to a query AST node.
|
|
811
|
+
*/
|
|
812
|
+
attachHydrationMeta(ast, plan) {
|
|
719
813
|
return {
|
|
720
814
|
...ast,
|
|
721
815
|
meta: {
|
|
@@ -725,11 +819,154 @@ var HydrationManager = class _HydrationManager {
|
|
|
725
819
|
};
|
|
726
820
|
}
|
|
727
821
|
/**
|
|
728
|
-
*
|
|
729
|
-
*
|
|
822
|
+
* Determines whether the query needs pagination rewriting to keep LIMIT/OFFSET
|
|
823
|
+
* applied to parent rows when eager-loading multiplicative relations.
|
|
730
824
|
*/
|
|
731
|
-
|
|
732
|
-
|
|
825
|
+
requiresParentPagination(ast, plan) {
|
|
826
|
+
const hasPagination = ast.limit !== void 0 || ast.offset !== void 0;
|
|
827
|
+
return hasPagination && this.hasMultiplyingRelations(plan);
|
|
828
|
+
}
|
|
829
|
+
hasMultiplyingRelations(plan) {
|
|
830
|
+
return plan.relations.some(
|
|
831
|
+
(rel) => rel.type === RelationKinds.HasMany || rel.type === RelationKinds.BelongsToMany
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* Rewrites the query using CTEs so LIMIT/OFFSET target distinct parent rows
|
|
836
|
+
* instead of the joined result set.
|
|
837
|
+
*
|
|
838
|
+
* The strategy:
|
|
839
|
+
* - Hoist the original query (minus limit/offset) into a base CTE.
|
|
840
|
+
* - Select distinct parent ids from that base CTE with the original ordering and pagination.
|
|
841
|
+
* - Join the base CTE against the paged ids to retrieve the joined rows for just that page.
|
|
842
|
+
*/
|
|
843
|
+
wrapForParentPagination(ast, plan) {
|
|
844
|
+
const projectionNames = this.getProjectionNames(ast.columns);
|
|
845
|
+
if (!projectionNames) {
|
|
846
|
+
return ast;
|
|
847
|
+
}
|
|
848
|
+
const projectionAliases = this.buildProjectionAliasMap(ast.columns);
|
|
849
|
+
const projectionSet = new Set(projectionNames);
|
|
850
|
+
const rootPkAlias = projectionAliases.get(`${plan.rootTable}.${plan.rootPrimaryKey}`) ?? plan.rootPrimaryKey;
|
|
851
|
+
const baseCteName = this.nextCteName(ast.ctes, "__metal_pagination_base");
|
|
852
|
+
const baseQuery = {
|
|
853
|
+
...ast,
|
|
854
|
+
ctes: void 0,
|
|
855
|
+
limit: void 0,
|
|
856
|
+
offset: void 0,
|
|
857
|
+
orderBy: void 0,
|
|
858
|
+
meta: void 0
|
|
859
|
+
};
|
|
860
|
+
const baseCte = {
|
|
861
|
+
type: "CommonTableExpression",
|
|
862
|
+
name: baseCteName,
|
|
863
|
+
query: baseQuery,
|
|
864
|
+
recursive: false
|
|
865
|
+
};
|
|
866
|
+
const orderBy = this.mapOrderBy(ast.orderBy, plan, projectionAliases, baseCteName, projectionSet);
|
|
867
|
+
if (orderBy === null) {
|
|
868
|
+
return ast;
|
|
869
|
+
}
|
|
870
|
+
const pageCteName = this.nextCteName([...ast.ctes ?? [], baseCte], "__metal_pagination_page");
|
|
871
|
+
const pagingColumns = this.buildPagingColumns(rootPkAlias, orderBy, baseCteName);
|
|
872
|
+
const pageCte = {
|
|
873
|
+
type: "CommonTableExpression",
|
|
874
|
+
name: pageCteName,
|
|
875
|
+
query: {
|
|
876
|
+
type: "SelectQuery",
|
|
877
|
+
from: { type: "Table", name: baseCteName },
|
|
878
|
+
columns: pagingColumns,
|
|
879
|
+
joins: [],
|
|
880
|
+
distinct: [{ type: "Column", table: baseCteName, name: rootPkAlias }],
|
|
881
|
+
orderBy,
|
|
882
|
+
limit: ast.limit,
|
|
883
|
+
offset: ast.offset
|
|
884
|
+
},
|
|
885
|
+
recursive: false
|
|
886
|
+
};
|
|
887
|
+
const joinCondition = eq(
|
|
888
|
+
{ type: "Column", table: baseCteName, name: rootPkAlias },
|
|
889
|
+
{ type: "Column", table: pageCteName, name: rootPkAlias }
|
|
890
|
+
);
|
|
891
|
+
const outerColumns = projectionNames.map((name) => ({
|
|
892
|
+
type: "Column",
|
|
893
|
+
table: baseCteName,
|
|
894
|
+
name,
|
|
895
|
+
alias: name
|
|
896
|
+
}));
|
|
897
|
+
return {
|
|
898
|
+
type: "SelectQuery",
|
|
899
|
+
from: { type: "Table", name: baseCteName },
|
|
900
|
+
columns: outerColumns,
|
|
901
|
+
joins: [createJoinNode(JOIN_KINDS.INNER, pageCteName, joinCondition)],
|
|
902
|
+
orderBy,
|
|
903
|
+
ctes: [...ast.ctes ?? [], baseCte, pageCte]
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
nextCteName(existing, baseName) {
|
|
907
|
+
const names = new Set((existing ?? []).map((cte) => cte.name));
|
|
908
|
+
let candidate = baseName;
|
|
909
|
+
let suffix = 1;
|
|
910
|
+
while (names.has(candidate)) {
|
|
911
|
+
suffix += 1;
|
|
912
|
+
candidate = `${baseName}_${suffix}`;
|
|
913
|
+
}
|
|
914
|
+
return candidate;
|
|
915
|
+
}
|
|
916
|
+
getProjectionNames(columns) {
|
|
917
|
+
const names = [];
|
|
918
|
+
for (const col2 of columns) {
|
|
919
|
+
const alias = col2.alias ?? col2.name;
|
|
920
|
+
if (!alias) return void 0;
|
|
921
|
+
names.push(alias);
|
|
922
|
+
}
|
|
923
|
+
return names;
|
|
924
|
+
}
|
|
925
|
+
buildProjectionAliasMap(columns) {
|
|
926
|
+
const map = /* @__PURE__ */ new Map();
|
|
927
|
+
for (const col2 of columns) {
|
|
928
|
+
if (col2.type !== "Column") continue;
|
|
929
|
+
const node = col2;
|
|
930
|
+
const key = `${node.table}.${node.name}`;
|
|
931
|
+
map.set(key, node.alias ?? node.name);
|
|
932
|
+
}
|
|
933
|
+
return map;
|
|
934
|
+
}
|
|
935
|
+
mapOrderBy(orderBy, plan, projectionAliases, baseAlias, availableColumns) {
|
|
936
|
+
if (!orderBy || orderBy.length === 0) {
|
|
937
|
+
return void 0;
|
|
938
|
+
}
|
|
939
|
+
const mapped = [];
|
|
940
|
+
for (const ob of orderBy) {
|
|
941
|
+
if (ob.column.table !== plan.rootTable) {
|
|
942
|
+
return null;
|
|
943
|
+
}
|
|
944
|
+
const alias = projectionAliases.get(`${ob.column.table}.${ob.column.name}`) ?? ob.column.name;
|
|
945
|
+
if (!availableColumns.has(alias)) {
|
|
946
|
+
return null;
|
|
947
|
+
}
|
|
948
|
+
mapped.push({
|
|
949
|
+
type: "OrderBy",
|
|
950
|
+
column: { type: "Column", table: baseAlias, name: alias },
|
|
951
|
+
direction: ob.direction
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
return mapped;
|
|
955
|
+
}
|
|
956
|
+
buildPagingColumns(primaryKey, orderBy, tableAlias) {
|
|
957
|
+
const columns = [{ type: "Column", table: tableAlias, name: primaryKey, alias: primaryKey }];
|
|
958
|
+
if (!orderBy) return columns;
|
|
959
|
+
for (const ob of orderBy) {
|
|
960
|
+
if (!columns.some((col2) => col2.name === ob.column.name)) {
|
|
961
|
+
columns.push({
|
|
962
|
+
type: "Column",
|
|
963
|
+
table: tableAlias,
|
|
964
|
+
name: ob.column.name,
|
|
965
|
+
alias: ob.column.name
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
return columns;
|
|
733
970
|
}
|
|
734
971
|
};
|
|
735
972
|
|
|
@@ -992,6 +1229,20 @@ var QueryAstService = class {
|
|
|
992
1229
|
};
|
|
993
1230
|
return this.state.withCte(cte);
|
|
994
1231
|
}
|
|
1232
|
+
/**
|
|
1233
|
+
* Adds a set operation (UNION/UNION ALL/INTERSECT/EXCEPT) to the query
|
|
1234
|
+
* @param operator - Set operator
|
|
1235
|
+
* @param query - Right-hand side query
|
|
1236
|
+
* @returns Updated query state with set operation
|
|
1237
|
+
*/
|
|
1238
|
+
withSetOperation(operator, query) {
|
|
1239
|
+
const op = {
|
|
1240
|
+
type: "SetOperation",
|
|
1241
|
+
operator,
|
|
1242
|
+
query
|
|
1243
|
+
};
|
|
1244
|
+
return this.state.withSetOperation(op);
|
|
1245
|
+
}
|
|
995
1246
|
/**
|
|
996
1247
|
* Selects a subquery as a column
|
|
997
1248
|
* @param alias - Alias for the subquery
|
|
@@ -1144,15 +1395,6 @@ var RelationProjectionHelper = class {
|
|
|
1144
1395
|
}
|
|
1145
1396
|
};
|
|
1146
1397
|
|
|
1147
|
-
// src/core/ast/join-node.ts
|
|
1148
|
-
var createJoinNode = (kind, tableName, condition, relationName) => ({
|
|
1149
|
-
type: "Join",
|
|
1150
|
-
kind,
|
|
1151
|
-
table: { type: "Table", name: tableName },
|
|
1152
|
-
condition,
|
|
1153
|
-
relationName
|
|
1154
|
-
});
|
|
1155
|
-
|
|
1156
1398
|
// src/query-builder/relation-conditions.ts
|
|
1157
1399
|
var assertNever = (value) => {
|
|
1158
1400
|
throw new Error(`Unhandled relation type: ${JSON.stringify(value)}`);
|
|
@@ -1208,62 +1450,6 @@ var buildRelationCorrelation = (root, relation) => {
|
|
|
1208
1450
|
return baseRelationCondition(root, relation);
|
|
1209
1451
|
};
|
|
1210
1452
|
|
|
1211
|
-
// src/core/sql/sql.ts
|
|
1212
|
-
var SQL_OPERATORS = {
|
|
1213
|
-
/** Equality operator */
|
|
1214
|
-
EQUALS: "=",
|
|
1215
|
-
/** Not equals operator */
|
|
1216
|
-
NOT_EQUALS: "!=",
|
|
1217
|
-
/** Greater than operator */
|
|
1218
|
-
GREATER_THAN: ">",
|
|
1219
|
-
/** Greater than or equal operator */
|
|
1220
|
-
GREATER_OR_EQUAL: ">=",
|
|
1221
|
-
/** Less than operator */
|
|
1222
|
-
LESS_THAN: "<",
|
|
1223
|
-
/** Less than or equal operator */
|
|
1224
|
-
LESS_OR_EQUAL: "<=",
|
|
1225
|
-
/** LIKE pattern matching operator */
|
|
1226
|
-
LIKE: "LIKE",
|
|
1227
|
-
/** NOT LIKE pattern matching operator */
|
|
1228
|
-
NOT_LIKE: "NOT LIKE",
|
|
1229
|
-
/** IN membership operator */
|
|
1230
|
-
IN: "IN",
|
|
1231
|
-
/** NOT IN membership operator */
|
|
1232
|
-
NOT_IN: "NOT IN",
|
|
1233
|
-
/** BETWEEN range operator */
|
|
1234
|
-
BETWEEN: "BETWEEN",
|
|
1235
|
-
/** NOT BETWEEN range operator */
|
|
1236
|
-
NOT_BETWEEN: "NOT BETWEEN",
|
|
1237
|
-
/** IS NULL null check operator */
|
|
1238
|
-
IS_NULL: "IS NULL",
|
|
1239
|
-
/** IS NOT NULL null check operator */
|
|
1240
|
-
IS_NOT_NULL: "IS NOT NULL",
|
|
1241
|
-
/** Logical AND operator */
|
|
1242
|
-
AND: "AND",
|
|
1243
|
-
/** Logical OR operator */
|
|
1244
|
-
OR: "OR",
|
|
1245
|
-
/** EXISTS operator */
|
|
1246
|
-
EXISTS: "EXISTS",
|
|
1247
|
-
/** NOT EXISTS operator */
|
|
1248
|
-
NOT_EXISTS: "NOT EXISTS"
|
|
1249
|
-
};
|
|
1250
|
-
var JOIN_KINDS = {
|
|
1251
|
-
/** INNER JOIN type */
|
|
1252
|
-
INNER: "INNER",
|
|
1253
|
-
/** LEFT JOIN type */
|
|
1254
|
-
LEFT: "LEFT",
|
|
1255
|
-
/** RIGHT JOIN type */
|
|
1256
|
-
RIGHT: "RIGHT",
|
|
1257
|
-
/** CROSS JOIN type */
|
|
1258
|
-
CROSS: "CROSS"
|
|
1259
|
-
};
|
|
1260
|
-
var ORDER_DIRECTIONS = {
|
|
1261
|
-
/** Ascending order */
|
|
1262
|
-
ASC: "ASC",
|
|
1263
|
-
/** Descending order */
|
|
1264
|
-
DESC: "DESC"
|
|
1265
|
-
};
|
|
1266
|
-
|
|
1267
1453
|
// src/query-builder/relation-service.ts
|
|
1268
1454
|
var RelationService = class {
|
|
1269
1455
|
/**
|
|
@@ -1709,6 +1895,16 @@ var hasEntityMeta = (entity) => {
|
|
|
1709
1895
|
|
|
1710
1896
|
// src/orm/relations/has-many.ts
|
|
1711
1897
|
var toKey2 = (value) => value === null || value === void 0 ? "" : String(value);
|
|
1898
|
+
var hideInternal = (obj, keys) => {
|
|
1899
|
+
for (const key of keys) {
|
|
1900
|
+
Object.defineProperty(obj, key, {
|
|
1901
|
+
value: obj[key],
|
|
1902
|
+
writable: false,
|
|
1903
|
+
configurable: false,
|
|
1904
|
+
enumerable: false
|
|
1905
|
+
});
|
|
1906
|
+
}
|
|
1907
|
+
};
|
|
1712
1908
|
var DefaultHasManyCollection = class {
|
|
1713
1909
|
constructor(ctx, meta, root, relationName, relation, rootTable, loader, createEntity, localKey) {
|
|
1714
1910
|
this.ctx = ctx;
|
|
@@ -1724,6 +1920,7 @@ var DefaultHasManyCollection = class {
|
|
|
1724
1920
|
this.items = [];
|
|
1725
1921
|
this.added = /* @__PURE__ */ new Set();
|
|
1726
1922
|
this.removed = /* @__PURE__ */ new Set();
|
|
1923
|
+
hideInternal(this, ["ctx", "meta", "root", "relationName", "relation", "rootTable", "loader", "createEntity", "localKey"]);
|
|
1727
1924
|
this.hydrateFromCache();
|
|
1728
1925
|
}
|
|
1729
1926
|
async load() {
|
|
@@ -1799,10 +1996,23 @@ var DefaultHasManyCollection = class {
|
|
|
1799
1996
|
this.items = rows.map((row) => this.createEntity(row));
|
|
1800
1997
|
this.loaded = true;
|
|
1801
1998
|
}
|
|
1999
|
+
toJSON() {
|
|
2000
|
+
return this.items;
|
|
2001
|
+
}
|
|
1802
2002
|
};
|
|
1803
2003
|
|
|
1804
2004
|
// src/orm/relations/belongs-to.ts
|
|
1805
2005
|
var toKey3 = (value) => value === null || value === void 0 ? "" : String(value);
|
|
2006
|
+
var hideInternal2 = (obj, keys) => {
|
|
2007
|
+
for (const key of keys) {
|
|
2008
|
+
Object.defineProperty(obj, key, {
|
|
2009
|
+
value: obj[key],
|
|
2010
|
+
writable: false,
|
|
2011
|
+
configurable: false,
|
|
2012
|
+
enumerable: false
|
|
2013
|
+
});
|
|
2014
|
+
}
|
|
2015
|
+
};
|
|
1806
2016
|
var DefaultBelongsToReference = class {
|
|
1807
2017
|
constructor(ctx, meta, root, relationName, relation, rootTable, loader, createEntity, targetKey) {
|
|
1808
2018
|
this.ctx = ctx;
|
|
@@ -1816,6 +2026,7 @@ var DefaultBelongsToReference = class {
|
|
|
1816
2026
|
this.targetKey = targetKey;
|
|
1817
2027
|
this.loaded = false;
|
|
1818
2028
|
this.current = null;
|
|
2029
|
+
hideInternal2(this, ["ctx", "meta", "root", "relationName", "relation", "rootTable", "loader", "createEntity", "targetKey"]);
|
|
1819
2030
|
this.populateFromHydrationCache();
|
|
1820
2031
|
}
|
|
1821
2032
|
async load() {
|
|
@@ -1876,10 +2087,23 @@ var DefaultBelongsToReference = class {
|
|
|
1876
2087
|
this.current = this.createEntity(row);
|
|
1877
2088
|
this.loaded = true;
|
|
1878
2089
|
}
|
|
2090
|
+
toJSON() {
|
|
2091
|
+
return this.current;
|
|
2092
|
+
}
|
|
1879
2093
|
};
|
|
1880
2094
|
|
|
1881
2095
|
// src/orm/relations/many-to-many.ts
|
|
1882
2096
|
var toKey4 = (value) => value === null || value === void 0 ? "" : String(value);
|
|
2097
|
+
var hideInternal3 = (obj, keys) => {
|
|
2098
|
+
for (const key of keys) {
|
|
2099
|
+
Object.defineProperty(obj, key, {
|
|
2100
|
+
value: obj[key],
|
|
2101
|
+
writable: false,
|
|
2102
|
+
configurable: false,
|
|
2103
|
+
enumerable: false
|
|
2104
|
+
});
|
|
2105
|
+
}
|
|
2106
|
+
};
|
|
1883
2107
|
var DefaultManyToManyCollection = class {
|
|
1884
2108
|
constructor(ctx, meta, root, relationName, relation, rootTable, loader, createEntity, localKey) {
|
|
1885
2109
|
this.ctx = ctx;
|
|
@@ -1893,6 +2117,7 @@ var DefaultManyToManyCollection = class {
|
|
|
1893
2117
|
this.localKey = localKey;
|
|
1894
2118
|
this.loaded = false;
|
|
1895
2119
|
this.items = [];
|
|
2120
|
+
hideInternal3(this, ["ctx", "meta", "root", "relationName", "relation", "rootTable", "loader", "createEntity", "localKey"]);
|
|
1896
2121
|
this.hydrateFromCache();
|
|
1897
2122
|
}
|
|
1898
2123
|
async load() {
|
|
@@ -1998,6 +2223,9 @@ var DefaultManyToManyCollection = class {
|
|
|
1998
2223
|
});
|
|
1999
2224
|
this.loaded = true;
|
|
2000
2225
|
}
|
|
2226
|
+
toJSON() {
|
|
2227
|
+
return this.items;
|
|
2228
|
+
}
|
|
2001
2229
|
};
|
|
2002
2230
|
|
|
2003
2231
|
// src/orm/lazy-batch.ts
|
|
@@ -2358,9 +2586,15 @@ var flattenResults = (results) => {
|
|
|
2358
2586
|
return rows;
|
|
2359
2587
|
};
|
|
2360
2588
|
async function executeHydrated(ctx, qb) {
|
|
2361
|
-
const
|
|
2589
|
+
const ast = qb.getAST();
|
|
2590
|
+
const compiled = ctx.dialect.compileSelect(ast);
|
|
2362
2591
|
const executed = await ctx.executor.executeSql(compiled.sql, compiled.params);
|
|
2363
2592
|
const rows = flattenResults(executed);
|
|
2593
|
+
if (ast.setOps && ast.setOps.length > 0) {
|
|
2594
|
+
return rows.map(
|
|
2595
|
+
(row) => createEntityProxy(ctx, qb.getTable(), row, qb.getLazyRelations())
|
|
2596
|
+
);
|
|
2597
|
+
}
|
|
2364
2598
|
const hydrated = hydrateRows(rows, qb.getHydrationPlan());
|
|
2365
2599
|
return hydrated.map(
|
|
2366
2600
|
(row) => createEntityFromRow(ctx, qb.getTable(), row, qb.getLazyRelations())
|
|
@@ -2407,6 +2641,10 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
|
|
|
2407
2641
|
const joinNode = createJoinNode(kind, table.name, condition);
|
|
2408
2642
|
return this.applyAst(context, (service) => service.withJoin(joinNode));
|
|
2409
2643
|
}
|
|
2644
|
+
applySetOperation(operator, query) {
|
|
2645
|
+
const subAst = this.resolveQueryNode(query);
|
|
2646
|
+
return this.applyAst(this.context, (service) => service.withSetOperation(operator, subAst));
|
|
2647
|
+
}
|
|
2410
2648
|
/**
|
|
2411
2649
|
* Selects specific columns for the query
|
|
2412
2650
|
* @param columns - Record of column definitions, function nodes, case expressions, or window functions
|
|
@@ -2595,6 +2833,38 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
|
|
|
2595
2833
|
const nextContext = this.applyAst(this.context, (service) => service.withOffset(n));
|
|
2596
2834
|
return this.clone(nextContext);
|
|
2597
2835
|
}
|
|
2836
|
+
/**
|
|
2837
|
+
* Combines this query with another using UNION
|
|
2838
|
+
* @param query - Query to union with
|
|
2839
|
+
* @returns New query builder instance with the set operation
|
|
2840
|
+
*/
|
|
2841
|
+
union(query) {
|
|
2842
|
+
return this.clone(this.applySetOperation("UNION", query));
|
|
2843
|
+
}
|
|
2844
|
+
/**
|
|
2845
|
+
* Combines this query with another using UNION ALL
|
|
2846
|
+
* @param query - Query to union with
|
|
2847
|
+
* @returns New query builder instance with the set operation
|
|
2848
|
+
*/
|
|
2849
|
+
unionAll(query) {
|
|
2850
|
+
return this.clone(this.applySetOperation("UNION ALL", query));
|
|
2851
|
+
}
|
|
2852
|
+
/**
|
|
2853
|
+
* Combines this query with another using INTERSECT
|
|
2854
|
+
* @param query - Query to intersect with
|
|
2855
|
+
* @returns New query builder instance with the set operation
|
|
2856
|
+
*/
|
|
2857
|
+
intersect(query) {
|
|
2858
|
+
return this.clone(this.applySetOperation("INTERSECT", query));
|
|
2859
|
+
}
|
|
2860
|
+
/**
|
|
2861
|
+
* Combines this query with another using EXCEPT
|
|
2862
|
+
* @param query - Query to subtract
|
|
2863
|
+
* @returns New query builder instance with the set operation
|
|
2864
|
+
*/
|
|
2865
|
+
except(query) {
|
|
2866
|
+
return this.clone(this.applySetOperation("EXCEPT", query));
|
|
2867
|
+
}
|
|
2598
2868
|
/**
|
|
2599
2869
|
* Adds a WHERE EXISTS condition to the query
|
|
2600
2870
|
* @param subquery - Subquery to check for existence
|
|
@@ -2884,7 +3154,8 @@ var Dialect = class {
|
|
|
2884
3154
|
*/
|
|
2885
3155
|
compileSelect(ast) {
|
|
2886
3156
|
const ctx = this.createCompilerContext();
|
|
2887
|
-
const
|
|
3157
|
+
const normalized = this.normalizeSelectAst(ast);
|
|
3158
|
+
const rawSql = this.compileSelectAst(normalized, ctx).trim();
|
|
2888
3159
|
const sql = rawSql.endsWith(";") ? rawSql : `${rawSql};`;
|
|
2889
3160
|
return {
|
|
2890
3161
|
sql,
|
|
@@ -2918,6 +3189,9 @@ var Dialect = class {
|
|
|
2918
3189
|
params: [...ctx.params]
|
|
2919
3190
|
};
|
|
2920
3191
|
}
|
|
3192
|
+
supportsReturning() {
|
|
3193
|
+
return false;
|
|
3194
|
+
}
|
|
2921
3195
|
/**
|
|
2922
3196
|
* Compiles a WHERE clause
|
|
2923
3197
|
* @param where - WHERE expression
|
|
@@ -2942,7 +3216,11 @@ var Dialect = class {
|
|
|
2942
3216
|
* @returns SQL for EXISTS subquery
|
|
2943
3217
|
*/
|
|
2944
3218
|
compileSelectForExists(ast, ctx) {
|
|
2945
|
-
const
|
|
3219
|
+
const normalized = this.normalizeSelectAst(ast);
|
|
3220
|
+
const full = this.compileSelectAst(normalized, ctx).trim().replace(/;$/, "");
|
|
3221
|
+
if (normalized.setOps && normalized.setOps.length > 0) {
|
|
3222
|
+
return `SELECT 1 FROM (${full}) AS _exists`;
|
|
3223
|
+
}
|
|
2946
3224
|
const upper = full.toUpperCase();
|
|
2947
3225
|
const fromIndex = upper.indexOf(" FROM ");
|
|
2948
3226
|
if (fromIndex === -1) {
|
|
@@ -2975,6 +3253,65 @@ var Dialect = class {
|
|
|
2975
3253
|
formatPlaceholder(index) {
|
|
2976
3254
|
return "?";
|
|
2977
3255
|
}
|
|
3256
|
+
/**
|
|
3257
|
+
* Whether the current dialect supports a given set operation.
|
|
3258
|
+
* Override in concrete dialects to restrict support.
|
|
3259
|
+
*/
|
|
3260
|
+
supportsSetOperation(kind) {
|
|
3261
|
+
return true;
|
|
3262
|
+
}
|
|
3263
|
+
/**
|
|
3264
|
+
* Validates set-operation semantics:
|
|
3265
|
+
* - Ensures the dialect supports requested operators.
|
|
3266
|
+
* - Enforces that only the outermost compound query may have ORDER/LIMIT/OFFSET.
|
|
3267
|
+
* @param ast - Query to validate
|
|
3268
|
+
* @param isOutermost - Whether this node is the outermost compound query
|
|
3269
|
+
*/
|
|
3270
|
+
validateSetOperations(ast, isOutermost = true) {
|
|
3271
|
+
const hasSetOps = !!(ast.setOps && ast.setOps.length);
|
|
3272
|
+
if (!isOutermost && (ast.orderBy || ast.limit !== void 0 || ast.offset !== void 0)) {
|
|
3273
|
+
throw new Error("ORDER BY / LIMIT / OFFSET are only allowed on the outermost compound query.");
|
|
3274
|
+
}
|
|
3275
|
+
if (hasSetOps) {
|
|
3276
|
+
for (const op of ast.setOps) {
|
|
3277
|
+
if (!this.supportsSetOperation(op.operator)) {
|
|
3278
|
+
throw new Error(`Set operation ${op.operator} is not supported by this dialect.`);
|
|
3279
|
+
}
|
|
3280
|
+
this.validateSetOperations(op.query, false);
|
|
3281
|
+
}
|
|
3282
|
+
}
|
|
3283
|
+
}
|
|
3284
|
+
/**
|
|
3285
|
+
* Hoists CTEs from set-operation operands to the outermost query so WITH appears once.
|
|
3286
|
+
* @param ast - Query AST
|
|
3287
|
+
* @returns Normalized AST without inner CTEs and a list of hoisted CTEs
|
|
3288
|
+
*/
|
|
3289
|
+
hoistCtes(ast) {
|
|
3290
|
+
let hoisted = [];
|
|
3291
|
+
const normalizedSetOps = ast.setOps?.map((op) => {
|
|
3292
|
+
const { normalized: child, hoistedCtes: childHoisted } = this.hoistCtes(op.query);
|
|
3293
|
+
const childCtes = child.ctes ?? [];
|
|
3294
|
+
if (childCtes.length) {
|
|
3295
|
+
hoisted = hoisted.concat(childCtes);
|
|
3296
|
+
}
|
|
3297
|
+
hoisted = hoisted.concat(childHoisted);
|
|
3298
|
+
const queryWithoutCtes = childCtes.length ? { ...child, ctes: void 0 } : child;
|
|
3299
|
+
return { ...op, query: queryWithoutCtes };
|
|
3300
|
+
});
|
|
3301
|
+
const normalized = normalizedSetOps ? { ...ast, setOps: normalizedSetOps } : ast;
|
|
3302
|
+
return { normalized, hoistedCtes: hoisted };
|
|
3303
|
+
}
|
|
3304
|
+
/**
|
|
3305
|
+
* Normalizes a SELECT AST before compilation (validation + CTE hoisting).
|
|
3306
|
+
* @param ast - Query AST
|
|
3307
|
+
* @returns Normalized query AST
|
|
3308
|
+
*/
|
|
3309
|
+
normalizeSelectAst(ast) {
|
|
3310
|
+
this.validateSetOperations(ast, true);
|
|
3311
|
+
const { normalized, hoistedCtes } = this.hoistCtes(ast);
|
|
3312
|
+
const combinedCtes = [...normalized.ctes ?? [], ...hoistedCtes];
|
|
3313
|
+
return combinedCtes.length ? { ...normalized, ctes: combinedCtes } : normalized;
|
|
3314
|
+
}
|
|
2978
3315
|
constructor() {
|
|
2979
3316
|
this.expressionCompilers = /* @__PURE__ */ new Map();
|
|
2980
3317
|
this.operandCompilers = /* @__PURE__ */ new Map();
|
|
@@ -3123,16 +3460,18 @@ var SqlDialectBase = class extends Dialect {
|
|
|
3123
3460
|
* Compiles SELECT query AST to SQL using common rules.
|
|
3124
3461
|
*/
|
|
3125
3462
|
compileSelectAst(ast, ctx) {
|
|
3463
|
+
const hasSetOps = !!(ast.setOps && ast.setOps.length);
|
|
3126
3464
|
const ctes = this.compileCtes(ast, ctx);
|
|
3127
|
-
const
|
|
3128
|
-
const
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
const
|
|
3465
|
+
const baseAst = hasSetOps ? { ...ast, setOps: void 0, orderBy: void 0, limit: void 0, offset: void 0 } : ast;
|
|
3466
|
+
const baseSelect = this.compileSelectCore(baseAst, ctx);
|
|
3467
|
+
if (!hasSetOps) {
|
|
3468
|
+
return `${ctes}${baseSelect}`;
|
|
3469
|
+
}
|
|
3470
|
+
const compound = ast.setOps.map((op) => `${op.operator} ${this.wrapSetOperand(this.compileSelectAst(op.query, ctx))}`).join(" ");
|
|
3133
3471
|
const orderBy = this.compileOrderBy(ast);
|
|
3134
3472
|
const pagination = this.compilePagination(ast, orderBy);
|
|
3135
|
-
|
|
3473
|
+
const combined = `${this.wrapSetOperand(baseSelect)} ${compound}`;
|
|
3474
|
+
return `${ctes}${combined}${orderBy}${pagination}`;
|
|
3136
3475
|
}
|
|
3137
3476
|
compileInsertAst(ast, ctx) {
|
|
3138
3477
|
const table = this.compileTableName(ast.into);
|
|
@@ -3141,6 +3480,20 @@ var SqlDialectBase = class extends Dialect {
|
|
|
3141
3480
|
const returning = this.compileReturning(ast.returning, ctx);
|
|
3142
3481
|
return `INSERT INTO ${table} (${columnList}) VALUES ${values}${returning}`;
|
|
3143
3482
|
}
|
|
3483
|
+
/**
|
|
3484
|
+
* Compiles a single SELECT (no set operations, no CTE prefix).
|
|
3485
|
+
*/
|
|
3486
|
+
compileSelectCore(ast, ctx) {
|
|
3487
|
+
const columns = this.compileSelectColumns(ast, ctx);
|
|
3488
|
+
const from = this.compileFrom(ast.from);
|
|
3489
|
+
const joins = this.compileJoins(ast, ctx);
|
|
3490
|
+
const whereClause = this.compileWhere(ast.where, ctx);
|
|
3491
|
+
const groupBy = this.compileGroupBy(ast);
|
|
3492
|
+
const having = this.compileHaving(ast, ctx);
|
|
3493
|
+
const orderBy = this.compileOrderBy(ast);
|
|
3494
|
+
const pagination = this.compilePagination(ast, orderBy);
|
|
3495
|
+
return `SELECT ${this.compileDistinct(ast)}${columns} FROM ${from}${joins}${whereClause}${groupBy}${having}${orderBy}${pagination}`;
|
|
3496
|
+
}
|
|
3144
3497
|
compileUpdateAst(ast, ctx) {
|
|
3145
3498
|
const table = this.compileTableName(ast.table);
|
|
3146
3499
|
const assignments = ast.set.map((assignment) => {
|
|
@@ -3166,6 +3519,13 @@ var SqlDialectBase = class extends Dialect {
|
|
|
3166
3519
|
if (!returning || returning.length === 0) return "";
|
|
3167
3520
|
throw new Error("RETURNING is not supported by this dialect.");
|
|
3168
3521
|
}
|
|
3522
|
+
formatReturningColumns(returning) {
|
|
3523
|
+
return returning.map((column) => {
|
|
3524
|
+
const tablePart = column.table ? `${this.quoteIdentifier(column.table)}.` : "";
|
|
3525
|
+
const aliasPart = column.alias ? ` AS ${this.quoteIdentifier(column.alias)}` : "";
|
|
3526
|
+
return `${tablePart}${this.quoteIdentifier(column.name)}${aliasPart}`;
|
|
3527
|
+
}).join(", ");
|
|
3528
|
+
}
|
|
3169
3529
|
/**
|
|
3170
3530
|
* DISTINCT clause. Override for DISTINCT ON support.
|
|
3171
3531
|
*/
|
|
@@ -3231,7 +3591,7 @@ var SqlDialectBase = class extends Dialect {
|
|
|
3231
3591
|
const cteDefs = ast.ctes.map((cte) => {
|
|
3232
3592
|
const name = this.quoteIdentifier(cte.name);
|
|
3233
3593
|
const cols = cte.columns && cte.columns.length ? `(${cte.columns.map((c) => this.quoteIdentifier(c)).join(", ")})` : "";
|
|
3234
|
-
const query = this.stripTrailingSemicolon(this.compileSelectAst(cte.query, ctx));
|
|
3594
|
+
const query = this.stripTrailingSemicolon(this.compileSelectAst(this.normalizeSelectAst(cte.query), ctx));
|
|
3235
3595
|
return `${name}${cols} AS (${query})`;
|
|
3236
3596
|
}).join(", ");
|
|
3237
3597
|
return `${prefix}${cteDefs} `;
|
|
@@ -3239,6 +3599,10 @@ var SqlDialectBase = class extends Dialect {
|
|
|
3239
3599
|
stripTrailingSemicolon(sql) {
|
|
3240
3600
|
return sql.trim().replace(/;$/, "");
|
|
3241
3601
|
}
|
|
3602
|
+
wrapSetOperand(sql) {
|
|
3603
|
+
const trimmed = this.stripTrailingSemicolon(sql);
|
|
3604
|
+
return `(${trimmed})`;
|
|
3605
|
+
}
|
|
3242
3606
|
};
|
|
3243
3607
|
|
|
3244
3608
|
// src/core/dialect/mysql/index.ts
|
|
@@ -3308,6 +3672,43 @@ var SqlServerDialect = class extends Dialect {
|
|
|
3308
3672
|
* @returns SQL Server SQL string
|
|
3309
3673
|
*/
|
|
3310
3674
|
compileSelectAst(ast, ctx) {
|
|
3675
|
+
const hasSetOps = !!(ast.setOps && ast.setOps.length);
|
|
3676
|
+
const ctes = this.compileCtes(ast, ctx);
|
|
3677
|
+
const baseAst = hasSetOps ? { ...ast, setOps: void 0, orderBy: void 0, limit: void 0, offset: void 0 } : ast;
|
|
3678
|
+
const baseSelect = this.compileSelectCore(baseAst, ctx);
|
|
3679
|
+
if (!hasSetOps) {
|
|
3680
|
+
return `${ctes}${baseSelect}`;
|
|
3681
|
+
}
|
|
3682
|
+
const compound = ast.setOps.map((op) => `${op.operator} ${this.wrapSetOperand(this.compileSelectAst(op.query, ctx))}`).join(" ");
|
|
3683
|
+
const orderBy = this.compileOrderBy(ast);
|
|
3684
|
+
const pagination = this.compilePagination(ast, orderBy);
|
|
3685
|
+
const combined = `${this.wrapSetOperand(baseSelect)} ${compound}`;
|
|
3686
|
+
const tail = pagination || orderBy;
|
|
3687
|
+
return `${ctes}${combined}${tail}`;
|
|
3688
|
+
}
|
|
3689
|
+
compileInsertAst(ast, ctx) {
|
|
3690
|
+
const table = this.quoteIdentifier(ast.into.name);
|
|
3691
|
+
const columnList = ast.columns.map((column) => `${this.quoteIdentifier(column.table)}.${this.quoteIdentifier(column.name)}`).join(", ");
|
|
3692
|
+
const values = ast.values.map((row) => `(${row.map((value) => this.compileOperand(value, ctx)).join(", ")})`).join(", ");
|
|
3693
|
+
return `INSERT INTO ${table} (${columnList}) VALUES ${values};`;
|
|
3694
|
+
}
|
|
3695
|
+
compileUpdateAst(ast, ctx) {
|
|
3696
|
+
const table = this.quoteIdentifier(ast.table.name);
|
|
3697
|
+
const assignments = ast.set.map((assignment) => {
|
|
3698
|
+
const col2 = assignment.column;
|
|
3699
|
+
const target = `${this.quoteIdentifier(col2.table)}.${this.quoteIdentifier(col2.name)}`;
|
|
3700
|
+
const value = this.compileOperand(assignment.value, ctx);
|
|
3701
|
+
return `${target} = ${value}`;
|
|
3702
|
+
}).join(", ");
|
|
3703
|
+
const whereClause = this.compileWhere(ast.where, ctx);
|
|
3704
|
+
return `UPDATE ${table} SET ${assignments}${whereClause};`;
|
|
3705
|
+
}
|
|
3706
|
+
compileDeleteAst(ast, ctx) {
|
|
3707
|
+
const table = this.quoteIdentifier(ast.from.name);
|
|
3708
|
+
const whereClause = this.compileWhere(ast.where, ctx);
|
|
3709
|
+
return `DELETE FROM ${table}${whereClause};`;
|
|
3710
|
+
}
|
|
3711
|
+
compileSelectCore(ast, ctx) {
|
|
3311
3712
|
const columns = ast.columns.map((c) => {
|
|
3312
3713
|
let expr = "";
|
|
3313
3714
|
if (c.type === "Function") {
|
|
@@ -3335,46 +3736,42 @@ var SqlServerDialect = class extends Dialect {
|
|
|
3335
3736
|
const whereClause = this.compileWhere(ast.where, ctx);
|
|
3336
3737
|
const groupBy = ast.groupBy && ast.groupBy.length > 0 ? " GROUP BY " + ast.groupBy.map((c) => `${this.quoteIdentifier(c.table)}.${this.quoteIdentifier(c.name)}`).join(", ") : "";
|
|
3337
3738
|
const having = ast.having ? ` HAVING ${this.compileExpression(ast.having, ctx)}` : "";
|
|
3338
|
-
const orderBy =
|
|
3339
|
-
|
|
3340
|
-
if (
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3739
|
+
const orderBy = this.compileOrderBy(ast);
|
|
3740
|
+
const pagination = this.compilePagination(ast, orderBy);
|
|
3741
|
+
if (pagination) {
|
|
3742
|
+
return `SELECT ${distinct}${columns} FROM ${from}${joins ? " " + joins : ""}${whereClause}${groupBy}${having}${pagination}`;
|
|
3743
|
+
}
|
|
3744
|
+
return `SELECT ${distinct}${columns} FROM ${from}${joins ? " " + joins : ""}${whereClause}${groupBy}${having}${orderBy}`;
|
|
3745
|
+
}
|
|
3746
|
+
compileOrderBy(ast) {
|
|
3747
|
+
if (!ast.orderBy || ast.orderBy.length === 0) return "";
|
|
3748
|
+
return " ORDER BY " + ast.orderBy.map((o) => `${this.quoteIdentifier(o.column.table)}.${this.quoteIdentifier(o.column.name)} ${o.direction}`).join(", ");
|
|
3749
|
+
}
|
|
3750
|
+
compilePagination(ast, orderBy) {
|
|
3751
|
+
const hasLimit = ast.limit !== void 0;
|
|
3752
|
+
const hasOffset = ast.offset !== void 0;
|
|
3753
|
+
if (!hasLimit && !hasOffset) return "";
|
|
3754
|
+
const off = ast.offset ?? 0;
|
|
3755
|
+
const orderClause = orderBy || " ORDER BY (SELECT NULL)";
|
|
3756
|
+
let pagination = `${orderClause} OFFSET ${off} ROWS`;
|
|
3757
|
+
if (hasLimit) {
|
|
3758
|
+
pagination += ` FETCH NEXT ${ast.limit} ROWS ONLY`;
|
|
3348
3759
|
}
|
|
3349
|
-
|
|
3760
|
+
return pagination;
|
|
3761
|
+
}
|
|
3762
|
+
compileCtes(ast, ctx) {
|
|
3763
|
+
if (!ast.ctes || ast.ctes.length === 0) return "";
|
|
3764
|
+
const defs = ast.ctes.map((cte) => {
|
|
3350
3765
|
const name = this.quoteIdentifier(cte.name);
|
|
3351
3766
|
const cols = cte.columns ? `(${cte.columns.map((c) => this.quoteIdentifier(c)).join(", ")})` : "";
|
|
3352
|
-
const query = this.compileSelectAst(cte.query, ctx).trim().replace(/;$/, "");
|
|
3767
|
+
const query = this.compileSelectAst(this.normalizeSelectAst(cte.query), ctx).trim().replace(/;$/, "");
|
|
3353
3768
|
return `${name}${cols} AS (${query})`;
|
|
3354
|
-
}).join(", ") + " " : "";
|
|
3355
|
-
return `${ctes}SELECT ${distinct}${columns} FROM ${from}${joins ? " " + joins : ""}${whereClause}${groupBy}${having}${orderBy};`;
|
|
3356
|
-
}
|
|
3357
|
-
compileInsertAst(ast, ctx) {
|
|
3358
|
-
const table = this.quoteIdentifier(ast.into.name);
|
|
3359
|
-
const columnList = ast.columns.map((column) => `${this.quoteIdentifier(column.table)}.${this.quoteIdentifier(column.name)}`).join(", ");
|
|
3360
|
-
const values = ast.values.map((row) => `(${row.map((value) => this.compileOperand(value, ctx)).join(", ")})`).join(", ");
|
|
3361
|
-
return `INSERT INTO ${table} (${columnList}) VALUES ${values};`;
|
|
3362
|
-
}
|
|
3363
|
-
compileUpdateAst(ast, ctx) {
|
|
3364
|
-
const table = this.quoteIdentifier(ast.table.name);
|
|
3365
|
-
const assignments = ast.set.map((assignment) => {
|
|
3366
|
-
const col2 = assignment.column;
|
|
3367
|
-
const target = `${this.quoteIdentifier(col2.table)}.${this.quoteIdentifier(col2.name)}`;
|
|
3368
|
-
const value = this.compileOperand(assignment.value, ctx);
|
|
3369
|
-
return `${target} = ${value}`;
|
|
3370
3769
|
}).join(", ");
|
|
3371
|
-
|
|
3372
|
-
return `UPDATE ${table} SET ${assignments}${whereClause};`;
|
|
3770
|
+
return `WITH ${defs} `;
|
|
3373
3771
|
}
|
|
3374
|
-
|
|
3375
|
-
const
|
|
3376
|
-
|
|
3377
|
-
return `DELETE FROM ${table}${whereClause};`;
|
|
3772
|
+
wrapSetOperand(sql) {
|
|
3773
|
+
const trimmed = sql.trim().replace(/;$/, "");
|
|
3774
|
+
return `(${trimmed})`;
|
|
3378
3775
|
}
|
|
3379
3776
|
};
|
|
3380
3777
|
|
|
@@ -3405,12 +3802,12 @@ var SqliteDialect = class extends SqlDialectBase {
|
|
|
3405
3802
|
}
|
|
3406
3803
|
compileReturning(returning, ctx) {
|
|
3407
3804
|
if (!returning || returning.length === 0) return "";
|
|
3408
|
-
const columns =
|
|
3409
|
-
const tablePart = column.table ? `${this.quoteIdentifier(column.table)}.` : "";
|
|
3410
|
-
return `${tablePart}${this.quoteIdentifier(column.name)}`;
|
|
3411
|
-
}).join(", ");
|
|
3805
|
+
const columns = this.formatReturningColumns(returning);
|
|
3412
3806
|
return ` RETURNING ${columns}`;
|
|
3413
3807
|
}
|
|
3808
|
+
supportsReturning() {
|
|
3809
|
+
return true;
|
|
3810
|
+
}
|
|
3414
3811
|
};
|
|
3415
3812
|
|
|
3416
3813
|
// src/core/dialect/postgres/index.ts
|
|
@@ -3440,12 +3837,12 @@ var PostgresDialect = class extends SqlDialectBase {
|
|
|
3440
3837
|
}
|
|
3441
3838
|
compileReturning(returning, ctx) {
|
|
3442
3839
|
if (!returning || returning.length === 0) return "";
|
|
3443
|
-
const columns =
|
|
3444
|
-
const tablePart = column.table ? `${this.quoteIdentifier(column.table)}.` : "";
|
|
3445
|
-
return `${tablePart}${this.quoteIdentifier(column.name)}`;
|
|
3446
|
-
}).join(", ");
|
|
3840
|
+
const columns = this.formatReturningColumns(returning);
|
|
3447
3841
|
return ` RETURNING ${columns}`;
|
|
3448
3842
|
}
|
|
3843
|
+
supportsReturning() {
|
|
3844
|
+
return true;
|
|
3845
|
+
}
|
|
3449
3846
|
};
|
|
3450
3847
|
|
|
3451
3848
|
// src/core/ddl/dialects/base-schema-dialect.ts
|
|
@@ -5209,9 +5606,13 @@ var UnitOfWork = class {
|
|
|
5209
5606
|
async flushInsert(tracked) {
|
|
5210
5607
|
await this.runHook(tracked.table.hooks?.beforeInsert, tracked);
|
|
5211
5608
|
const payload = this.extractColumns(tracked.table, tracked.entity);
|
|
5212
|
-
|
|
5609
|
+
let builder = new InsertQueryBuilder(tracked.table).values(payload);
|
|
5610
|
+
if (this.dialect.supportsReturning()) {
|
|
5611
|
+
builder = builder.returning(...this.getReturningColumns(tracked.table));
|
|
5612
|
+
}
|
|
5213
5613
|
const compiled = builder.compile(this.dialect);
|
|
5214
|
-
await this.executeCompiled(compiled);
|
|
5614
|
+
const results = await this.executeCompiled(compiled);
|
|
5615
|
+
this.applyReturningResults(tracked, results);
|
|
5215
5616
|
tracked.status = "managed" /* Managed */;
|
|
5216
5617
|
tracked.original = this.createSnapshot(tracked.table, tracked.entity);
|
|
5217
5618
|
tracked.pk = this.getPrimaryKeyValue(tracked);
|
|
@@ -5228,9 +5629,13 @@ var UnitOfWork = class {
|
|
|
5228
5629
|
await this.runHook(tracked.table.hooks?.beforeUpdate, tracked);
|
|
5229
5630
|
const pkColumn = tracked.table.columns[findPrimaryKey(tracked.table)];
|
|
5230
5631
|
if (!pkColumn) return;
|
|
5231
|
-
|
|
5632
|
+
let builder = new UpdateQueryBuilder(tracked.table).set(changes).where(eq(pkColumn, tracked.pk));
|
|
5633
|
+
if (this.dialect.supportsReturning()) {
|
|
5634
|
+
builder = builder.returning(...this.getReturningColumns(tracked.table));
|
|
5635
|
+
}
|
|
5232
5636
|
const compiled = builder.compile(this.dialect);
|
|
5233
|
-
await this.executeCompiled(compiled);
|
|
5637
|
+
const results = await this.executeCompiled(compiled);
|
|
5638
|
+
this.applyReturningResults(tracked, results);
|
|
5234
5639
|
tracked.status = "managed" /* Managed */;
|
|
5235
5640
|
tracked.original = this.createSnapshot(tracked.table, tracked.entity);
|
|
5236
5641
|
this.registerIdentity(tracked);
|
|
@@ -5272,7 +5677,31 @@ var UnitOfWork = class {
|
|
|
5272
5677
|
return payload;
|
|
5273
5678
|
}
|
|
5274
5679
|
async executeCompiled(compiled) {
|
|
5275
|
-
|
|
5680
|
+
return this.executor.executeSql(compiled.sql, compiled.params);
|
|
5681
|
+
}
|
|
5682
|
+
getReturningColumns(table) {
|
|
5683
|
+
return Object.values(table.columns).map((column) => ({
|
|
5684
|
+
type: "Column",
|
|
5685
|
+
table: table.name,
|
|
5686
|
+
name: column.name,
|
|
5687
|
+
alias: column.name
|
|
5688
|
+
}));
|
|
5689
|
+
}
|
|
5690
|
+
applyReturningResults(tracked, results) {
|
|
5691
|
+
if (!this.dialect.supportsReturning()) return;
|
|
5692
|
+
const first = results[0];
|
|
5693
|
+
if (!first || first.values.length === 0) return;
|
|
5694
|
+
const row = first.values[0];
|
|
5695
|
+
for (let i = 0; i < first.columns.length; i++) {
|
|
5696
|
+
const columnName = this.normalizeColumnName(first.columns[i]);
|
|
5697
|
+
if (!(columnName in tracked.table.columns)) continue;
|
|
5698
|
+
tracked.entity[columnName] = row[i];
|
|
5699
|
+
}
|
|
5700
|
+
}
|
|
5701
|
+
normalizeColumnName(column) {
|
|
5702
|
+
const parts = column.split(".");
|
|
5703
|
+
const candidate = parts[parts.length - 1];
|
|
5704
|
+
return candidate.replace(/^["`[\]]+|["`[\]]+$/g, "");
|
|
5276
5705
|
}
|
|
5277
5706
|
registerIdentity(tracked) {
|
|
5278
5707
|
if (tracked.pk == null) return;
|
|
@@ -5293,22 +5722,46 @@ var UnitOfWork = class {
|
|
|
5293
5722
|
}
|
|
5294
5723
|
};
|
|
5295
5724
|
|
|
5725
|
+
// src/orm/query-logger.ts
|
|
5726
|
+
var createQueryLoggingExecutor = (executor, logger) => {
|
|
5727
|
+
if (!logger) {
|
|
5728
|
+
return executor;
|
|
5729
|
+
}
|
|
5730
|
+
const wrapped = {
|
|
5731
|
+
async executeSql(sql, params) {
|
|
5732
|
+
logger({ sql, params });
|
|
5733
|
+
return executor.executeSql(sql, params);
|
|
5734
|
+
}
|
|
5735
|
+
};
|
|
5736
|
+
if (executor.beginTransaction) {
|
|
5737
|
+
wrapped.beginTransaction = executor.beginTransaction.bind(executor);
|
|
5738
|
+
}
|
|
5739
|
+
if (executor.commitTransaction) {
|
|
5740
|
+
wrapped.commitTransaction = executor.commitTransaction.bind(executor);
|
|
5741
|
+
}
|
|
5742
|
+
if (executor.rollbackTransaction) {
|
|
5743
|
+
wrapped.rollbackTransaction = executor.rollbackTransaction.bind(executor);
|
|
5744
|
+
}
|
|
5745
|
+
return wrapped;
|
|
5746
|
+
};
|
|
5747
|
+
|
|
5296
5748
|
// src/orm/orm-context.ts
|
|
5297
5749
|
var OrmContext = class {
|
|
5298
5750
|
constructor(options) {
|
|
5299
5751
|
this.options = options;
|
|
5300
5752
|
this.identityMap = new IdentityMap();
|
|
5301
5753
|
this.interceptors = [...options.interceptors ?? []];
|
|
5754
|
+
this.executorWithLogging = createQueryLoggingExecutor(options.executor, options.queryLogger);
|
|
5302
5755
|
this.unitOfWork = new UnitOfWork(
|
|
5303
5756
|
options.dialect,
|
|
5304
|
-
|
|
5757
|
+
this.executorWithLogging,
|
|
5305
5758
|
this.identityMap,
|
|
5306
5759
|
() => this
|
|
5307
5760
|
);
|
|
5308
5761
|
this.relationChanges = new RelationChangeProcessor(
|
|
5309
5762
|
this.unitOfWork,
|
|
5310
5763
|
options.dialect,
|
|
5311
|
-
|
|
5764
|
+
this.executorWithLogging
|
|
5312
5765
|
);
|
|
5313
5766
|
this.domainEvents = new DomainEventBus(options.domainEventHandlers);
|
|
5314
5767
|
}
|
|
@@ -5316,7 +5769,7 @@ var OrmContext = class {
|
|
|
5316
5769
|
return this.options.dialect;
|
|
5317
5770
|
}
|
|
5318
5771
|
get executor() {
|
|
5319
|
-
return this.
|
|
5772
|
+
return this.executorWithLogging;
|
|
5320
5773
|
}
|
|
5321
5774
|
get identityBuckets() {
|
|
5322
5775
|
return this.unitOfWork.identityBuckets;
|