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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.DatabaseContext = exports.DbEntityTable = exports.EntityInsertBuilder = exports.DataContext = exports.TableAccessor = exports.InsertBuilder = exports.QueryExecutor = exports.TimeTracer = void 0;
|
|
4
|
+
const database_client_interface_1 = require("../database/database-client.interface");
|
|
4
5
|
const entity_base_1 = require("./entity-base");
|
|
5
6
|
const model_config_1 = require("./model-config");
|
|
6
7
|
const query_builder_1 = require("../query/query-builder");
|
|
@@ -528,15 +529,21 @@ class TableAccessor {
|
|
|
528
529
|
const column = this.schema.columns[key];
|
|
529
530
|
if (column) {
|
|
530
531
|
const config = column.build();
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
placeholders.push(`$${paramIndex++}`);
|
|
532
|
+
// Skip auto-increment columns
|
|
533
|
+
if (config.autoIncrement) {
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
536
|
+
// Skip columns with undefined values if they have a default - let the DB use the default
|
|
537
|
+
if (value === undefined && (config.default !== undefined || config.identity)) {
|
|
538
|
+
continue;
|
|
539
539
|
}
|
|
540
|
+
columns.push(`"${config.name}"`);
|
|
541
|
+
// Apply toDriver mapper if present
|
|
542
|
+
const mappedValue = config.mapper
|
|
543
|
+
? config.mapper.toDriver(value)
|
|
544
|
+
: value;
|
|
545
|
+
values.push(mappedValue);
|
|
546
|
+
placeholders.push(`$${paramIndex++}`);
|
|
540
547
|
}
|
|
541
548
|
}
|
|
542
549
|
const returningColumns = (0, sql_utils_1.buildReturningColumnList)(this.schema);
|
|
@@ -600,7 +607,12 @@ class TableAccessor {
|
|
|
600
607
|
sql += '\n OVERRIDING SYSTEM VALUE';
|
|
601
608
|
}
|
|
602
609
|
sql += `
|
|
603
|
-
VALUES ${valueClauses.join(', ')}
|
|
610
|
+
VALUES ${valueClauses.join(', ')}`;
|
|
611
|
+
// Add ON CONFLICT DO NOTHING if specified
|
|
612
|
+
if (insertConfig?.onConflictDoNothing) {
|
|
613
|
+
sql += '\n ON CONFLICT DO NOTHING';
|
|
614
|
+
}
|
|
615
|
+
sql += `
|
|
604
616
|
RETURNING ${returningColumns}
|
|
605
617
|
`;
|
|
606
618
|
const result = this.executor
|
|
@@ -663,65 +675,6 @@ class TableAccessor {
|
|
|
663
675
|
}
|
|
664
676
|
return builder.execute();
|
|
665
677
|
}
|
|
666
|
-
/**
|
|
667
|
-
* Bulk insert multiple rows (simple version, kept for compatibility)
|
|
668
|
-
*/
|
|
669
|
-
async insertMany(dataArray) {
|
|
670
|
-
if (dataArray.length === 0) {
|
|
671
|
-
return [];
|
|
672
|
-
}
|
|
673
|
-
// Extract all unique column names from all data objects
|
|
674
|
-
const columnSet = new Set();
|
|
675
|
-
for (const data of dataArray) {
|
|
676
|
-
for (const key of Object.keys(data)) {
|
|
677
|
-
const column = this.schema.columns[key];
|
|
678
|
-
if (column) {
|
|
679
|
-
const config = column.build();
|
|
680
|
-
if (!config.autoIncrement) {
|
|
681
|
-
columnSet.add(key);
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
const columns = Array.from(columnSet);
|
|
687
|
-
const values = [];
|
|
688
|
-
const valuePlaceholders = [];
|
|
689
|
-
let paramIndex = 1;
|
|
690
|
-
// Build placeholders for each row
|
|
691
|
-
for (const data of dataArray) {
|
|
692
|
-
const rowPlaceholders = [];
|
|
693
|
-
for (const key of columns) {
|
|
694
|
-
const value = data[key];
|
|
695
|
-
const column = this.schema.columns[key];
|
|
696
|
-
const config = column.build();
|
|
697
|
-
// Apply toDriver mapper if present
|
|
698
|
-
const mappedValue = config.mapper
|
|
699
|
-
? config.mapper.toDriver(value !== undefined ? value : null)
|
|
700
|
-
: (value !== undefined ? value : null);
|
|
701
|
-
values.push(mappedValue);
|
|
702
|
-
rowPlaceholders.push(`$${paramIndex++}`);
|
|
703
|
-
}
|
|
704
|
-
valuePlaceholders.push(`(${rowPlaceholders.join(', ')})`);
|
|
705
|
-
}
|
|
706
|
-
const columnNames = columns.map(key => {
|
|
707
|
-
const column = this.schema.columns[key];
|
|
708
|
-
const config = column.build();
|
|
709
|
-
return `"${config.name}"`;
|
|
710
|
-
});
|
|
711
|
-
const returningColumns = Object.entries(this.schema.columns)
|
|
712
|
-
.map(([_, col]) => `"${col.build().name}"`)
|
|
713
|
-
.join(', ');
|
|
714
|
-
const qualifiedTableName = (0, sql_utils_1.getQualifiedTableName)(this.schema);
|
|
715
|
-
const sql = `
|
|
716
|
-
INSERT INTO ${qualifiedTableName} (${columnNames.join(', ')})
|
|
717
|
-
VALUES ${valuePlaceholders.join(', ')}
|
|
718
|
-
RETURNING ${returningColumns}
|
|
719
|
-
`;
|
|
720
|
-
const result = this.executor
|
|
721
|
-
? await this.executor.query(sql, values)
|
|
722
|
-
: await this.client.query(sql, values);
|
|
723
|
-
return result.rows;
|
|
724
|
-
}
|
|
725
678
|
/**
|
|
726
679
|
* Insert with conflict resolution (upsert)
|
|
727
680
|
*/
|
|
@@ -846,13 +799,18 @@ class DataContext {
|
|
|
846
799
|
}
|
|
847
800
|
/**
|
|
848
801
|
* Get table accessor by name
|
|
802
|
+
* When in a transaction, creates a fresh accessor with the transactional client
|
|
849
803
|
*/
|
|
850
804
|
getTable(name) {
|
|
851
|
-
const
|
|
852
|
-
if (!
|
|
805
|
+
const cachedAccessor = this.tableAccessors.get(name);
|
|
806
|
+
if (!cachedAccessor) {
|
|
853
807
|
throw new Error(`Table ${String(name)} not found in schema`);
|
|
854
808
|
}
|
|
855
|
-
|
|
809
|
+
// If in a transaction, create a new accessor with the current (transactional) client
|
|
810
|
+
if (this.client.isInTransaction()) {
|
|
811
|
+
return new TableAccessor(cachedAccessor.tableBuilder, this.client, this.schemaRegistry, this.executor, this.queryOptions?.collectionStrategy);
|
|
812
|
+
}
|
|
813
|
+
return cachedAccessor;
|
|
856
814
|
}
|
|
857
815
|
/**
|
|
858
816
|
* Execute raw SQL query with optional type parameter for results
|
|
@@ -875,22 +833,25 @@ class DataContext {
|
|
|
875
833
|
}
|
|
876
834
|
/**
|
|
877
835
|
* Execute in transaction
|
|
836
|
+
* Uses the database client's native transaction support for proper handling
|
|
837
|
+
* across different drivers (pg uses BEGIN/COMMIT, postgres uses sql.begin())
|
|
878
838
|
*/
|
|
879
839
|
async transaction(fn) {
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
840
|
+
// Store the original client
|
|
841
|
+
const originalClient = this.client;
|
|
842
|
+
return await this.client.transaction(async (queryFn) => {
|
|
843
|
+
// Create a transactional client that routes all queries through the transaction
|
|
844
|
+
const txClient = new database_client_interface_1.TransactionalClient(queryFn, originalClient);
|
|
845
|
+
// Temporarily swap the client to use the transactional one
|
|
846
|
+
this.client = txClient;
|
|
847
|
+
try {
|
|
848
|
+
return await fn(this);
|
|
849
|
+
}
|
|
850
|
+
finally {
|
|
851
|
+
// Restore the original client
|
|
852
|
+
this.client = originalClient;
|
|
853
|
+
}
|
|
854
|
+
});
|
|
894
855
|
}
|
|
895
856
|
/**
|
|
896
857
|
* Get schema manager for create/drop operations and automatic migrations
|
|
@@ -984,6 +945,119 @@ class DbEntityTable {
|
|
|
984
945
|
_getCollectionStrategy() {
|
|
985
946
|
return this.context.queryOptions?.collectionStrategy;
|
|
986
947
|
}
|
|
948
|
+
/**
|
|
949
|
+
* Get information about all columns in this table.
|
|
950
|
+
* This method returns metadata about database columns only, excluding navigation properties.
|
|
951
|
+
*
|
|
952
|
+
* @param options - Optional configuration
|
|
953
|
+
* @param options.includeNavigation - If false (default), only returns database columns.
|
|
954
|
+
* Navigation properties are never included as they are not columns.
|
|
955
|
+
*
|
|
956
|
+
* @returns Array of column information objects
|
|
957
|
+
*
|
|
958
|
+
* @example
|
|
959
|
+
* ```typescript
|
|
960
|
+
* // Get all columns
|
|
961
|
+
* const columns = db.users.getColumns();
|
|
962
|
+
* // Returns: [
|
|
963
|
+
* // { propertyName: 'id', columnName: 'id', type: 'integer', isPrimaryKey: true, ... },
|
|
964
|
+
* // { propertyName: 'username', columnName: 'username', type: 'varchar', ... },
|
|
965
|
+
* // { propertyName: 'email', columnName: 'email', type: 'text', ... },
|
|
966
|
+
* // ]
|
|
967
|
+
*
|
|
968
|
+
* // Get column names only
|
|
969
|
+
* const columnNames = db.users.getColumns().map(c => c.propertyName);
|
|
970
|
+
* // Returns: ['id', 'username', 'email', ...]
|
|
971
|
+
*
|
|
972
|
+
* // Get database column names
|
|
973
|
+
* const dbColumnNames = db.users.getColumns().map(c => c.columnName);
|
|
974
|
+
* // Returns: ['id', 'username', 'email', ...]
|
|
975
|
+
* ```
|
|
976
|
+
*/
|
|
977
|
+
getColumns() {
|
|
978
|
+
const schema = this._getSchema();
|
|
979
|
+
const columns = [];
|
|
980
|
+
for (const [propName, colBuilder] of Object.entries(schema.columns)) {
|
|
981
|
+
const config = colBuilder.build();
|
|
982
|
+
columns.push({
|
|
983
|
+
propertyName: propName,
|
|
984
|
+
columnName: config.name,
|
|
985
|
+
type: config.type,
|
|
986
|
+
isPrimaryKey: config.primaryKey || false,
|
|
987
|
+
isAutoIncrement: config.autoIncrement || !!config.identity,
|
|
988
|
+
isNullable: config.nullable !== false,
|
|
989
|
+
isUnique: config.unique || false,
|
|
990
|
+
defaultValue: config.default,
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
return columns;
|
|
994
|
+
}
|
|
995
|
+
/**
|
|
996
|
+
* Get an array of all column property names (keys) for this entity.
|
|
997
|
+
* Returns a strongly typed array where each element is a valid column key of TEntity.
|
|
998
|
+
*
|
|
999
|
+
* This is a convenience method equivalent to `getColumns().map(c => c.propertyName)`
|
|
1000
|
+
* but with better type inference.
|
|
1001
|
+
*
|
|
1002
|
+
* @returns Array of column property names typed as ExtractDbColumnKeys<TEntity>
|
|
1003
|
+
*
|
|
1004
|
+
* @example
|
|
1005
|
+
* ```typescript
|
|
1006
|
+
* // Get all column keys
|
|
1007
|
+
* const keys = db.users.getColumnKeys();
|
|
1008
|
+
* // Type: ExtractDbColumnKeys<User>[] which is ('id' | 'username' | 'email' | ...)[]
|
|
1009
|
+
*
|
|
1010
|
+
* // Use for dynamic property access
|
|
1011
|
+
* const user = await db.users.findOne(u => eq(u.id, 1));
|
|
1012
|
+
* for (const key of db.users.getColumnKeys()) {
|
|
1013
|
+
* console.log(`${key}: ${user[key]}`); // TypeScript knows key is valid
|
|
1014
|
+
* }
|
|
1015
|
+
*
|
|
1016
|
+
* // Use for building dynamic queries
|
|
1017
|
+
* const columnKeys = db.users.getColumnKeys();
|
|
1018
|
+
* // columnKeys[0] is typed as 'id' | 'username' | 'email' | ...
|
|
1019
|
+
* ```
|
|
1020
|
+
*/
|
|
1021
|
+
getColumnKeys() {
|
|
1022
|
+
const schema = this._getSchema();
|
|
1023
|
+
return Object.keys(schema.columns);
|
|
1024
|
+
}
|
|
1025
|
+
/**
|
|
1026
|
+
* Get an object containing all entity properties as DbColumn references.
|
|
1027
|
+
* Useful for building dynamic queries or accessing column metadata.
|
|
1028
|
+
*
|
|
1029
|
+
* @param options - Optional configuration
|
|
1030
|
+
* @param options.excludeNavigation - If true (default), excludes navigation properties.
|
|
1031
|
+
* Set to false to include navigation properties.
|
|
1032
|
+
*
|
|
1033
|
+
* @returns Object with property names as keys and their DbColumn/navigation references as values
|
|
1034
|
+
*
|
|
1035
|
+
* @example
|
|
1036
|
+
* ```typescript
|
|
1037
|
+
* // Get all column properties (excludes navigation by default)
|
|
1038
|
+
* const cols = db.users.props();
|
|
1039
|
+
* // Use in select: db.users.select(u => ({ id: cols.id, name: cols.username }))
|
|
1040
|
+
*
|
|
1041
|
+
* // Include navigation properties
|
|
1042
|
+
* const allProps = db.users.props({ excludeNavigation: false });
|
|
1043
|
+
* ```
|
|
1044
|
+
*/
|
|
1045
|
+
props(options) {
|
|
1046
|
+
const schema = this._getSchema();
|
|
1047
|
+
const excludeNav = options?.excludeNavigation !== false; // Default true
|
|
1048
|
+
// Create a temporary QueryBuilder to reuse the createMockRow logic
|
|
1049
|
+
const qb = new query_builder_1.QueryBuilder(schema, this._getClient(), undefined, undefined, undefined, undefined, this._getExecutor(), undefined, undefined, this._getCollectionStrategy(), this.context.schemaRegistry);
|
|
1050
|
+
const mockRow = qb._createMockRow();
|
|
1051
|
+
if (excludeNav) {
|
|
1052
|
+
// Filter out navigation properties, keep only columns
|
|
1053
|
+
const result = {};
|
|
1054
|
+
for (const propName of Object.keys(schema.columns)) {
|
|
1055
|
+
result[propName] = mockRow[propName];
|
|
1056
|
+
}
|
|
1057
|
+
return result;
|
|
1058
|
+
}
|
|
1059
|
+
return mockRow;
|
|
1060
|
+
}
|
|
987
1061
|
/**
|
|
988
1062
|
* Get qualified table name with schema prefix if specified
|
|
989
1063
|
* @internal
|
|
@@ -1196,21 +1270,20 @@ class DbEntityTable {
|
|
|
1196
1270
|
*/
|
|
1197
1271
|
where(condition) {
|
|
1198
1272
|
const schema = this._getSchema();
|
|
1199
|
-
// Create a selector that selects all columns
|
|
1273
|
+
// Create a selector that selects all columns only (not navigation properties)
|
|
1200
1274
|
const allColumnsSelector = (e) => {
|
|
1201
1275
|
const result = {};
|
|
1202
1276
|
// Copy all column properties
|
|
1203
1277
|
for (const colName of Object.keys(schema.columns)) {
|
|
1204
1278
|
result[colName] = e[colName];
|
|
1205
1279
|
}
|
|
1206
|
-
// Add navigation properties as enumerable getters
|
|
1207
|
-
//
|
|
1208
|
-
//
|
|
1209
|
-
// When the selection is analyzed, the builder is detected and handled appropriately
|
|
1280
|
+
// Add navigation properties as non-enumerable getters
|
|
1281
|
+
// This allows chained selectors like .select(u => ({ posts: u.posts!.where(...) }))
|
|
1282
|
+
// but they won't be included in the default query output
|
|
1210
1283
|
for (const relName of Object.keys(schema.relations)) {
|
|
1211
1284
|
Object.defineProperty(result, relName, {
|
|
1212
1285
|
get: () => e[relName],
|
|
1213
|
-
enumerable:
|
|
1286
|
+
enumerable: false, // Non-enumerable so it's NOT included in default selection
|
|
1214
1287
|
configurable: true,
|
|
1215
1288
|
});
|
|
1216
1289
|
}
|
|
@@ -1226,21 +1299,20 @@ class DbEntityTable {
|
|
|
1226
1299
|
*/
|
|
1227
1300
|
with(...ctes) {
|
|
1228
1301
|
const schema = this._getSchema();
|
|
1229
|
-
// Create a selector that selects all columns
|
|
1302
|
+
// Create a selector that selects all columns only (not navigation properties)
|
|
1230
1303
|
const allColumnsSelector = (e) => {
|
|
1231
1304
|
const result = {};
|
|
1232
1305
|
// Copy all column properties
|
|
1233
1306
|
for (const colName of Object.keys(schema.columns)) {
|
|
1234
1307
|
result[colName] = e[colName];
|
|
1235
1308
|
}
|
|
1236
|
-
// Add navigation properties as enumerable getters
|
|
1237
|
-
//
|
|
1238
|
-
//
|
|
1239
|
-
// When the selection is analyzed, the builder is detected and handled appropriately
|
|
1309
|
+
// Add navigation properties as non-enumerable getters
|
|
1310
|
+
// This allows chained selectors like .select(u => ({ posts: u.posts!.where(...) }))
|
|
1311
|
+
// but they won't be included in the default query output
|
|
1240
1312
|
for (const relName of Object.keys(schema.relations)) {
|
|
1241
1313
|
Object.defineProperty(result, relName, {
|
|
1242
1314
|
get: () => e[relName],
|
|
1243
|
-
enumerable:
|
|
1315
|
+
enumerable: false, // Non-enumerable so it's NOT included in default selection
|
|
1244
1316
|
configurable: true,
|
|
1245
1317
|
});
|
|
1246
1318
|
}
|
|
@@ -1276,77 +1348,22 @@ class DbEntityTable {
|
|
|
1276
1348
|
* ```
|
|
1277
1349
|
*/
|
|
1278
1350
|
insert(data) {
|
|
1279
|
-
const
|
|
1280
|
-
const executeInsert = async (returning) => {
|
|
1281
|
-
const schema = table._getSchema();
|
|
1282
|
-
const executor = table._getExecutor();
|
|
1283
|
-
const client = table._getClient();
|
|
1284
|
-
// Build INSERT columns and values
|
|
1285
|
-
const columns = [];
|
|
1286
|
-
const values = [];
|
|
1287
|
-
const placeholders = [];
|
|
1288
|
-
let paramIndex = 1;
|
|
1289
|
-
for (const [key, value] of Object.entries(data)) {
|
|
1290
|
-
const column = schema.columns[key];
|
|
1291
|
-
if (column) {
|
|
1292
|
-
const config = column.build();
|
|
1293
|
-
if (!config.autoIncrement) {
|
|
1294
|
-
columns.push(`"${config.name}"`);
|
|
1295
|
-
const mappedValue = config.mapper ? config.mapper.toDriver(value) : value;
|
|
1296
|
-
values.push(mappedValue);
|
|
1297
|
-
placeholders.push(`$${paramIndex++}`);
|
|
1298
|
-
}
|
|
1299
|
-
}
|
|
1300
|
-
}
|
|
1301
|
-
// Build RETURNING clause
|
|
1302
|
-
const returningClause = table.buildReturningClause(returning);
|
|
1303
|
-
const qualifiedTableName = table._getQualifiedTableName();
|
|
1304
|
-
let sql = `INSERT INTO ${qualifiedTableName} (${columns.join(', ')}) VALUES (${placeholders.join(', ')})`;
|
|
1305
|
-
if (returningClause) {
|
|
1306
|
-
sql += ` RETURNING ${returningClause.sql}`;
|
|
1307
|
-
}
|
|
1308
|
-
const result = executor
|
|
1309
|
-
? await executor.query(sql, values)
|
|
1310
|
-
: await client.query(sql, values);
|
|
1311
|
-
if (!returningClause) {
|
|
1312
|
-
return undefined;
|
|
1313
|
-
}
|
|
1314
|
-
const mappedResults = table.mapReturningResults(result.rows, returning);
|
|
1315
|
-
return mappedResults[0];
|
|
1316
|
-
};
|
|
1351
|
+
const bulkBuilder = this.insertBulk([data]);
|
|
1317
1352
|
return {
|
|
1318
1353
|
then(onfulfilled, onrejected) {
|
|
1319
|
-
return
|
|
1354
|
+
return bulkBuilder.then(onfulfilled, onrejected);
|
|
1320
1355
|
},
|
|
1321
1356
|
returning(selector) {
|
|
1322
|
-
const
|
|
1357
|
+
const bulkReturning = selector ? bulkBuilder.returning(selector) : bulkBuilder.returning();
|
|
1323
1358
|
return {
|
|
1324
1359
|
then(onfulfilled, onrejected) {
|
|
1325
|
-
|
|
1360
|
+
// Unwrap array to single item
|
|
1361
|
+
return bulkReturning.then((results) => results?.[0], undefined).then(onfulfilled, onrejected);
|
|
1326
1362
|
}
|
|
1327
1363
|
};
|
|
1328
1364
|
}
|
|
1329
1365
|
};
|
|
1330
1366
|
}
|
|
1331
|
-
/**
|
|
1332
|
-
* Insert multiple records
|
|
1333
|
-
* Returns a fluent builder that can be awaited directly or chained with .returning()
|
|
1334
|
-
*
|
|
1335
|
-
* @example
|
|
1336
|
-
* ```typescript
|
|
1337
|
-
* // No returning (default) - returns void
|
|
1338
|
-
* await db.users.insertMany([{ username: 'alice' }, { username: 'bob' }]);
|
|
1339
|
-
*
|
|
1340
|
-
* // With returning() - returns full entities
|
|
1341
|
-
* const users = await db.users.insertMany([{ username: 'alice' }]).returning();
|
|
1342
|
-
*
|
|
1343
|
-
* // With returning(selector) - returns selected columns
|
|
1344
|
-
* const results = await db.users.insertMany([{ username: 'alice' }]).returning(u => ({ id: u.id }));
|
|
1345
|
-
* ```
|
|
1346
|
-
*/
|
|
1347
|
-
insertMany(data) {
|
|
1348
|
-
return this.insertBulk(data);
|
|
1349
|
-
}
|
|
1350
1367
|
/**
|
|
1351
1368
|
* Upsert (insert or update on conflict)
|
|
1352
1369
|
* Returns a fluent builder that can be awaited directly or chained with .returning()
|
|
@@ -1379,8 +1396,11 @@ class DbEntityTable {
|
|
|
1379
1396
|
* // With returning(selector) - returns selected columns
|
|
1380
1397
|
* const results = await db.users.insertBulk([{ username: 'alice' }]).returning(u => ({ id: u.id }));
|
|
1381
1398
|
*
|
|
1382
|
-
* // With
|
|
1399
|
+
* // With chunk size
|
|
1383
1400
|
* await db.users.insertBulk([{ username: 'alice' }], { chunkSize: 100 });
|
|
1401
|
+
*
|
|
1402
|
+
* // Skip duplicates (ON CONFLICT DO NOTHING)
|
|
1403
|
+
* await db.users.insertBulk([{ username: 'alice' }], { onConflictDoNothing: true });
|
|
1384
1404
|
* ```
|
|
1385
1405
|
*/
|
|
1386
1406
|
insertBulk(value, options) {
|
|
@@ -1403,13 +1423,13 @@ class DbEntityTable {
|
|
|
1403
1423
|
const allResults = [];
|
|
1404
1424
|
for (let i = 0; i < dataArray.length; i += chunkSize) {
|
|
1405
1425
|
const chunk = dataArray.slice(i, i + chunkSize);
|
|
1406
|
-
const chunkResults = await table.insertBulkSingle(chunk, returning, options?.overridingSystemValue);
|
|
1426
|
+
const chunkResults = await table.insertBulkSingle(chunk, returning, options?.overridingSystemValue, options?.onConflictDoNothing);
|
|
1407
1427
|
if (chunkResults)
|
|
1408
1428
|
allResults.push(...chunkResults);
|
|
1409
1429
|
}
|
|
1410
1430
|
return returning === undefined ? undefined : allResults;
|
|
1411
1431
|
}
|
|
1412
|
-
return table.insertBulkSingle(dataArray, returning, options?.overridingSystemValue);
|
|
1432
|
+
return table.insertBulkSingle(dataArray, returning, options?.overridingSystemValue, options?.onConflictDoNothing);
|
|
1413
1433
|
};
|
|
1414
1434
|
return {
|
|
1415
1435
|
then(onfulfilled, onrejected) {
|
|
@@ -1429,25 +1449,42 @@ class DbEntityTable {
|
|
|
1429
1449
|
* Execute a single bulk insert batch
|
|
1430
1450
|
* @internal
|
|
1431
1451
|
*/
|
|
1432
|
-
async insertBulkSingle(data, returning, overridingSystemValue) {
|
|
1452
|
+
async insertBulkSingle(data, returning, overridingSystemValue, onConflictDoNothing) {
|
|
1433
1453
|
const schema = this._getSchema();
|
|
1434
1454
|
const executor = this._getExecutor();
|
|
1435
1455
|
const client = this._getClient();
|
|
1436
1456
|
const qualifiedTableName = this._getQualifiedTableName();
|
|
1437
|
-
// Get columns from
|
|
1438
|
-
const referenceItem = data[0];
|
|
1457
|
+
// Get columns from all rows - a column is included if ANY row has a non-undefined value for it
|
|
1439
1458
|
const columnConfigs = [];
|
|
1440
1459
|
for (const [propName, colBuilder] of Object.entries(schema.columns)) {
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1460
|
+
const config = colBuilder.build();
|
|
1461
|
+
// Skip auto-increment columns (unless overriding)
|
|
1462
|
+
if (config.autoIncrement && !overridingSystemValue) {
|
|
1463
|
+
continue;
|
|
1464
|
+
}
|
|
1465
|
+
// Check if any row has a defined (non-undefined) value for this column
|
|
1466
|
+
const hasDefinedValue = data.some(record => {
|
|
1467
|
+
const value = record[propName];
|
|
1468
|
+
return value !== undefined;
|
|
1469
|
+
});
|
|
1470
|
+
// If column has a default and ALL rows have undefined, skip it (let DB use default)
|
|
1471
|
+
// Otherwise, include it if at least one row has a defined value
|
|
1472
|
+
if (!hasDefinedValue) {
|
|
1473
|
+
// All rows have undefined - if there's a default, skip the column
|
|
1474
|
+
if (config.default !== undefined || config.identity) {
|
|
1475
|
+
continue;
|
|
1476
|
+
}
|
|
1477
|
+
// No default, but column is present with undefined in data - include it (will become NULL)
|
|
1478
|
+
const isPresentInAnyRow = data.some(record => propName in record);
|
|
1479
|
+
if (!isPresentInAnyRow) {
|
|
1480
|
+
continue;
|
|
1449
1481
|
}
|
|
1450
1482
|
}
|
|
1483
|
+
columnConfigs.push({
|
|
1484
|
+
propName,
|
|
1485
|
+
dbName: config.name,
|
|
1486
|
+
mapper: config.mapper,
|
|
1487
|
+
});
|
|
1451
1488
|
}
|
|
1452
1489
|
// Build VALUES clauses
|
|
1453
1490
|
const valuesClauses = [];
|
|
@@ -1472,6 +1509,9 @@ class DbEntityTable {
|
|
|
1472
1509
|
sql += ' OVERRIDING SYSTEM VALUE';
|
|
1473
1510
|
}
|
|
1474
1511
|
sql += ` VALUES ${valuesClauses.join(', ')}`;
|
|
1512
|
+
if (onConflictDoNothing) {
|
|
1513
|
+
sql += ' ON CONFLICT DO NOTHING';
|
|
1514
|
+
}
|
|
1475
1515
|
if (returningClause) {
|
|
1476
1516
|
sql += ` RETURNING ${returningClause.sql}`;
|
|
1477
1517
|
}
|
|
@@ -1772,8 +1812,10 @@ class DbEntityTable {
|
|
|
1772
1812
|
throw new Error('No valid columns to update');
|
|
1773
1813
|
}
|
|
1774
1814
|
// No WHERE clause - updates all records
|
|
1775
|
-
// Build RETURNING clause
|
|
1776
|
-
const returningClause =
|
|
1815
|
+
// Build RETURNING clause (not needed for count-only)
|
|
1816
|
+
const returningClause = returning !== 'count'
|
|
1817
|
+
? table.buildReturningClause(returning)
|
|
1818
|
+
: undefined;
|
|
1777
1819
|
const qualifiedTableName = table._getQualifiedTableName();
|
|
1778
1820
|
let sql = `UPDATE ${qualifiedTableName} SET ${setClauses.join(', ')}`;
|
|
1779
1821
|
if (returningClause) {
|
|
@@ -1782,6 +1824,10 @@ class DbEntityTable {
|
|
|
1782
1824
|
const result = executor
|
|
1783
1825
|
? await executor.query(sql, values)
|
|
1784
1826
|
: await client.query(sql, values);
|
|
1827
|
+
// Return affected count
|
|
1828
|
+
if (returning === 'count') {
|
|
1829
|
+
return result.rowCount ?? 0;
|
|
1830
|
+
}
|
|
1785
1831
|
if (!returningClause) {
|
|
1786
1832
|
return undefined;
|
|
1787
1833
|
}
|
|
@@ -1791,6 +1837,13 @@ class DbEntityTable {
|
|
|
1791
1837
|
then(onfulfilled, onrejected) {
|
|
1792
1838
|
return executeUpdate(undefined).then(onfulfilled, onrejected);
|
|
1793
1839
|
},
|
|
1840
|
+
affectedCount() {
|
|
1841
|
+
return {
|
|
1842
|
+
then(onfulfilled, onrejected) {
|
|
1843
|
+
return executeUpdate('count').then(onfulfilled, onrejected);
|
|
1844
|
+
}
|
|
1845
|
+
};
|
|
1846
|
+
},
|
|
1794
1847
|
returning(selector) {
|
|
1795
1848
|
const returningConfig = selector ?? true;
|
|
1796
1849
|
return {
|
|
@@ -2011,11 +2064,14 @@ WHERE ${whereClause}`.trim();
|
|
|
2011
2064
|
const baseSql = `DELETE FROM ${qualifiedTableName}`;
|
|
2012
2065
|
const table = this;
|
|
2013
2066
|
const executeDelete = async (returningConfig) => {
|
|
2014
|
-
if (!returningConfig) {
|
|
2067
|
+
if (!returningConfig || returningConfig === 'count') {
|
|
2015
2068
|
const sql = baseSql;
|
|
2016
2069
|
const result = executor
|
|
2017
2070
|
? await executor.query(sql, [])
|
|
2018
2071
|
: await client.query(sql, []);
|
|
2072
|
+
if (returningConfig === 'count') {
|
|
2073
|
+
return result.rowCount ?? 0;
|
|
2074
|
+
}
|
|
2019
2075
|
return;
|
|
2020
2076
|
}
|
|
2021
2077
|
// Build RETURNING clause
|
|
@@ -2032,6 +2088,13 @@ WHERE ${whereClause}`.trim();
|
|
|
2032
2088
|
then: (resolve, reject) => {
|
|
2033
2089
|
return executeDelete().then(resolve, reject);
|
|
2034
2090
|
},
|
|
2091
|
+
affectedCount: () => {
|
|
2092
|
+
return {
|
|
2093
|
+
then: (resolve, reject) => {
|
|
2094
|
+
return executeDelete('count').then(resolve, reject);
|
|
2095
|
+
}
|
|
2096
|
+
};
|
|
2097
|
+
},
|
|
2035
2098
|
returning: ((selector) => {
|
|
2036
2099
|
const returningConfig = selector ?? true;
|
|
2037
2100
|
return {
|