Skip to content

Commit

Permalink
AS statement support for createTable (#771)
Browse files Browse the repository at this point in the history
* feat: added support of as statement for create table

* fix: formatting

* chore: added transformNode for selectQuery
  • Loading branch information
Alex Vershinin authored Nov 20, 2023
1 parent 2365556 commit 31c5258
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 11 deletions.
1 change: 1 addition & 0 deletions src/operation-node/create-table-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface CreateTableNode extends OperationNode {
readonly onCommit?: OnCommitAction
readonly frontModifiers?: ReadonlyArray<OperationNode>
readonly endModifiers?: ReadonlyArray<OperationNode>
readonly selectQuery?: OperationNode
}

/**
Expand Down
1 change: 1 addition & 0 deletions src/operation-node/operation-node-transformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),
})
}

Expand Down
27 changes: 17 additions & 10 deletions src/query-compiler/default-query-compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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, ' ')
}
}
}

Expand Down
29 changes: 29 additions & 0 deletions src/schema/create-table-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -357,6 +358,34 @@ export class CreateTableBuilder<TB extends string, C extends string = never>
})
}

/**
* 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<unknown>) {
return new CreateTableBuilder({
...this.#props,
node: CreateTableNode.cloneWith(this.#props.node, {
selectQuery: parseExpression(expression),
}),
})
}

/**
* Calls the given function passing `this` as the only argument.
*
Expand Down
107 changes: 107 additions & 0 deletions test/node/src/schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand Down
8 changes: 8 additions & 0 deletions test/typings/test-d/create-table.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { expectError } from 'tsd'
import { Kysely } from '..'
import { Database } from '../shared'

async function testCreateTableWithAsStatement(db: Kysely<Database>) {
expectError(db.schema.createTable('test').as())
expectError(db.schema.createTable('test').as('test'))
}
2 changes: 1 addition & 1 deletion test/typings/test-d/delete-query-builder.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,4 +240,4 @@ async function testIf(db: Kysely<Database>) {
f19?: string
f20?: string
}>(r)
}
}

1 comment on commit 31c5258

@vercel
Copy link

@vercel vercel bot commented on 31c5258 Nov 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

kysely – ./

kysely.dev
kysely-kysely-team.vercel.app
www.kysely.dev
kysely-git-master-kysely-team.vercel.app

Please sign in to comment.