mirror of
https://github.com/typeorm/typeorm.git
synced 2025-12-08 21:26:23 +00:00
feat: allow {delete,insert}().returning() on MariaDB (#8673)
* feat: allow `returning()` on MariaDB >= 10.5.0 Closes: #7235 * build: update docker mariadb version to 10.5.13 * fix: MySqlDriver behavior returning is supported * feat: what kind of DML returning is supported * test: imporve test #7235
This commit is contained in:
parent
1f54c70b76
commit
7facbabd26
@ -26,7 +26,7 @@ services:
|
||||
|
||||
# mariadb
|
||||
mariadb:
|
||||
image: "mariadb:10.4.8"
|
||||
image: "mariadb:10.5.13"
|
||||
container_name: "typeorm-mariadb"
|
||||
ports:
|
||||
- "3307:3306"
|
||||
|
||||
@ -9,10 +9,12 @@ import {BaseConnectionOptions} from "../connection/BaseConnectionOptions";
|
||||
import {TableColumn} from "../schema-builder/table/TableColumn";
|
||||
import {EntityMetadata} from "../metadata/EntityMetadata";
|
||||
import {ReplicationMode} from "./types/ReplicationMode";
|
||||
import { Table } from "../schema-builder/table/Table";
|
||||
import { View } from "../schema-builder/view/View";
|
||||
import { TableForeignKey } from "../schema-builder/table/TableForeignKey";
|
||||
import { UpsertType } from "./types/UpsertType";
|
||||
import {Table} from "../schema-builder/table/Table";
|
||||
import {View} from "../schema-builder/view/View";
|
||||
import {TableForeignKey} from "../schema-builder/table/TableForeignKey";
|
||||
import {UpsertType} from "./types/UpsertType";
|
||||
|
||||
export type ReturningType = "insert" | "update" | "delete";
|
||||
|
||||
/**
|
||||
* Driver organizes TypeORM communication with specific database management system.
|
||||
@ -206,7 +208,7 @@ export interface Driver {
|
||||
/**
|
||||
* Returns true if driver supports RETURNING / OUTPUT statement.
|
||||
*/
|
||||
isReturningSqlSupported(): boolean;
|
||||
isReturningSqlSupported(returningType: ReturningType): boolean;
|
||||
|
||||
/**
|
||||
* Returns true if driver supports uuid values generation on its own.
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import {Driver} from "../Driver";
|
||||
import {Driver, ReturningType} from "../Driver";
|
||||
import {ConnectionIsNotSetError} from "../../error/ConnectionIsNotSetError";
|
||||
import {DriverPackageNotInstalledError} from "../../error/DriverPackageNotInstalledError";
|
||||
import {DriverUtils} from "../DriverUtils";
|
||||
@ -19,10 +19,11 @@ import {EntityMetadata} from "../../metadata/EntityMetadata";
|
||||
import {OrmUtils} from "../../util/OrmUtils";
|
||||
import {ApplyValueTransformers} from "../../util/ApplyValueTransformers";
|
||||
import {ReplicationMode} from "../types/ReplicationMode";
|
||||
import { TypeORMError } from "../../error";
|
||||
import { Table } from "../../schema-builder/table/Table";
|
||||
import { View } from "../../schema-builder/view/View";
|
||||
import { TableForeignKey } from "../../schema-builder/table/TableForeignKey";
|
||||
import {TypeORMError} from "../../error";
|
||||
import {Table} from "../../schema-builder/table/Table";
|
||||
import {View} from "../../schema-builder/view/View";
|
||||
import {TableForeignKey} from "../../schema-builder/table/TableForeignKey";
|
||||
import {VersionUtils} from "../../util/VersionUtils";
|
||||
|
||||
/**
|
||||
* Organizes communication with MySQL DBMS.
|
||||
@ -304,6 +305,16 @@ export class MysqlDriver implements Driver {
|
||||
*/
|
||||
maxAliasLength = 63;
|
||||
|
||||
|
||||
/**
|
||||
* Supported returning types
|
||||
*/
|
||||
private readonly _isReturningSqlSupported: Record<ReturningType, boolean> = {
|
||||
delete: false,
|
||||
insert: false,
|
||||
update: false,
|
||||
};
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Constructor
|
||||
// -------------------------------------------------------------------------
|
||||
@ -360,6 +371,19 @@ export class MysqlDriver implements Driver {
|
||||
|
||||
await queryRunner.release();
|
||||
}
|
||||
|
||||
if (this.options.type === "mariadb") {
|
||||
const result = await this.createQueryRunner("master")
|
||||
.query(`SELECT VERSION() AS \`version\``) as { version: string; }[];
|
||||
const dbVersion = result[0].version;
|
||||
|
||||
if (VersionUtils.isGreaterOrEqual(dbVersion, "10.0.5")) {
|
||||
this._isReturningSqlSupported.delete = true;
|
||||
}
|
||||
if (VersionUtils.isGreaterOrEqual(dbVersion, "10.5.0")) {
|
||||
this._isReturningSqlSupported.insert = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -795,6 +819,21 @@ export class MysqlDriver implements Driver {
|
||||
* Creates generated map of values generated or returned by database after INSERT query.
|
||||
*/
|
||||
createGeneratedMap(metadata: EntityMetadata, insertResult: any, entityIndex: number) {
|
||||
if (!insertResult) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (insertResult.insertId === undefined) {
|
||||
return Object.keys(insertResult).reduce((map, key) => {
|
||||
const column = metadata.findColumnWithDatabaseName(key);
|
||||
if (column) {
|
||||
OrmUtils.mergeDeep(map, column.createValueMap(insertResult[key]));
|
||||
// OrmUtils.mergeDeep(map, column.createValueMap(this.prepareHydratedValue(insertResult[key], column))); // TODO: probably should be like there, but fails on enums, fix later
|
||||
}
|
||||
return map;
|
||||
}, {} as ObjectLiteral);
|
||||
}
|
||||
|
||||
const generatedMap = metadata.generatedColumns.reduce((map, generatedColumn) => {
|
||||
let value: any;
|
||||
if (generatedColumn.generationStrategy === "increment" && insertResult.insertId) {
|
||||
@ -874,8 +913,8 @@ export class MysqlDriver implements Driver {
|
||||
/**
|
||||
* Returns true if driver supports RETURNING / OUTPUT statement.
|
||||
*/
|
||||
isReturningSqlSupported(): boolean {
|
||||
return false;
|
||||
isReturningSqlSupported(returningType: ReturningType): boolean {
|
||||
return this._isReturningSqlSupported[returningType];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -961,8 +1000,8 @@ export class MysqlDriver implements Driver {
|
||||
socketPath: credentials.socketPath
|
||||
},
|
||||
options.acquireTimeout === undefined
|
||||
? {}
|
||||
: { acquireTimeout: options.acquireTimeout },
|
||||
? {}
|
||||
: { acquireTimeout: options.acquireTimeout },
|
||||
options.extra || {});
|
||||
}
|
||||
|
||||
@ -994,8 +1033,8 @@ export class MysqlDriver implements Driver {
|
||||
private prepareDbConnection(connection: any): any {
|
||||
const { logger } = this.connection;
|
||||
/*
|
||||
Attaching an error handler to connection errors is essential, as, otherwise, errors raised will go unhandled and
|
||||
cause the hosting app to crash.
|
||||
* Attaching an error handler to connection errors is essential, as, otherwise, errors raised will go unhandled and
|
||||
* cause the hosting app to crash.
|
||||
*/
|
||||
if (connection.listeners("error").length === 0) {
|
||||
connection.on("error", (error: any) => logger.log("warn", `MySQL connection raised an error. ${error}`));
|
||||
|
||||
@ -7,7 +7,7 @@ import {TypeORMError} from "./TypeORMError";
|
||||
export class ReturningStatementNotSupportedError extends TypeORMError {
|
||||
constructor() {
|
||||
super(
|
||||
`OUTPUT or RETURNING clause only supported by Microsoft SQL Server or PostgreSQL databases.`
|
||||
`OUTPUT or RETURNING clause only supported by Microsoft SQL Server or PostgreSQL or MariaDB databases.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -832,7 +832,8 @@ export class SubjectExecutor {
|
||||
protected groupBulkSubjects(subjects: Subject[], type: "insert" | "delete"): [{ [key: string]: Subject[] }, string[]] {
|
||||
const group: { [key: string]: Subject[] } = {};
|
||||
const keys: string[] = [];
|
||||
const groupingAllowed = type === "delete" || this.queryRunner.connection.driver.isReturningSqlSupported();
|
||||
const groupingAllowed = type === "delete" ||
|
||||
this.queryRunner.connection.driver.isReturningSqlSupported("insert");
|
||||
|
||||
subjects.forEach((subject, index) => {
|
||||
const key = groupingAllowed || subject.metadata.isJunction ? subject.metadata.name : subject.metadata.name + "_" + index;
|
||||
|
||||
@ -1,11 +1,9 @@
|
||||
import {CockroachDriver} from "../driver/cockroachdb/CockroachDriver";
|
||||
import {QueryBuilder} from "./QueryBuilder";
|
||||
import {ObjectLiteral} from "../common/ObjectLiteral";
|
||||
import {EntityTarget} from "../common/EntityTarget";
|
||||
import {Connection} from "../connection/Connection";
|
||||
import {QueryRunner} from "../query-runner/QueryRunner";
|
||||
import {SqlServerDriver} from "../driver/sqlserver/SqlServerDriver";
|
||||
import {PostgresDriver} from "../driver/postgres/PostgresDriver";
|
||||
import {WhereExpressionBuilder} from "./WhereExpressionBuilder";
|
||||
import {Brackets} from "./Brackets";
|
||||
import {DeleteResult} from "./result/DeleteResult";
|
||||
@ -212,8 +210,9 @@ export class DeleteQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
returning(returning: string|string[]): this {
|
||||
|
||||
// not all databases support returning/output cause
|
||||
if (!this.connection.driver.isReturningSqlSupported())
|
||||
if (!this.connection.driver.isReturningSqlSupported("delete")) {
|
||||
throw new ReturningStatementNotSupportedError();
|
||||
}
|
||||
|
||||
this.expressionMap.returning = returning;
|
||||
return this;
|
||||
@ -229,17 +228,15 @@ export class DeleteQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
protected createDeleteExpression() {
|
||||
const tableName = this.getTableName(this.getMainTableName());
|
||||
const whereExpression = this.createWhereExpression();
|
||||
const returningExpression = this.createReturningExpression();
|
||||
const returningExpression = this.createReturningExpression("delete");
|
||||
|
||||
if (returningExpression && (this.connection.driver instanceof PostgresDriver || this.connection.driver instanceof CockroachDriver)) {
|
||||
return `DELETE FROM ${tableName}${whereExpression} RETURNING ${returningExpression}`;
|
||||
|
||||
} else if (returningExpression !== "" && this.connection.driver instanceof SqlServerDriver) {
|
||||
return `DELETE FROM ${tableName} OUTPUT ${returningExpression}${whereExpression}`;
|
||||
|
||||
} else {
|
||||
if (returningExpression === "") {
|
||||
return `DELETE FROM ${tableName}${whereExpression}`;
|
||||
}
|
||||
if (this.connection.driver instanceof SqlServerDriver) {
|
||||
return `DELETE FROM ${tableName} OUTPUT ${returningExpression}${whereExpression}`;
|
||||
}
|
||||
return `DELETE FROM ${tableName}${whereExpression} RETURNING ${returningExpression}`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -17,8 +17,8 @@ import {BroadcasterResult} from "../subscriber/BroadcasterResult";
|
||||
import {EntitySchema} from "../entity-schema/EntitySchema";
|
||||
import {OracleDriver} from "../driver/oracle/OracleDriver";
|
||||
import {AuroraDataApiDriver} from "../driver/aurora-data-api/AuroraDataApiDriver";
|
||||
import { TypeORMError } from "../error";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import {TypeORMError} from "../error";
|
||||
import {v4 as uuidv4} from "uuid";
|
||||
|
||||
/**
|
||||
* Allows to build complex sql queries in a fashion way and execute those queries.
|
||||
@ -244,8 +244,9 @@ export class InsertQueryBuilder<Entity> extends QueryBuilder<Entity> {
|
||||
returning(returning: string|string[]): this {
|
||||
|
||||
// not all databases support returning/output cause
|
||||
if (!this.connection.driver.isReturningSqlSupported())
|
||||
if (!this.connection.driver.isReturningSqlSupported("insert")) {
|
||||
throw new ReturningStatementNotSupportedError();
|
||||
}
|
||||
|
||||
this.expressionMap.returning = returning;
|
||||
return this;
|
||||
@ -316,12 +317,15 @@ export class InsertQueryBuilder<Entity> extends QueryBuilder<Entity> {
|
||||
protected createInsertExpression() {
|
||||
const tableName = this.getTableName(this.getMainTableName());
|
||||
const valuesExpression = this.createValuesExpression(); // its important to get values before returning expression because oracle rely on native parameters and ordering of them is important
|
||||
const returningExpression = (this.connection.driver instanceof OracleDriver && this.getValueSets().length > 1) ? null : this.createReturningExpression(); // oracle doesnt support returning with multi-row insert
|
||||
const returningExpression =
|
||||
(this.connection.driver instanceof OracleDriver && this.getValueSets().length > 1)
|
||||
? null
|
||||
: this.createReturningExpression("insert"); // oracle doesnt support returning with multi-row insert
|
||||
const columnsExpression = this.createColumnNamesExpression();
|
||||
let query = "INSERT ";
|
||||
|
||||
if (this.connection.driver instanceof MysqlDriver || this.connection.driver instanceof AuroraDataApiDriver) {
|
||||
query += `${this.expressionMap.onIgnore ? " IGNORE " : ""}`;
|
||||
query += `${this.expressionMap.onIgnore ? " IGNORE " : ""}`;
|
||||
}
|
||||
|
||||
query += `INTO ${tableName}`;
|
||||
@ -400,7 +404,13 @@ export class InsertQueryBuilder<Entity> extends QueryBuilder<Entity> {
|
||||
}
|
||||
|
||||
// add RETURNING expression
|
||||
if (returningExpression && (this.connection.driver instanceof PostgresDriver || this.connection.driver instanceof OracleDriver || this.connection.driver instanceof CockroachDriver)) {
|
||||
if (
|
||||
returningExpression &&
|
||||
(this.connection.driver instanceof PostgresDriver ||
|
||||
this.connection.driver instanceof OracleDriver ||
|
||||
this.connection.driver instanceof CockroachDriver ||
|
||||
this.connection.driver instanceof MysqlDriver)
|
||||
) {
|
||||
query += ` RETURNING ${returningExpression}`;
|
||||
}
|
||||
|
||||
@ -504,7 +514,7 @@ export class InsertQueryBuilder<Entity> extends QueryBuilder<Entity> {
|
||||
|
||||
if (!(value instanceof Function)) {
|
||||
// make sure our value is normalized by a driver
|
||||
value = this.connection.driver.preparePersistentValue(value, column);
|
||||
value = this.connection.driver.preparePersistentValue(value, column);
|
||||
}
|
||||
|
||||
// newly inserted entities always have a version equal to 1 (first version)
|
||||
@ -584,9 +594,9 @@ export class InsertQueryBuilder<Entity> extends QueryBuilder<Entity> {
|
||||
}
|
||||
} else if (this.connection.driver instanceof PostgresDriver && this.connection.driver.spatialTypes.indexOf(column.type) !== -1) {
|
||||
if (column.srid != null) {
|
||||
expression += `ST_SetSRID(ST_GeomFromGeoJSON(${paramName}), ${column.srid})::${column.type}`;
|
||||
expression += `ST_SetSRID(ST_GeomFromGeoJSON(${paramName}), ${column.srid})::${column.type}`;
|
||||
} else {
|
||||
expression += `ST_GeomFromGeoJSON(${paramName})::${column.type}`;
|
||||
expression += `ST_GeomFromGeoJSON(${paramName})::${column.type}`;
|
||||
}
|
||||
} else if (this.connection.driver instanceof SqlServerDriver && this.connection.driver.spatialTypes.indexOf(column.type) !== -1) {
|
||||
expression += column.type + "::STGeomFromText(" + paramName + ", " + (column.srid || "0") + ")";
|
||||
|
||||
@ -22,9 +22,10 @@ import {EntitySchema} from "../entity-schema/EntitySchema";
|
||||
import {FindOperator} from "../find-options/FindOperator";
|
||||
import {In} from "../find-options/operator/In";
|
||||
import {EntityColumnNotFound} from "../error/EntityColumnNotFound";
|
||||
import { TypeORMError } from "../error";
|
||||
import { WhereClause, WhereClauseCondition } from "./WhereClause";
|
||||
import {TypeORMError} from "../error";
|
||||
import {WhereClause, WhereClauseCondition} from "./WhereClause";
|
||||
import {NotBrackets} from "./NotBrackets";
|
||||
import {ReturningType} from "../driver/Driver";
|
||||
|
||||
// todo: completely cover query builder with tests
|
||||
// todo: entityOrProperty can be target name. implement proper behaviour if it is.
|
||||
@ -737,7 +738,7 @@ export abstract class QueryBuilder<Entity> {
|
||||
/**
|
||||
* Creates "RETURNING" / "OUTPUT" expression.
|
||||
*/
|
||||
protected createReturningExpression(): string {
|
||||
protected createReturningExpression(returningType: ReturningType): string {
|
||||
const columns = this.getReturningColumns();
|
||||
const driver = this.connection.driver;
|
||||
|
||||
@ -745,7 +746,7 @@ export abstract class QueryBuilder<Entity> {
|
||||
// if user gave his own returning
|
||||
if (typeof this.expressionMap.returning !== "string" &&
|
||||
this.expressionMap.extraReturningColumns.length > 0 &&
|
||||
driver.isReturningSqlSupported()) {
|
||||
driver.isReturningSqlSupported(returningType)) {
|
||||
columns.push(...this.expressionMap.extraReturningColumns.filter(column => {
|
||||
return columns.indexOf(column) === -1;
|
||||
}));
|
||||
|
||||
@ -5,7 +5,7 @@ import {ColumnMetadata} from "../metadata/ColumnMetadata";
|
||||
import {UpdateResult} from "./result/UpdateResult";
|
||||
import {InsertResult} from "./result/InsertResult";
|
||||
import {OracleDriver} from "../driver/oracle/OracleDriver";
|
||||
import { TypeORMError } from "../error";
|
||||
import {TypeORMError} from "../error";
|
||||
|
||||
/**
|
||||
* Updates entity with returning results in the entity insert and update operations.
|
||||
@ -33,7 +33,7 @@ export class ReturningResultsEntityUpdator {
|
||||
await Promise.all(entities.map(async (entity, entityIndex) => {
|
||||
|
||||
// if database supports returning/output statement then we already should have updating values in the raw data returned by insert query
|
||||
if (this.queryRunner.connection.driver.isReturningSqlSupported()) {
|
||||
if (this.queryRunner.connection.driver.isReturningSqlSupported("update")) {
|
||||
if (this.queryRunner.connection.driver instanceof OracleDriver && Array.isArray(updateResult.raw) && this.expressionMap.extraReturningColumns.length > 0) {
|
||||
updateResult.raw = updateResult.raw.reduce((newRaw, rawItem, rawItemIndex) => {
|
||||
newRaw[this.expressionMap.extraReturningColumns[rawItemIndex].databaseName] = rawItem[0];
|
||||
@ -116,7 +116,10 @@ export class ReturningResultsEntityUpdator {
|
||||
|
||||
// for postgres and mssql we use returning/output statement to get values of inserted default and generated values
|
||||
// for other drivers we have to re-select this data from the database
|
||||
if (this.queryRunner.connection.driver.isReturningSqlSupported() === false && insertionColumns.length > 0) {
|
||||
if (
|
||||
insertionColumns.length > 0 &&
|
||||
!this.queryRunner.connection.driver.isReturningSqlSupported("insert")
|
||||
) {
|
||||
const entityIds = entities.map((entity) => {
|
||||
const entityId = metadata.getEntityIdMap(entity)!;
|
||||
|
||||
@ -173,7 +176,7 @@ export class ReturningResultsEntityUpdator {
|
||||
|
||||
// for databases which support returning statement we need to return extra columns like id
|
||||
// for other databases we don't need to return id column since its returned by a driver already
|
||||
const needToCheckGenerated = this.queryRunner.connection.driver.isReturningSqlSupported();
|
||||
const needToCheckGenerated = this.queryRunner.connection.driver.isReturningSqlSupported("insert");
|
||||
|
||||
// filter out the columns of which we need database inserted values to update our entity
|
||||
return this.expressionMap.mainAlias!.metadata.columns.filter(column => {
|
||||
|
||||
@ -1,11 +1,9 @@
|
||||
import {CockroachDriver} from "../driver/cockroachdb/CockroachDriver";
|
||||
import {QueryBuilder} from "./QueryBuilder";
|
||||
import {ObjectLiteral} from "../common/ObjectLiteral";
|
||||
import {EntityTarget} from "../common/EntityTarget";
|
||||
import {Connection} from "../connection/Connection";
|
||||
import {QueryRunner} from "../query-runner/QueryRunner";
|
||||
import {SqlServerDriver} from "../driver/sqlserver/SqlServerDriver";
|
||||
import {PostgresDriver} from "../driver/postgres/PostgresDriver";
|
||||
import {WhereExpressionBuilder} from "./WhereExpressionBuilder";
|
||||
import {Brackets} from "./Brackets";
|
||||
import {UpdateResult} from "./result/UpdateResult";
|
||||
@ -15,10 +13,9 @@ import {MysqlDriver} from "../driver/mysql/MysqlDriver";
|
||||
import {OrderByCondition} from "../find-options/OrderByCondition";
|
||||
import {LimitOnUpdateNotSupportedError} from "../error/LimitOnUpdateNotSupportedError";
|
||||
import {MissingDeleteDateColumnError} from "../error/MissingDeleteDateColumnError";
|
||||
import {OracleDriver} from "../driver/oracle/OracleDriver";
|
||||
import {UpdateValuesMissingError} from "../error/UpdateValuesMissingError";
|
||||
import {EntitySchema} from "../entity-schema/EntitySchema";
|
||||
import { TypeORMError } from "../error";
|
||||
import {TypeORMError} from "../error";
|
||||
|
||||
/**
|
||||
* Allows to build complex sql queries in a fashion way and execute those queries.
|
||||
@ -241,8 +238,9 @@ export class SoftDeleteQueryBuilder<Entity> extends QueryBuilder<Entity> impleme
|
||||
returning(returning: string|string[]): this {
|
||||
|
||||
// not all databases support returning/output cause
|
||||
if (!this.connection.driver.isReturningSqlSupported())
|
||||
if (!this.connection.driver.isReturningSqlSupported("update")) {
|
||||
throw new ReturningStatementNotSupportedError();
|
||||
}
|
||||
|
||||
this.expressionMap.returning = returning;
|
||||
return this;
|
||||
@ -386,18 +384,15 @@ export class SoftDeleteQueryBuilder<Entity> extends QueryBuilder<Entity> impleme
|
||||
|
||||
// get a table name and all column database names
|
||||
const whereExpression = this.createWhereExpression();
|
||||
const returningExpression = this.createReturningExpression();
|
||||
const returningExpression = this.createReturningExpression("update");
|
||||
|
||||
// generate and return sql update query
|
||||
if (returningExpression && (this.connection.driver instanceof PostgresDriver || this.connection.driver instanceof OracleDriver || this.connection.driver instanceof CockroachDriver)) {
|
||||
return `UPDATE ${this.getTableName(this.getMainTableName())} SET ${updateColumnAndValues.join(", ")}${whereExpression} RETURNING ${returningExpression}`;
|
||||
|
||||
} else if (returningExpression && this.connection.driver instanceof SqlServerDriver) {
|
||||
return `UPDATE ${this.getTableName(this.getMainTableName())} SET ${updateColumnAndValues.join(", ")} OUTPUT ${returningExpression}${whereExpression}`;
|
||||
|
||||
} else {
|
||||
if (returningExpression === "") {
|
||||
return `UPDATE ${this.getTableName(this.getMainTableName())} SET ${updateColumnAndValues.join(", ")}${whereExpression}`; // todo: how do we replace aliases in where to nothing?
|
||||
}
|
||||
if (this.connection.driver instanceof SqlServerDriver) {
|
||||
return `UPDATE ${this.getTableName(this.getMainTableName())} SET ${updateColumnAndValues.join(", ")} OUTPUT ${returningExpression}${whereExpression}`;
|
||||
}
|
||||
return `UPDATE ${this.getTableName(this.getMainTableName())} SET ${updateColumnAndValues.join(", ")}${whereExpression} RETURNING ${returningExpression}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import {CockroachDriver} from "../driver/cockroachdb/CockroachDriver";
|
||||
import {SapDriver} from "../driver/sap/SapDriver";
|
||||
import {ColumnMetadata} from "../metadata/ColumnMetadata";
|
||||
import {QueryBuilder} from "./QueryBuilder";
|
||||
@ -15,12 +14,11 @@ import {ReturningResultsEntityUpdator} from "./ReturningResultsEntityUpdator";
|
||||
import {MysqlDriver} from "../driver/mysql/MysqlDriver";
|
||||
import {OrderByCondition} from "../find-options/OrderByCondition";
|
||||
import {LimitOnUpdateNotSupportedError} from "../error/LimitOnUpdateNotSupportedError";
|
||||
import {OracleDriver} from "../driver/oracle/OracleDriver";
|
||||
import {UpdateValuesMissingError} from "../error/UpdateValuesMissingError";
|
||||
import {EntityColumnNotFound} from "../error/EntityColumnNotFound";
|
||||
import {QueryDeepPartialEntity} from "./QueryPartialEntity";
|
||||
import {AuroraDataApiDriver} from "../driver/aurora-data-api/AuroraDataApiDriver";
|
||||
import { TypeORMError } from "../error";
|
||||
import {TypeORMError} from "../error";
|
||||
|
||||
/**
|
||||
* Allows to build complex sql queries in a fashion way and execute those queries.
|
||||
@ -265,8 +263,9 @@ export class UpdateQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
returning(returning: string|string[]): this {
|
||||
|
||||
// not all databases support returning/output cause
|
||||
if (!this.connection.driver.isReturningSqlSupported())
|
||||
if (!this.connection.driver.isReturningSqlSupported("update")) {
|
||||
throw new ReturningStatementNotSupportedError();
|
||||
}
|
||||
|
||||
this.expressionMap.returning = returning;
|
||||
return this;
|
||||
@ -429,9 +428,9 @@ export class UpdateQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
}
|
||||
} else if (this.connection.driver instanceof PostgresDriver && this.connection.driver.spatialTypes.indexOf(column.type) !== -1) {
|
||||
if (column.srid != null) {
|
||||
expression = `ST_SetSRID(ST_GeomFromGeoJSON(${paramName}), ${column.srid})::${column.type}`;
|
||||
expression = `ST_SetSRID(ST_GeomFromGeoJSON(${paramName}), ${column.srid})::${column.type}`;
|
||||
} else {
|
||||
expression = `ST_GeomFromGeoJSON(${paramName})::${column.type}`;
|
||||
expression = `ST_GeomFromGeoJSON(${paramName})::${column.type}`;
|
||||
}
|
||||
} else if (this.connection.driver instanceof SqlServerDriver && this.connection.driver.spatialTypes.indexOf(column.type) !== -1) {
|
||||
expression = column.type + "::STGeomFromText(" + paramName + ", " + (column.srid || "0") + ")";
|
||||
@ -477,18 +476,15 @@ export class UpdateQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
|
||||
// get a table name and all column database names
|
||||
const whereExpression = this.createWhereExpression();
|
||||
const returningExpression = this.createReturningExpression();
|
||||
const returningExpression = this.createReturningExpression("update");
|
||||
|
||||
// generate and return sql update query
|
||||
if (returningExpression && (this.connection.driver instanceof PostgresDriver || this.connection.driver instanceof OracleDriver || this.connection.driver instanceof CockroachDriver)) {
|
||||
return `UPDATE ${this.getTableName(this.getMainTableName())} SET ${updateColumnAndValues.join(", ")}${whereExpression} RETURNING ${returningExpression}`;
|
||||
|
||||
} else if (returningExpression && this.connection.driver instanceof SqlServerDriver) {
|
||||
return `UPDATE ${this.getTableName(this.getMainTableName())} SET ${updateColumnAndValues.join(", ")} OUTPUT ${returningExpression}${whereExpression}`;
|
||||
|
||||
} else {
|
||||
if (returningExpression === "") {
|
||||
return `UPDATE ${this.getTableName(this.getMainTableName())} SET ${updateColumnAndValues.join(", ")}${whereExpression}`; // todo: how do we replace aliases in where to nothing?
|
||||
}
|
||||
if (this.connection.driver instanceof SqlServerDriver) {
|
||||
return `UPDATE ${this.getTableName(this.getMainTableName())} SET ${updateColumnAndValues.join(", ")} OUTPUT ${returningExpression}${whereExpression}`;
|
||||
}
|
||||
return `UPDATE ${this.getTableName(this.getMainTableName())} SET ${updateColumnAndValues.join(", ")}${whereExpression} RETURNING ${returningExpression}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
10
test/github-issues/7235/entity/Animal.ts
Normal file
10
test/github-issues/7235/entity/Animal.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import {Column, Entity, PrimaryGeneratedColumn} from "../../../../src";
|
||||
|
||||
@Entity()
|
||||
export class Animal {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column({ type: "varchar" })
|
||||
name: string;
|
||||
}
|
||||
81
test/github-issues/7235/issue-7235.ts
Normal file
81
test/github-issues/7235/issue-7235.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import "reflect-metadata";
|
||||
import {createTestingConnections, closeTestingConnections, reloadTestingDatabases} from "../../utils/test-utils";
|
||||
import {Animal} from "./entity/Animal";
|
||||
import {Connection} from "../../../src/connection/Connection";
|
||||
import {expect} from "chai";
|
||||
import {VersionUtils} from "../../../src/util/VersionUtils";
|
||||
|
||||
describe('github issues > #7235 Use "INSERT...RETURNING" in MariaDB.', () => {
|
||||
const runOnSpecificVersion = (version: string, fn: Function) =>
|
||||
async () => Promise.all(connections.map(async (connection) => {
|
||||
const result = await connection.query(`SELECT VERSION() AS \`version\``);
|
||||
const dbVersion = result[0]["version"];
|
||||
if (VersionUtils.isGreaterOrEqual(dbVersion, version)) {
|
||||
await fn(connection);
|
||||
}
|
||||
}));
|
||||
|
||||
let connections: Connection[];
|
||||
|
||||
before(async () => connections = await createTestingConnections({
|
||||
entities: [__dirname + "/entity/*{.js,.ts}"],
|
||||
schemaCreate: true,
|
||||
dropSchema: true,
|
||||
enabledDrivers: ["mariadb"],
|
||||
}));
|
||||
beforeEach(() => reloadTestingDatabases(connections));
|
||||
after(() => closeTestingConnections(connections));
|
||||
|
||||
it("should allow `DELETE...RETURNING` on MariaDB >= 10.0.5",
|
||||
runOnSpecificVersion("10.0.5", async (connection: Connection) => {
|
||||
const animalRepository = connection.getRepository(Animal);
|
||||
|
||||
await animalRepository
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.values([{ name: "Cat" }, { name: "Wolf" }])
|
||||
.execute();
|
||||
|
||||
const deleteCat = await animalRepository
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
.where({ name: "Cat" })
|
||||
.returning(["id", "name"])
|
||||
.execute();
|
||||
expect(deleteCat.raw[0]).to.deep.equal({ id: 1, name: "Cat" });
|
||||
const deleteWolf = await animalRepository
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
.where({ name: "Wolf" })
|
||||
.returning("name")
|
||||
.execute();
|
||||
expect(deleteWolf.raw[0]).to.deep.equal({ name: "Wolf" });
|
||||
})
|
||||
);
|
||||
|
||||
it("should allow `INSERT...RETURNING` on MariaDB >= 10.5.0",
|
||||
runOnSpecificVersion("10.5.0", async (connection: Connection) => {
|
||||
const animalRepository = connection.getRepository(Animal);
|
||||
const insertDogFox = await animalRepository
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.values([{ name: "Dog" }, { name: "Fox" }])
|
||||
.returning("name")
|
||||
.execute();
|
||||
expect(insertDogFox.raw).to.deep.equal([
|
||||
{ name: "Dog" },
|
||||
{ name: "Fox" },
|
||||
]);
|
||||
|
||||
const insertUnicorn = await animalRepository
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.values({ name: "Unicorn" })
|
||||
.returning(["id", "name"])
|
||||
.execute();
|
||||
expect(insertUnicorn.raw[0]).to.deep.equal(
|
||||
{ id: 3, name: "Unicorn" },
|
||||
);
|
||||
})
|
||||
);
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user