linkgress-orm 0.0.3 → 0.1.1
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 +3 -3
- package/dist/entity/db-column.d.ts +38 -1
- 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 +429 -50
- package/dist/entity/db-context.d.ts.map +1 -1
- package/dist/entity/db-context.js +884 -203
- package/dist/entity/db-context.js.map +1 -1
- package/dist/entity/entity-base.d.ts +8 -0
- package/dist/entity/entity-base.d.ts.map +1 -1
- package/dist/entity/entity-base.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/query/collection-strategy.factory.d.ts.map +1 -1
- package/dist/query/collection-strategy.factory.js +7 -3
- package/dist/query/collection-strategy.factory.js.map +1 -1
- package/dist/query/collection-strategy.interface.d.ts +12 -6
- package/dist/query/collection-strategy.interface.d.ts.map +1 -1
- package/dist/query/conditions.d.ts +134 -23
- package/dist/query/conditions.d.ts.map +1 -1
- package/dist/query/conditions.js +58 -0
- package/dist/query/conditions.js.map +1 -1
- package/dist/query/cte-builder.d.ts +24 -5
- package/dist/query/cte-builder.d.ts.map +1 -1
- package/dist/query/cte-builder.js +45 -7
- package/dist/query/cte-builder.js.map +1 -1
- package/dist/query/grouped-query.d.ts +196 -8
- package/dist/query/grouped-query.d.ts.map +1 -1
- package/dist/query/grouped-query.js +586 -54
- package/dist/query/grouped-query.js.map +1 -1
- package/dist/query/join-builder.d.ts +5 -4
- package/dist/query/join-builder.d.ts.map +1 -1
- package/dist/query/join-builder.js +21 -47
- package/dist/query/join-builder.js.map +1 -1
- package/dist/query/query-builder.d.ts +118 -20
- package/dist/query/query-builder.d.ts.map +1 -1
- package/dist/query/query-builder.js +511 -280
- package/dist/query/query-builder.js.map +1 -1
- package/dist/query/query-utils.d.ts +45 -0
- package/dist/query/query-utils.d.ts.map +1 -0
- package/dist/query/query-utils.js +103 -0
- package/dist/query/query-utils.js.map +1 -0
- package/dist/query/sql-utils.d.ts +83 -0
- package/dist/query/sql-utils.d.ts.map +1 -0
- package/dist/query/sql-utils.js +218 -0
- package/dist/query/sql-utils.js.map +1 -0
- package/dist/query/strategies/cte-collection-strategy.d.ts +85 -0
- package/dist/query/strategies/cte-collection-strategy.d.ts.map +1 -0
- package/dist/query/strategies/cte-collection-strategy.js +338 -0
- package/dist/query/strategies/cte-collection-strategy.js.map +1 -0
- package/dist/query/strategies/lateral-collection-strategy.d.ts +59 -0
- package/dist/query/strategies/lateral-collection-strategy.d.ts.map +1 -0
- package/dist/query/strategies/lateral-collection-strategy.js +243 -0
- package/dist/query/strategies/lateral-collection-strategy.js.map +1 -0
- package/dist/query/strategies/temptable-collection-strategy.d.ts +21 -0
- package/dist/query/strategies/temptable-collection-strategy.d.ts.map +1 -1
- package/dist/query/strategies/temptable-collection-strategy.js +160 -38
- package/dist/query/strategies/temptable-collection-strategy.js.map +1 -1
- package/dist/query/subquery.d.ts +24 -1
- package/dist/query/subquery.d.ts.map +1 -1
- package/dist/query/subquery.js +38 -2
- package/dist/query/subquery.js.map +1 -1
- package/dist/schema/table-builder.d.ts +16 -0
- package/dist/schema/table-builder.d.ts.map +1 -1
- package/dist/schema/table-builder.js +23 -1
- package/dist/schema/table-builder.js.map +1 -1
- package/package.json +1 -1
- package/dist/query/strategies/jsonb-collection-strategy.d.ts +0 -51
- package/dist/query/strategies/jsonb-collection-strategy.d.ts.map +0 -1
- package/dist/query/strategies/jsonb-collection-strategy.js +0 -210
- package/dist/query/strategies/jsonb-collection-strategy.js.map +0 -1
|
@@ -7,6 +7,7 @@ const conditions_1 = require("../query/conditions");
|
|
|
7
7
|
const query_builder_1 = require("../query/query-builder");
|
|
8
8
|
const db_schema_manager_1 = require("../migration/db-schema-manager");
|
|
9
9
|
const sequence_builder_1 = require("../schema/sequence-builder");
|
|
10
|
+
const sql_utils_1 = require("../query/sql-utils");
|
|
10
11
|
/**
|
|
11
12
|
* Query executor with optional logging
|
|
12
13
|
*/
|
|
@@ -134,14 +135,6 @@ class InsertBuilder {
|
|
|
134
135
|
this.conflictAction = 'nothing';
|
|
135
136
|
this.overridingSystemValue = false;
|
|
136
137
|
}
|
|
137
|
-
/**
|
|
138
|
-
* Get qualified table name with schema prefix if specified
|
|
139
|
-
*/
|
|
140
|
-
getQualifiedTableName() {
|
|
141
|
-
return this.schema.schema
|
|
142
|
-
? `"${this.schema.schema}"."${this.schema.name}"`
|
|
143
|
-
: `"${this.schema.name}"`;
|
|
144
|
-
}
|
|
145
138
|
/**
|
|
146
139
|
* Set the values to insert (single row or multiple rows)
|
|
147
140
|
*/
|
|
@@ -241,15 +234,9 @@ class InsertBuilder {
|
|
|
241
234
|
}
|
|
242
235
|
valuePlaceholders.push(`(${rowPlaceholders.join(', ')})`);
|
|
243
236
|
}
|
|
244
|
-
const columnNames =
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
return `"${config.name}"`;
|
|
248
|
-
});
|
|
249
|
-
const returningColumns = Object.entries(this.schema.columns)
|
|
250
|
-
.map(([_, col]) => `"${col.build().name}"`)
|
|
251
|
-
.join(', ');
|
|
252
|
-
const qualifiedTableName = this.getQualifiedTableName();
|
|
237
|
+
const columnNames = (0, sql_utils_1.buildColumnNamesList)(this.schema, columns);
|
|
238
|
+
const returningColumns = (0, sql_utils_1.buildReturningColumnList)(this.schema);
|
|
239
|
+
const qualifiedTableName = (0, sql_utils_1.getQualifiedTableName)(this.schema);
|
|
253
240
|
let sql = `INSERT INTO ${qualifiedTableName} (${columnNames.join(', ')})`;
|
|
254
241
|
// Add OVERRIDING SYSTEM VALUE if specified
|
|
255
242
|
if (this.overridingSystemValue) {
|
|
@@ -354,6 +341,7 @@ class TableAccessor {
|
|
|
354
341
|
}
|
|
355
342
|
/**
|
|
356
343
|
* Start a select query with automatic type inference
|
|
344
|
+
* UnwrapSelection extracts the value types from SqlFragment<T> expressions
|
|
357
345
|
*/
|
|
358
346
|
select(selector) {
|
|
359
347
|
return new query_builder_1.SelectQueryBuilder(this.schema, this.client, selector, undefined, undefined, undefined, undefined, this.executor, undefined, undefined, undefined, this.schemaRegistry, undefined, this.collectionStrategy);
|
|
@@ -365,6 +353,13 @@ class TableAccessor {
|
|
|
365
353
|
const qb = new query_builder_1.QueryBuilder(this.schema, this.client, undefined, undefined, undefined, undefined, this.executor, undefined, undefined, this.collectionStrategy);
|
|
366
354
|
return qb.where(condition);
|
|
367
355
|
}
|
|
356
|
+
/**
|
|
357
|
+
* Add CTEs (Common Table Expressions) to the query
|
|
358
|
+
*/
|
|
359
|
+
with(...ctes) {
|
|
360
|
+
const qb = new query_builder_1.QueryBuilder(this.schema, this.client, undefined, undefined, undefined, undefined, this.executor, undefined, undefined, this.collectionStrategy);
|
|
361
|
+
return qb.with(...ctes);
|
|
362
|
+
}
|
|
368
363
|
/**
|
|
369
364
|
* Left join with another table and selector
|
|
370
365
|
*/
|
|
@@ -397,14 +392,6 @@ class TableAccessor {
|
|
|
397
392
|
getTableName() {
|
|
398
393
|
return this.schema.name;
|
|
399
394
|
}
|
|
400
|
-
/**
|
|
401
|
-
* Get qualified table name with schema prefix if specified
|
|
402
|
-
*/
|
|
403
|
-
getQualifiedTableName() {
|
|
404
|
-
return this.schema.schema
|
|
405
|
-
? `"${this.schema.schema}"."${this.schema.name}"`
|
|
406
|
-
: `"${this.schema.name}"`;
|
|
407
|
-
}
|
|
408
395
|
/**
|
|
409
396
|
* Insert a row
|
|
410
397
|
*/
|
|
@@ -428,10 +415,8 @@ class TableAccessor {
|
|
|
428
415
|
}
|
|
429
416
|
}
|
|
430
417
|
}
|
|
431
|
-
const returningColumns =
|
|
432
|
-
|
|
433
|
-
.join(', ');
|
|
434
|
-
const qualifiedTableName = this.getQualifiedTableName();
|
|
418
|
+
const returningColumns = (0, sql_utils_1.buildReturningColumnList)(this.schema);
|
|
419
|
+
const qualifiedTableName = (0, sql_utils_1.getQualifiedTableName)(this.schema);
|
|
435
420
|
const sql = `
|
|
436
421
|
INSERT INTO ${qualifiedTableName} (${columns.join(', ')})
|
|
437
422
|
VALUES (${placeholders.join(', ')})
|
|
@@ -451,15 +436,10 @@ class TableAccessor {
|
|
|
451
436
|
return [];
|
|
452
437
|
}
|
|
453
438
|
// Calculate chunk size based on max rows per batch
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
const POSTGRES_MAX_PARAMS = 65535; // PostgreSQL parameter limit
|
|
457
|
-
const columnCount = Object.keys(dataArray[0]).length;
|
|
458
|
-
const maxRowsPerBatch = Math.floor(POSTGRES_MAX_PARAMS / columnCount);
|
|
459
|
-
chunkSize = Math.floor(maxRowsPerBatch * 0.6); // Use 60% of max to be safe
|
|
460
|
-
}
|
|
439
|
+
const columnCount = Object.keys(dataArray[0]).length;
|
|
440
|
+
const chunkSize = (0, sql_utils_1.calculateOptimalChunkSize)(columnCount, insertConfig?.chunkSize);
|
|
461
441
|
// Check if we need to chunk
|
|
462
|
-
if (
|
|
442
|
+
if (dataArray.length > chunkSize) {
|
|
463
443
|
const results = [];
|
|
464
444
|
for (let i = 0; i < dataArray.length; i += chunkSize) {
|
|
465
445
|
const chunk = dataArray.slice(i, i + chunkSize);
|
|
@@ -480,56 +460,28 @@ class TableAccessor {
|
|
|
480
460
|
return [];
|
|
481
461
|
}
|
|
482
462
|
// Extract all unique column names from all data objects
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
const column = this.schema.columns[key];
|
|
487
|
-
if (column) {
|
|
488
|
-
const config = column.build();
|
|
489
|
-
if (!config.autoIncrement || insertConfig?.overridingSystemValue) {
|
|
490
|
-
columnSet.add(key);
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
const columns = Array.from(columnSet);
|
|
496
|
-
const values = [];
|
|
497
|
-
const valuePlaceholders = [];
|
|
498
|
-
let paramIndex = 1;
|
|
499
|
-
// Build placeholders for each row
|
|
500
|
-
for (const data of dataArray) {
|
|
501
|
-
const rowPlaceholders = [];
|
|
502
|
-
for (const key of columns) {
|
|
503
|
-
const value = data[key];
|
|
504
|
-
const column = this.schema.columns[key];
|
|
505
|
-
const config = column.build();
|
|
506
|
-
// Apply toDriver mapper if present
|
|
507
|
-
const mappedValue = config.mapper
|
|
508
|
-
? config.mapper.toDriver(value !== undefined ? value : null)
|
|
509
|
-
: (value !== undefined ? value : null);
|
|
510
|
-
values.push(mappedValue);
|
|
511
|
-
rowPlaceholders.push(`$${paramIndex++}`);
|
|
512
|
-
}
|
|
513
|
-
valuePlaceholders.push(`(${rowPlaceholders.join(', ')})`);
|
|
463
|
+
const columns = (0, sql_utils_1.extractUniqueColumnKeys)(dataArray, this.schema, insertConfig?.overridingSystemValue);
|
|
464
|
+
if (columns.length === 0) {
|
|
465
|
+
return [];
|
|
514
466
|
}
|
|
515
|
-
const
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
const qualifiedTableName = this.getQualifiedTableName();
|
|
524
|
-
let sql = `INSERT INTO ${qualifiedTableName} (${columnNames.join(', ')})`;
|
|
467
|
+
const columnConfigs = (0, sql_utils_1.buildColumnConfigs)(this.schema, columns, insertConfig?.overridingSystemValue);
|
|
468
|
+
const { valueClauses, params } = (0, sql_utils_1.buildValuesClause)(dataArray, columnConfigs);
|
|
469
|
+
const columnNames = columnConfigs.map(c => `"${c.dbName}"`);
|
|
470
|
+
const returningColumns = (0, sql_utils_1.buildReturningColumnList)(this.schema);
|
|
471
|
+
const qualifiedTableName = (0, sql_utils_1.getQualifiedTableName)(this.schema);
|
|
472
|
+
let sql = `
|
|
473
|
+
INSERT INTO ${qualifiedTableName} (${columnNames.join(', ')})`;
|
|
474
|
+
// Add OVERRIDING SYSTEM VALUE if specified
|
|
525
475
|
if (insertConfig?.overridingSystemValue) {
|
|
526
476
|
sql += '\n OVERRIDING SYSTEM VALUE';
|
|
527
477
|
}
|
|
528
|
-
sql +=
|
|
529
|
-
|
|
478
|
+
sql += `
|
|
479
|
+
VALUES ${valueClauses.join(', ')}
|
|
480
|
+
RETURNING ${returningColumns}
|
|
481
|
+
`;
|
|
530
482
|
const result = this.executor
|
|
531
|
-
? await this.executor.query(sql,
|
|
532
|
-
: await this.client.query(sql,
|
|
483
|
+
? await this.executor.query(sql, params)
|
|
484
|
+
: await this.client.query(sql, params);
|
|
533
485
|
return result.rows;
|
|
534
486
|
}
|
|
535
487
|
/**
|
|
@@ -541,33 +493,12 @@ class TableAccessor {
|
|
|
541
493
|
}
|
|
542
494
|
const referenceItem = config?.referenceItem || values[0];
|
|
543
495
|
// Determine primary keys
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
}
|
|
548
|
-
else {
|
|
549
|
-
// Auto-detect from schema
|
|
550
|
-
for (const [key, colBuilder] of Object.entries(this.schema.columns)) {
|
|
551
|
-
const colConfig = colBuilder.build();
|
|
552
|
-
if (colConfig.primaryKey) {
|
|
553
|
-
primaryKeys.push(key);
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
}
|
|
496
|
+
const primaryKeys = config?.primaryKey
|
|
497
|
+
? (Array.isArray(config.primaryKey) ? config.primaryKey : [config.primaryKey])
|
|
498
|
+
: (0, sql_utils_1.detectPrimaryKeys)(this.schema);
|
|
557
499
|
// Auto-detect overridingSystemValue
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
for (const key of Object.keys(referenceItem)) {
|
|
561
|
-
const column = this.schema.columns[key];
|
|
562
|
-
if (column) {
|
|
563
|
-
const colConfig = column.build();
|
|
564
|
-
if (colConfig.primaryKey && colConfig.autoIncrement) {
|
|
565
|
-
overridingSystemValue = true;
|
|
566
|
-
break;
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
}
|
|
500
|
+
const overridingSystemValue = config?.overridingSystemValue ??
|
|
501
|
+
(0, sql_utils_1.hasAutoIncrementPrimaryKey)(this.schema, Object.keys(referenceItem));
|
|
571
502
|
// Determine which columns to update
|
|
572
503
|
let updateColumnFilter = config?.updateColumnFilter;
|
|
573
504
|
if (updateColumnFilter == null && config?.updateColumns) {
|
|
@@ -578,15 +509,10 @@ class TableAccessor {
|
|
|
578
509
|
updateColumnFilter = (colId) => !primaryKeys.includes(colId);
|
|
579
510
|
}
|
|
580
511
|
// Calculate chunk size based on max rows per batch
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
const POSTGRES_MAX_PARAMS = 65535;
|
|
584
|
-
const columnCount = Object.keys(values[0]).length;
|
|
585
|
-
const maxRowsPerBatch = Math.floor(POSTGRES_MAX_PARAMS / columnCount);
|
|
586
|
-
chunkSize = Math.floor(maxRowsPerBatch * 0.6); // Use 60% of max to be safe
|
|
587
|
-
}
|
|
512
|
+
const columnCount = Object.keys(values[0]).length;
|
|
513
|
+
const chunkSize = (0, sql_utils_1.calculateOptimalChunkSize)(columnCount, config?.chunkSize);
|
|
588
514
|
// Check if we need to chunk
|
|
589
|
-
if (
|
|
515
|
+
if (values.length > chunkSize) {
|
|
590
516
|
const results = [];
|
|
591
517
|
for (let i = 0; i < values.length; i += chunkSize) {
|
|
592
518
|
const chunk = values.slice(i, i + chunkSize);
|
|
@@ -661,7 +587,7 @@ class TableAccessor {
|
|
|
661
587
|
const returningColumns = Object.entries(this.schema.columns)
|
|
662
588
|
.map(([_, col]) => `"${col.build().name}"`)
|
|
663
589
|
.join(', ');
|
|
664
|
-
const qualifiedTableName =
|
|
590
|
+
const qualifiedTableName = (0, sql_utils_1.getQualifiedTableName)(this.schema);
|
|
665
591
|
const sql = `
|
|
666
592
|
INSERT INTO ${qualifiedTableName} (${columnNames.join(', ')})
|
|
667
593
|
VALUES ${valuePlaceholders.join(', ')}
|
|
@@ -725,7 +651,7 @@ class TableAccessor {
|
|
|
725
651
|
const returningColumns = Object.entries(this.schema.columns)
|
|
726
652
|
.map(([_, col]) => `"${col.build().name}"`)
|
|
727
653
|
.join(', ');
|
|
728
|
-
const qualifiedTableName =
|
|
654
|
+
const qualifiedTableName = (0, sql_utils_1.getQualifiedTableName)(this.schema);
|
|
729
655
|
const sql = `
|
|
730
656
|
UPDATE ${qualifiedTableName}
|
|
731
657
|
SET ${setClauses.join(', ')}
|
|
@@ -753,7 +679,7 @@ class TableAccessor {
|
|
|
753
679
|
if (!pkColumnName) {
|
|
754
680
|
throw new Error(`Table ${this.schema.name} has no primary key`);
|
|
755
681
|
}
|
|
756
|
-
const qualifiedTableName =
|
|
682
|
+
const qualifiedTableName = (0, sql_utils_1.getQualifiedTableName)(this.schema);
|
|
757
683
|
const sql = `DELETE FROM ${qualifiedTableName} WHERE "${pkColumnName}" = $1`;
|
|
758
684
|
const result = this.executor
|
|
759
685
|
? await this.executor.query(sql, [id])
|
|
@@ -1007,6 +933,53 @@ class DbEntityTable {
|
|
|
1007
933
|
: await client.query(sql, []);
|
|
1008
934
|
return this.mapResultsToEntities(result.rows);
|
|
1009
935
|
}
|
|
936
|
+
/**
|
|
937
|
+
* Get first record
|
|
938
|
+
* @throws Error if no records exist
|
|
939
|
+
*/
|
|
940
|
+
async first() {
|
|
941
|
+
const queryBuilder = this.context.getTable(this.tableName);
|
|
942
|
+
const schema = this._getSchema();
|
|
943
|
+
// Build SELECT with all columns and LIMIT 1
|
|
944
|
+
const columns = Object.entries(schema.columns)
|
|
945
|
+
.map(([_, col]) => `"${col.build().name}"`)
|
|
946
|
+
.join(', ');
|
|
947
|
+
const qualifiedTableName = this._getQualifiedTableName();
|
|
948
|
+
const sql = `SELECT ${columns} FROM ${qualifiedTableName} LIMIT 1`;
|
|
949
|
+
const executor = this._getExecutor();
|
|
950
|
+
const client = this._getClient();
|
|
951
|
+
const result = executor
|
|
952
|
+
? await executor.query(sql, [])
|
|
953
|
+
: await client.query(sql, []);
|
|
954
|
+
if (result.rows.length === 0) {
|
|
955
|
+
throw new Error('Sequence contains no elements');
|
|
956
|
+
}
|
|
957
|
+
const mapped = this.mapResultsToEntities(result.rows);
|
|
958
|
+
return mapped[0];
|
|
959
|
+
}
|
|
960
|
+
/**
|
|
961
|
+
* Get first record or null if none exist
|
|
962
|
+
*/
|
|
963
|
+
async firstOrDefault() {
|
|
964
|
+
const queryBuilder = this.context.getTable(this.tableName);
|
|
965
|
+
const schema = this._getSchema();
|
|
966
|
+
// Build SELECT with all columns and LIMIT 1
|
|
967
|
+
const columns = Object.entries(schema.columns)
|
|
968
|
+
.map(([_, col]) => `"${col.build().name}"`)
|
|
969
|
+
.join(', ');
|
|
970
|
+
const qualifiedTableName = this._getQualifiedTableName();
|
|
971
|
+
const sql = `SELECT ${columns} FROM ${qualifiedTableName} LIMIT 1`;
|
|
972
|
+
const executor = this._getExecutor();
|
|
973
|
+
const client = this._getClient();
|
|
974
|
+
const result = executor
|
|
975
|
+
? await executor.query(sql, [])
|
|
976
|
+
: await client.query(sql, []);
|
|
977
|
+
if (result.rows.length === 0) {
|
|
978
|
+
return null;
|
|
979
|
+
}
|
|
980
|
+
const mapped = this.mapResultsToEntities(result.rows);
|
|
981
|
+
return mapped[0];
|
|
982
|
+
}
|
|
1010
983
|
/**
|
|
1011
984
|
* Count all records
|
|
1012
985
|
*/
|
|
@@ -1065,6 +1038,7 @@ class DbEntityTable {
|
|
|
1065
1038
|
}
|
|
1066
1039
|
/**
|
|
1067
1040
|
* Select query
|
|
1041
|
+
* UnwrapSelection extracts the value types from SqlFragment<T> expressions
|
|
1068
1042
|
*/
|
|
1069
1043
|
select(selector) {
|
|
1070
1044
|
const queryBuilder = this.context.getTable(this.tableName).select(selector);
|
|
@@ -1092,19 +1066,16 @@ class DbEntityTable {
|
|
|
1092
1066
|
for (const colName of Object.keys(schema.columns)) {
|
|
1093
1067
|
result[colName] = e[colName];
|
|
1094
1068
|
}
|
|
1095
|
-
// Add navigation properties
|
|
1069
|
+
// Add navigation properties as enumerable getters
|
|
1070
|
+
// The getter returns the actual navigation builder (CollectionQueryBuilder/ReferenceQueryBuilder)
|
|
1071
|
+
// which allows chained selectors like .select(u => ({ posts: u.posts!.where(...) }))
|
|
1072
|
+
// When the selection is analyzed, the builder is detected and handled appropriately
|
|
1096
1073
|
for (const relName of Object.keys(schema.relations)) {
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
//
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
}
|
|
1103
|
-
else {
|
|
1104
|
-
// For references, copy the ReferenceQueryBuilder mock
|
|
1105
|
-
// This allows accessing fields like o.user!.username in chained selectors
|
|
1106
|
-
result[relName] = e[relName];
|
|
1107
|
-
}
|
|
1074
|
+
Object.defineProperty(result, relName, {
|
|
1075
|
+
get: () => e[relName],
|
|
1076
|
+
enumerable: true, // Enumerable so it's included in Object.entries
|
|
1077
|
+
configurable: true,
|
|
1078
|
+
});
|
|
1108
1079
|
}
|
|
1109
1080
|
return result;
|
|
1110
1081
|
};
|
|
@@ -1114,85 +1085,472 @@ class DbEntityTable {
|
|
|
1114
1085
|
return queryBuilder;
|
|
1115
1086
|
}
|
|
1116
1087
|
/**
|
|
1117
|
-
*
|
|
1088
|
+
* Add CTEs (Common Table Expressions) to the query
|
|
1118
1089
|
*/
|
|
1090
|
+
with(...ctes) {
|
|
1091
|
+
const schema = this._getSchema();
|
|
1092
|
+
// Create a selector that selects all columns with navigation property access
|
|
1093
|
+
const allColumnsSelector = (e) => {
|
|
1094
|
+
const result = {};
|
|
1095
|
+
// Copy all column properties
|
|
1096
|
+
for (const colName of Object.keys(schema.columns)) {
|
|
1097
|
+
result[colName] = e[colName];
|
|
1098
|
+
}
|
|
1099
|
+
// Add navigation properties as enumerable getters
|
|
1100
|
+
// The getter returns the actual navigation builder (CollectionQueryBuilder/ReferenceQueryBuilder)
|
|
1101
|
+
// which allows chained selectors like .select(u => ({ posts: u.posts!.where(...) }))
|
|
1102
|
+
// When the selection is analyzed, the builder is detected and handled appropriately
|
|
1103
|
+
for (const relName of Object.keys(schema.relations)) {
|
|
1104
|
+
Object.defineProperty(result, relName, {
|
|
1105
|
+
get: () => e[relName],
|
|
1106
|
+
enumerable: true, // Enumerable so it's included in Object.entries
|
|
1107
|
+
configurable: true,
|
|
1108
|
+
});
|
|
1109
|
+
}
|
|
1110
|
+
return result;
|
|
1111
|
+
};
|
|
1112
|
+
const queryBuilder = this.context.getTable(this.tableName)
|
|
1113
|
+
.with(...ctes)
|
|
1114
|
+
.select(allColumnsSelector);
|
|
1115
|
+
return queryBuilder;
|
|
1116
|
+
}
|
|
1119
1117
|
leftJoin(rightTable, condition, selector, alias) {
|
|
1120
1118
|
const queryBuilder = this.context.getTable(this.tableName).leftJoin(rightTable, condition, selector, alias);
|
|
1121
1119
|
return queryBuilder;
|
|
1122
1120
|
}
|
|
1123
|
-
/**
|
|
1124
|
-
* Inner join with another table or subquery and selector
|
|
1125
|
-
*/
|
|
1126
1121
|
innerJoin(rightTable, condition, selector, alias) {
|
|
1127
1122
|
const queryBuilder = this.context.getTable(this.tableName).innerJoin(rightTable, condition, selector, alias);
|
|
1128
1123
|
return queryBuilder;
|
|
1129
1124
|
}
|
|
1130
1125
|
/**
|
|
1131
1126
|
* Insert - accepts only DbColumn properties (excludes navigation properties)
|
|
1127
|
+
* Returns a fluent builder that can be awaited directly or chained with .returning()
|
|
1128
|
+
*
|
|
1129
|
+
* @example
|
|
1130
|
+
* ```typescript
|
|
1131
|
+
* // No returning (default) - returns void
|
|
1132
|
+
* await db.users.insert({ username: 'alice', email: 'alice@test.com' });
|
|
1133
|
+
*
|
|
1134
|
+
* // With returning() - returns full entity
|
|
1135
|
+
* const user = await db.users.insert({ username: 'alice' }).returning();
|
|
1136
|
+
*
|
|
1137
|
+
* // With returning(selector) - returns selected columns
|
|
1138
|
+
* const { id } = await db.users.insert({ username: 'alice' }).returning(u => ({ id: u.id }));
|
|
1139
|
+
* ```
|
|
1132
1140
|
*/
|
|
1133
|
-
|
|
1134
|
-
const
|
|
1135
|
-
|
|
1141
|
+
insert(data) {
|
|
1142
|
+
const table = this;
|
|
1143
|
+
const executeInsert = async (returning) => {
|
|
1144
|
+
const schema = table._getSchema();
|
|
1145
|
+
const executor = table._getExecutor();
|
|
1146
|
+
const client = table._getClient();
|
|
1147
|
+
// Build INSERT columns and values
|
|
1148
|
+
const columns = [];
|
|
1149
|
+
const values = [];
|
|
1150
|
+
const placeholders = [];
|
|
1151
|
+
let paramIndex = 1;
|
|
1152
|
+
for (const [key, value] of Object.entries(data)) {
|
|
1153
|
+
const column = schema.columns[key];
|
|
1154
|
+
if (column) {
|
|
1155
|
+
const config = column.build();
|
|
1156
|
+
if (!config.autoIncrement) {
|
|
1157
|
+
columns.push(`"${config.name}"`);
|
|
1158
|
+
const mappedValue = config.mapper ? config.mapper.toDriver(value) : value;
|
|
1159
|
+
values.push(mappedValue);
|
|
1160
|
+
placeholders.push(`$${paramIndex++}`);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
// Build RETURNING clause
|
|
1165
|
+
const returningClause = table.buildReturningClause(returning);
|
|
1166
|
+
const qualifiedTableName = table._getQualifiedTableName();
|
|
1167
|
+
let sql = `INSERT INTO ${qualifiedTableName} (${columns.join(', ')}) VALUES (${placeholders.join(', ')})`;
|
|
1168
|
+
if (returningClause) {
|
|
1169
|
+
sql += ` RETURNING ${returningClause.sql}`;
|
|
1170
|
+
}
|
|
1171
|
+
const result = executor
|
|
1172
|
+
? await executor.query(sql, values)
|
|
1173
|
+
: await client.query(sql, values);
|
|
1174
|
+
if (!returningClause) {
|
|
1175
|
+
return undefined;
|
|
1176
|
+
}
|
|
1177
|
+
const mappedResults = table.mapReturningResults(result.rows, returning);
|
|
1178
|
+
return mappedResults[0];
|
|
1179
|
+
};
|
|
1180
|
+
return {
|
|
1181
|
+
then(onfulfilled, onrejected) {
|
|
1182
|
+
return executeInsert(undefined).then(onfulfilled, onrejected);
|
|
1183
|
+
},
|
|
1184
|
+
returning(selector) {
|
|
1185
|
+
const returningConfig = selector ?? true;
|
|
1186
|
+
return {
|
|
1187
|
+
then(onfulfilled, onrejected) {
|
|
1188
|
+
return executeInsert(returningConfig).then(onfulfilled, onrejected);
|
|
1189
|
+
}
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
};
|
|
1136
1193
|
}
|
|
1137
1194
|
/**
|
|
1138
1195
|
* Insert multiple records
|
|
1196
|
+
* Returns a fluent builder that can be awaited directly or chained with .returning()
|
|
1197
|
+
*
|
|
1198
|
+
* @example
|
|
1199
|
+
* ```typescript
|
|
1200
|
+
* // No returning (default) - returns void
|
|
1201
|
+
* await db.users.insertMany([{ username: 'alice' }, { username: 'bob' }]);
|
|
1202
|
+
*
|
|
1203
|
+
* // With returning() - returns full entities
|
|
1204
|
+
* const users = await db.users.insertMany([{ username: 'alice' }]).returning();
|
|
1205
|
+
*
|
|
1206
|
+
* // With returning(selector) - returns selected columns
|
|
1207
|
+
* const results = await db.users.insertMany([{ username: 'alice' }]).returning(u => ({ id: u.id }));
|
|
1208
|
+
* ```
|
|
1139
1209
|
*/
|
|
1140
|
-
|
|
1210
|
+
insertMany(data) {
|
|
1141
1211
|
return this.insertBulk(data);
|
|
1142
1212
|
}
|
|
1143
1213
|
/**
|
|
1144
1214
|
* Upsert (insert or update on conflict)
|
|
1215
|
+
* Returns a fluent builder that can be awaited directly or chained with .returning()
|
|
1216
|
+
*
|
|
1217
|
+
* @example
|
|
1218
|
+
* ```typescript
|
|
1219
|
+
* // No returning (default) - returns void
|
|
1220
|
+
* await db.users.upsert([{ username: 'alice' }], { primaryKey: 'username' });
|
|
1221
|
+
*
|
|
1222
|
+
* // With returning() - returns full entities
|
|
1223
|
+
* const users = await db.users.upsert([{ username: 'alice' }], { primaryKey: 'username' }).returning();
|
|
1224
|
+
* ```
|
|
1145
1225
|
*/
|
|
1146
|
-
|
|
1226
|
+
upsert(data, config) {
|
|
1147
1227
|
return this.upsertBulk(data, config);
|
|
1148
1228
|
}
|
|
1149
1229
|
/**
|
|
1150
1230
|
* Bulk insert with advanced configuration
|
|
1151
1231
|
* Supports automatic chunking for large datasets
|
|
1232
|
+
* Returns a fluent builder that can be awaited directly or chained with .returning()
|
|
1233
|
+
*
|
|
1234
|
+
* @example
|
|
1235
|
+
* ```typescript
|
|
1236
|
+
* // No returning (default) - returns void
|
|
1237
|
+
* await db.users.insertBulk([{ username: 'alice' }]);
|
|
1238
|
+
*
|
|
1239
|
+
* // With returning() - returns full entities
|
|
1240
|
+
* const users = await db.users.insertBulk([{ username: 'alice' }]).returning();
|
|
1241
|
+
*
|
|
1242
|
+
* // With returning(selector) - returns selected columns
|
|
1243
|
+
* const results = await db.users.insertBulk([{ username: 'alice' }]).returning(u => ({ id: u.id }));
|
|
1244
|
+
*
|
|
1245
|
+
* // With options
|
|
1246
|
+
* await db.users.insertBulk([{ username: 'alice' }], { chunkSize: 100 });
|
|
1247
|
+
* ```
|
|
1152
1248
|
*/
|
|
1153
|
-
|
|
1154
|
-
const
|
|
1155
|
-
|
|
1249
|
+
insertBulk(value, options) {
|
|
1250
|
+
const table = this;
|
|
1251
|
+
const dataArray = Array.isArray(value) ? value : [value];
|
|
1252
|
+
const executeInsertBulk = async (returning) => {
|
|
1253
|
+
if (dataArray.length === 0) {
|
|
1254
|
+
return returning === undefined ? undefined : [];
|
|
1255
|
+
}
|
|
1256
|
+
// Calculate chunk size
|
|
1257
|
+
let chunkSize = options?.chunkSize;
|
|
1258
|
+
if (chunkSize == null) {
|
|
1259
|
+
const POSTGRES_MAX_PARAMS = 65535;
|
|
1260
|
+
const columnCount = Object.keys(dataArray[0]).length;
|
|
1261
|
+
const maxRowsPerBatch = Math.floor(POSTGRES_MAX_PARAMS / columnCount);
|
|
1262
|
+
chunkSize = Math.floor(maxRowsPerBatch * 0.6);
|
|
1263
|
+
}
|
|
1264
|
+
// Process in chunks if needed
|
|
1265
|
+
if (dataArray.length > chunkSize) {
|
|
1266
|
+
const allResults = [];
|
|
1267
|
+
for (let i = 0; i < dataArray.length; i += chunkSize) {
|
|
1268
|
+
const chunk = dataArray.slice(i, i + chunkSize);
|
|
1269
|
+
const chunkResults = await table.insertBulkSingle(chunk, returning, options?.overridingSystemValue);
|
|
1270
|
+
if (chunkResults)
|
|
1271
|
+
allResults.push(...chunkResults);
|
|
1272
|
+
}
|
|
1273
|
+
return returning === undefined ? undefined : allResults;
|
|
1274
|
+
}
|
|
1275
|
+
return table.insertBulkSingle(dataArray, returning, options?.overridingSystemValue);
|
|
1276
|
+
};
|
|
1277
|
+
return {
|
|
1278
|
+
then(onfulfilled, onrejected) {
|
|
1279
|
+
return executeInsertBulk(undefined).then(onfulfilled, onrejected);
|
|
1280
|
+
},
|
|
1281
|
+
returning(selector) {
|
|
1282
|
+
const returningConfig = selector ?? true;
|
|
1283
|
+
return {
|
|
1284
|
+
then(onfulfilled, onrejected) {
|
|
1285
|
+
return executeInsertBulk(returningConfig).then(onfulfilled, onrejected);
|
|
1286
|
+
}
|
|
1287
|
+
};
|
|
1288
|
+
}
|
|
1289
|
+
};
|
|
1290
|
+
}
|
|
1291
|
+
/**
|
|
1292
|
+
* Execute a single bulk insert batch
|
|
1293
|
+
* @internal
|
|
1294
|
+
*/
|
|
1295
|
+
async insertBulkSingle(data, returning, overridingSystemValue) {
|
|
1296
|
+
const schema = this._getSchema();
|
|
1297
|
+
const executor = this._getExecutor();
|
|
1298
|
+
const client = this._getClient();
|
|
1299
|
+
const qualifiedTableName = this._getQualifiedTableName();
|
|
1300
|
+
// Get columns from first row
|
|
1301
|
+
const referenceItem = data[0];
|
|
1302
|
+
const columnConfigs = [];
|
|
1303
|
+
for (const [propName, colBuilder] of Object.entries(schema.columns)) {
|
|
1304
|
+
if (propName in referenceItem) {
|
|
1305
|
+
const config = colBuilder.build();
|
|
1306
|
+
if (!config.autoIncrement || overridingSystemValue) {
|
|
1307
|
+
columnConfigs.push({
|
|
1308
|
+
propName,
|
|
1309
|
+
dbName: config.name,
|
|
1310
|
+
mapper: config.mapper,
|
|
1311
|
+
});
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
// Build VALUES clauses
|
|
1316
|
+
const valuesClauses = [];
|
|
1317
|
+
const params = [];
|
|
1318
|
+
let paramIndex = 1;
|
|
1319
|
+
for (const record of data) {
|
|
1320
|
+
const rowValues = [];
|
|
1321
|
+
for (const col of columnConfigs) {
|
|
1322
|
+
const value = record[col.propName];
|
|
1323
|
+
const mappedValue = col.mapper ? col.mapper.toDriver(value) : value;
|
|
1324
|
+
rowValues.push(`$${paramIndex++}`);
|
|
1325
|
+
params.push(mappedValue);
|
|
1326
|
+
}
|
|
1327
|
+
valuesClauses.push(`(${rowValues.join(', ')})`);
|
|
1328
|
+
}
|
|
1329
|
+
const columnList = columnConfigs.map(c => `"${c.dbName}"`).join(', ');
|
|
1330
|
+
const returningClause = this.buildReturningClause(returning);
|
|
1331
|
+
let sql = `INSERT INTO ${qualifiedTableName} (${columnList})`;
|
|
1332
|
+
if (overridingSystemValue) {
|
|
1333
|
+
sql += ' OVERRIDING SYSTEM VALUE';
|
|
1334
|
+
}
|
|
1335
|
+
sql += ` VALUES ${valuesClauses.join(', ')}`;
|
|
1336
|
+
if (returningClause) {
|
|
1337
|
+
sql += ` RETURNING ${returningClause.sql}`;
|
|
1338
|
+
}
|
|
1339
|
+
const result = executor
|
|
1340
|
+
? await executor.query(sql, params)
|
|
1341
|
+
: await client.query(sql, params);
|
|
1342
|
+
if (!returningClause) {
|
|
1343
|
+
return undefined;
|
|
1344
|
+
}
|
|
1345
|
+
return this.mapReturningResults(result.rows, returning);
|
|
1156
1346
|
}
|
|
1157
1347
|
/**
|
|
1158
1348
|
* Upsert with advanced configuration
|
|
1159
1349
|
* Auto-detects primary keys and supports chunking
|
|
1350
|
+
* Returns a fluent builder that can be awaited directly or chained with .returning()
|
|
1351
|
+
*
|
|
1352
|
+
* @example
|
|
1353
|
+
* ```typescript
|
|
1354
|
+
* // No returning (default) - returns void
|
|
1355
|
+
* await db.users.upsertBulk([{ id: 1, username: 'alice' }], { primaryKey: 'id' });
|
|
1356
|
+
*
|
|
1357
|
+
* // With returning() - returns full entities
|
|
1358
|
+
* const users = await db.users.upsertBulk([{ id: 1, username: 'alice' }]).returning();
|
|
1359
|
+
*
|
|
1360
|
+
* // With returning(selector) - returns selected columns
|
|
1361
|
+
* const results = await db.users.upsertBulk([{ id: 1, username: 'alice' }])
|
|
1362
|
+
* .returning(u => ({ id: u.id }));
|
|
1363
|
+
* ```
|
|
1160
1364
|
*/
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
const
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1365
|
+
upsertBulk(values, config) {
|
|
1366
|
+
const table = this;
|
|
1367
|
+
const executeUpsertBulk = async (returning) => {
|
|
1368
|
+
if (values.length === 0) {
|
|
1369
|
+
return returning === undefined ? undefined : [];
|
|
1370
|
+
}
|
|
1371
|
+
const schema = table._getSchema();
|
|
1372
|
+
// Handle primaryKey (can be lambda, string, or array)
|
|
1373
|
+
let primaryKeys = [];
|
|
1374
|
+
if (config?.primaryKey) {
|
|
1375
|
+
if (typeof config.primaryKey === 'function') {
|
|
1376
|
+
const pkProps = table.extractPropertyNames(config.primaryKey);
|
|
1377
|
+
primaryKeys = pkProps;
|
|
1378
|
+
}
|
|
1379
|
+
else if (Array.isArray(config.primaryKey)) {
|
|
1380
|
+
primaryKeys = config.primaryKey;
|
|
1381
|
+
}
|
|
1382
|
+
else {
|
|
1383
|
+
primaryKeys = [config.primaryKey];
|
|
1384
|
+
}
|
|
1177
1385
|
}
|
|
1178
1386
|
else {
|
|
1179
|
-
//
|
|
1180
|
-
|
|
1387
|
+
// Auto-detect from schema
|
|
1388
|
+
for (const [key, colBuilder] of Object.entries(schema.columns)) {
|
|
1389
|
+
const colConfig = colBuilder.build();
|
|
1390
|
+
if (colConfig.primaryKey) {
|
|
1391
|
+
primaryKeys.push(key);
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
// Handle updateColumns (can be lambda or array)
|
|
1396
|
+
let updateColumns;
|
|
1397
|
+
if (config?.updateColumns) {
|
|
1398
|
+
if (typeof config.updateColumns === 'function') {
|
|
1399
|
+
updateColumns = table.extractPropertyNames(config.updateColumns);
|
|
1400
|
+
}
|
|
1401
|
+
else {
|
|
1402
|
+
updateColumns = config.updateColumns;
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
// Auto-detect overridingSystemValue
|
|
1406
|
+
let overridingSystemValue = config?.overridingSystemValue;
|
|
1407
|
+
if (overridingSystemValue == null) {
|
|
1408
|
+
const referenceItem = config?.referenceItem || values[0];
|
|
1409
|
+
for (const key of Object.keys(referenceItem)) {
|
|
1410
|
+
const column = schema.columns[key];
|
|
1411
|
+
if (column) {
|
|
1412
|
+
const colConfig = column.build();
|
|
1413
|
+
if (colConfig.primaryKey && colConfig.autoIncrement) {
|
|
1414
|
+
overridingSystemValue = true;
|
|
1415
|
+
break;
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
// Calculate chunk size
|
|
1421
|
+
let chunkSize = config?.chunkSize;
|
|
1422
|
+
if (chunkSize == null) {
|
|
1423
|
+
const POSTGRES_MAX_PARAMS = 65535;
|
|
1424
|
+
const columnCount = Object.keys(values[0]).length;
|
|
1425
|
+
const maxRowsPerBatch = Math.floor(POSTGRES_MAX_PARAMS / columnCount);
|
|
1426
|
+
chunkSize = Math.floor(maxRowsPerBatch * 0.6);
|
|
1427
|
+
}
|
|
1428
|
+
// Process in chunks if needed
|
|
1429
|
+
if (values.length > chunkSize) {
|
|
1430
|
+
const allResults = [];
|
|
1431
|
+
for (let i = 0; i < values.length; i += chunkSize) {
|
|
1432
|
+
const chunk = values.slice(i, i + chunkSize);
|
|
1433
|
+
const chunkResults = await table.upsertBulkSingle(chunk, primaryKeys, updateColumns, config?.updateColumnFilter, overridingSystemValue || false, config?.targetWhere, config?.setWhere, returning);
|
|
1434
|
+
if (chunkResults)
|
|
1435
|
+
allResults.push(...chunkResults);
|
|
1436
|
+
}
|
|
1437
|
+
return returning === undefined ? undefined : allResults;
|
|
1438
|
+
}
|
|
1439
|
+
return table.upsertBulkSingle(values, primaryKeys, updateColumns, config?.updateColumnFilter, overridingSystemValue || false, config?.targetWhere, config?.setWhere, returning);
|
|
1440
|
+
};
|
|
1441
|
+
return {
|
|
1442
|
+
then(onfulfilled, onrejected) {
|
|
1443
|
+
return executeUpsertBulk(undefined).then(onfulfilled, onrejected);
|
|
1444
|
+
},
|
|
1445
|
+
returning(selector) {
|
|
1446
|
+
const returningConfig = selector ?? true;
|
|
1447
|
+
return {
|
|
1448
|
+
then(onfulfilled, onrejected) {
|
|
1449
|
+
return executeUpsertBulk(returningConfig).then(onfulfilled, onrejected);
|
|
1450
|
+
}
|
|
1451
|
+
};
|
|
1452
|
+
}
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1455
|
+
/**
|
|
1456
|
+
* Execute a single upsert batch
|
|
1457
|
+
* @internal
|
|
1458
|
+
*/
|
|
1459
|
+
async upsertBulkSingle(values, primaryKeys, updateColumns, updateColumnFilter, overridingSystemValue, targetWhere, setWhere, returning) {
|
|
1460
|
+
const schema = this._getSchema();
|
|
1461
|
+
const executor = this._getExecutor();
|
|
1462
|
+
const client = this._getClient();
|
|
1463
|
+
const qualifiedTableName = this._getQualifiedTableName();
|
|
1464
|
+
// Extract all unique column names from all data objects
|
|
1465
|
+
const columnConfigs = [];
|
|
1466
|
+
const columnSet = new Set();
|
|
1467
|
+
for (const data of values) {
|
|
1468
|
+
for (const key of Object.keys(data)) {
|
|
1469
|
+
if (!columnSet.has(key)) {
|
|
1470
|
+
const column = schema.columns[key];
|
|
1471
|
+
if (column) {
|
|
1472
|
+
const config = column.build();
|
|
1473
|
+
if (!config.autoIncrement || overridingSystemValue) {
|
|
1474
|
+
columnSet.add(key);
|
|
1475
|
+
columnConfigs.push({
|
|
1476
|
+
propName: key,
|
|
1477
|
+
dbName: config.name,
|
|
1478
|
+
mapper: config.mapper,
|
|
1479
|
+
});
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1181
1483
|
}
|
|
1182
1484
|
}
|
|
1183
|
-
//
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1485
|
+
// Build VALUES clauses
|
|
1486
|
+
const valuesClauses = [];
|
|
1487
|
+
const params = [];
|
|
1488
|
+
let paramIndex = 1;
|
|
1489
|
+
for (const record of values) {
|
|
1490
|
+
const rowValues = [];
|
|
1491
|
+
for (const col of columnConfigs) {
|
|
1492
|
+
const value = record[col.propName];
|
|
1493
|
+
const mappedValue = col.mapper
|
|
1494
|
+
? col.mapper.toDriver(value !== undefined ? value : null)
|
|
1495
|
+
: (value !== undefined ? value : null);
|
|
1496
|
+
rowValues.push(`$${paramIndex++}`);
|
|
1497
|
+
params.push(mappedValue);
|
|
1188
1498
|
}
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1499
|
+
valuesClauses.push(`(${rowValues.join(', ')})`);
|
|
1500
|
+
}
|
|
1501
|
+
const columnList = columnConfigs.map(c => `"${c.dbName}"`).join(', ');
|
|
1502
|
+
// Build SQL
|
|
1503
|
+
let sql = `INSERT INTO ${qualifiedTableName} (${columnList})`;
|
|
1504
|
+
if (overridingSystemValue) {
|
|
1505
|
+
sql += ' OVERRIDING SYSTEM VALUE';
|
|
1506
|
+
}
|
|
1507
|
+
sql += ` VALUES ${valuesClauses.join(', ')}`;
|
|
1508
|
+
// Add ON CONFLICT clause
|
|
1509
|
+
const conflictCols = primaryKeys.map(pk => {
|
|
1510
|
+
const col = columnConfigs.find(c => c.propName === pk);
|
|
1511
|
+
return col ? `"${col.dbName}"` : `"${pk}"`;
|
|
1512
|
+
}).join(', ');
|
|
1513
|
+
sql += ` ON CONFLICT (${conflictCols})`;
|
|
1514
|
+
if (targetWhere) {
|
|
1515
|
+
sql += ` WHERE ${targetWhere}`;
|
|
1516
|
+
}
|
|
1517
|
+
// Determine columns to update
|
|
1518
|
+
let columnsToUpdate;
|
|
1519
|
+
if (updateColumns) {
|
|
1520
|
+
columnsToUpdate = updateColumns;
|
|
1521
|
+
}
|
|
1522
|
+
else if (updateColumnFilter) {
|
|
1523
|
+
columnsToUpdate = Array.from(columnSet).filter(updateColumnFilter);
|
|
1524
|
+
}
|
|
1525
|
+
else {
|
|
1526
|
+
columnsToUpdate = Array.from(columnSet).filter(key => !primaryKeys.includes(key));
|
|
1527
|
+
}
|
|
1528
|
+
if (columnsToUpdate.length === 0) {
|
|
1529
|
+
sql += ' DO NOTHING';
|
|
1530
|
+
}
|
|
1531
|
+
else {
|
|
1532
|
+
const updateSetClauses = columnsToUpdate.map(propName => {
|
|
1533
|
+
const col = columnConfigs.find(c => c.propName === propName);
|
|
1534
|
+
const dbName = col ? col.dbName : propName;
|
|
1535
|
+
return `"${dbName}" = EXCLUDED."${dbName}"`;
|
|
1536
|
+
});
|
|
1537
|
+
sql += ` DO UPDATE SET ${updateSetClauses.join(', ')}`;
|
|
1538
|
+
if (setWhere) {
|
|
1539
|
+
sql += ` WHERE ${setWhere}`;
|
|
1192
1540
|
}
|
|
1193
1541
|
}
|
|
1194
|
-
|
|
1195
|
-
|
|
1542
|
+
// Add RETURNING clause
|
|
1543
|
+
const returningClause = this.buildReturningClause(returning);
|
|
1544
|
+
if (returningClause) {
|
|
1545
|
+
sql += ` RETURNING ${returningClause.sql}`;
|
|
1546
|
+
}
|
|
1547
|
+
const result = executor
|
|
1548
|
+
? await executor.query(sql, params)
|
|
1549
|
+
: await client.query(sql, params);
|
|
1550
|
+
if (!returningClause) {
|
|
1551
|
+
return undefined;
|
|
1552
|
+
}
|
|
1553
|
+
return this.mapReturningResults(result.rows, returning);
|
|
1196
1554
|
}
|
|
1197
1555
|
/**
|
|
1198
1556
|
* Map database column names back to property names
|
|
@@ -1244,48 +1602,268 @@ class DbEntityTable {
|
|
|
1244
1602
|
}
|
|
1245
1603
|
/**
|
|
1246
1604
|
* Update records matching condition
|
|
1247
|
-
*
|
|
1605
|
+
* Returns a fluent builder that can be awaited directly or chained with .returning()
|
|
1606
|
+
*
|
|
1607
|
+
* @example
|
|
1608
|
+
* ```typescript
|
|
1609
|
+
* // No returning (default) - returns void
|
|
1610
|
+
* await db.users.update({ age: 30 }, u => eq(u.id, 1));
|
|
1611
|
+
*
|
|
1612
|
+
* // With returning() - returns full entities
|
|
1613
|
+
* const users = await db.users.update({ age: 30 }, u => eq(u.id, 1)).returning();
|
|
1614
|
+
*
|
|
1615
|
+
* // With returning(selector) - returns selected columns
|
|
1616
|
+
* const results = await db.users.update({ age: 30 }, u => eq(u.id, 1))
|
|
1617
|
+
* .returning(u => ({ id: u.id, age: u.age }));
|
|
1618
|
+
* ```
|
|
1619
|
+
*/
|
|
1620
|
+
update(data, condition) {
|
|
1621
|
+
const table = this;
|
|
1622
|
+
const executeUpdate = async (returning) => {
|
|
1623
|
+
const schema = table._getSchema();
|
|
1624
|
+
const executor = table._getExecutor();
|
|
1625
|
+
const client = table._getClient();
|
|
1626
|
+
// Build SET clause
|
|
1627
|
+
const setClauses = [];
|
|
1628
|
+
const values = [];
|
|
1629
|
+
let paramIndex = 1;
|
|
1630
|
+
for (const [key, value] of Object.entries(data)) {
|
|
1631
|
+
const column = schema.columns[key];
|
|
1632
|
+
if (column) {
|
|
1633
|
+
const config = column.build();
|
|
1634
|
+
setClauses.push(`"${config.name}" = $${paramIndex++}`);
|
|
1635
|
+
values.push(value);
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
if (setClauses.length === 0) {
|
|
1639
|
+
throw new Error('No valid columns to update');
|
|
1640
|
+
}
|
|
1641
|
+
// Build WHERE clause
|
|
1642
|
+
const mockEntity = table.createMockEntity();
|
|
1643
|
+
const whereCondition = condition(mockEntity);
|
|
1644
|
+
const condBuilder = new conditions_1.ConditionBuilder();
|
|
1645
|
+
const { sql: whereSql, params: whereParams } = condBuilder.build(whereCondition, paramIndex);
|
|
1646
|
+
values.push(...whereParams);
|
|
1647
|
+
// Build RETURNING clause
|
|
1648
|
+
const returningClause = table.buildReturningClause(returning);
|
|
1649
|
+
const qualifiedTableName = table._getQualifiedTableName();
|
|
1650
|
+
let sql = `UPDATE ${qualifiedTableName} SET ${setClauses.join(', ')} WHERE ${whereSql}`;
|
|
1651
|
+
if (returningClause) {
|
|
1652
|
+
sql += ` RETURNING ${returningClause.sql}`;
|
|
1653
|
+
}
|
|
1654
|
+
const result = executor
|
|
1655
|
+
? await executor.query(sql, values)
|
|
1656
|
+
: await client.query(sql, values);
|
|
1657
|
+
if (!returningClause) {
|
|
1658
|
+
return undefined;
|
|
1659
|
+
}
|
|
1660
|
+
return table.mapReturningResults(result.rows, returning);
|
|
1661
|
+
};
|
|
1662
|
+
return {
|
|
1663
|
+
then(onfulfilled, onrejected) {
|
|
1664
|
+
return executeUpdate(undefined).then(onfulfilled, onrejected);
|
|
1665
|
+
},
|
|
1666
|
+
returning(selector) {
|
|
1667
|
+
const returningConfig = selector ?? true;
|
|
1668
|
+
return {
|
|
1669
|
+
then(onfulfilled, onrejected) {
|
|
1670
|
+
return executeUpdate(returningConfig).then(onfulfilled, onrejected);
|
|
1671
|
+
}
|
|
1672
|
+
};
|
|
1673
|
+
}
|
|
1674
|
+
};
|
|
1675
|
+
}
|
|
1676
|
+
/**
|
|
1677
|
+
* Bulk update multiple records efficiently using PostgreSQL VALUES clause
|
|
1678
|
+
* Updates records matching primary key(s) with provided data
|
|
1679
|
+
* Returns a fluent builder that can be awaited directly or chained with .returning()
|
|
1680
|
+
*
|
|
1681
|
+
* @param data Array of objects with primary key(s) and columns to update
|
|
1682
|
+
* @param config Optional configuration for the bulk update
|
|
1683
|
+
*
|
|
1684
|
+
* @example
|
|
1685
|
+
* ```typescript
|
|
1686
|
+
* // No returning (default) - returns void
|
|
1687
|
+
* await db.users.bulkUpdate([
|
|
1688
|
+
* { id: 1, age: 30 },
|
|
1689
|
+
* { id: 2, age: 25 },
|
|
1690
|
+
* ]);
|
|
1691
|
+
*
|
|
1692
|
+
* // With returning() - returns full entities
|
|
1693
|
+
* const updated = await db.users.bulkUpdate([{ id: 1, age: 30 }]).returning();
|
|
1694
|
+
*
|
|
1695
|
+
* // With returning(selector) - returns selected columns
|
|
1696
|
+
* const results = await db.users.bulkUpdate([{ id: 1, age: 30 }])
|
|
1697
|
+
* .returning(u => ({ id: u.id, age: u.age }));
|
|
1698
|
+
*
|
|
1699
|
+
* // With custom primary key
|
|
1700
|
+
* await db.users.bulkUpdate(
|
|
1701
|
+
* [{ username: 'alice', age: 31 }],
|
|
1702
|
+
* { primaryKey: 'username' }
|
|
1703
|
+
* );
|
|
1704
|
+
* ```
|
|
1248
1705
|
*/
|
|
1249
|
-
|
|
1706
|
+
bulkUpdate(data, config) {
|
|
1707
|
+
const table = this;
|
|
1708
|
+
const executeBulkUpdate = async (returning) => {
|
|
1709
|
+
if (data.length === 0) {
|
|
1710
|
+
return returning === undefined ? undefined : [];
|
|
1711
|
+
}
|
|
1712
|
+
const schema = table._getSchema();
|
|
1713
|
+
// Determine primary keys
|
|
1714
|
+
let primaryKeys = [];
|
|
1715
|
+
if (config?.primaryKey) {
|
|
1716
|
+
primaryKeys = Array.isArray(config.primaryKey) ? config.primaryKey : [config.primaryKey];
|
|
1717
|
+
}
|
|
1718
|
+
else {
|
|
1719
|
+
// Auto-detect from schema
|
|
1720
|
+
for (const [key, colBuilder] of Object.entries(schema.columns)) {
|
|
1721
|
+
const colConfig = colBuilder.build();
|
|
1722
|
+
if (colConfig.primaryKey) {
|
|
1723
|
+
primaryKeys.push(key);
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
if (primaryKeys.length === 0) {
|
|
1728
|
+
throw new Error('bulkUpdate requires at least one primary key column');
|
|
1729
|
+
}
|
|
1730
|
+
// Validate all records have primary keys
|
|
1731
|
+
for (let i = 0; i < data.length; i++) {
|
|
1732
|
+
for (const pk of primaryKeys) {
|
|
1733
|
+
if (data[i][pk] === undefined) {
|
|
1734
|
+
throw new Error(`Record at index ${i} is missing primary key "${pk}"`);
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
// Calculate chunk size
|
|
1739
|
+
let chunkSize = config?.chunkSize;
|
|
1740
|
+
if (chunkSize == null) {
|
|
1741
|
+
const POSTGRES_MAX_PARAMS = 65535;
|
|
1742
|
+
const referenceItem = data[0];
|
|
1743
|
+
const columnCount = Object.keys(referenceItem).length;
|
|
1744
|
+
const maxRowsPerBatch = Math.floor(POSTGRES_MAX_PARAMS / columnCount);
|
|
1745
|
+
chunkSize = Math.floor(maxRowsPerBatch * 0.6);
|
|
1746
|
+
}
|
|
1747
|
+
// Process in chunks if needed
|
|
1748
|
+
if (data.length > chunkSize) {
|
|
1749
|
+
const allResults = [];
|
|
1750
|
+
for (let i = 0; i < data.length; i += chunkSize) {
|
|
1751
|
+
const chunk = data.slice(i, i + chunkSize);
|
|
1752
|
+
const chunkResults = await table.bulkUpdateSingle(chunk, primaryKeys, returning);
|
|
1753
|
+
if (chunkResults)
|
|
1754
|
+
allResults.push(...chunkResults);
|
|
1755
|
+
}
|
|
1756
|
+
return returning === undefined ? undefined : allResults;
|
|
1757
|
+
}
|
|
1758
|
+
return table.bulkUpdateSingle(data, primaryKeys, returning);
|
|
1759
|
+
};
|
|
1760
|
+
return {
|
|
1761
|
+
then(onfulfilled, onrejected) {
|
|
1762
|
+
return executeBulkUpdate(undefined).then(onfulfilled, onrejected);
|
|
1763
|
+
},
|
|
1764
|
+
returning(selector) {
|
|
1765
|
+
const returningConfig = selector ?? true;
|
|
1766
|
+
return {
|
|
1767
|
+
then(onfulfilled, onrejected) {
|
|
1768
|
+
return executeBulkUpdate(returningConfig).then(onfulfilled, onrejected);
|
|
1769
|
+
}
|
|
1770
|
+
};
|
|
1771
|
+
}
|
|
1772
|
+
};
|
|
1773
|
+
}
|
|
1774
|
+
/**
|
|
1775
|
+
* Execute a single bulk update batch
|
|
1776
|
+
* @internal
|
|
1777
|
+
*/
|
|
1778
|
+
async bulkUpdateSingle(data, primaryKeys, returning) {
|
|
1250
1779
|
const schema = this._getSchema();
|
|
1251
1780
|
const executor = this._getExecutor();
|
|
1252
1781
|
const client = this._getClient();
|
|
1253
|
-
|
|
1782
|
+
const qualifiedTableName = this._getQualifiedTableName();
|
|
1783
|
+
const primaryKeySet = new Set(primaryKeys);
|
|
1784
|
+
// Single pass: collect columns and build column info simultaneously
|
|
1785
|
+
const updateColumnsSet = new Set();
|
|
1786
|
+
const allColumnsSet = new Set(primaryKeys);
|
|
1787
|
+
for (const record of data) {
|
|
1788
|
+
for (const key of Object.keys(record)) {
|
|
1789
|
+
if (schema.columns[key]) {
|
|
1790
|
+
allColumnsSet.add(key);
|
|
1791
|
+
if (!primaryKeySet.has(key)) {
|
|
1792
|
+
updateColumnsSet.add(key);
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
if (updateColumnsSet.size === 0) {
|
|
1798
|
+
throw new Error('No columns to update (only primary keys provided)');
|
|
1799
|
+
}
|
|
1800
|
+
// Build column info - access config once per column
|
|
1801
|
+
const columnInfoList = [];
|
|
1802
|
+
const valueColumnParts = [];
|
|
1254
1803
|
const setClauses = [];
|
|
1255
|
-
const
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
const
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1804
|
+
const whereClauseParts = [];
|
|
1805
|
+
for (const propName of allColumnsSet) {
|
|
1806
|
+
const colConfig = schema.columns[propName].build();
|
|
1807
|
+
const dbName = colConfig.name;
|
|
1808
|
+
const pgType = DbEntityTable.PG_TYPE_MAP[colConfig.type] || colConfig.type;
|
|
1809
|
+
const isPK = primaryKeySet.has(propName);
|
|
1810
|
+
const info = { propName, dbName, pgType, isPK };
|
|
1811
|
+
columnInfoList.push(info);
|
|
1812
|
+
// Build VALUES column list
|
|
1813
|
+
valueColumnParts.push(`"${dbName}"`);
|
|
1814
|
+
if (!isPK) {
|
|
1815
|
+
valueColumnParts.push(`"${dbName}__provided"`);
|
|
1816
|
+
// Build SET clause
|
|
1817
|
+
setClauses.push(`"${dbName}" = CASE WHEN v."${dbName}__provided" THEN v."${dbName}" ELSE t."${dbName}" END`);
|
|
1818
|
+
}
|
|
1819
|
+
else {
|
|
1820
|
+
// Build WHERE clause for PK
|
|
1821
|
+
whereClauseParts.push(`t."${dbName}" = v."${dbName}"`);
|
|
1263
1822
|
}
|
|
1264
1823
|
}
|
|
1265
|
-
|
|
1266
|
-
|
|
1824
|
+
const valueColumnList = valueColumnParts.join(', ');
|
|
1825
|
+
const whereClause = whereClauseParts.join(' AND ');
|
|
1826
|
+
// Build VALUES clause with parameters - single pass over data
|
|
1827
|
+
const valuesClauses = [];
|
|
1828
|
+
const params = [];
|
|
1829
|
+
let paramIndex = 1;
|
|
1830
|
+
for (const record of data) {
|
|
1831
|
+
const rowValues = [];
|
|
1832
|
+
for (const col of columnInfoList) {
|
|
1833
|
+
const hasKey = col.propName in record;
|
|
1834
|
+
const value = record[col.propName];
|
|
1835
|
+
// Add the value (always cast to correct type for NULL to work in CASE expressions)
|
|
1836
|
+
if (value === undefined || value === null) {
|
|
1837
|
+
rowValues.push(`NULL::${col.pgType}`);
|
|
1838
|
+
}
|
|
1839
|
+
else {
|
|
1840
|
+
rowValues.push(`$${paramIndex++}::${col.pgType}`);
|
|
1841
|
+
params.push(value);
|
|
1842
|
+
}
|
|
1843
|
+
// Add the "was provided" flag for non-PK columns
|
|
1844
|
+
if (!col.isPK) {
|
|
1845
|
+
rowValues.push(hasKey ? 'true' : 'false');
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
valuesClauses.push(`(${rowValues.join(', ')})`);
|
|
1267
1849
|
}
|
|
1268
|
-
// Build WHERE clause
|
|
1269
|
-
const mockEntity = this.createMockEntity();
|
|
1270
|
-
const whereCondition = condition(mockEntity);
|
|
1271
|
-
const condBuilder = new conditions_1.ConditionBuilder();
|
|
1272
|
-
const { sql: whereSql, params: whereParams } = condBuilder.build(whereCondition, paramIndex);
|
|
1273
|
-
values.push(...whereParams);
|
|
1274
1850
|
// Build RETURNING clause
|
|
1275
|
-
const
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
`;
|
|
1851
|
+
const returningClause = this.buildReturningClause(returning, 't');
|
|
1852
|
+
let sql = `
|
|
1853
|
+
UPDATE ${qualifiedTableName} AS t
|
|
1854
|
+
SET ${setClauses.join(', ')}
|
|
1855
|
+
FROM (VALUES ${valuesClauses.join(', ')}) AS v(${valueColumnList})
|
|
1856
|
+
WHERE ${whereClause}`.trim();
|
|
1857
|
+
if (returningClause) {
|
|
1858
|
+
sql += ` RETURNING ${returningClause.sql}`;
|
|
1859
|
+
}
|
|
1285
1860
|
const result = executor
|
|
1286
|
-
? await executor.query(sql,
|
|
1287
|
-
: await client.query(sql,
|
|
1288
|
-
|
|
1861
|
+
? await executor.query(sql, params)
|
|
1862
|
+
: await client.query(sql, params);
|
|
1863
|
+
if (!returningClause) {
|
|
1864
|
+
return undefined;
|
|
1865
|
+
}
|
|
1866
|
+
return this.mapReturningResults(result.rows, returning);
|
|
1289
1867
|
}
|
|
1290
1868
|
/**
|
|
1291
1869
|
* Delete records matching condition
|
|
@@ -1349,8 +1927,111 @@ class DbEntityTable {
|
|
|
1349
1927
|
}
|
|
1350
1928
|
return mock;
|
|
1351
1929
|
}
|
|
1930
|
+
/**
|
|
1931
|
+
* Build RETURNING clause SQL based on config
|
|
1932
|
+
* @internal
|
|
1933
|
+
*/
|
|
1934
|
+
buildReturningClause(returning, tableAlias) {
|
|
1935
|
+
if (returning === undefined) {
|
|
1936
|
+
return null; // No RETURNING
|
|
1937
|
+
}
|
|
1938
|
+
const schema = this._getSchema();
|
|
1939
|
+
const prefix = tableAlias ? `${tableAlias}.` : '';
|
|
1940
|
+
if (returning === true) {
|
|
1941
|
+
// Return all columns
|
|
1942
|
+
const columns = Object.values(schema.columns).map(col => col.build().name);
|
|
1943
|
+
const sql = columns.map(name => `${prefix}"${name}"`).join(', ');
|
|
1944
|
+
return { sql, columns };
|
|
1945
|
+
}
|
|
1946
|
+
// Selector function - extract selected columns
|
|
1947
|
+
const mockEntity = this.createMockEntity();
|
|
1948
|
+
const selection = returning(mockEntity);
|
|
1949
|
+
if (typeof selection === 'object' && selection !== null) {
|
|
1950
|
+
const columns = [];
|
|
1951
|
+
const sqlParts = [];
|
|
1952
|
+
for (const [alias, field] of Object.entries(selection)) {
|
|
1953
|
+
if (field && typeof field === 'object' && '__dbColumnName' in field) {
|
|
1954
|
+
const dbName = field.__dbColumnName;
|
|
1955
|
+
columns.push(alias);
|
|
1956
|
+
sqlParts.push(`${prefix}"${dbName}" AS "${alias}"`);
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
return { sql: sqlParts.join(', '), columns };
|
|
1960
|
+
}
|
|
1961
|
+
// Single field selection
|
|
1962
|
+
if (selection && typeof selection === 'object' && '__dbColumnName' in selection) {
|
|
1963
|
+
const dbName = selection.__dbColumnName;
|
|
1964
|
+
return { sql: `${prefix}"${dbName}"`, columns: [dbName] };
|
|
1965
|
+
}
|
|
1966
|
+
return null;
|
|
1967
|
+
}
|
|
1968
|
+
/**
|
|
1969
|
+
* Map row results based on returning config
|
|
1970
|
+
* @internal
|
|
1971
|
+
*/
|
|
1972
|
+
mapReturningResults(rows, returning) {
|
|
1973
|
+
if (returning === true) {
|
|
1974
|
+
// Full entity mapping
|
|
1975
|
+
return this.mapResultsToEntities(rows);
|
|
1976
|
+
}
|
|
1977
|
+
// For selector functions, rows are already in the correct shape
|
|
1978
|
+
// Just apply any type mappers needed
|
|
1979
|
+
const schema = this._getSchema();
|
|
1980
|
+
return rows.map(row => {
|
|
1981
|
+
const mapped = {};
|
|
1982
|
+
for (const [key, value] of Object.entries(row)) {
|
|
1983
|
+
// Try to find column by alias or name
|
|
1984
|
+
const colEntry = Object.entries(schema.columns).find(([propName, col]) => {
|
|
1985
|
+
const config = col.build();
|
|
1986
|
+
return propName === key || config.name === key;
|
|
1987
|
+
});
|
|
1988
|
+
if (colEntry) {
|
|
1989
|
+
const [propName, col] = colEntry;
|
|
1990
|
+
const config = col.build();
|
|
1991
|
+
// Apply fromDriver mapper if present
|
|
1992
|
+
mapped[key] = config.mapper ? config.mapper.fromDriver(value) : value;
|
|
1993
|
+
}
|
|
1994
|
+
else {
|
|
1995
|
+
mapped[key] = value;
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
return mapped;
|
|
1999
|
+
});
|
|
2000
|
+
}
|
|
1352
2001
|
}
|
|
1353
2002
|
exports.DbEntityTable = DbEntityTable;
|
|
2003
|
+
/** Static type map for PostgreSQL type casting - computed once */
|
|
2004
|
+
DbEntityTable.PG_TYPE_MAP = {
|
|
2005
|
+
'smallint': 'smallint',
|
|
2006
|
+
'integer': 'integer',
|
|
2007
|
+
'bigint': 'bigint',
|
|
2008
|
+
'serial': 'integer',
|
|
2009
|
+
'smallserial': 'smallint',
|
|
2010
|
+
'bigserial': 'bigint',
|
|
2011
|
+
'decimal': 'decimal',
|
|
2012
|
+
'numeric': 'numeric',
|
|
2013
|
+
'real': 'real',
|
|
2014
|
+
'double precision': 'double precision',
|
|
2015
|
+
'money': 'money',
|
|
2016
|
+
'varchar': 'varchar',
|
|
2017
|
+
'char': 'char',
|
|
2018
|
+
'text': 'text',
|
|
2019
|
+
'bytea': 'bytea',
|
|
2020
|
+
'timestamp': 'timestamp',
|
|
2021
|
+
'timestamptz': 'timestamptz',
|
|
2022
|
+
'date': 'date',
|
|
2023
|
+
'time': 'time',
|
|
2024
|
+
'timetz': 'timetz',
|
|
2025
|
+
'interval': 'interval',
|
|
2026
|
+
'boolean': 'boolean',
|
|
2027
|
+
'uuid': 'uuid',
|
|
2028
|
+
'json': 'json',
|
|
2029
|
+
'jsonb': 'jsonb',
|
|
2030
|
+
'inet': 'inet',
|
|
2031
|
+
'cidr': 'cidr',
|
|
2032
|
+
'macaddr': 'macaddr',
|
|
2033
|
+
'macaddr8': 'macaddr8',
|
|
2034
|
+
};
|
|
1354
2035
|
/**
|
|
1355
2036
|
* Base database context with entity-first approach
|
|
1356
2037
|
*/
|