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:
Nix 2022-02-21 22:21:14 +09:00 committed by GitHub
parent 1f54c70b76
commit 7facbabd26
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 211 additions and 76 deletions

View File

@ -26,7 +26,7 @@ services:
# mariadb
mariadb:
image: "mariadb:10.4.8"
image: "mariadb:10.5.13"
container_name: "typeorm-mariadb"
ports:
- "3307:3306"

View File

@ -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.

View File

@ -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}`));

View File

@ -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.`
);
}
}

View File

@ -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;

View File

@ -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}`;
}
}

View File

@ -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") + ")";

View File

@ -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;
}));

View File

@ -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 => {

View File

@ -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}`;
}
/**

View File

@ -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}`;
}
/**

View File

@ -0,0 +1,10 @@
import {Column, Entity, PrimaryGeneratedColumn} from "../../../../src";
@Entity()
export class Animal {
@PrimaryGeneratedColumn()
id: number;
@Column({ type: "varchar" })
name: string;
}

View 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" },
);
})
);
});