Skip to content

Commit e4aee2d

Browse files
committed
feat(columns): support setting identity_generation
1 parent 5830e80 commit e4aee2d

File tree

2 files changed

+82
-49
lines changed

2 files changed

+82
-49
lines changed

src/api/columns.ts

Lines changed: 79 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Router } from 'express'
2-
import format, { literal } from 'pg-format'
2+
import format, { ident, literal } from 'pg-format'
33
import SQL from 'sql-template-strings'
44
import { RunQuery } from '../lib/connectionPool'
55
import sql = require('../lib/sql')
@@ -59,13 +59,9 @@ router.patch('/:id', async (req, res) => {
5959
const [tableId, ordinalPos] = req.params.id.split('.').map(Number)
6060
const getColumnQuery = getColumnByPosSqlize(tableId, ordinalPos)
6161
const column = (await RunQuery(req.headers.pg, getColumnQuery)).data[0]
62-
const { schema, table, name: oldName } = column
6362

6463
const alterColumnArgs = req.body
65-
alterColumnArgs.schema = schema
66-
alterColumnArgs.table = table
67-
alterColumnArgs.oldName = oldName
68-
const query = alterColumnSqlize(alterColumnArgs)
64+
const query = alterColumnSqlize(column, alterColumnArgs)
6965
await RunQuery(req.headers.pg, query)
7066

7167
const updated = (await RunQuery(req.headers.pg, getColumnQuery)).data[0]
@@ -104,6 +100,7 @@ const addColumnSqlize = ({
104100
default_value,
105101
default_value_format = 'literal',
106102
is_identity = false,
103+
identity_generation,
107104
is_nullable = true,
108105
is_primary_key = false,
109106
is_unique = false,
@@ -116,6 +113,7 @@ const addColumnSqlize = ({
116113
default_value?: any
117114
default_value_format?: 'expression' | 'literal'
118115
is_identity?: boolean
116+
identity_generation?: 'BY DEFAULT' | 'ALWAYS'
119117
is_nullable?: boolean
120118
is_primary_key?: boolean
121119
is_unique?: boolean
@@ -129,7 +127,7 @@ const addColumnSqlize = ({
129127
} else {
130128
defaultValueSql = `DEFAULT ${literal(default_value)}`
131129
}
132-
const isIdentitySql = is_identity ? 'GENERATED BY DEFAULT AS IDENTITY' : ''
130+
const isIdentitySql = is_identity ? `GENERATED ${identity_generation} AS IDENTITY` : ''
133131
const isNullableSql = is_nullable ? 'NULL' : 'NOT NULL'
134132
const isPrimaryKeySql = is_primary_key ? 'PRIMARY KEY' : ''
135133
const isUniqueSql = is_unique ? 'UNIQUE' : ''
@@ -161,81 +159,113 @@ const getColumnByPosSqlize = (tableId: number, ordinalPos: number) => {
161159
.append(columns)
162160
.append(SQL` WHERE c.oid = ${tableId} AND ordinal_position = ${ordinalPos}`)
163161
}
164-
const alterColumnSqlize = ({
165-
schema,
166-
table,
167-
oldName,
168-
name,
169-
type,
170-
drop_default = false,
171-
default_value,
172-
default_value_format = 'literal',
173-
is_nullable,
174-
comment,
175-
}: {
176-
schema: string
177-
table: string
178-
oldName: string
179-
name?: string
180-
type?: string
181-
drop_default?: boolean
182-
default_value?: any
183-
default_value_format?: 'expression' | 'literal'
184-
is_nullable?: boolean
185-
comment?: string
186-
}) => {
162+
const alterColumnSqlize = (
163+
old: any,
164+
{
165+
name,
166+
type,
167+
drop_default = false,
168+
default_value,
169+
default_value_format = 'literal',
170+
is_identity,
171+
identity_generation,
172+
is_nullable,
173+
comment,
174+
}: {
175+
name?: string
176+
type?: string
177+
drop_default?: boolean
178+
default_value?: any
179+
default_value_format?: 'expression' | 'literal'
180+
is_identity?: boolean
181+
identity_generation?: 'BY DEFAULT' | 'ALWAYS'
182+
is_nullable?: boolean
183+
comment?: string
184+
}
185+
) => {
187186
const nameSql =
188-
name === undefined || name === oldName
187+
name === undefined || name === old.name
189188
? ''
190-
: format('ALTER TABLE %I.%I RENAME COLUMN %I TO %I;', schema, table, oldName, name)
189+
: format('ALTER TABLE %I.%I RENAME COLUMN %I TO %I;', old.schema, old.table, old.name, name)
191190
// We use USING to allow implicit conversion of incompatible types (e.g. int4 -> text).
192191
const typeSql =
193192
type === undefined
194193
? ''
195194
: format(
196195
'ALTER TABLE %I.%I ALTER COLUMN %I SET DATA TYPE %I USING %I::%I;',
197-
schema,
198-
table,
199-
oldName,
196+
old.schema,
197+
old.table,
198+
old.name,
200199
type,
201-
oldName,
200+
old.name,
202201
type
203202
)
204-
let defaultValueSql = ''
203+
let defaultValueSql: string
205204
if (drop_default) {
206205
defaultValueSql = format(
207206
'ALTER TABLE %I.%I ALTER COLUMN %I DROP DEFAULT;',
208-
schema,
209-
table,
210-
oldName
207+
old.schema,
208+
old.table,
209+
old.name
211210
)
212-
} else if (default_value !== undefined) {
211+
} else if (default_value === undefined) {
212+
defaultValueSql = ''
213+
} else {
213214
let defaultValue =
214215
default_value_format === 'expression' ? default_value : literal(default_value)
215216
defaultValueSql = format(
216217
`ALTER TABLE %I.%I ALTER COLUMN %I SET DEFAULT ${defaultValue};`,
217-
schema,
218-
table,
219-
oldName
218+
old.schema,
219+
old.table,
220+
old.name
220221
)
221222
}
222-
let isNullableSql = ''
223-
if (is_nullable !== undefined) {
223+
// What identitySql does vary depending on the old and new values of
224+
// is_identity and identity_generation.
225+
//
226+
// | is_identity: old \ new | undefined | true | false |
227+
// |------------------------+--------------------+--------------------+----------------|
228+
// | true | maybe set identity | maybe set identity | drop if exists |
229+
// |------------------------+--------------------+--------------------+----------------|
230+
// | false | - | add identity | drop if exists |
231+
let identitySql = `ALTER TABLE ${ident(old.schema)}.${ident(old.table)} ALTER COLUMN ${ident(
232+
old.name
233+
)} `
234+
if (is_identity === false) {
235+
identitySql += 'DROP IDENTITY IF EXISTS;'
236+
} else if (old.is_identity === true) {
237+
if (identity_generation === undefined) {
238+
identitySql = ''
239+
} else {
240+
identitySql += `SET GENERATED ${identity_generation};`
241+
}
242+
} else if (is_identity === undefined) {
243+
identitySql = ''
244+
} else {
245+
identitySql += `ADD GENERATED ${identity_generation} AS IDENTITY;`
246+
}
247+
let isNullableSql: string
248+
if (is_nullable === undefined) {
249+
isNullableSql = ''
250+
} else {
224251
isNullableSql = is_nullable
225-
? format('ALTER TABLE %I.%I ALTER COLUMN %I DROP NOT NULL;', schema, table, oldName)
226-
: format('ALTER TABLE %I.%I ALTER COLUMN %I SET NOT NULL;', schema, table, oldName)
252+
? format('ALTER TABLE %I.%I ALTER COLUMN %I DROP NOT NULL;', old.schema, old.table, old.name)
253+
: format('ALTER TABLE %I.%I ALTER COLUMN %I SET NOT NULL;', old.schema, old.table, old.name)
227254
}
228255
const commentSql =
229256
comment === undefined
230257
? ''
231-
: format('COMMENT ON COLUMN %I.%I.%I IS %L;', schema, table, oldName, comment)
258+
: format('COMMENT ON COLUMN %I.%I.%I IS %L;', old.schema, old.table, old.name, comment)
232259

233260
// nameSql must be last.
261+
// TODO: Can't set default if column is previously identity even if is_identity: false.
262+
// Must do two separate PATCHes (once to drop identity and another to set default).
234263
return `
235264
BEGIN;
236265
${isNullableSql}
237266
${defaultValueSql}
238267
${typeSql}
268+
${identitySql}
239269
${commentSql}
240270
${nameSql}
241271
COMMIT;`

test/integration/index.spec.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,8 @@ describe('/tables', async () => {
352352
name: 'foo bar',
353353
type: 'int4',
354354
drop_default: true,
355+
is_identity: true,
356+
identity_generation: 'ALWAYS',
355357
is_nullable: false,
356358
comment: 'bar',
357359
})
@@ -362,6 +364,7 @@ describe('/tables', async () => {
362364
column.id === `${newTable.id}.1` && column.name === 'foo bar' && column.format === 'int4'
363365
)
364366
assert.equal(updatedColumn.default_value, null)
367+
assert.equal(updatedColumn.identity_generation, 'ALWAYS')
365368
assert.equal(updatedColumn.is_nullable, false)
366369
assert.equal(updatedColumn.comment, 'bar')
367370

0 commit comments

Comments
 (0)