@@ -4,6 +4,26 @@ import { DEFAULT_SYSTEM_SCHEMAS } from './constants'
44import { columnsSql } from './sql'
55import { PostgresMetaResult , PostgresColumn } from './types'
66
7+ interface ColumnCreateRequest {
8+ name : string
9+ type : string
10+ default_value ?: any
11+ default_value_format ?: 'expression' | 'literal'
12+ is_identity ?: boolean
13+ identity_generation ?: 'BY DEFAULT' | 'ALWAYS'
14+ is_nullable ?: boolean
15+ is_primary_key ?: boolean
16+ is_unique ?: boolean
17+ comment ?: string
18+ check ?: string
19+ }
20+
21+ interface ColumnInfoRequest {
22+ id ?: string
23+ name ?: string
24+ table ?: string
25+ schema ?: string
26+ }
727// TODO: Fix handling of `type` in `create()` and `update()`.
828// `type` on its own is not enough, e.g. `1::my type` should be `1::"my type"`.
929// `ident(type)` is not enough, e.g. `"int2[]"` should be `"int2"[]`.
@@ -38,6 +58,8 @@ export default class PostgresMetaColumns {
3858 return await this . query ( sql )
3959 }
4060
61+ async retrieve ( columns : ColumnInfoRequest [ ] ) : Promise < PostgresMetaResult < PostgresColumn > > { }
62+
4163 async retrieve ( { id } : { id : string } ) : Promise < PostgresMetaResult < PostgresColumn > >
4264 async retrieve ( {
4365 name,
@@ -53,12 +75,7 @@ export default class PostgresMetaColumns {
5375 name,
5476 table,
5577 schema = 'public' ,
56- } : {
57- id ?: string
58- name ?: string
59- table ?: string
60- schema ?: string
61- } ) : Promise < PostgresMetaResult < PostgresColumn > > {
78+ } : ColumnInfoRequest ) : Promise < PostgresMetaResult < PostgresColumn > > {
6279 if ( id ) {
6380 const regexp = / ^ ( \d + ) \. ( \d + ) $ /
6481 if ( ! regexp . test ( id ) ) {
@@ -95,49 +112,29 @@ export default class PostgresMetaColumns {
95112 }
96113 }
97114
98- async create ( {
99- table_id,
100- name,
101- type,
102- default_value,
103- default_value_format = 'literal' ,
104- is_identity = false ,
105- identity_generation = 'BY DEFAULT' ,
106- // Can't pick a value as default since regular columns are nullable by default but PK columns aren't
107- is_nullable,
108- is_primary_key = false ,
109- is_unique = false ,
110- comment,
111- check,
112- } : {
113- table_id : number
114- name : string
115- type : string
116- default_value ?: any
117- default_value_format ?: 'expression' | 'literal'
118- is_identity ?: boolean
119- identity_generation ?: 'BY DEFAULT' | 'ALWAYS'
120- is_nullable ?: boolean
121- is_primary_key ?: boolean
122- is_unique ?: boolean
123- comment ?: string
124- check ?: string
125- } ) : Promise < PostgresMetaResult < PostgresColumn > > {
126- const { data, error } = await this . metaTables . retrieve ( { id : table_id } )
127- if ( error ) {
128- return { data : null , error }
129- }
130- const { name : table , schema } = data !
131-
115+ generateColumnSql (
116+ {
117+ name,
118+ type,
119+ default_value,
120+ default_value_format = 'literal' ,
121+ is_identity = false ,
122+ identity_generation = 'BY DEFAULT' ,
123+ // Can't pick a value as default since regular columns are nullable by default but PK columns aren't
124+ is_nullable,
125+ is_primary_key = false ,
126+ is_unique = false ,
127+ comment,
128+ check,
129+ } : ColumnCreateRequest ,
130+ schema : string ,
131+ table : string
132+ ) : string {
132133 let defaultValueClause = ''
133134 if ( is_identity ) {
134135 if ( default_value !== undefined ) {
135- return {
136- data : null ,
137- error : { message : 'Columns cannot both be identity and have a default value' } ,
138- }
136+ throw new Error ( `Column ${ name } cannot both be identity and have a default value` )
139137 }
140-
141138 defaultValueClause = `GENERATED ${ identity_generation } AS IDENTITY`
142139 } else {
143140 if ( default_value === undefined ) {
@@ -161,25 +158,65 @@ export default class PostgresMetaColumns {
161158 ? ''
162159 : `COMMENT ON COLUMN ${ ident ( schema ) } .${ ident ( table ) } .${ ident ( name ) } IS ${ literal ( comment ) } `
163160
164- const sql = `
165- BEGIN;
161+ return `
166162 ALTER TABLE ${ ident ( schema ) } .${ ident ( table ) } ADD COLUMN ${ ident ( name ) } ${ type }
167163 ${ defaultValueClause }
168164 ${ isNullableClause }
169165 ${ isPrimaryKeyClause }
170166 ${ isUniqueClause }
171167 ${ checkSql } ;
172- ${ commentSql } ;
173- COMMIT;`
168+ ${ commentSql } ;`
169+ }
170+
171+ async createBatch ( {
172+ table_id,
173+ columns,
174+ } : {
175+ table_id : number
176+ columns : ColumnCreateRequest [ ]
177+ } ) : Promise < PostgresMetaResult < PostgresColumn > > {
178+ const { data, error } = await this . metaTables . retrieve ( { id : table_id } )
179+ if ( error ) {
180+ return { data : null , error }
181+ }
182+ const { name : table , schema } = data !
183+ let columnsSql : string
184+ try {
185+ columnsSql = columns . map ( ( column ) => this . generateColumnSql ( column , schema , table ) ) . join ( '\n' )
186+ } catch ( e : any ) {
187+ return { data : null , error : { message : e . toString ( ) } }
188+ }
189+
174190 {
175- const { error } = await this . query ( sql )
191+ const { error } = await this . query ( `
192+ BEGIN;
193+ ${ columnsSql }
194+ COMMIT;
195+ ` )
176196 if ( error ) {
177197 return { data : null , error }
178198 }
179199 }
180200 return await this . retrieve ( { name, table, schema } )
181201 }
182202
203+ async create ( body : {
204+ table_id : number
205+ name : string
206+ type : string
207+ default_value ?: any
208+ default_value_format ?: 'expression' | 'literal'
209+ is_identity ?: boolean
210+ identity_generation ?: 'BY DEFAULT' | 'ALWAYS'
211+ is_nullable ?: boolean
212+ is_primary_key ?: boolean
213+ is_unique ?: boolean
214+ comment ?: string
215+ check ?: string
216+ } ) : Promise < PostgresMetaResult < PostgresColumn > > {
217+ return this . createBatch ( body . table_id , [ body ] )
218+ }
219+
183220 async update (
184221 id : string ,
185222 {
@@ -215,29 +252,29 @@ COMMIT;`
215252 name === undefined || name === old ! . name
216253 ? ''
217254 : `ALTER TABLE ${ ident ( old ! . schema ) } .${ ident ( old ! . table ) } RENAME COLUMN ${ ident (
218- old ! . name
219- ) } TO ${ ident ( name ) } ;`
255+ old ! . name
256+ ) } TO ${ ident ( name ) } ; `
220257 // We use USING to allow implicit conversion of incompatible types (e.g. int4 -> text).
221258 const typeSql =
222259 type === undefined
223260 ? ''
224261 : `ALTER TABLE ${ ident ( old ! . schema ) } .${ ident ( old ! . table ) } ALTER COLUMN ${ ident (
225- old ! . name
226- ) } SET DATA TYPE ${ ident ( type ) } USING ${ ident ( old ! . name ) } ::${ ident ( type ) } ;`
262+ old ! . name
263+ ) } SET DATA TYPE ${ ident ( type ) } USING ${ ident ( old ! . name ) } :: ${ ident ( type ) } ; `
227264
228265 let defaultValueSql : string
229266 if ( drop_default ) {
230267 defaultValueSql = `ALTER TABLE ${ ident ( old ! . schema ) } .${ ident (
231268 old ! . table
232- ) } ALTER COLUMN ${ ident ( old ! . name ) } DROP DEFAULT;`
269+ ) } ALTER COLUMN ${ ident ( old ! . name ) } DROP DEFAULT; `
233270 } else if ( default_value === undefined ) {
234271 defaultValueSql = ''
235272 } else {
236273 const defaultValue =
237274 default_value_format === 'expression' ? default_value : literal ( default_value )
238275 defaultValueSql = `ALTER TABLE ${ ident ( old ! . schema ) } .${ ident (
239276 old ! . table
240- ) } ALTER COLUMN ${ ident ( old ! . name ) } SET DEFAULT ${ defaultValue } ;`
277+ ) } ALTER COLUMN ${ ident ( old ! . name ) } SET DEFAULT ${ defaultValue } ; `
241278 }
242279 // What identitySql does vary depending on the old and new values of
243280 // is_identity and identity_generation.
@@ -249,64 +286,64 @@ COMMIT;`
249286 // | false | - | add identity | drop if exists |
250287 let identitySql = `ALTER TABLE ${ ident ( old ! . schema ) } .${ ident ( old ! . table ) } ALTER COLUMN ${ ident (
251288 old ! . name
252- ) } `
289+ ) } `
253290 if ( is_identity === false ) {
254291 identitySql += ' DROP IDENTITY IF EXISTS;'
255292 } else if ( old ! . is_identity === true ) {
256293 if ( identity_generation === undefined ) {
257294 identitySql = ''
258295 } else {
259- identitySql += ` SET GENERATED ${ identity_generation } ;`
296+ identitySql += ` SET GENERATED ${ identity_generation } ; `
260297 }
261298 } else if ( is_identity === undefined ) {
262299 identitySql = ''
263300 } else {
264- identitySql += ` ADD GENERATED ${ identity_generation } AS IDENTITY;`
301+ identitySql += ` ADD GENERATED ${ identity_generation } AS IDENTITY; `
265302 }
266303 let isNullableSql : string
267304 if ( is_nullable === undefined ) {
268305 isNullableSql = ''
269306 } else {
270307 isNullableSql = is_nullable
271308 ? `ALTER TABLE ${ ident ( old ! . schema ) } .${ ident ( old ! . table ) } ALTER COLUMN ${ ident (
272- old ! . name
273- ) } DROP NOT NULL;`
309+ old ! . name
310+ ) } DROP NOT NULL; `
274311 : `ALTER TABLE ${ ident ( old ! . schema ) } .${ ident ( old ! . table ) } ALTER COLUMN ${ ident (
275- old ! . name
276- ) } SET NOT NULL;`
312+ old ! . name
313+ ) } SET NOT NULL; `
277314 }
278315 let isUniqueSql = ''
279316 if ( old ! . is_unique === true && is_unique === false ) {
280317 isUniqueSql = `
281- DO $$
282- DECLARE
283- r record;
284- BEGIN
285- FOR r IN
318+ DO $$
319+ DECLARE
320+ r record;
321+ BEGIN
322+ FOR r IN
286323 SELECT conname FROM pg_constraint WHERE
287- contype = 'u'
288- AND cardinality(conkey) = 1
289- AND conrelid = ${ literal ( old ! . table_id ) }
290- AND conkey[1] = ${ literal ( old ! . ordinal_position ) }
291- LOOP
324+ contype = 'u'
325+ AND cardinality(conkey) = 1
326+ AND conrelid = ${ literal ( old ! . table_id ) }
327+ AND conkey[1] = ${ literal ( old ! . ordinal_position ) }
328+ LOOP
292329 EXECUTE ${ literal (
293- `ALTER TABLE ${ ident ( old ! . schema ) } .${ ident ( old ! . table ) } DROP CONSTRAINT `
294- ) } || quote_ident(r.conname);
295- END LOOP;
296- END
297- $$;
298- `
330+ `ALTER TABLE ${ ident ( old ! . schema ) } .${ ident ( old ! . table ) } DROP CONSTRAINT `
331+ ) } || quote_ident(r.conname);
332+ END LOOP;
333+ END
334+ $$;
335+ `
299336 } else if ( old ! . is_unique === false && is_unique === true ) {
300- isUniqueSql = `ALTER TABLE ${ ident ( old ! . schema ) } .${ ident ( old ! . table ) } ADD UNIQUE (${ ident (
337+ isUniqueSql = `ALTER TABLE ${ ident ( old ! . schema ) } .${ ident ( old ! . table ) } ADD UNIQUE(${ ident (
301338 old ! . name
302339 ) } );`
303340 }
304341 const commentSql =
305342 comment === undefined
306343 ? ''
307344 : `COMMENT ON COLUMN ${ ident ( old ! . schema ) } .${ ident ( old ! . table ) } .${ ident (
308- old ! . name
309- ) } IS ${ literal ( comment ) } ;`
345+ old ! . name
346+ ) } IS ${ literal ( comment ) } ; `
310347
311348 // TODO: Can't set default if column is previously identity even if
312349 // is_identity: false. Must do two separate PATCHes (once to drop identity
@@ -315,14 +352,14 @@ $$;
315352 // identitySql must be after isNullableSql.
316353 const sql = `
317354BEGIN;
318- ${ isNullableSql }
319- ${ typeSql }
320- ${ defaultValueSql }
321- ${ identitySql }
322- ${ isUniqueSql }
323- ${ commentSql }
324- ${ nameSql }
325- COMMIT;`
355+ ${ isNullableSql }
356+ ${ typeSql }
357+ ${ defaultValueSql }
358+ ${ identitySql }
359+ ${ isUniqueSql }
360+ ${ commentSql }
361+ ${ nameSql }
362+ COMMIT; `
326363 {
327364 const { error } = await this . query ( sql )
328365 if ( error ) {
@@ -339,7 +376,7 @@ COMMIT;`
339376 }
340377 const sql = `ALTER TABLE ${ ident ( column ! . schema ) } .${ ident ( column ! . table ) } DROP COLUMN ${ ident (
341378 column ! . name
342- ) } ;`
379+ ) } ; `
343380 {
344381 const { error } = await this . query ( sql )
345382 if ( error ) {
0 commit comments