typeorm 0.3.25-dev.5003aaa → 0.3.25-dev.63a3b9a
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 +9 -9
- package/browser/driver/oracle/OracleDriver.js +1 -1
- package/browser/driver/oracle/OracleDriver.js.map +1 -1
- package/browser/driver/postgres/PostgresDriver.js +2 -1
- package/browser/driver/postgres/PostgresDriver.js.map +1 -1
- package/browser/driver/postgres/PostgresQueryRunner.js +8 -0
- package/browser/driver/postgres/PostgresQueryRunner.js.map +1 -1
- package/browser/driver/sap/SapDriver.js +1 -1
- package/browser/driver/sap/SapDriver.js.map +1 -1
- package/browser/driver/spanner/SpannerConnectionCredentialsOptions.d.ts +13 -0
- package/browser/driver/spanner/SpannerConnectionCredentialsOptions.js.map +1 -1
- package/browser/driver/spanner/SpannerDriver.js +7 -0
- package/browser/driver/spanner/SpannerDriver.js.map +1 -1
- package/browser/driver/sqlserver/SqlServerDriver.js +1 -1
- package/browser/driver/sqlserver/SqlServerDriver.js.map +1 -1
- package/browser/driver/types/UpsertType.d.ts +1 -1
- package/browser/driver/types/UpsertType.js.map +1 -1
- package/browser/entity-manager/EntityManager.d.ts +1 -1
- package/browser/entity-manager/EntityManager.js +1 -1
- package/browser/entity-manager/EntityManager.js.map +1 -1
- package/browser/error/InitializedRelationError.d.ts +1 -1
- package/browser/error/InitializedRelationError.js +1 -1
- package/browser/error/InitializedRelationError.js.map +1 -1
- package/browser/metadata/ColumnMetadata.d.ts +2 -2
- package/browser/metadata/ColumnMetadata.js +1 -1
- package/browser/metadata/ColumnMetadata.js.map +1 -1
- package/browser/metadata-builder/ClosureJunctionEntityMetadataBuilder.js +14 -0
- package/browser/metadata-builder/ClosureJunctionEntityMetadataBuilder.js.map +1 -1
- package/browser/metadata-builder/RelationJoinColumnBuilder.js +7 -0
- package/browser/metadata-builder/RelationJoinColumnBuilder.js.map +1 -1
- package/browser/query-builder/InsertOrUpdateOptions.d.ts +6 -0
- package/browser/query-builder/InsertOrUpdateOptions.js.map +1 -1
- package/browser/query-builder/InsertQueryBuilder.d.ts +17 -0
- package/browser/query-builder/InsertQueryBuilder.js +483 -156
- package/browser/query-builder/InsertQueryBuilder.js.map +1 -1
- package/browser/query-builder/QueryExpressionMap.d.ts +1 -0
- package/browser/query-builder/QueryExpressionMap.js.map +1 -1
- package/browser/query-builder/SelectQueryBuilder.js +21 -18
- package/browser/query-builder/SelectQueryBuilder.js.map +1 -1
- package/browser/util/OrmUtils.d.ts +4 -0
- package/browser/util/OrmUtils.js +9 -0
- package/browser/util/OrmUtils.js.map +1 -1
- package/driver/oracle/OracleDriver.js +1 -1
- package/driver/oracle/OracleDriver.js.map +1 -1
- package/driver/postgres/PostgresDriver.js +2 -1
- package/driver/postgres/PostgresDriver.js.map +1 -1
- package/driver/postgres/PostgresQueryRunner.js +8 -0
- package/driver/postgres/PostgresQueryRunner.js.map +1 -1
- package/driver/sap/SapDriver.js +1 -1
- package/driver/sap/SapDriver.js.map +1 -1
- package/driver/spanner/SpannerConnectionCredentialsOptions.d.ts +13 -0
- package/driver/spanner/SpannerConnectionCredentialsOptions.js.map +1 -1
- package/driver/spanner/SpannerDriver.js +7 -0
- package/driver/spanner/SpannerDriver.js.map +1 -1
- package/driver/sqlserver/SqlServerDriver.js +1 -1
- package/driver/sqlserver/SqlServerDriver.js.map +1 -1
- package/driver/types/UpsertType.d.ts +1 -1
- package/driver/types/UpsertType.js.map +1 -1
- package/entity-manager/EntityManager.d.ts +1 -1
- package/entity-manager/EntityManager.js +1 -1
- package/entity-manager/EntityManager.js.map +1 -1
- package/error/InitializedRelationError.d.ts +1 -1
- package/error/InitializedRelationError.js +1 -1
- package/error/InitializedRelationError.js.map +1 -1
- package/metadata/ColumnMetadata.d.ts +2 -2
- package/metadata/ColumnMetadata.js +1 -1
- package/metadata/ColumnMetadata.js.map +1 -1
- package/metadata-builder/ClosureJunctionEntityMetadataBuilder.js +14 -0
- package/metadata-builder/ClosureJunctionEntityMetadataBuilder.js.map +1 -1
- package/metadata-builder/RelationJoinColumnBuilder.js +7 -0
- package/metadata-builder/RelationJoinColumnBuilder.js.map +1 -1
- package/package.json +1 -1
- package/query-builder/InsertOrUpdateOptions.d.ts +6 -0
- package/query-builder/InsertOrUpdateOptions.js.map +1 -1
- package/query-builder/InsertQueryBuilder.d.ts +17 -0
- package/query-builder/InsertQueryBuilder.js +483 -156
- package/query-builder/InsertQueryBuilder.js.map +1 -1
- package/query-builder/QueryExpressionMap.d.ts +1 -0
- package/query-builder/QueryExpressionMap.js.map +1 -1
- package/query-builder/SelectQueryBuilder.js +21 -18
- package/query-builder/SelectQueryBuilder.js.map +1 -1
- package/util/OrmUtils.d.ts +4 -0
- package/util/OrmUtils.js +9 -0
- package/util/OrmUtils.js.map +1 -1
|
@@ -215,6 +215,15 @@ export class InsertQueryBuilder extends QueryBuilder {
|
|
|
215
215
|
* Adds additional update statement supported in databases.
|
|
216
216
|
*/
|
|
217
217
|
orUpdate(statementOrOverwrite, conflictTarget, orUpdateOptions) {
|
|
218
|
+
const { where, parameters } = orUpdateOptions?.overwriteCondition ?? {};
|
|
219
|
+
let wheres;
|
|
220
|
+
if (where) {
|
|
221
|
+
const condition = this.getWhereCondition(where);
|
|
222
|
+
if (Array.isArray(condition) ? condition.length !== 0 : condition)
|
|
223
|
+
wheres = [{ type: "simple", condition: condition }];
|
|
224
|
+
}
|
|
225
|
+
if (parameters)
|
|
226
|
+
this.setParameters(parameters);
|
|
218
227
|
if (!Array.isArray(statementOrOverwrite)) {
|
|
219
228
|
this.expressionMap.onUpdate = {
|
|
220
229
|
conflict: statementOrOverwrite?.conflict_target,
|
|
@@ -222,6 +231,7 @@ export class InsertQueryBuilder extends QueryBuilder {
|
|
|
222
231
|
overwrite: statementOrOverwrite?.overwrite,
|
|
223
232
|
skipUpdateIfNoValuesChanged: orUpdateOptions?.skipUpdateIfNoValuesChanged,
|
|
224
233
|
upsertType: orUpdateOptions?.upsertType,
|
|
234
|
+
overwriteCondition: wheres,
|
|
225
235
|
};
|
|
226
236
|
return this;
|
|
227
237
|
}
|
|
@@ -231,6 +241,7 @@ export class InsertQueryBuilder extends QueryBuilder {
|
|
|
231
241
|
skipUpdateIfNoValuesChanged: orUpdateOptions?.skipUpdateIfNoValuesChanged,
|
|
232
242
|
indexPredicate: orUpdateOptions?.indexPredicate,
|
|
233
243
|
upsertType: orUpdateOptions?.upsertType,
|
|
244
|
+
overwriteCondition: wheres,
|
|
234
245
|
};
|
|
235
246
|
return this;
|
|
236
247
|
}
|
|
@@ -241,7 +252,16 @@ export class InsertQueryBuilder extends QueryBuilder {
|
|
|
241
252
|
* Creates INSERT express used to perform insert query.
|
|
242
253
|
*/
|
|
243
254
|
createInsertExpression() {
|
|
255
|
+
if (this.expressionMap.onUpdate || this.expressionMap.onIgnore) {
|
|
256
|
+
if ((this.expressionMap.onUpdate?.upsertType ?? "merge-into") ===
|
|
257
|
+
"merge-into" &&
|
|
258
|
+
this.connection.driver.supportedUpsertTypes.includes("merge-into"))
|
|
259
|
+
return this.createMergeExpression();
|
|
260
|
+
}
|
|
244
261
|
const tableName = this.getTableName(this.getMainTableName());
|
|
262
|
+
const tableOrAliasName = this.alias !== this.getMainTableName()
|
|
263
|
+
? this.escape(this.alias)
|
|
264
|
+
: tableName;
|
|
245
265
|
const valuesExpression = this.createValuesExpression(); // its important to get values before returning expression because oracle rely on native parameters and ordering of them is important
|
|
246
266
|
const returningExpression = this.connection.driver.options.type === "oracle" &&
|
|
247
267
|
this.getValueSets().length > 1
|
|
@@ -350,13 +370,22 @@ export class InsertQueryBuilder extends QueryBuilder {
|
|
|
350
370
|
query += updatePart.join(", ");
|
|
351
371
|
}
|
|
352
372
|
if (Array.isArray(overwrite) &&
|
|
353
|
-
skipUpdateIfNoValuesChanged
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
373
|
+
skipUpdateIfNoValuesChanged) {
|
|
374
|
+
this.expressionMap.onUpdate.overwriteCondition ??= [];
|
|
375
|
+
const wheres = overwrite.map((column) => ({
|
|
376
|
+
type: "or",
|
|
377
|
+
condition: `${tableOrAliasName}.${this.escape(column)} IS DISTINCT FROM EXCLUDED.${this.escape(column)}`,
|
|
378
|
+
}));
|
|
379
|
+
this.expressionMap.onUpdate.overwriteCondition.push({
|
|
380
|
+
type: "and",
|
|
381
|
+
condition: wheres,
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
if (DriverUtils.isPostgresFamily(this.connection.driver) &&
|
|
385
|
+
this.expressionMap.onUpdate.overwriteCondition &&
|
|
386
|
+
this.expressionMap.onUpdate.overwriteCondition.length >
|
|
387
|
+
0) {
|
|
388
|
+
query += ` WHERE ${this.createUpsertConditionExpression()}`;
|
|
360
389
|
}
|
|
361
390
|
}
|
|
362
391
|
}
|
|
@@ -487,165 +516,26 @@ export class InsertQueryBuilder extends QueryBuilder {
|
|
|
487
516
|
expression += "(";
|
|
488
517
|
}
|
|
489
518
|
}
|
|
490
|
-
|
|
491
|
-
let value = column.getEntityValue(valueSet);
|
|
492
|
-
// if column is relational and value is an object then get real referenced column value from this object
|
|
493
|
-
// for example column value is { question: { id: 1 } }, value will be equal to { id: 1 }
|
|
494
|
-
// and we extract "1" from this object
|
|
495
|
-
/*if (column.referencedColumn && value instanceof Object && !(typeof value === "function")) { // todo: check if we still need it since getEntityValue already has similar code
|
|
496
|
-
value = column.referencedColumn.getEntityValue(value);
|
|
497
|
-
}*/
|
|
498
|
-
if (!(typeof value === "function")) {
|
|
499
|
-
// make sure our value is normalized by a driver
|
|
500
|
-
value = this.connection.driver.preparePersistentValue(value, column);
|
|
501
|
-
}
|
|
502
|
-
// newly inserted entities always have a version equal to 1 (first version)
|
|
503
|
-
// also, user-specified version must be empty
|
|
504
|
-
if (column.isVersion && value === undefined) {
|
|
505
|
-
expression += "1";
|
|
506
|
-
// } else if (column.isNestedSetLeft) {
|
|
507
|
-
// const tableName = this.connection.driver.escape(column.entityMetadata.tablePath);
|
|
508
|
-
// const rightColumnName = this.connection.driver.escape(column.entityMetadata.nestedSetRightColumn!.databaseName);
|
|
509
|
-
// const subQuery = `(SELECT c.max + 1 FROM (SELECT MAX(${rightColumnName}) as max from ${tableName}) c)`;
|
|
510
|
-
// expression += subQuery;
|
|
511
|
-
//
|
|
512
|
-
// } else if (column.isNestedSetRight) {
|
|
513
|
-
// const tableName = this.connection.driver.escape(column.entityMetadata.tablePath);
|
|
514
|
-
// const rightColumnName = this.connection.driver.escape(column.entityMetadata.nestedSetRightColumn!.databaseName);
|
|
515
|
-
// const subQuery = `(SELECT c.max + 2 FROM (SELECT MAX(${rightColumnName}) as max from ${tableName}) c)`;
|
|
516
|
-
// expression += subQuery;
|
|
517
|
-
}
|
|
518
|
-
else if (column.isDiscriminator) {
|
|
519
|
-
expression += this.createParameter(this.expressionMap.mainAlias.metadata
|
|
520
|
-
.discriminatorValue);
|
|
521
|
-
// return "1";
|
|
522
|
-
// for create and update dates we insert current date
|
|
523
|
-
// no, we don't do it because this constant is already in "default" value of the column
|
|
524
|
-
// with extended timestamp functionality, like CURRENT_TIMESTAMP(6) for example
|
|
525
|
-
// } else if (column.isCreateDate || column.isUpdateDate) {
|
|
526
|
-
// return "CURRENT_TIMESTAMP";
|
|
527
|
-
// if column is generated uuid and database does not support its generation and custom generated value was not provided by a user - we generate a new uuid value for insertion
|
|
528
|
-
}
|
|
529
|
-
else if (column.isGenerated &&
|
|
530
|
-
column.generationStrategy === "uuid" &&
|
|
531
|
-
!this.connection.driver.isUUIDGenerationSupported() &&
|
|
532
|
-
value === undefined) {
|
|
533
|
-
value = uuidv4();
|
|
534
|
-
expression += this.createParameter(value);
|
|
535
|
-
if (!(valueSetIndex in
|
|
536
|
-
this.expressionMap.locallyGenerated)) {
|
|
537
|
-
this.expressionMap.locallyGenerated[valueSetIndex] =
|
|
538
|
-
{};
|
|
539
|
-
}
|
|
540
|
-
column.setEntityValue(this.expressionMap.locallyGenerated[valueSetIndex], value);
|
|
541
|
-
// if value for this column was not provided then insert default value
|
|
542
|
-
}
|
|
543
|
-
else if (value === undefined) {
|
|
544
|
-
if ((this.connection.driver.options.type === "oracle" &&
|
|
545
|
-
valueSets.length > 1) ||
|
|
546
|
-
DriverUtils.isSQLiteFamily(this.connection.driver) ||
|
|
547
|
-
this.connection.driver.options.type === "sap" ||
|
|
548
|
-
this.connection.driver.options.type === "spanner") {
|
|
549
|
-
// unfortunately sqlite does not support DEFAULT expression in INSERT queries
|
|
550
|
-
if (column.default !== undefined &&
|
|
551
|
-
column.default !== null) {
|
|
552
|
-
// try to use default defined in the column
|
|
553
|
-
expression +=
|
|
554
|
-
this.connection.driver.normalizeDefault(column);
|
|
555
|
-
}
|
|
556
|
-
else if (this.connection.driver.options.type ===
|
|
557
|
-
"spanner" &&
|
|
558
|
-
column.isGenerated &&
|
|
559
|
-
column.generationStrategy === "uuid") {
|
|
560
|
-
expression += "GENERATE_UUID()"; // Produces a random universally unique identifier (UUID) as a STRING value.
|
|
561
|
-
}
|
|
562
|
-
else {
|
|
563
|
-
expression += "NULL"; // otherwise simply use NULL and pray if column is nullable
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
else {
|
|
567
|
-
expression += "DEFAULT";
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
else if (value === null &&
|
|
571
|
-
(this.connection.driver.options.type === "spanner" ||
|
|
572
|
-
this.connection.driver.options.type === "oracle")) {
|
|
573
|
-
expression += "NULL";
|
|
574
|
-
// support for SQL expressions in queries
|
|
575
|
-
}
|
|
576
|
-
else if (typeof value === "function") {
|
|
577
|
-
expression += value();
|
|
578
|
-
// just any other regular value
|
|
579
|
-
}
|
|
580
|
-
else {
|
|
581
|
-
if (this.connection.driver.options.type === "mssql")
|
|
582
|
-
value = this.connection.driver.parametrizeValue(column, value);
|
|
583
|
-
// we need to store array values in a special class to make sure parameter replacement will work correctly
|
|
584
|
-
// if (value instanceof Array)
|
|
585
|
-
// value = new ArrayParameter(value);
|
|
586
|
-
const paramName = this.createParameter(value);
|
|
587
|
-
if ((DriverUtils.isMySQLFamily(this.connection.driver) ||
|
|
588
|
-
this.connection.driver.options.type ===
|
|
589
|
-
"aurora-mysql") &&
|
|
590
|
-
this.connection.driver.spatialTypes.indexOf(column.type) !== -1) {
|
|
591
|
-
const useLegacy = this.connection.driver.options.legacySpatialSupport;
|
|
592
|
-
const geomFromText = useLegacy
|
|
593
|
-
? "GeomFromText"
|
|
594
|
-
: "ST_GeomFromText";
|
|
595
|
-
if (column.srid != null) {
|
|
596
|
-
expression += `${geomFromText}(${paramName}, ${column.srid})`;
|
|
597
|
-
}
|
|
598
|
-
else {
|
|
599
|
-
expression += `${geomFromText}(${paramName})`;
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
else if (DriverUtils.isPostgresFamily(this.connection.driver) &&
|
|
603
|
-
this.connection.driver.spatialTypes.indexOf(column.type) !== -1) {
|
|
604
|
-
if (column.srid != null) {
|
|
605
|
-
expression += `ST_SetSRID(ST_GeomFromGeoJSON(${paramName}), ${column.srid})::${column.type}`;
|
|
606
|
-
}
|
|
607
|
-
else {
|
|
608
|
-
expression += `ST_GeomFromGeoJSON(${paramName})::${column.type}`;
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
else if (this.connection.driver.options.type === "mssql" &&
|
|
612
|
-
this.connection.driver.spatialTypes.indexOf(column.type) !== -1) {
|
|
613
|
-
expression +=
|
|
614
|
-
column.type +
|
|
615
|
-
"::STGeomFromText(" +
|
|
616
|
-
paramName +
|
|
617
|
-
", " +
|
|
618
|
-
(column.srid || "0") +
|
|
619
|
-
")";
|
|
620
|
-
}
|
|
621
|
-
else {
|
|
622
|
-
expression += paramName;
|
|
623
|
-
}
|
|
624
|
-
}
|
|
519
|
+
expression += this.createColumnValueExpression(valueSets, valueSetIndex, column);
|
|
625
520
|
if (columnIndex === columns.length - 1) {
|
|
626
521
|
if (valueSetIndex === valueSets.length - 1) {
|
|
627
|
-
if (this.connection.driver.options.type
|
|
628
|
-
"oracle" &&
|
|
629
|
-
valueSets.length > 1) {
|
|
630
|
-
expression += " FROM DUAL ";
|
|
631
|
-
}
|
|
632
|
-
else if (this.connection.driver.options.type === "sap" &&
|
|
522
|
+
if (["oracle", "sap"].includes(this.connection.driver.options.type) &&
|
|
633
523
|
valueSets.length > 1) {
|
|
634
|
-
expression +=
|
|
524
|
+
expression +=
|
|
525
|
+
" FROM " +
|
|
526
|
+
this.connection.driver.dummyTableName;
|
|
635
527
|
}
|
|
636
528
|
else {
|
|
637
529
|
expression += ")";
|
|
638
530
|
}
|
|
639
531
|
}
|
|
640
532
|
else {
|
|
641
|
-
if (this.connection.driver.options.type
|
|
642
|
-
"oracle" &&
|
|
533
|
+
if (["oracle", "sap"].includes(this.connection.driver.options.type) &&
|
|
643
534
|
valueSets.length > 1) {
|
|
644
|
-
expression +=
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
expression += " FROM dummy UNION ALL ";
|
|
535
|
+
expression +=
|
|
536
|
+
" FROM " +
|
|
537
|
+
this.connection.driver.dummyTableName +
|
|
538
|
+
" UNION ALL ";
|
|
649
539
|
}
|
|
650
540
|
else {
|
|
651
541
|
expression += "), ";
|
|
@@ -736,6 +626,443 @@ export class InsertQueryBuilder extends QueryBuilder {
|
|
|
736
626
|
this.getValueSets().some((valueSet) => column.getEntityValue(valueSet) !== undefined &&
|
|
737
627
|
column.getEntityValue(valueSet) !== null));
|
|
738
628
|
}
|
|
629
|
+
/**
|
|
630
|
+
* Creates MERGE express used to perform insert query.
|
|
631
|
+
*/
|
|
632
|
+
createMergeExpression() {
|
|
633
|
+
if (!this.connection.driver.supportedUpsertTypes.includes("merge-into"))
|
|
634
|
+
throw new TypeORMError(`Upsert type "merge-into" is not supported by current database driver`);
|
|
635
|
+
if (this.expressionMap.onUpdate?.upsertType &&
|
|
636
|
+
this.expressionMap.onUpdate.upsertType !== "merge-into") {
|
|
637
|
+
throw new TypeORMError(`Upsert type "${this.expressionMap.onUpdate.upsertType}" is not supported by current database driver`);
|
|
638
|
+
}
|
|
639
|
+
// const mainAlias = this.expressionMap.mainAlias!
|
|
640
|
+
const tableName = this.getTableName(this.getMainTableName());
|
|
641
|
+
const tableAlias = this.escape(this.alias);
|
|
642
|
+
const columns = this.getInsertedColumns();
|
|
643
|
+
const columnsExpression = this.createColumnNamesExpression();
|
|
644
|
+
let query = `MERGE INTO ${tableName} ${this.escape(this.alias)}`;
|
|
645
|
+
const mergeSourceAlias = this.escape("mergeIntoSource");
|
|
646
|
+
const mergeSourceExpression = this.createMergeIntoSourceExpression(mergeSourceAlias);
|
|
647
|
+
query += ` ${mergeSourceExpression}`;
|
|
648
|
+
// build on condition
|
|
649
|
+
if (this.expressionMap.onIgnore) {
|
|
650
|
+
const primaryKey = columns.find((column) => column.isPrimary);
|
|
651
|
+
if (primaryKey) {
|
|
652
|
+
query += ` ON (${tableAlias}.${this.escape(primaryKey.databaseName)} = ${mergeSourceAlias}.${this.escape(primaryKey.databaseName)})`;
|
|
653
|
+
}
|
|
654
|
+
else {
|
|
655
|
+
query += `ON (${this.expressionMap
|
|
656
|
+
.mainAlias.metadata.uniques.map((unique) => {
|
|
657
|
+
return `(${unique.columns
|
|
658
|
+
.map((column) => {
|
|
659
|
+
return `${tableAlias}.${this.escape(column.databaseName)} = ${mergeSourceAlias}.${this.escape(column.databaseName)}`;
|
|
660
|
+
})
|
|
661
|
+
.join(" AND ")})`;
|
|
662
|
+
})
|
|
663
|
+
.join(" OR ")})`;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
else if (this.expressionMap.onUpdate) {
|
|
667
|
+
const { conflict, indexPredicate } = this.expressionMap.onUpdate;
|
|
668
|
+
if (indexPredicate) {
|
|
669
|
+
throw new TypeORMError(`indexPredicate option is not supported by upsert type "merge-into"`);
|
|
670
|
+
}
|
|
671
|
+
if (Array.isArray(conflict)) {
|
|
672
|
+
query += ` ON (${conflict
|
|
673
|
+
.map((column) => `${tableAlias}.${this.escape(column)} = ${mergeSourceAlias}.${this.escape(column)}`)
|
|
674
|
+
.join(" AND ")})`;
|
|
675
|
+
}
|
|
676
|
+
else if (conflict) {
|
|
677
|
+
query += ` ON (${tableAlias}.${this.escape(conflict)} = ${mergeSourceAlias}.${this.escape(conflict)})`;
|
|
678
|
+
}
|
|
679
|
+
else {
|
|
680
|
+
query += `ON (${this.expressionMap
|
|
681
|
+
.mainAlias.metadata.uniques.map((unique) => {
|
|
682
|
+
return `(${unique.columns
|
|
683
|
+
.map((column) => {
|
|
684
|
+
return `${tableAlias}.${this.escape(column.databaseName)} = ${mergeSourceAlias}.${this.escape(column.databaseName)}`;
|
|
685
|
+
})
|
|
686
|
+
.join(" AND ")})`;
|
|
687
|
+
})
|
|
688
|
+
.join(" OR ")})`;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
if (this.expressionMap.onUpdate) {
|
|
692
|
+
const { overwrite, columns, conflict, skipUpdateIfNoValuesChanged, } = this.expressionMap.onUpdate;
|
|
693
|
+
let updateExpression = "";
|
|
694
|
+
if (Array.isArray(overwrite)) {
|
|
695
|
+
updateExpression += (overwrite || columns)
|
|
696
|
+
?.filter((column) => !conflict?.includes(column))
|
|
697
|
+
.map((column) => `${tableAlias}.${this.escape(column)} = ${mergeSourceAlias}.${this.escape(column)}`)
|
|
698
|
+
.join(", ");
|
|
699
|
+
}
|
|
700
|
+
if (Array.isArray(overwrite) && skipUpdateIfNoValuesChanged) {
|
|
701
|
+
this.expressionMap.onUpdate.overwriteCondition ??= [];
|
|
702
|
+
const wheres = overwrite.map((column) => ({
|
|
703
|
+
type: "or",
|
|
704
|
+
condition: {
|
|
705
|
+
operator: "notEqual",
|
|
706
|
+
parameters: [
|
|
707
|
+
`${tableAlias}.${this.escape(column)}`,
|
|
708
|
+
`${mergeSourceAlias}.${this.escape(column)}`,
|
|
709
|
+
],
|
|
710
|
+
},
|
|
711
|
+
}));
|
|
712
|
+
this.expressionMap.onUpdate.overwriteCondition.push({
|
|
713
|
+
type: "and",
|
|
714
|
+
condition: wheres,
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
const mergeCondition = this.createUpsertConditionExpression();
|
|
718
|
+
if (updateExpression.trim()) {
|
|
719
|
+
if ((this.connection.driver.options.type === "mssql" ||
|
|
720
|
+
this.connection.driver.options.type === "sap") &&
|
|
721
|
+
mergeCondition != "") {
|
|
722
|
+
query += ` WHEN MATCHED AND ${mergeCondition} THEN UPDATE SET ${updateExpression}`;
|
|
723
|
+
}
|
|
724
|
+
else {
|
|
725
|
+
query += ` WHEN MATCHED THEN UPDATE SET ${updateExpression}`;
|
|
726
|
+
if (mergeCondition != "") {
|
|
727
|
+
query += ` WHERE ${mergeCondition}`;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
const valuesExpression = this.createMergeIntoInsertValuesExpression(mergeSourceAlias);
|
|
733
|
+
const returningExpression = this.connection.driver.options.type === "mssql"
|
|
734
|
+
? this.createReturningExpression("insert")
|
|
735
|
+
: null;
|
|
736
|
+
query += " WHEN NOT MATCHED THEN INSERT";
|
|
737
|
+
// add columns expression
|
|
738
|
+
if (columnsExpression) {
|
|
739
|
+
query += `(${columnsExpression})`;
|
|
740
|
+
}
|
|
741
|
+
// add VALUES expression
|
|
742
|
+
if (valuesExpression) {
|
|
743
|
+
query += ` VALUES ${valuesExpression}`;
|
|
744
|
+
}
|
|
745
|
+
// add OUTPUT expression
|
|
746
|
+
if (returningExpression &&
|
|
747
|
+
this.connection.driver.options.type === "mssql") {
|
|
748
|
+
query += ` OUTPUT ${returningExpression}`;
|
|
749
|
+
}
|
|
750
|
+
if (this.connection.driver.options.type === "mssql") {
|
|
751
|
+
query += `;`;
|
|
752
|
+
}
|
|
753
|
+
return query;
|
|
754
|
+
}
|
|
755
|
+
/**
|
|
756
|
+
* Creates list of values needs to be inserted in the VALUES expression.
|
|
757
|
+
*/
|
|
758
|
+
createMergeIntoSourceExpression(mergeSourceAlias) {
|
|
759
|
+
const valueSets = this.getValueSets();
|
|
760
|
+
const columns = this.getInsertedColumns();
|
|
761
|
+
let expression = "USING (";
|
|
762
|
+
// if column metadatas are given then apply all necessary operations with values
|
|
763
|
+
if (columns.length > 0) {
|
|
764
|
+
if (this.connection.driver.options.type === "mssql") {
|
|
765
|
+
expression += "VALUES ";
|
|
766
|
+
}
|
|
767
|
+
valueSets.forEach((valueSet, valueSetIndex) => {
|
|
768
|
+
columns.forEach((column, columnIndex) => {
|
|
769
|
+
if (columnIndex === 0) {
|
|
770
|
+
if (this.connection.driver.options.type === "mssql") {
|
|
771
|
+
expression += "(";
|
|
772
|
+
}
|
|
773
|
+
else {
|
|
774
|
+
expression += "SELECT ";
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
const value = column.getEntityValue(valueSet);
|
|
778
|
+
if (value === undefined &&
|
|
779
|
+
!(column.isGenerated &&
|
|
780
|
+
column.generationStrategy === "uuid" &&
|
|
781
|
+
!this.connection.driver.isUUIDGenerationSupported())) {
|
|
782
|
+
if (column.default !== undefined &&
|
|
783
|
+
column.default !== null) {
|
|
784
|
+
// try to use default defined in the column
|
|
785
|
+
expression +=
|
|
786
|
+
this.connection.driver.normalizeDefault(column);
|
|
787
|
+
}
|
|
788
|
+
else {
|
|
789
|
+
expression += "NULL"; // otherwise simply use NULL and pray if column is nullable
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
else if (value === null) {
|
|
793
|
+
expression += "NULL";
|
|
794
|
+
}
|
|
795
|
+
else {
|
|
796
|
+
expression += this.createColumnValueExpression(valueSets, valueSetIndex, column);
|
|
797
|
+
}
|
|
798
|
+
if (this.connection.driver.options.type !== "mssql")
|
|
799
|
+
expression += ` AS ${this.escape(column.databaseName)}`;
|
|
800
|
+
if (columnIndex === columns.length - 1) {
|
|
801
|
+
if (valueSetIndex === valueSets.length - 1) {
|
|
802
|
+
if (["oracle", "sap"].includes(this.connection.driver.options.type)) {
|
|
803
|
+
expression +=
|
|
804
|
+
" FROM " +
|
|
805
|
+
this.connection.driver.dummyTableName;
|
|
806
|
+
}
|
|
807
|
+
else if (this.connection.driver.options.type === "mssql") {
|
|
808
|
+
expression += ")";
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
else {
|
|
812
|
+
if (["oracle", "sap"].includes(this.connection.driver.options.type) &&
|
|
813
|
+
valueSets.length > 1) {
|
|
814
|
+
expression +=
|
|
815
|
+
" FROM " +
|
|
816
|
+
this.connection.driver.dummyTableName +
|
|
817
|
+
" UNION ALL ";
|
|
818
|
+
}
|
|
819
|
+
else if (this.connection.driver.options.type === "mssql") {
|
|
820
|
+
expression += "), ";
|
|
821
|
+
}
|
|
822
|
+
else {
|
|
823
|
+
expression += " UNION ALL ";
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
else {
|
|
828
|
+
expression += ", ";
|
|
829
|
+
}
|
|
830
|
+
});
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
else {
|
|
834
|
+
// for tables without metadata
|
|
835
|
+
throw new TypeORMError('Upsert type "merge-into" is not supported without metadata tables');
|
|
836
|
+
}
|
|
837
|
+
expression += `) ${mergeSourceAlias}`;
|
|
838
|
+
if (this.connection.driver.options.type === "mssql")
|
|
839
|
+
expression += ` (${columns
|
|
840
|
+
.map((column) => this.escape(column.databaseName))
|
|
841
|
+
.join(", ")})`;
|
|
842
|
+
return expression;
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* Creates list of values needs to be inserted in the VALUES expression.
|
|
846
|
+
*/
|
|
847
|
+
createMergeIntoInsertValuesExpression(mergeSourceAlias) {
|
|
848
|
+
const columns = this.getInsertedColumns();
|
|
849
|
+
let expression = "";
|
|
850
|
+
// if column metadatas are given then apply all necessary operations with values
|
|
851
|
+
if (columns.length > 0) {
|
|
852
|
+
columns.forEach((column, columnIndex) => {
|
|
853
|
+
if (columnIndex === 0) {
|
|
854
|
+
expression += "(";
|
|
855
|
+
}
|
|
856
|
+
if ((column.isGenerated &&
|
|
857
|
+
column.generationStrategy === "uuid" &&
|
|
858
|
+
this.connection.driver.isUUIDGenerationSupported()) ||
|
|
859
|
+
(column.isGenerated && column.generationStrategy !== "uuid")) {
|
|
860
|
+
expression += `DEFAULT`;
|
|
861
|
+
}
|
|
862
|
+
else {
|
|
863
|
+
expression += `${mergeSourceAlias}.${this.escape(column.databaseName)}`;
|
|
864
|
+
}
|
|
865
|
+
if (columnIndex === columns.length - 1) {
|
|
866
|
+
expression += ")";
|
|
867
|
+
}
|
|
868
|
+
else {
|
|
869
|
+
expression += ", ";
|
|
870
|
+
}
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
else {
|
|
874
|
+
// for tables without metadata
|
|
875
|
+
throw new TypeORMError('Upsert type "merge-into" is not supported without metadata tables');
|
|
876
|
+
}
|
|
877
|
+
if (expression === "()")
|
|
878
|
+
return "";
|
|
879
|
+
return expression;
|
|
880
|
+
}
|
|
881
|
+
/**
|
|
882
|
+
* Create upsert search condition expression.
|
|
883
|
+
*/
|
|
884
|
+
createUpsertConditionExpression() {
|
|
885
|
+
if (!this.expressionMap.onUpdate.overwriteCondition)
|
|
886
|
+
return "";
|
|
887
|
+
const conditionsArray = [];
|
|
888
|
+
const whereExpression = this.createWhereClausesExpression(this.expressionMap.onUpdate.overwriteCondition);
|
|
889
|
+
if (whereExpression.length > 0 && whereExpression !== "1=1") {
|
|
890
|
+
conditionsArray.push(whereExpression);
|
|
891
|
+
}
|
|
892
|
+
if (this.expressionMap.mainAlias.hasMetadata) {
|
|
893
|
+
const metadata = this.expressionMap.mainAlias.metadata;
|
|
894
|
+
// Adds the global condition of "non-deleted" for the entity with delete date columns in select query.
|
|
895
|
+
if (this.expressionMap.queryType === "select" &&
|
|
896
|
+
!this.expressionMap.withDeleted &&
|
|
897
|
+
metadata.deleteDateColumn) {
|
|
898
|
+
const column = this.expressionMap.aliasNamePrefixingEnabled
|
|
899
|
+
? this.expressionMap.mainAlias.name +
|
|
900
|
+
"." +
|
|
901
|
+
metadata.deleteDateColumn.propertyName
|
|
902
|
+
: metadata.deleteDateColumn.propertyName;
|
|
903
|
+
const condition = `${column} IS NULL`;
|
|
904
|
+
conditionsArray.push(condition);
|
|
905
|
+
}
|
|
906
|
+
if (metadata.discriminatorColumn && metadata.parentEntityMetadata) {
|
|
907
|
+
const column = this.expressionMap.aliasNamePrefixingEnabled
|
|
908
|
+
? this.expressionMap.mainAlias.name +
|
|
909
|
+
"." +
|
|
910
|
+
metadata.discriminatorColumn.databaseName
|
|
911
|
+
: metadata.discriminatorColumn.databaseName;
|
|
912
|
+
const condition = `${column} IN (:...discriminatorColumnValues)`;
|
|
913
|
+
conditionsArray.push(condition);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
if (this.expressionMap.extraAppendedAndWhereCondition) {
|
|
917
|
+
const condition = this.expressionMap.extraAppendedAndWhereCondition;
|
|
918
|
+
conditionsArray.push(condition);
|
|
919
|
+
}
|
|
920
|
+
let condition = "";
|
|
921
|
+
if (!conditionsArray.length) {
|
|
922
|
+
condition += "";
|
|
923
|
+
}
|
|
924
|
+
else if (conditionsArray.length === 1) {
|
|
925
|
+
condition += `${conditionsArray[0]}`;
|
|
926
|
+
}
|
|
927
|
+
else {
|
|
928
|
+
condition += `( ${conditionsArray.join(" ) AND ( ")} )`;
|
|
929
|
+
}
|
|
930
|
+
return condition;
|
|
931
|
+
}
|
|
932
|
+
createColumnValueExpression(valueSets, valueSetIndex, column) {
|
|
933
|
+
const valueSet = valueSets[valueSetIndex];
|
|
934
|
+
let expression = "";
|
|
935
|
+
// extract real value from the entity
|
|
936
|
+
let value = column.getEntityValue(valueSet);
|
|
937
|
+
// if column is relational and value is an object then get real referenced column value from this object
|
|
938
|
+
// for example column value is { question: { id: 1 } }, value will be equal to { id: 1 }
|
|
939
|
+
// and we extract "1" from this object
|
|
940
|
+
/*if (column.referencedColumn && value instanceof Object && !(typeof value === "function")) { // todo: check if we still need it since getEntityValue already has similar code
|
|
941
|
+
value = column.referencedColumn.getEntityValue(value);
|
|
942
|
+
}*/
|
|
943
|
+
if (!(typeof value === "function")) {
|
|
944
|
+
// make sure our value is normalized by a driver
|
|
945
|
+
value = this.connection.driver.preparePersistentValue(value, column);
|
|
946
|
+
}
|
|
947
|
+
// newly inserted entities always have a version equal to 1 (first version)
|
|
948
|
+
// also, user-specified version must be empty
|
|
949
|
+
if (column.isVersion && value === undefined) {
|
|
950
|
+
expression += "1";
|
|
951
|
+
// } else if (column.isNestedSetLeft) {
|
|
952
|
+
// const tableName = this.connection.driver.escape(column.entityMetadata.tablePath);
|
|
953
|
+
// const rightColumnName = this.connection.driver.escape(column.entityMetadata.nestedSetRightColumn!.databaseName);
|
|
954
|
+
// const subQuery = `(SELECT c.max + 1 FROM (SELECT MAX(${rightColumnName}) as max from ${tableName}) c)`;
|
|
955
|
+
// expression += subQuery;
|
|
956
|
+
//
|
|
957
|
+
// } else if (column.isNestedSetRight) {
|
|
958
|
+
// const tableName = this.connection.driver.escape(column.entityMetadata.tablePath);
|
|
959
|
+
// const rightColumnName = this.connection.driver.escape(column.entityMetadata.nestedSetRightColumn!.databaseName);
|
|
960
|
+
// const subQuery = `(SELECT c.max + 2 FROM (SELECT MAX(${rightColumnName}) as max from ${tableName}) c)`;
|
|
961
|
+
// expression += subQuery;
|
|
962
|
+
}
|
|
963
|
+
else if (column.isDiscriminator) {
|
|
964
|
+
expression += this.createParameter(this.expressionMap.mainAlias.metadata.discriminatorValue);
|
|
965
|
+
// return "1";
|
|
966
|
+
// for create and update dates we insert current date
|
|
967
|
+
// no, we don't do it because this constant is already in "default" value of the column
|
|
968
|
+
// with extended timestamp functionality, like CURRENT_TIMESTAMP(6) for example
|
|
969
|
+
// } else if (column.isCreateDate || column.isUpdateDate) {
|
|
970
|
+
// return "CURRENT_TIMESTAMP";
|
|
971
|
+
// if column is generated uuid and database does not support its generation and custom generated value was not provided by a user - we generate a new uuid value for insertion
|
|
972
|
+
}
|
|
973
|
+
else if (column.isGenerated &&
|
|
974
|
+
column.generationStrategy === "uuid" &&
|
|
975
|
+
!this.connection.driver.isUUIDGenerationSupported() &&
|
|
976
|
+
value === undefined) {
|
|
977
|
+
value = uuidv4();
|
|
978
|
+
expression += this.createParameter(value);
|
|
979
|
+
if (!(valueSetIndex in this.expressionMap.locallyGenerated)) {
|
|
980
|
+
this.expressionMap.locallyGenerated[valueSetIndex] = {};
|
|
981
|
+
}
|
|
982
|
+
column.setEntityValue(this.expressionMap.locallyGenerated[valueSetIndex], value);
|
|
983
|
+
// if value for this column was not provided then insert default value
|
|
984
|
+
}
|
|
985
|
+
else if (value === undefined) {
|
|
986
|
+
if ((this.connection.driver.options.type === "oracle" &&
|
|
987
|
+
valueSets.length > 1) ||
|
|
988
|
+
DriverUtils.isSQLiteFamily(this.connection.driver) ||
|
|
989
|
+
this.connection.driver.options.type === "sap" ||
|
|
990
|
+
this.connection.driver.options.type === "spanner") {
|
|
991
|
+
// unfortunately sqlite does not support DEFAULT expression in INSERT queries
|
|
992
|
+
if (column.default !== undefined && column.default !== null) {
|
|
993
|
+
// try to use default defined in the column
|
|
994
|
+
expression +=
|
|
995
|
+
this.connection.driver.normalizeDefault(column);
|
|
996
|
+
}
|
|
997
|
+
else if (this.connection.driver.options.type === "spanner" &&
|
|
998
|
+
column.isGenerated &&
|
|
999
|
+
column.generationStrategy === "uuid") {
|
|
1000
|
+
expression += "GENERATE_UUID()"; // Produces a random universally unique identifier (UUID) as a STRING value.
|
|
1001
|
+
}
|
|
1002
|
+
else {
|
|
1003
|
+
expression += "NULL"; // otherwise simply use NULL and pray if column is nullable
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
else {
|
|
1007
|
+
expression += "DEFAULT";
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
else if (value === null &&
|
|
1011
|
+
(this.connection.driver.options.type === "spanner" ||
|
|
1012
|
+
this.connection.driver.options.type === "oracle")) {
|
|
1013
|
+
expression += "NULL";
|
|
1014
|
+
// support for SQL expressions in queries
|
|
1015
|
+
}
|
|
1016
|
+
else if (typeof value === "function") {
|
|
1017
|
+
expression += value();
|
|
1018
|
+
// just any other regular value
|
|
1019
|
+
}
|
|
1020
|
+
else {
|
|
1021
|
+
if (this.connection.driver.options.type === "mssql")
|
|
1022
|
+
value = this.connection.driver.parametrizeValue(column, value);
|
|
1023
|
+
// we need to store array values in a special class to make sure parameter replacement will work correctly
|
|
1024
|
+
// if (value instanceof Array)
|
|
1025
|
+
// value = new ArrayParameter(value);
|
|
1026
|
+
const paramName = this.createParameter(value);
|
|
1027
|
+
if ((DriverUtils.isMySQLFamily(this.connection.driver) ||
|
|
1028
|
+
this.connection.driver.options.type === "aurora-mysql") &&
|
|
1029
|
+
this.connection.driver.spatialTypes.includes(column.type)) {
|
|
1030
|
+
const useLegacy = this.connection.driver.options.legacySpatialSupport;
|
|
1031
|
+
const geomFromText = useLegacy
|
|
1032
|
+
? "GeomFromText"
|
|
1033
|
+
: "ST_GeomFromText";
|
|
1034
|
+
if (column.srid != null) {
|
|
1035
|
+
expression += `${geomFromText}(${paramName}, ${column.srid})`;
|
|
1036
|
+
}
|
|
1037
|
+
else {
|
|
1038
|
+
expression += `${geomFromText}(${paramName})`;
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
else if (DriverUtils.isPostgresFamily(this.connection.driver) &&
|
|
1042
|
+
this.connection.driver.spatialTypes.includes(column.type)) {
|
|
1043
|
+
if (column.srid != null) {
|
|
1044
|
+
expression += `ST_SetSRID(ST_GeomFromGeoJSON(${paramName}), ${column.srid})::${column.type}`;
|
|
1045
|
+
}
|
|
1046
|
+
else {
|
|
1047
|
+
expression += `ST_GeomFromGeoJSON(${paramName})::${column.type}`;
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
else if (this.connection.driver.options.type === "mssql" &&
|
|
1051
|
+
this.connection.driver.spatialTypes.includes(column.type)) {
|
|
1052
|
+
expression +=
|
|
1053
|
+
column.type +
|
|
1054
|
+
"::STGeomFromText(" +
|
|
1055
|
+
paramName +
|
|
1056
|
+
", " +
|
|
1057
|
+
(column.srid || "0") +
|
|
1058
|
+
")";
|
|
1059
|
+
}
|
|
1060
|
+
else {
|
|
1061
|
+
expression += paramName;
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
return expression;
|
|
1065
|
+
}
|
|
739
1066
|
}
|
|
740
1067
|
|
|
741
1068
|
//# sourceMappingURL=InsertQueryBuilder.js.map
|