From f1afb23de28c175e9631d08c9a2ca2f1109ab580 Mon Sep 17 00:00:00 2001 From: Alex Vershinin Date: Mon, 20 Nov 2023 04:45:19 +0400 Subject: [PATCH 1/3] feat: added support of as statement for create table --- src/operation-node/create-table-node.ts | 1 + .../operation-node-transformer.ts | 1 + src/query-compiler/default-query-compiler.ts | 30 +++-- src/schema/create-table-builder.ts | 29 +++++ test/node/src/schema.test.ts | 107 ++++++++++++++++++ test/typings/test-d/create-table.test-d.ts | 8 ++ 6 files changed, 165 insertions(+), 11 deletions(-) create mode 100644 test/typings/test-d/create-table.test-d.ts diff --git a/src/operation-node/create-table-node.ts b/src/operation-node/create-table-node.ts index 3638d3053..1de7dc9a5 100644 --- a/src/operation-node/create-table-node.ts +++ b/src/operation-node/create-table-node.ts @@ -28,6 +28,7 @@ export interface CreateTableNode extends OperationNode { readonly onCommit?: OnCommitAction readonly frontModifiers?: ReadonlyArray readonly endModifiers?: ReadonlyArray + readonly selectQuery?: OperationNode } /** diff --git a/src/operation-node/operation-node-transformer.ts b/src/operation-node/operation-node-transformer.ts index f10c6bb9a..dd76c60aa 100644 --- a/src/operation-node/operation-node-transformer.ts +++ b/src/operation-node/operation-node-transformer.ts @@ -411,6 +411,7 @@ export class OperationNodeTransformer { onCommit: node.onCommit, frontModifiers: this.transformNodeList(node.frontModifiers), endModifiers: this.transformNodeList(node.endModifiers), + selectQuery: node.selectQuery }) } diff --git a/src/query-compiler/default-query-compiler.ts b/src/query-compiler/default-query-compiler.ts index 585652289..113478111 100644 --- a/src/query-compiler/default-query-compiler.ts +++ b/src/query-compiler/default-query-compiler.ts @@ -138,6 +138,7 @@ export class DefaultQueryCompiler this.parentNode !== undefined && !ParensNode.is(this.parentNode) && !InsertQueryNode.is(this.parentNode) && + !CreateTableNode.is(this.parentNode) && !CreateViewNode.is(this.parentNode) && !SetOperationNode.is(this.parentNode) @@ -549,18 +550,25 @@ export class DefaultQueryCompiler } this.visitNode(node.table) - this.append(' (') - this.compileList([...node.columns, ...(node.constraints ?? [])]) - this.append(')') - - if (node.onCommit) { - this.append(' on commit ') - this.append(node.onCommit) + + if (node.selectQuery) { + this.append(' as ') + this.visitNode(node.selectQuery) } - - if (node.endModifiers && node.endModifiers.length > 0) { - this.append(' ') - this.compileList(node.endModifiers, ' ') + else { + this.append(' (') + this.compileList([...node.columns, ...(node.constraints ?? [])]) + this.append(')') + + if (node.onCommit) { + this.append(' on commit ') + this.append(node.onCommit) + } + + if (node.endModifiers && node.endModifiers.length > 0) { + this.append(' ') + this.compileList(node.endModifiers, ' ') + } } } diff --git a/src/schema/create-table-builder.ts b/src/schema/create-table-builder.ts index fa1d472d3..d8e08c803 100644 --- a/src/schema/create-table-builder.ts +++ b/src/schema/create-table-builder.ts @@ -24,6 +24,7 @@ import { CheckConstraintNode } from '../operation-node/check-constraint-node.js' import { parseTable } from '../parser/table-parser.js' import { parseOnCommitAction } from '../parser/on-commit-action-parse.js' import { Expression } from '../expression/expression.js' +import { parseExpression } from '../parser/expression-parser.js' /** * This builder can be used to create a `create table` query. @@ -357,6 +358,34 @@ export class CreateTableBuilder }) } + /** + * Allows to create table from `select` query. + * + * ### Examples + * + * ```ts + * db.schema.createTable('copy') + * .temporary() + * .as(db.selectFrom('person').select(['first_name', 'last_name'])) + * .execute() + * ``` + * + * The generated SQL (PostgreSQL): + * + * ```sql + * create temporary table "copy" as + * select "first_name", "last_name" from "person" + * ``` + */ + as(expression: Expression) { + return new CreateTableBuilder({ + ...this.#props, + node: CreateTableNode.cloneWith(this.#props.node, { + selectQuery: parseExpression(expression), + }), + }) + } + /** * Calls the given function passing `this` as the only argument. * diff --git a/test/node/src/schema.test.ts b/test/node/src/schema.test.ts index 0edbfb58e..d028f98df 100644 --- a/test/node/src/schema.test.ts +++ b/test/node/src/schema.test.ts @@ -687,6 +687,113 @@ for (const dialect of DIALECTS) { await builder.execute() }) + + it('should create a table with as expression', async () => { + const builder = ctx.db.schema + .createTable('test') + .as(ctx.db.selectFrom('person').select(['first_name', 'last_name'])) + + testSql(builder, dialect, { + postgres: { + sql: 'create table "test" as select "first_name", "last_name" from "person"', + parameters: [], + }, + mysql: { + sql: 'create table `test` as select `first_name`, `last_name` from `person`', + parameters: [], + }, + mssql: NOT_SUPPORTED, + sqlite: { + sql: 'create table "test" as select "first_name", "last_name" from "person"', + parameters: [], + }, + }) + + await builder.execute() + }) + + it('should create a temporary table if not exists with as expression', async () => { + const builder = ctx.db.schema + .createTable('test') + .temporary() + .ifNotExists() + .as( + ctx.db + .selectFrom('person') + .select(['first_name', 'last_name']) + .where('first_name', '=', 'Jennifer') + ) + + testSql(builder, dialect, { + postgres: { + sql: 'create temporary table if not exists "test" as select "first_name", "last_name" from "person" where "first_name" = $1', + parameters: ['Jennifer'], + }, + mysql: { + sql: 'create temporary table if not exists `test` as select `first_name`, `last_name` from `person` where `first_name` = ?', + parameters: ['Jennifer'], + }, + mssql: NOT_SUPPORTED, + sqlite: { + sql: 'create temporary table if not exists "test" as select "first_name", "last_name" from "person" where "first_name" = ?', + parameters: ['Jennifer'], + }, + }) + + await builder.execute() + }) + + it('should create a table with as expression and raw sql', async () => { + let rawSql = sql`select "first_name", "last_name" from "person"` + if (dialect === 'mysql') { + rawSql = sql`select \`first_name\`, \`last_name\` from \`person\`` + } + + const builder = ctx.db.schema.createTable('test').as(rawSql) + + testSql(builder, dialect, { + postgres: { + sql: 'create table "test" as select "first_name", "last_name" from "person"', + parameters: [], + }, + mysql: { + sql: 'create table `test` as select `first_name`, `last_name` from `person`', + parameters: [], + }, + mssql: NOT_SUPPORTED, + sqlite: { + sql: 'create table "test" as select "first_name", "last_name" from "person"', + parameters: [], + }, + }) + + await builder.execute() + }) + + it('should create a table with as expression and ignore addColumn', async () => { + const builder = ctx.db.schema + .createTable('test') + .as(ctx.db.selectFrom('person').select(['first_name', 'last_name'])) + .addColumn('first_name', 'varchar(20)') + + testSql(builder, dialect, { + postgres: { + sql: 'create table "test" as select "first_name", "last_name" from "person"', + parameters: [], + }, + mysql: { + sql: 'create table `test` as select `first_name`, `last_name` from `person`', + parameters: [], + }, + mssql: NOT_SUPPORTED, + sqlite: { + sql: 'create table "test" as select "first_name", "last_name" from "person"', + parameters: [], + }, + }) + + await builder.execute() + }) } if (dialect === 'mssql') { diff --git a/test/typings/test-d/create-table.test-d.ts b/test/typings/test-d/create-table.test-d.ts new file mode 100644 index 000000000..f52056407 --- /dev/null +++ b/test/typings/test-d/create-table.test-d.ts @@ -0,0 +1,8 @@ +import { expectError } from 'tsd' +import { Kysely } from '..' +import { Database } from '../shared' + +async function testCreateTableWithAsStatement(db: Kysely) { + expectError(db.schema.createTable('test').as()) + expectError(db.schema.createTable('test').as('test')) +} From 87dc09f76cf8588f5163296fd80c75b7c6b97e16 Mon Sep 17 00:00:00 2001 From: Alex Vershinin Date: Mon, 20 Nov 2023 04:51:11 +0400 Subject: [PATCH 2/3] fix: formatting --- src/operation-node/operation-node-transformer.ts | 2 +- src/query-compiler/default-query-compiler.ts | 9 ++++----- src/schema/create-table-builder.ts | 2 +- test/typings/test-d/delete-query-builder.test-d.ts | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/operation-node/operation-node-transformer.ts b/src/operation-node/operation-node-transformer.ts index dd76c60aa..73f9f4c75 100644 --- a/src/operation-node/operation-node-transformer.ts +++ b/src/operation-node/operation-node-transformer.ts @@ -411,7 +411,7 @@ export class OperationNodeTransformer { onCommit: node.onCommit, frontModifiers: this.transformNodeList(node.frontModifiers), endModifiers: this.transformNodeList(node.endModifiers), - selectQuery: node.selectQuery + selectQuery: node.selectQuery, }) } diff --git a/src/query-compiler/default-query-compiler.ts b/src/query-compiler/default-query-compiler.ts index 113478111..e371cc2ac 100644 --- a/src/query-compiler/default-query-compiler.ts +++ b/src/query-compiler/default-query-compiler.ts @@ -550,21 +550,20 @@ export class DefaultQueryCompiler } this.visitNode(node.table) - + if (node.selectQuery) { this.append(' as ') this.visitNode(node.selectQuery) - } - else { + } else { this.append(' (') this.compileList([...node.columns, ...(node.constraints ?? [])]) this.append(')') - + if (node.onCommit) { this.append(' on commit ') this.append(node.onCommit) } - + if (node.endModifiers && node.endModifiers.length > 0) { this.append(' ') this.compileList(node.endModifiers, ' ') diff --git a/src/schema/create-table-builder.ts b/src/schema/create-table-builder.ts index d8e08c803..e22c30dcf 100644 --- a/src/schema/create-table-builder.ts +++ b/src/schema/create-table-builder.ts @@ -373,7 +373,7 @@ export class CreateTableBuilder * The generated SQL (PostgreSQL): * * ```sql - * create temporary table "copy" as + * create temporary table "copy" as * select "first_name", "last_name" from "person" * ``` */ diff --git a/test/typings/test-d/delete-query-builder.test-d.ts b/test/typings/test-d/delete-query-builder.test-d.ts index 1d94b8cd1..44e232618 100644 --- a/test/typings/test-d/delete-query-builder.test-d.ts +++ b/test/typings/test-d/delete-query-builder.test-d.ts @@ -240,4 +240,4 @@ async function testIf(db: Kysely) { f19?: string f20?: string }>(r) -} \ No newline at end of file +} From 69ad9aa078750950b0c33e865d4e527d01b43f07 Mon Sep 17 00:00:00 2001 From: Alex Vershinin Date: Mon, 20 Nov 2023 12:59:17 +0400 Subject: [PATCH 3/3] chore: added transformNode for selectQuery --- src/operation-node/operation-node-transformer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/operation-node/operation-node-transformer.ts b/src/operation-node/operation-node-transformer.ts index 73f9f4c75..c3768407d 100644 --- a/src/operation-node/operation-node-transformer.ts +++ b/src/operation-node/operation-node-transformer.ts @@ -411,7 +411,7 @@ export class OperationNodeTransformer { onCommit: node.onCommit, frontModifiers: this.transformNodeList(node.frontModifiers), endModifiers: this.transformNodeList(node.endModifiers), - selectQuery: node.selectQuery, + selectQuery: this.transformNode(node.selectQuery), }) }