linkgress-orm 0.1.17 → 0.1.19
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/dist/database/database-client.interface.d.ts +29 -0
- package/dist/database/database-client.interface.d.ts.map +1 -1
- package/dist/database/database-client.interface.js +45 -1
- package/dist/database/database-client.interface.js.map +1 -1
- package/dist/database/pg-client.d.ts +5 -0
- package/dist/database/pg-client.d.ts.map +1 -1
- package/dist/database/pg-client.js +27 -0
- package/dist/database/pg-client.js.map +1 -1
- package/dist/database/postgres-client.d.ts +6 -0
- package/dist/database/postgres-client.d.ts.map +1 -1
- package/dist/database/postgres-client.js +17 -0
- package/dist/database/postgres-client.js.map +1 -1
- package/dist/entity/db-column.d.ts +17 -0
- package/dist/entity/db-column.d.ts.map +1 -1
- package/dist/entity/db-column.js.map +1 -1
- package/dist/entity/db-context.d.ts +134 -25
- package/dist/entity/db-context.d.ts.map +1 -1
- package/dist/entity/db-context.js +237 -174
- package/dist/entity/db-context.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/query/cte-builder.d.ts +1 -1
- package/dist/query/cte-builder.js +8 -8
- package/dist/query/cte-builder.js.map +1 -1
- package/dist/query/grouped-query.d.ts +8 -0
- package/dist/query/grouped-query.d.ts.map +1 -1
- package/dist/query/grouped-query.js +12 -0
- package/dist/query/grouped-query.js.map +1 -1
- package/dist/query/query-builder.d.ts +13 -2
- package/dist/query/query-builder.d.ts.map +1 -1
- package/dist/query/query-builder.js +128 -42
- package/dist/query/query-builder.js.map +1 -1
- package/dist/query/sql-utils.d.ts +2 -0
- package/dist/query/sql-utils.d.ts.map +1 -1
- package/dist/query/sql-utils.js +24 -7
- package/dist/query/sql-utils.js.map +1 -1
- package/package.json +1 -1
|
@@ -27,6 +27,7 @@ var FieldType;
|
|
|
27
27
|
FieldType[FieldType["FIELD_REF_MAPPER"] = 6] = "FIELD_REF_MAPPER";
|
|
28
28
|
FieldType[FieldType["FIELD_REF_NO_MAPPER"] = 7] = "FIELD_REF_NO_MAPPER";
|
|
29
29
|
FieldType[FieldType["SIMPLE"] = 8] = "SIMPLE";
|
|
30
|
+
FieldType[FieldType["COLLECTION_SINGLE"] = 9] = "COLLECTION_SINGLE";
|
|
30
31
|
})(FieldType || (FieldType = {}));
|
|
31
32
|
/**
|
|
32
33
|
* Performance utility: Get column name map from schema, using cached version if available
|
|
@@ -175,7 +176,7 @@ class QueryBuilder {
|
|
|
175
176
|
* Multiple where() calls are chained with AND logic
|
|
176
177
|
*/
|
|
177
178
|
where(condition) {
|
|
178
|
-
const mockRow = this.
|
|
179
|
+
const mockRow = this._createMockRow();
|
|
179
180
|
const newCondition = condition(mockRow);
|
|
180
181
|
if (this.whereCond) {
|
|
181
182
|
this.whereCond = (0, conditions_1.and)(this.whereCond, newCondition);
|
|
@@ -194,8 +195,9 @@ class QueryBuilder {
|
|
|
194
195
|
}
|
|
195
196
|
/**
|
|
196
197
|
* Create mock row for analysis
|
|
198
|
+
* @internal - Also used by DbEntityTable.props() to avoid code duplication
|
|
197
199
|
*/
|
|
198
|
-
|
|
200
|
+
_createMockRow() {
|
|
199
201
|
// Performance: Return cached mock if available
|
|
200
202
|
if (this._cachedMockRow) {
|
|
201
203
|
return this._cachedMockRow;
|
|
@@ -245,24 +247,27 @@ class QueryBuilder {
|
|
|
245
247
|
targetSchema = getTargetSchemaForRelation(this.schema, relName, relConfig);
|
|
246
248
|
}
|
|
247
249
|
if (relConfig.type === 'many') {
|
|
250
|
+
// Non-enumerable to prevent Object.entries triggering getters (avoids stack overflow)
|
|
248
251
|
Object.defineProperty(mock, relName, {
|
|
249
252
|
get: () => {
|
|
250
253
|
return new CollectionQueryBuilder(relName, relConfig.targetTable, relConfig.foreignKey || relConfig.foreignKeys?.[0] || '', this.schema.name, targetSchema, this.schemaRegistry // Pass schema registry for nested navigation resolution
|
|
251
254
|
);
|
|
252
255
|
},
|
|
253
|
-
enumerable:
|
|
256
|
+
enumerable: false,
|
|
254
257
|
configurable: true,
|
|
255
258
|
});
|
|
256
259
|
}
|
|
257
260
|
else {
|
|
258
261
|
// Single reference navigation (many-to-one, one-to-one)
|
|
262
|
+
// Non-enumerable to prevent Object.entries triggering getters (avoids stack overflow
|
|
263
|
+
// with circular relations like User->Posts->User)
|
|
259
264
|
Object.defineProperty(mock, relName, {
|
|
260
265
|
get: () => {
|
|
261
266
|
const refBuilder = new ReferenceQueryBuilder(relName, relConfig.targetTable, relConfig.foreignKeys || [relConfig.foreignKey || ''], relConfig.matches || [], relConfig.isMandatory ?? false, targetSchema, this.schemaRegistry // Pass schema registry for nested navigation resolution
|
|
262
267
|
);
|
|
263
268
|
return refBuilder.createMockTargetRow();
|
|
264
269
|
},
|
|
265
|
-
enumerable:
|
|
270
|
+
enumerable: false,
|
|
266
271
|
configurable: true,
|
|
267
272
|
});
|
|
268
273
|
}
|
|
@@ -293,7 +298,7 @@ class QueryBuilder {
|
|
|
293
298
|
const rightAlias = `${rightSchema.name}_${this.joinCounter}`;
|
|
294
299
|
const newJoinCounter = this.joinCounter + 1;
|
|
295
300
|
// Create mock rows for condition evaluation
|
|
296
|
-
const mockLeft = this.
|
|
301
|
+
const mockLeft = this._createMockRow();
|
|
297
302
|
const mockRight = this.createMockRowForTable(rightSchema, rightAlias);
|
|
298
303
|
const joinCondition = condition(mockLeft, mockRight);
|
|
299
304
|
// Add the join to the list
|
|
@@ -306,7 +311,7 @@ class QueryBuilder {
|
|
|
306
311
|
}];
|
|
307
312
|
// Store schemas for creating fresh mocks in the selector
|
|
308
313
|
const leftSchema = this.schema;
|
|
309
|
-
const createLeftMock = () => this.
|
|
314
|
+
const createLeftMock = () => this._createMockRow();
|
|
310
315
|
const createRightMock = () => this.createMockRowForTable(rightSchema, rightAlias);
|
|
311
316
|
// Create a selector wrapper that generates fresh mocks and calls the user's selector
|
|
312
317
|
const wrappedSelector = (row) => {
|
|
@@ -342,7 +347,7 @@ class QueryBuilder {
|
|
|
342
347
|
const rightAlias = `${rightSchema.name}_${this.joinCounter}`;
|
|
343
348
|
const newJoinCounter = this.joinCounter + 1;
|
|
344
349
|
// Create mock rows for condition evaluation
|
|
345
|
-
const mockLeft = this.
|
|
350
|
+
const mockLeft = this._createMockRow();
|
|
346
351
|
const mockRight = this.createMockRowForTable(rightSchema, rightAlias);
|
|
347
352
|
const joinCondition = condition(mockLeft, mockRight);
|
|
348
353
|
// Add the join to the list
|
|
@@ -355,7 +360,7 @@ class QueryBuilder {
|
|
|
355
360
|
}];
|
|
356
361
|
// Store schemas for creating fresh mocks in the selector
|
|
357
362
|
const leftSchema = this.schema;
|
|
358
|
-
const createLeftMock = () => this.
|
|
363
|
+
const createLeftMock = () => this._createMockRow();
|
|
359
364
|
const createRightMock = () => this.createMockRowForTable(rightSchema, rightAlias);
|
|
360
365
|
// Create a selector wrapper that generates fresh mocks and calls the user's selector
|
|
361
366
|
const wrappedSelector = (row) => {
|
|
@@ -404,22 +409,24 @@ class QueryBuilder {
|
|
|
404
409
|
const targetSchema = getTargetSchemaForRelation(schema, relName, relConfig);
|
|
405
410
|
if (relConfig.type === 'many') {
|
|
406
411
|
// Collection navigation
|
|
412
|
+
// Non-enumerable to prevent Object.entries triggering getters (avoids stack overflow)
|
|
407
413
|
Object.defineProperty(mock, relName, {
|
|
408
414
|
get: () => {
|
|
409
415
|
return new CollectionQueryBuilder(relName, relConfig.targetTable, relConfig.foreignKey || relConfig.foreignKeys?.[0] || '', schema.name, targetSchema);
|
|
410
416
|
},
|
|
411
|
-
enumerable:
|
|
417
|
+
enumerable: false,
|
|
412
418
|
configurable: true,
|
|
413
419
|
});
|
|
414
420
|
}
|
|
415
421
|
else {
|
|
416
422
|
// Single reference navigation
|
|
423
|
+
// Non-enumerable to prevent Object.entries triggering getters (avoids stack overflow)
|
|
417
424
|
Object.defineProperty(mock, relName, {
|
|
418
425
|
get: () => {
|
|
419
426
|
const refBuilder = new ReferenceQueryBuilder(relName, relConfig.targetTable, relConfig.foreignKeys || [relConfig.foreignKey || ''], relConfig.matches || [], relConfig.isMandatory ?? false, targetSchema);
|
|
420
427
|
return refBuilder.createMockTargetRow();
|
|
421
428
|
},
|
|
422
|
-
enumerable:
|
|
429
|
+
enumerable: false,
|
|
423
430
|
configurable: true,
|
|
424
431
|
});
|
|
425
432
|
}
|
|
@@ -441,7 +448,7 @@ class QueryBuilder {
|
|
|
441
448
|
return this;
|
|
442
449
|
}
|
|
443
450
|
orderBy(selector) {
|
|
444
|
-
const mockRow = this.
|
|
451
|
+
const mockRow = this._createMockRow();
|
|
445
452
|
const result = selector(mockRow);
|
|
446
453
|
(0, query_utils_1.parseOrderBy)(result, this.orderByFields);
|
|
447
454
|
return this;
|
|
@@ -497,7 +504,7 @@ class SelectQueryBuilder {
|
|
|
497
504
|
* Note: The row parameter represents the selected shape (after select())
|
|
498
505
|
*/
|
|
499
506
|
where(condition) {
|
|
500
|
-
const mockRow = this.
|
|
507
|
+
const mockRow = this._createMockRow();
|
|
501
508
|
// Apply the selector to get the selected shape that the user sees in the WHERE condition
|
|
502
509
|
const selectedMock = this.selector(mockRow);
|
|
503
510
|
// Wrap in proxy - for WHERE, we preserve original column names
|
|
@@ -545,7 +552,7 @@ class SelectQueryBuilder {
|
|
|
545
552
|
return this;
|
|
546
553
|
}
|
|
547
554
|
orderBy(selector) {
|
|
548
|
-
const mockRow = this.
|
|
555
|
+
const mockRow = this._createMockRow();
|
|
549
556
|
const selectedMock = this.selector(mockRow);
|
|
550
557
|
// Wrap selectedMock in a proxy that returns FieldRefs for property access
|
|
551
558
|
const fieldRefProxy = this.createFieldRefProxy(selectedMock);
|
|
@@ -577,7 +584,7 @@ class SelectQueryBuilder {
|
|
|
577
584
|
leftJoinSubquery(subquery, alias, condition, selector) {
|
|
578
585
|
const newJoinCounter = this.joinCounter + 1;
|
|
579
586
|
// Create mock for the current selection (left side)
|
|
580
|
-
const mockRow = this.
|
|
587
|
+
const mockRow = this._createMockRow();
|
|
581
588
|
const mockLeftSelection = this.selector(mockRow);
|
|
582
589
|
// Create mock for the subquery result (right side)
|
|
583
590
|
// For subqueries, we create a mock based on the result type
|
|
@@ -621,7 +628,7 @@ class SelectQueryBuilder {
|
|
|
621
628
|
const newJoinCounter = this.joinCounter + 1;
|
|
622
629
|
// Create mock for the current selection (left side)
|
|
623
630
|
// IMPORTANT: We call the selector with the mock row to get a result that contains FieldRef objects
|
|
624
|
-
const mockRow = this.
|
|
631
|
+
const mockRow = this._createMockRow();
|
|
625
632
|
const mockLeftSelection = this.selector(mockRow);
|
|
626
633
|
// The mockLeftSelection now contains FieldRef objects (with __fieldName, __dbColumnName, __tableAlias)
|
|
627
634
|
// These FieldRef objects preserve the table context
|
|
@@ -652,7 +659,7 @@ class SelectQueryBuilder {
|
|
|
652
659
|
leftJoinCte(cte, condition, selector) {
|
|
653
660
|
const newJoinCounter = this.joinCounter + 1;
|
|
654
661
|
// Create mock for the current selection (left side)
|
|
655
|
-
const mockRow = this.
|
|
662
|
+
const mockRow = this._createMockRow();
|
|
656
663
|
const mockLeftSelection = this.selector(mockRow);
|
|
657
664
|
// Create mock for the CTE columns (right side)
|
|
658
665
|
const mockRight = this.createMockRowForCte(cte);
|
|
@@ -685,7 +692,7 @@ class SelectQueryBuilder {
|
|
|
685
692
|
innerJoinSubquery(subquery, alias, condition, selector) {
|
|
686
693
|
const newJoinCounter = this.joinCounter + 1;
|
|
687
694
|
// Create mock for the current selection (left side)
|
|
688
|
-
const mockRow = this.
|
|
695
|
+
const mockRow = this._createMockRow();
|
|
689
696
|
const mockLeftSelection = this.selector(mockRow);
|
|
690
697
|
// Create mock for the subquery result (right side)
|
|
691
698
|
const mockRight = this.createMockRowForSubquery(alias, subquery);
|
|
@@ -727,7 +734,7 @@ class SelectQueryBuilder {
|
|
|
727
734
|
const rightAlias = `${rightSchema.name}_${this.joinCounter}`;
|
|
728
735
|
const newJoinCounter = this.joinCounter + 1;
|
|
729
736
|
// Create mock for the current selection (left side)
|
|
730
|
-
const mockRow = this.
|
|
737
|
+
const mockRow = this._createMockRow();
|
|
731
738
|
const mockLeftSelection = this.selector(mockRow);
|
|
732
739
|
// Create mock for the right table
|
|
733
740
|
const mockRight = this.createMockRowForTable(rightSchema, rightAlias);
|
|
@@ -784,22 +791,24 @@ class SelectQueryBuilder {
|
|
|
784
791
|
const targetSchema = getTargetSchemaForRelation(schema, relName, relConfig);
|
|
785
792
|
if (relConfig.type === 'many') {
|
|
786
793
|
// Collection navigation
|
|
794
|
+
// Non-enumerable to prevent Object.entries triggering getters (avoids stack overflow)
|
|
787
795
|
Object.defineProperty(mock, relName, {
|
|
788
796
|
get: () => {
|
|
789
797
|
return new CollectionQueryBuilder(relName, relConfig.targetTable, relConfig.foreignKey || relConfig.foreignKeys?.[0] || '', schema.name, targetSchema);
|
|
790
798
|
},
|
|
791
|
-
enumerable:
|
|
799
|
+
enumerable: false,
|
|
792
800
|
configurable: true,
|
|
793
801
|
});
|
|
794
802
|
}
|
|
795
803
|
else {
|
|
796
804
|
// Single reference navigation
|
|
805
|
+
// Non-enumerable to prevent Object.entries triggering getters (avoids stack overflow)
|
|
797
806
|
Object.defineProperty(mock, relName, {
|
|
798
807
|
get: () => {
|
|
799
808
|
const refBuilder = new ReferenceQueryBuilder(relName, relConfig.targetTable, relConfig.foreignKeys || [relConfig.foreignKey || ''], relConfig.matches || [], relConfig.isMandatory ?? false, targetSchema);
|
|
800
809
|
return refBuilder.createMockTargetRow();
|
|
801
810
|
},
|
|
802
|
-
enumerable:
|
|
811
|
+
enumerable: false,
|
|
803
812
|
configurable: true,
|
|
804
813
|
});
|
|
805
814
|
}
|
|
@@ -936,13 +945,13 @@ class SelectQueryBuilder {
|
|
|
936
945
|
// If selector is provided, apply it to determine the field
|
|
937
946
|
let fieldToAggregate;
|
|
938
947
|
if (selector) {
|
|
939
|
-
const mockRow = this.
|
|
948
|
+
const mockRow = this._createMockRow();
|
|
940
949
|
const mockSelection = this.selector(mockRow);
|
|
941
950
|
fieldToAggregate = selector(mockSelection);
|
|
942
951
|
}
|
|
943
952
|
else {
|
|
944
953
|
// No selector - use the current selection
|
|
945
|
-
const mockRow = this.
|
|
954
|
+
const mockRow = this._createMockRow();
|
|
946
955
|
fieldToAggregate = this.selector(mockRow);
|
|
947
956
|
}
|
|
948
957
|
// Build aggregation query
|
|
@@ -967,13 +976,13 @@ class SelectQueryBuilder {
|
|
|
967
976
|
// If selector is provided, apply it to determine the field
|
|
968
977
|
let fieldToAggregate;
|
|
969
978
|
if (selector) {
|
|
970
|
-
const mockRow = this.
|
|
979
|
+
const mockRow = this._createMockRow();
|
|
971
980
|
const mockSelection = this.selector(mockRow);
|
|
972
981
|
fieldToAggregate = selector(mockSelection);
|
|
973
982
|
}
|
|
974
983
|
else {
|
|
975
984
|
// No selector - use the current selection
|
|
976
|
-
const mockRow = this.
|
|
985
|
+
const mockRow = this._createMockRow();
|
|
977
986
|
fieldToAggregate = this.selector(mockRow);
|
|
978
987
|
}
|
|
979
988
|
// Build aggregation query
|
|
@@ -998,13 +1007,13 @@ class SelectQueryBuilder {
|
|
|
998
1007
|
// If selector is provided, apply it to determine the field
|
|
999
1008
|
let fieldToAggregate;
|
|
1000
1009
|
if (selector) {
|
|
1001
|
-
const mockRow = this.
|
|
1010
|
+
const mockRow = this._createMockRow();
|
|
1002
1011
|
const mockSelection = this.selector(mockRow);
|
|
1003
1012
|
fieldToAggregate = selector(mockSelection);
|
|
1004
1013
|
}
|
|
1005
1014
|
else {
|
|
1006
1015
|
// No selector - use the current selection
|
|
1007
|
-
const mockRow = this.
|
|
1016
|
+
const mockRow = this._createMockRow();
|
|
1008
1017
|
fieldToAggregate = this.selector(mockRow);
|
|
1009
1018
|
}
|
|
1010
1019
|
// Build aggregation query
|
|
@@ -1052,7 +1061,7 @@ class SelectQueryBuilder {
|
|
|
1052
1061
|
executor: this.executor,
|
|
1053
1062
|
}));
|
|
1054
1063
|
// Analyze the selector to extract nested queries
|
|
1055
|
-
const mockRow = tracer.trace('createMockRow', () => this.
|
|
1064
|
+
const mockRow = tracer.trace('createMockRow', () => this._createMockRow());
|
|
1056
1065
|
const selectionResult = tracer.trace('evaluateSelector', () => this.selector(mockRow));
|
|
1057
1066
|
// Check if we're using temp table strategy and have collections
|
|
1058
1067
|
const collections = tracer.trace('detectCollections', () => this.detectCollections(selectionResult));
|
|
@@ -1319,7 +1328,7 @@ class SelectQueryBuilder {
|
|
|
1319
1328
|
const baseSelection = {};
|
|
1320
1329
|
const collectionNames = new Set(collections.map(c => c.name));
|
|
1321
1330
|
// Always ensure we have the primary key in the base selection with a known alias
|
|
1322
|
-
const mockRow = this.
|
|
1331
|
+
const mockRow = this._createMockRow();
|
|
1323
1332
|
baseSelection['__pk_id'] = mockRow.id; // Add primary key with a known alias
|
|
1324
1333
|
for (const [key, value] of Object.entries(selection)) {
|
|
1325
1334
|
if (!collectionNames.has(key)) {
|
|
@@ -1413,6 +1422,10 @@ class SelectQueryBuilder {
|
|
|
1413
1422
|
// Array aggregation
|
|
1414
1423
|
return [];
|
|
1415
1424
|
}
|
|
1425
|
+
else if (builder.isSingleResult()) {
|
|
1426
|
+
// firstOrDefault() - single item
|
|
1427
|
+
return null;
|
|
1428
|
+
}
|
|
1416
1429
|
else {
|
|
1417
1430
|
// JSONB aggregation (object array)
|
|
1418
1431
|
return [];
|
|
@@ -1467,8 +1480,10 @@ class SelectQueryBuilder {
|
|
|
1467
1480
|
}
|
|
1468
1481
|
const condBuilder = new conditions_1.ConditionBuilder();
|
|
1469
1482
|
const { sql: whereSql, params: whereParams } = condBuilder.build(queryBuilder.whereCond, 1);
|
|
1470
|
-
// Build RETURNING clause
|
|
1471
|
-
const returningClause =
|
|
1483
|
+
// Build RETURNING clause (not needed for count-only)
|
|
1484
|
+
const returningClause = returning !== 'count'
|
|
1485
|
+
? queryBuilder.buildDeleteReturningClause(returning)
|
|
1486
|
+
: undefined;
|
|
1472
1487
|
const qualifiedTableName = queryBuilder.getQualifiedTableName(queryBuilder.schema.name, queryBuilder.schema.schema);
|
|
1473
1488
|
let sql = `DELETE FROM ${qualifiedTableName} WHERE ${whereSql}`;
|
|
1474
1489
|
if (returningClause) {
|
|
@@ -1477,6 +1492,10 @@ class SelectQueryBuilder {
|
|
|
1477
1492
|
const result = queryBuilder.executor
|
|
1478
1493
|
? await queryBuilder.executor.query(sql, whereParams)
|
|
1479
1494
|
: await queryBuilder.client.query(sql, whereParams);
|
|
1495
|
+
// Return affected count
|
|
1496
|
+
if (returning === 'count') {
|
|
1497
|
+
return result.rowCount ?? 0;
|
|
1498
|
+
}
|
|
1480
1499
|
if (!returningClause) {
|
|
1481
1500
|
return undefined;
|
|
1482
1501
|
}
|
|
@@ -1486,6 +1505,13 @@ class SelectQueryBuilder {
|
|
|
1486
1505
|
then(onfulfilled, onrejected) {
|
|
1487
1506
|
return executeDelete(undefined).then(onfulfilled, onrejected);
|
|
1488
1507
|
},
|
|
1508
|
+
affectedCount() {
|
|
1509
|
+
return {
|
|
1510
|
+
then(onfulfilled, onrejected) {
|
|
1511
|
+
return executeDelete('count').then(onfulfilled, onrejected);
|
|
1512
|
+
}
|
|
1513
|
+
};
|
|
1514
|
+
},
|
|
1489
1515
|
returning(selector) {
|
|
1490
1516
|
const returningConfig = selector ?? true;
|
|
1491
1517
|
return {
|
|
@@ -1544,8 +1570,10 @@ class SelectQueryBuilder {
|
|
|
1544
1570
|
const condBuilder = new conditions_1.ConditionBuilder();
|
|
1545
1571
|
const { sql: whereSql, params: whereParams } = condBuilder.build(queryBuilder.whereCond, paramIndex);
|
|
1546
1572
|
values.push(...whereParams);
|
|
1547
|
-
// Build RETURNING clause
|
|
1548
|
-
const returningClause =
|
|
1573
|
+
// Build RETURNING clause (not needed for count-only)
|
|
1574
|
+
const returningClause = returning !== 'count'
|
|
1575
|
+
? queryBuilder.buildDeleteReturningClause(returning)
|
|
1576
|
+
: undefined;
|
|
1549
1577
|
const qualifiedTableName = queryBuilder.getQualifiedTableName(queryBuilder.schema.name, queryBuilder.schema.schema);
|
|
1550
1578
|
let sql = `UPDATE ${qualifiedTableName} SET ${setClauses.join(', ')} WHERE ${whereSql}`;
|
|
1551
1579
|
if (returningClause) {
|
|
@@ -1554,6 +1582,10 @@ class SelectQueryBuilder {
|
|
|
1554
1582
|
const result = queryBuilder.executor
|
|
1555
1583
|
? await queryBuilder.executor.query(sql, values)
|
|
1556
1584
|
: await queryBuilder.client.query(sql, values);
|
|
1585
|
+
// Return affected count
|
|
1586
|
+
if (returning === 'count') {
|
|
1587
|
+
return result.rowCount ?? 0;
|
|
1588
|
+
}
|
|
1557
1589
|
if (!returningClause) {
|
|
1558
1590
|
return undefined;
|
|
1559
1591
|
}
|
|
@@ -1563,6 +1595,13 @@ class SelectQueryBuilder {
|
|
|
1563
1595
|
then(onfulfilled, onrejected) {
|
|
1564
1596
|
return executeUpdate(undefined).then(onfulfilled, onrejected);
|
|
1565
1597
|
},
|
|
1598
|
+
affectedCount() {
|
|
1599
|
+
return {
|
|
1600
|
+
then(onfulfilled, onrejected) {
|
|
1601
|
+
return executeUpdate('count').then(onfulfilled, onrejected);
|
|
1602
|
+
}
|
|
1603
|
+
};
|
|
1604
|
+
},
|
|
1566
1605
|
returning(selector) {
|
|
1567
1606
|
const returningConfig = selector ?? true;
|
|
1568
1607
|
return {
|
|
@@ -1588,7 +1627,7 @@ class SelectQueryBuilder {
|
|
|
1588
1627
|
return { sql, columns };
|
|
1589
1628
|
}
|
|
1590
1629
|
// Selector function - extract selected columns
|
|
1591
|
-
const mockRow = this.
|
|
1630
|
+
const mockRow = this._createMockRow();
|
|
1592
1631
|
const selectedMock = this.selector(mockRow);
|
|
1593
1632
|
const selection = returning(selectedMock);
|
|
1594
1633
|
if (typeof selection === 'object' && selection !== null) {
|
|
@@ -1647,8 +1686,9 @@ class SelectQueryBuilder {
|
|
|
1647
1686
|
}
|
|
1648
1687
|
/**
|
|
1649
1688
|
* Create mock row for analysis
|
|
1689
|
+
* @internal
|
|
1650
1690
|
*/
|
|
1651
|
-
|
|
1691
|
+
_createMockRow() {
|
|
1652
1692
|
const mock = {};
|
|
1653
1693
|
const tableAlias = this.schema.name;
|
|
1654
1694
|
// Performance: Use pre-computed column name map if available
|
|
@@ -1730,18 +1770,20 @@ class SelectQueryBuilder {
|
|
|
1730
1770
|
targetSchema = getTargetSchemaForRelation(this.schema, relName, relConfig);
|
|
1731
1771
|
}
|
|
1732
1772
|
if (relConfig.type === 'many') {
|
|
1773
|
+
// Non-enumerable to prevent Object.entries triggering getters (avoids stack overflow)
|
|
1733
1774
|
Object.defineProperty(mock, relName, {
|
|
1734
1775
|
get: () => {
|
|
1735
1776
|
return new CollectionQueryBuilder(relName, relConfig.targetTable, relConfig.foreignKey || relConfig.foreignKeys?.[0] || '', this.schema.name, targetSchema, // Pass the target schema directly
|
|
1736
1777
|
this.schemaRegistry // Pass schema registry for nested resolution
|
|
1737
1778
|
);
|
|
1738
1779
|
},
|
|
1739
|
-
enumerable:
|
|
1780
|
+
enumerable: false,
|
|
1740
1781
|
configurable: true,
|
|
1741
1782
|
});
|
|
1742
1783
|
}
|
|
1743
1784
|
else {
|
|
1744
1785
|
// For single reference (many-to-one), create a ReferenceQueryBuilder
|
|
1786
|
+
// Non-enumerable to prevent Object.entries triggering getters (avoids stack overflow)
|
|
1745
1787
|
Object.defineProperty(mock, relName, {
|
|
1746
1788
|
get: () => {
|
|
1747
1789
|
const refBuilder = new ReferenceQueryBuilder(relName, relConfig.targetTable, relConfig.foreignKeys || [relConfig.foreignKey || ''], relConfig.matches || [], relConfig.isMandatory ?? false, targetSchema, // Pass the target schema directly
|
|
@@ -1750,7 +1792,7 @@ class SelectQueryBuilder {
|
|
|
1750
1792
|
// Return a mock object that exposes the target table's columns
|
|
1751
1793
|
return refBuilder.createMockTargetRow();
|
|
1752
1794
|
},
|
|
1753
|
-
enumerable:
|
|
1795
|
+
enumerable: false,
|
|
1754
1796
|
configurable: true,
|
|
1755
1797
|
});
|
|
1756
1798
|
}
|
|
@@ -2436,9 +2478,18 @@ class SelectQueryBuilder {
|
|
|
2436
2478
|
}
|
|
2437
2479
|
else {
|
|
2438
2480
|
const isArrayAgg = value && typeof value === 'object' && 'isArrayAggregation' in value && value.isArrayAggregation();
|
|
2481
|
+
const isSingleResult = value instanceof CollectionQueryBuilder && value.isSingleResult();
|
|
2439
2482
|
if (isArrayAgg) {
|
|
2440
2483
|
fieldConfigs.push({ key, type: 2 /* FieldType.COLLECTION_ARRAY */, value });
|
|
2441
2484
|
}
|
|
2485
|
+
else if (isSingleResult) {
|
|
2486
|
+
fieldConfigs.push({
|
|
2487
|
+
key,
|
|
2488
|
+
type: 9 /* FieldType.COLLECTION_SINGLE */,
|
|
2489
|
+
value,
|
|
2490
|
+
collectionBuilder: value
|
|
2491
|
+
});
|
|
2492
|
+
}
|
|
2442
2493
|
else {
|
|
2443
2494
|
fieldConfigs.push({
|
|
2444
2495
|
key,
|
|
@@ -2549,6 +2600,18 @@ class SelectQueryBuilder {
|
|
|
2549
2600
|
}
|
|
2550
2601
|
break;
|
|
2551
2602
|
}
|
|
2603
|
+
case 9 /* FieldType.COLLECTION_SINGLE */: {
|
|
2604
|
+
// firstOrDefault() - return first item or null
|
|
2605
|
+
const items = rawValue || [];
|
|
2606
|
+
if (config.collectionBuilder && items.length > 0) {
|
|
2607
|
+
const transformedItems = this.transformCollectionItems(items, config.collectionBuilder);
|
|
2608
|
+
result[key] = transformedItems[0] ?? null;
|
|
2609
|
+
}
|
|
2610
|
+
else {
|
|
2611
|
+
result[key] = items[0] ?? null;
|
|
2612
|
+
}
|
|
2613
|
+
break;
|
|
2614
|
+
}
|
|
2552
2615
|
case 4 /* FieldType.CTE_AGGREGATION */: {
|
|
2553
2616
|
const items = rawValue || [];
|
|
2554
2617
|
if (config.innerMetadata && !disableMappers) {
|
|
@@ -2851,7 +2914,7 @@ class SelectQueryBuilder {
|
|
|
2851
2914
|
executor: this.executor,
|
|
2852
2915
|
};
|
|
2853
2916
|
// Analyze the selector to extract nested queries
|
|
2854
|
-
const mockRow = this.
|
|
2917
|
+
const mockRow = this._createMockRow();
|
|
2855
2918
|
const selectionResult = this.selector(mockRow);
|
|
2856
2919
|
// Build the query
|
|
2857
2920
|
const { sql } = this.buildQuery(selectionResult, context);
|
|
@@ -2862,7 +2925,7 @@ class SelectQueryBuilder {
|
|
|
2862
2925
|
// For table subqueries, preserve the selection metadata (includes SqlFragments with mappers)
|
|
2863
2926
|
let selectionMetadata;
|
|
2864
2927
|
if (mode === 'table') {
|
|
2865
|
-
const mockRow = this.
|
|
2928
|
+
const mockRow = this._createMockRow();
|
|
2866
2929
|
selectionMetadata = this.selector(mockRow);
|
|
2867
2930
|
}
|
|
2868
2931
|
// Extract outer field refs from the WHERE condition
|
|
@@ -3005,6 +3068,7 @@ class ReferenceQueryBuilder {
|
|
|
3005
3068
|
}
|
|
3006
3069
|
if (relConfig.type === 'many') {
|
|
3007
3070
|
// Collection navigation
|
|
3071
|
+
// Non-enumerable to prevent Object.entries triggering getters (avoids stack overflow)
|
|
3008
3072
|
Object.defineProperty(mock, relName, {
|
|
3009
3073
|
get: () => {
|
|
3010
3074
|
const fk = relConfig.foreignKey || relConfig.foreignKeys?.[0] || '';
|
|
@@ -3012,12 +3076,14 @@ class ReferenceQueryBuilder {
|
|
|
3012
3076
|
this.schemaRegistry // Pass schema registry for nested resolution
|
|
3013
3077
|
);
|
|
3014
3078
|
},
|
|
3015
|
-
enumerable:
|
|
3079
|
+
enumerable: false,
|
|
3016
3080
|
configurable: true,
|
|
3017
3081
|
});
|
|
3018
3082
|
}
|
|
3019
3083
|
else {
|
|
3020
3084
|
// Reference navigation
|
|
3085
|
+
// Non-enumerable to prevent Object.entries triggering getters (avoids stack overflow
|
|
3086
|
+
// with circular relations like User->Posts->User)
|
|
3021
3087
|
Object.defineProperty(mock, relName, {
|
|
3022
3088
|
get: () => {
|
|
3023
3089
|
const refBuilder = new ReferenceQueryBuilder(relName, relConfig.targetTable, relConfig.foreignKeys || [relConfig.foreignKey || ''], relConfig.matches || [], relConfig.isMandatory ?? false, nestedTargetSchema, // Pass the target schema directly
|
|
@@ -3025,7 +3091,7 @@ class ReferenceQueryBuilder {
|
|
|
3025
3091
|
);
|
|
3026
3092
|
return refBuilder.createMockTargetRow();
|
|
3027
3093
|
},
|
|
3028
|
-
enumerable:
|
|
3094
|
+
enumerable: false,
|
|
3029
3095
|
configurable: true,
|
|
3030
3096
|
});
|
|
3031
3097
|
}
|
|
@@ -3142,6 +3208,7 @@ class CollectionQueryBuilder {
|
|
|
3142
3208
|
for (const [relName, relConfig] of Object.entries(this.targetTableSchema.relations)) {
|
|
3143
3209
|
if (relConfig.type === 'many') {
|
|
3144
3210
|
// Collection navigation
|
|
3211
|
+
// Non-enumerable to prevent Object.entries triggering getters (avoids stack overflow)
|
|
3145
3212
|
Object.defineProperty(mock, relName, {
|
|
3146
3213
|
get: () => {
|
|
3147
3214
|
// Don't call build() - it returns schema without relations
|
|
@@ -3150,12 +3217,13 @@ class CollectionQueryBuilder {
|
|
|
3150
3217
|
this.schemaRegistry // Pass schema registry for nested resolution
|
|
3151
3218
|
);
|
|
3152
3219
|
},
|
|
3153
|
-
enumerable:
|
|
3220
|
+
enumerable: false,
|
|
3154
3221
|
configurable: true,
|
|
3155
3222
|
});
|
|
3156
3223
|
}
|
|
3157
3224
|
else {
|
|
3158
3225
|
// Reference navigation
|
|
3226
|
+
// Non-enumerable to prevent Object.entries triggering getters (avoids stack overflow)
|
|
3159
3227
|
Object.defineProperty(mock, relName, {
|
|
3160
3228
|
get: () => {
|
|
3161
3229
|
// Don't call build() - it returns schema without relations
|
|
@@ -3165,7 +3233,7 @@ class CollectionQueryBuilder {
|
|
|
3165
3233
|
);
|
|
3166
3234
|
return refBuilder.createMockTargetRow();
|
|
3167
3235
|
},
|
|
3168
|
-
enumerable:
|
|
3236
|
+
enumerable: false,
|
|
3169
3237
|
configurable: true,
|
|
3170
3238
|
});
|
|
3171
3239
|
}
|
|
@@ -3282,6 +3350,18 @@ class CollectionQueryBuilder {
|
|
|
3282
3350
|
// At runtime, this is still a CollectionQueryBuilder, but TypeScript sees it as CollectionResult
|
|
3283
3351
|
return this;
|
|
3284
3352
|
}
|
|
3353
|
+
/**
|
|
3354
|
+
* Get first item from collection or null if empty
|
|
3355
|
+
* Automatically applies LIMIT 1 and returns a single item instead of array
|
|
3356
|
+
*/
|
|
3357
|
+
firstOrDefault(name) {
|
|
3358
|
+
if (name) {
|
|
3359
|
+
this.asName = name;
|
|
3360
|
+
}
|
|
3361
|
+
this.limitValue = 1;
|
|
3362
|
+
this.isMarkedAsList = false; // Single item, not a list
|
|
3363
|
+
return this;
|
|
3364
|
+
}
|
|
3285
3365
|
/**
|
|
3286
3366
|
* Get target table schema
|
|
3287
3367
|
*/
|
|
@@ -3300,6 +3380,12 @@ class CollectionQueryBuilder {
|
|
|
3300
3380
|
isScalarAggregation() {
|
|
3301
3381
|
return this.aggregationType !== undefined;
|
|
3302
3382
|
}
|
|
3383
|
+
/**
|
|
3384
|
+
* Check if this is a single item result (firstOrDefault)
|
|
3385
|
+
*/
|
|
3386
|
+
isSingleResult() {
|
|
3387
|
+
return !this.isMarkedAsList && this.limitValue === 1;
|
|
3388
|
+
}
|
|
3303
3389
|
/**
|
|
3304
3390
|
* Get the aggregation type
|
|
3305
3391
|
*/
|