Skip to content

Commit 9f037a2

Browse files
shuaixrigalklebanov
andcommitted
feat: support IF EXISTS in DROP COLUMN (#1692)
Co-authored-by: Igal Klebanov <igalklebanov@gmail.com>
1 parent 8af8c22 commit 9f037a2

8 files changed

Lines changed: 157 additions & 11 deletions

File tree

src/dialect/mssql/mssql-query-compiler.ts

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,25 +52,38 @@ export class MssqlQueryCompiler extends DefaultQueryCompiler {
5252
// multiple of these are not really supported by mssql,
5353
// but for the sake of WYSIWYG.
5454
if (nodesByKind.AlterColumnNode) {
55-
if (!first) this.append(', ')
55+
if (!first) {
56+
this.append(', ')
57+
}
58+
5659
this.compileList(nodesByKind.AlterColumnNode)
5760
}
5861

5962
if (nodesByKind.DropColumnNode) {
60-
if (!first) this.append(', ')
61-
this.append('drop column ')
63+
if (!first) {
64+
this.append(', ')
65+
}
66+
67+
this.append('drop ')
6268
this.compileList(nodesByKind.DropColumnNode)
69+
first = false
6370
}
6471

6572
// not really supported by mssql, but for the sake of WYSIWYG.
6673
if (nodesByKind.ModifyColumnNode) {
67-
if (!first) this.append(', ')
74+
if (!first) {
75+
this.append(', ')
76+
}
77+
6878
this.compileList(nodesByKind.ModifyColumnNode)
6979
}
7080

7181
// not really supported by mssql, but for the sake of WYSIWYG.
7282
if (nodesByKind.RenameColumnNode) {
73-
if (!first) this.append(', ')
83+
if (!first) {
84+
this.append(', ')
85+
}
86+
7487
this.compileList(nodesByKind.RenameColumnNode)
7588
}
7689
}
@@ -80,6 +93,12 @@ export class MssqlQueryCompiler extends DefaultQueryCompiler {
8093
}
8194

8295
protected override visitDropColumn(node: DropColumnNode): void {
96+
this.append('column ')
97+
98+
if (node.ifExists) {
99+
this.append('if exists ')
100+
}
101+
83102
this.visitNode(node.column)
84103
}
85104

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export * from './schema/create-view-builder.js'
6666
export * from './schema/refresh-materialized-view-builder.js'
6767
export * from './schema/drop-view-builder.js'
6868
export * from './schema/alter-column-builder.js'
69+
export * from './schema/drop-column-builder.js'
6970

7071
export * from './dynamic/dynamic.js'
7172
export * from './dynamic/dynamic-reference-builder.js'

src/operation-node/drop-column-node.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,21 @@ import type { OperationNode } from './operation-node.js'
22
import { freeze } from '../util/object-utils.js'
33
import { ColumnNode } from './column-node.js'
44

5+
export type DropColumnNodeProps = Omit<DropColumnNode, 'kind' | 'column'>
6+
57
export interface DropColumnNode extends OperationNode {
68
readonly kind: 'DropColumnNode'
79
readonly column: ColumnNode
10+
readonly ifExists?: boolean
811
}
912

1013
type DropColumnNodeFactory = Readonly<{
1114
is(node: OperationNode): node is DropColumnNode
1215
create(column: string): Readonly<DropColumnNode>
16+
cloneWith(
17+
node: DropColumnNode,
18+
props: DropColumnNodeProps,
19+
): Readonly<DropColumnNode>
1320
}>
1421

1522
/**
@@ -27,4 +34,11 @@ export const DropColumnNode: DropColumnNodeFactory =
2734
column: ColumnNode.create(column),
2835
})
2936
},
37+
38+
cloneWith(node, props) {
39+
return freeze({
40+
...node,
41+
...props,
42+
})
43+
},
3044
})

src/operation-node/operation-node-transformer.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -865,6 +865,7 @@ export class OperationNodeTransformer {
865865
return {
866866
kind: 'DropColumnNode',
867867
column: this.transformNode(node.column, queryId),
868+
ifExists: node.ifExists,
868869
} satisfies AllProps<DropColumnNode>
869870
}
870871

src/query-compiler/default-query-compiler.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1232,9 +1232,13 @@ export class DefaultQueryCompiler
12321232

12331233
protected override visitDropColumn(node: DropColumnNode): void {
12341234
this.append('drop column ')
1235+
1236+
if (node.ifExists) {
1237+
this.append('if exists ')
1238+
}
1239+
12351240
this.visitNode(node.column)
12361241
}
1237-
12381242
protected override visitAlterColumn(node: AlterColumnNode): void {
12391243
this.append('alter column ')
12401244
this.visitNode(node.column)

src/schema/alter-table-builder.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ import {
5959
type ExpressionOrFactory,
6060
parseExpression,
6161
} from '../parser/expression-parser.js'
62+
import {
63+
DropColumnBuilder,
64+
type DropColumnBuilderCallback,
65+
} from './drop-column-builder.js'
6266

6367
/**
6468
* This builder can be used to create a `alter table` query.
@@ -103,12 +107,19 @@ export class AlterTableBuilder implements ColumnAlteringInterface {
103107
})
104108
}
105109

106-
dropColumn(column: string): AlterTableColumnAlteringBuilder {
110+
dropColumn(
111+
column: string,
112+
build: DropColumnBuilderCallback = noop,
113+
): AlterTableColumnAlteringBuilder {
114+
const builder = build(
115+
new DropColumnBuilder({ node: DropColumnNode.create(column) }),
116+
)
117+
107118
return new AlterTableColumnAlteringBuilder({
108119
...this.#props,
109120
node: AlterTableNode.cloneWithColumnAlteration(
110121
this.#props.node,
111-
DropColumnNode.create(column),
122+
builder.toOperationNode(),
112123
),
113124
})
114125
}
@@ -431,12 +442,19 @@ export class AlterTableColumnAlteringBuilder
431442
})
432443
}
433444

434-
dropColumn(column: string): AlterTableColumnAlteringBuilder {
445+
dropColumn(
446+
column: string,
447+
build: DropColumnBuilderCallback = noop,
448+
): AlterTableColumnAlteringBuilder {
449+
const builder = build(
450+
new DropColumnBuilder({ node: DropColumnNode.create(column) }),
451+
)
452+
435453
return new AlterTableColumnAlteringBuilder({
436454
...this.#props,
437455
node: AlterTableNode.cloneWithColumnAlteration(
438456
this.#props.node,
439-
DropColumnNode.create(column),
457+
builder.toOperationNode(),
440458
),
441459
})
442460
}

src/schema/drop-column-builder.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { DropColumnNode } from '../operation-node/drop-column-node.js'
2+
import type { OperationNodeSource } from '../operation-node/operation-node-source.js'
3+
import { freeze } from '../util/object-utils.js'
4+
5+
export class DropColumnBuilder implements OperationNodeSource {
6+
readonly #props: DropColumnBuilderProps
7+
8+
constructor(props: DropColumnBuilderProps) {
9+
this.#props = freeze({ ...props })
10+
}
11+
12+
ifExists(): DropColumnBuilder {
13+
return new DropColumnBuilder({
14+
...this.#props,
15+
node: DropColumnNode.cloneWith(this.#props.node, { ifExists: true }),
16+
})
17+
}
18+
19+
toOperationNode(): DropColumnNode {
20+
return this.#props.node
21+
}
22+
}
23+
24+
export interface DropColumnBuilderProps {
25+
readonly node: DropColumnNode
26+
}
27+
28+
export type DropColumnBuilderCallback = (
29+
builder: DropColumnBuilder,
30+
) => DropColumnBuilder

test/node/src/schema.test.ts

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2954,6 +2954,7 @@ for (const dialect of DIALECTS) {
29542954
.createTable('test')
29552955
.addColumn('varchar_col', 'varchar(255)')
29562956
.addColumn('integer_col', 'integer')
2957+
.addColumn('different_col', 'text')
29572958
.execute()
29582959
})
29592960

@@ -3397,6 +3398,29 @@ for (const dialect of DIALECTS) {
33973398
await builder.execute()
33983399
})
33993400

3401+
if (sqlSpec === 'postgres' || sqlSpec === 'mssql') {
3402+
it('should drop a column if it exists', async () => {
3403+
const builder = ctx.db.schema
3404+
.alterTable('test')
3405+
.dropColumn('varchar_col', (col) => col.ifExists())
3406+
3407+
testSql(builder, dialect, {
3408+
postgres: {
3409+
sql: 'alter table "test" drop column if exists "varchar_col"',
3410+
parameters: [],
3411+
},
3412+
mysql: NOT_SUPPORTED,
3413+
mssql: {
3414+
sql: 'alter table "test" drop column if exists "varchar_col"',
3415+
parameters: [],
3416+
},
3417+
sqlite: NOT_SUPPORTED,
3418+
})
3419+
3420+
await builder.execute()
3421+
})
3422+
}
3423+
34003424
if (
34013425
sqlSpec === 'postgres' ||
34023426
sqlSpec === 'mysql' ||
@@ -3434,7 +3458,7 @@ for (const dialect of DIALECTS) {
34343458
sql: [
34353459
'alter table "test"',
34363460
'drop column "varchar_col",',
3437-
'"text_col"',
3461+
'column "text_col"',
34383462
],
34393463
parameters: [],
34403464
},
@@ -3453,6 +3477,41 @@ for (const dialect of DIALECTS) {
34533477
}
34543478
})
34553479

3480+
if (sqlSpec === 'postgres' || sqlSpec === 'mssql') {
3481+
it('should drop multiple columns if it exists', async () => {
3482+
const builder = ctx.db.schema
3483+
.alterTable('test')
3484+
.dropColumn('varchar_col')
3485+
.dropColumn('not_exist_col', (col) => col.ifExists())
3486+
.dropColumn('different_col')
3487+
3488+
testSql(builder, dialect, {
3489+
postgres: {
3490+
sql: [
3491+
'alter table "test"',
3492+
'drop column "varchar_col",',
3493+
'drop column if exists "not_exist_col",',
3494+
'drop column "different_col"',
3495+
],
3496+
parameters: [],
3497+
},
3498+
mysql: NOT_SUPPORTED,
3499+
mssql: {
3500+
sql: [
3501+
'alter table "test"',
3502+
'drop column "varchar_col",',
3503+
'column if exists "not_exist_col",',
3504+
'column "different_col"',
3505+
],
3506+
parameters: [],
3507+
},
3508+
sqlite: NOT_SUPPORTED,
3509+
})
3510+
3511+
await builder.execute()
3512+
})
3513+
}
3514+
34563515
if (
34573516
sqlSpec === 'postgres' ||
34583517
sqlSpec === 'mysql' ||

0 commit comments

Comments
 (0)