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..c3768407d 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: this.transformNode(node.selectQuery), }) } diff --git a/src/query-compiler/default-query-compiler.ts b/src/query-compiler/default-query-compiler.ts index 585652289..e371cc2ac 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,24 @@ 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) + } else { + this.append(' (') + this.compileList([...node.columns, ...(node.constraints ?? [])]) + this.append(')') - if (node.endModifiers && node.endModifiers.length > 0) { - this.append(' ') - this.compileList(node.endModifiers, ' ') + 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..e22c30dcf 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')) +} 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 +}