Skip to content

Commit

Permalink
feat: support for Common Table Expressions (#8534)
Browse files Browse the repository at this point in the history
* feat: support for Common Table Expressions

Resolves #1116 #5899

* test: disable test for #4753 if no MySQL is present in ormconfig.json
  • Loading branch information
Ginden committed Mar 23, 2022
1 parent a641c5d commit 7cc1848
Show file tree
Hide file tree
Showing 25 changed files with 505 additions and 13 deletions.
34 changes: 34 additions & 0 deletions docs/select-query-builder.md
Expand Up @@ -1155,3 +1155,37 @@ const users = await dataSource
```

You will get all the rows, including the ones which are deleted.

## Common table expressions

`QueryBuilder` instances
support [common table expressions](https://en.wikipedia.org/wiki/Hierarchical_and_recursive_queries_in_SQL#Common_table_expression)
, if minimal supported version of your database supports them. Common table expressions aren't supported for Oracle yet.

```typescript
const users = await connection.getRepository(User)
.createQueryBuilder('user')
.select("user.id", 'id')
.addCommonTableExpression(`
SELECT "userId" FROM "post"
`, 'post_users_ids')
.where(`user.id IN (SELECT "userId" FROM 'post_users_ids')`)
.getMany();
```

Result values of `InsertQueryBuilder` or `UpdateQueryBuilder` can be used in Postgres:

```typescript
const insertQueryBuilder = await connection.getRepository(User)
.createQueryBuilder()
.insert({
name: 'John Smith'
})
.returning(['id']);

const users = await connection.getRepository(User)
.createQueryBuilder('user')
.addCommonTableExpression(insertQueryBuilder, 'insert_results')
.where(`user.id IN (SELECT "id" FROM 'insert_results')`)
.getMany();
```
3 changes: 3 additions & 0 deletions src/driver/Driver.ts
Expand Up @@ -2,6 +2,7 @@ import { QueryRunner } from "../query-runner/QueryRunner"
import { ColumnMetadata } from "../metadata/ColumnMetadata"
import { ObjectLiteral } from "../common/ObjectLiteral"
import { ColumnType } from "./types/ColumnTypes"
import { CteCapabilities } from "./types/CteCapabilities"
import { MappedColumnTypes } from "./types/MappedColumnTypes"
import { SchemaBuilder } from "../schema-builder/SchemaBuilder"
import { DataTypeDefaults } from "./types/DataTypeDefaults"
Expand Down Expand Up @@ -99,6 +100,8 @@ export interface Driver {
*/
maxAliasLength?: number

cteCapabilities: CteCapabilities

/**
* Performs connection to the database.
* Depend on driver type it may create a connection pool.
Expand Down
5 changes: 5 additions & 0 deletions src/driver/aurora-mysql/AuroraMysqlDriver.ts
@@ -1,5 +1,6 @@
import { Driver } from "../Driver"
import { DriverUtils } from "../DriverUtils"
import { CteCapabilities } from "../types/CteCapabilities"
import { AuroraMysqlQueryRunner } from "./AuroraMysqlQueryRunner"
import { ObjectLiteral } from "../../common/ObjectLiteral"
import { ColumnMetadata } from "../../metadata/ColumnMetadata"
Expand Down Expand Up @@ -310,6 +311,10 @@ export class AuroraMysqlDriver implements Driver {
*/
maxAliasLength = 63

cteCapabilities: CteCapabilities = {
enabled: false,
}

// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------
Expand Down
8 changes: 8 additions & 0 deletions src/driver/cockroachdb/CockroachDriver.ts
Expand Up @@ -4,6 +4,7 @@ import { ObjectLiteral } from "../../common/ObjectLiteral"
import { DriverPackageNotInstalledError } from "../../error/DriverPackageNotInstalledError"
import { DriverUtils } from "../DriverUtils"
import { ColumnMetadata } from "../../metadata/ColumnMetadata"
import { CteCapabilities } from "../types/CteCapabilities"
import { CockroachConnectionCredentialsOptions } from "./CockroachConnectionCredentialsOptions"
import { CockroachConnectionOptions } from "./CockroachConnectionOptions"
import { DateUtils } from "../../util/DateUtils"
Expand Down Expand Up @@ -230,6 +231,13 @@ export class CockroachDriver implements Driver {
*/
maxAliasLength?: number

cteCapabilities: CteCapabilities = {
enabled: true,
writable: true,
materializedHint: true,
requiresRecursiveHint: true,
}

// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------
Expand Down
5 changes: 5 additions & 0 deletions src/driver/mongodb/MongoDriver.ts
@@ -1,6 +1,7 @@
import { Driver } from "../Driver"
import { ConnectionIsNotSetError } from "../../error/ConnectionIsNotSetError"
import { DriverPackageNotInstalledError } from "../../error/DriverPackageNotInstalledError"
import { CteCapabilities } from "../types/CteCapabilities"
import { MongoQueryRunner } from "./MongoQueryRunner"
import { ObjectLiteral } from "../../common/ObjectLiteral"
import { ColumnMetadata } from "../../metadata/ColumnMetadata"
Expand Down Expand Up @@ -212,6 +213,10 @@ export class MongoDriver implements Driver {
"retryWrites",
]

cteCapabilities: CteCapabilities = {
enabled: false,
}

// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------
Expand Down
23 changes: 18 additions & 5 deletions src/driver/mysql/MysqlDriver.ts
Expand Up @@ -2,6 +2,7 @@ import { Driver, ReturningType } from "../Driver"
import { ConnectionIsNotSetError } from "../../error/ConnectionIsNotSetError"
import { DriverPackageNotInstalledError } from "../../error/DriverPackageNotInstalledError"
import { DriverUtils } from "../DriverUtils"
import { CteCapabilities } from "../types/CteCapabilities"
import { MysqlQueryRunner } from "./MysqlQueryRunner"
import { ObjectLiteral } from "../../common/ObjectLiteral"
import { ColumnMetadata } from "../../metadata/ColumnMetadata"
Expand Down Expand Up @@ -309,6 +310,11 @@ export class MysqlDriver implements Driver {
*/
maxAliasLength = 63

cteCapabilities: CteCapabilities = {
enabled: false,
requiresRecursiveHint: true,
}

/**
* Supported returning types
*/
Expand Down Expand Up @@ -391,18 +397,25 @@ 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
const result = (await this.createQueryRunner("master").query(
`SELECT VERSION() AS \`version\``,
)) as { version: string }[]
const dbVersion = result[0].version

if (this.options.type === "mariadb") {
if (VersionUtils.isGreaterOrEqual(dbVersion, "10.0.5")) {
this._isReturningSqlSupported.delete = true
}
if (VersionUtils.isGreaterOrEqual(dbVersion, "10.5.0")) {
this._isReturningSqlSupported.insert = true
}
if (VersionUtils.isGreaterOrEqual(dbVersion, "10.2.0")) {
this.cteCapabilities.enabled = true
}
} else if (this.options.type === "mysql") {
if (VersionUtils.isGreaterOrEqual(dbVersion, "8.0.0")) {
this.cteCapabilities.enabled = true
}
}
}

Expand Down
5 changes: 5 additions & 0 deletions src/driver/oracle/OracleDriver.ts
@@ -1,6 +1,7 @@
import { Driver } from "../Driver"
import { ConnectionIsNotSetError } from "../../error/ConnectionIsNotSetError"
import { DriverPackageNotInstalledError } from "../../error/DriverPackageNotInstalledError"
import { CteCapabilities } from "../types/CteCapabilities"
import { OracleQueryRunner } from "./OracleQueryRunner"
import { ObjectLiteral } from "../../common/ObjectLiteral"
import { ColumnMetadata } from "../../metadata/ColumnMetadata"
Expand Down Expand Up @@ -220,6 +221,10 @@ export class OracleDriver implements Driver {
*/
maxAliasLength = 29

cteCapabilities: CteCapabilities = {
enabled: false, // TODO: enable
}

// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------
Expand Down
8 changes: 8 additions & 0 deletions src/driver/postgres/PostgresDriver.ts
Expand Up @@ -13,6 +13,7 @@ import { DateUtils } from "../../util/DateUtils"
import { OrmUtils } from "../../util/OrmUtils"
import { Driver } from "../Driver"
import { ColumnType } from "../types/ColumnTypes"
import { CteCapabilities } from "../types/CteCapabilities"
import { DataTypeDefaults } from "../types/DataTypeDefaults"
import { MappedColumnTypes } from "../types/MappedColumnTypes"
import { ReplicationMode } from "../types/ReplicationMode"
Expand Down Expand Up @@ -272,6 +273,13 @@ export class PostgresDriver implements Driver {

isGeneratedColumnsSupported: boolean = false

cteCapabilities: CteCapabilities = {
enabled: true,
writable: true,
requiresRecursiveHint: true,
materializedHint: true,
}

// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------
Expand Down
5 changes: 5 additions & 0 deletions src/driver/sap/SapDriver.ts
Expand Up @@ -16,6 +16,7 @@ import { ApplyValueTransformers } from "../../util/ApplyValueTransformers"
import { DateUtils } from "../../util/DateUtils"
import { OrmUtils } from "../../util/OrmUtils"
import { Driver } from "../Driver"
import { CteCapabilities } from "../types/CteCapabilities"
import { DataTypeDefaults } from "../types/DataTypeDefaults"
import { MappedColumnTypes } from "../types/MappedColumnTypes"
import { SapConnectionOptions } from "./SapConnectionOptions"
Expand Down Expand Up @@ -204,6 +205,10 @@ export class SapDriver implements Driver {
*/
maxAliasLength = 128

cteCapabilities: CteCapabilities = {
enabled: true,
}

// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------
Expand Down
6 changes: 6 additions & 0 deletions src/driver/sqlite-abstract/AbstractSqliteDriver.ts
Expand Up @@ -4,6 +4,7 @@ import { ColumnMetadata } from "../../metadata/ColumnMetadata"
import { DateUtils } from "../../util/DateUtils"
import { DataSource } from "../../data-source/DataSource"
import { RdbmsSchemaBuilder } from "../../schema-builder/RdbmsSchemaBuilder"
import { CteCapabilities } from "../types/CteCapabilities"
import { MappedColumnTypes } from "../types/MappedColumnTypes"
import { ColumnType } from "../types/ColumnTypes"
import { QueryRunner } from "../../query-runner/QueryRunner"
Expand Down Expand Up @@ -223,6 +224,11 @@ export abstract class AbstractSqliteDriver implements Driver {
*/
maxAliasLength?: number

cteCapabilities: CteCapabilities = {
enabled: true,
requiresRecursiveHint: true,
}

// -------------------------------------------------------------------------
// Protected Properties
// -------------------------------------------------------------------------
Expand Down
7 changes: 7 additions & 0 deletions src/driver/sqlserver/SqlServerDriver.ts
Expand Up @@ -2,6 +2,7 @@ import { Driver } from "../Driver"
import { ConnectionIsNotSetError } from "../../error/ConnectionIsNotSetError"
import { DriverPackageNotInstalledError } from "../../error/DriverPackageNotInstalledError"
import { DriverUtils } from "../DriverUtils"
import { CteCapabilities } from "../types/CteCapabilities"
import { SqlServerQueryRunner } from "./SqlServerQueryRunner"
import { ObjectLiteral } from "../../common/ObjectLiteral"
import { ColumnMetadata } from "../../metadata/ColumnMetadata"
Expand Down Expand Up @@ -222,6 +223,12 @@ export class SqlServerDriver implements Driver {
datetimeoffset: { precision: 7 },
}

cteCapabilities: CteCapabilities = {
enabled: true,
// todo: enable it for SQL Server - it's partially supported, but there are issues with generation of non-standard OUTPUT clause
writable: false,
}

/**
* Max length allowed by MSSQL Server for aliases (identifiers).
* @see https://docs.microsoft.com/en-us/sql/sql-server/maximum-capacity-specifications-for-sql-server
Expand Down
20 changes: 20 additions & 0 deletions src/driver/types/CteCapabilities.ts
@@ -0,0 +1,20 @@
export interface CteCapabilities {
/**
* Are CTEs supported at all?
*/
enabled: boolean

/**
* Are RETURNING clauses supported in CTEs?
*/
writable?: boolean
/**
* Is RECURSIVE clause required for recursive CTEs?
*/
requiresRecursiveHint?: boolean

/**
* Is MATERIALIZED clause supported?
*/
materializedHint?: boolean
}
1 change: 1 addition & 0 deletions src/query-builder/DeleteQueryBuilder.ts
Expand Up @@ -39,6 +39,7 @@ export class DeleteQueryBuilder<Entity>
*/
getQuery(): string {
let sql = this.createComment()
sql += this.createCteExpression()
sql += this.createDeleteExpression()
return sql.trim()
}
Expand Down
1 change: 1 addition & 0 deletions src/query-builder/InsertQueryBuilder.ts
Expand Up @@ -33,6 +33,7 @@ export class InsertQueryBuilder<Entity> extends QueryBuilder<Entity> {
*/
getQuery(): string {
let sql = this.createComment()
sql += this.createCteExpression()
sql += this.createInsertExpression()
return sql.trim()
}
Expand Down

0 comments on commit 7cc1848

Please sign in to comment.