rake-db 2.2.6 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,6 +4,7 @@ export namespace DbStructure {
4
4
  export type Table = {
5
5
  schemaName: string;
6
6
  name: string;
7
+ comment?: string;
7
8
  };
8
9
 
9
10
  export type View = {
@@ -35,17 +36,31 @@ export namespace DbStructure {
35
36
  dateTimePrecision?: number;
36
37
  default?: string;
37
38
  isNullable: boolean;
39
+ collation?: string;
40
+ compression?: 'p' | 'l'; // p for pglz, l for lz4
41
+ comment?: string;
38
42
  };
39
43
 
40
44
  export type Index = {
41
45
  schemaName: string;
42
46
  tableName: string;
43
- columnNames: string[];
44
47
  name: string;
48
+ using: string;
45
49
  isUnique: boolean;
46
- isPrimary: boolean;
50
+ columns: (({ column: string } | { expression: string }) & {
51
+ collate?: string;
52
+ opclass?: string;
53
+ order?: string;
54
+ })[];
55
+ include?: string[];
56
+ with?: string;
57
+ tablespace?: string;
58
+ where?: string;
47
59
  };
48
60
 
61
+ // a = no action, r = restrict, c = cascade, n = set null, d = set default
62
+ type ForeignKeyAction = 'a' | 'r' | 'c' | 'n' | 'd';
63
+
49
64
  export type ForeignKey = {
50
65
  schemaName: string;
51
66
  tableName: string;
@@ -54,13 +69,15 @@ export namespace DbStructure {
54
69
  name: string;
55
70
  columnNames: string[];
56
71
  foreignColumnNames: string[];
72
+ match: 'f' | 'p' | 's'; // FULL | PARTIAL | SIMPLE
73
+ onUpdate: ForeignKeyAction;
74
+ onDelete: ForeignKeyAction;
57
75
  };
58
76
 
59
- export type Constraint = {
77
+ export type PrimaryKey = {
60
78
  schemaName: string;
61
79
  tableName: string;
62
80
  name: string;
63
- type: 'CHECK' | 'FOREIGN KEY' | 'PRIMARY KEY' | 'UNIQUE';
64
81
  columnNames: string[];
65
82
  };
66
83
 
@@ -101,12 +118,14 @@ ORDER BY "name"`,
101
118
  async getTables() {
102
119
  const { rows } = await this.db.query<DbStructure.Table>(
103
120
  `SELECT
104
- table_schema "schemaName",
105
- table_name "name"
106
- FROM information_schema.tables
107
- WHERE table_type = 'BASE TABLE'
108
- AND ${filterSchema('table_schema')}
109
- ORDER BY table_name`,
121
+ nspname AS "schemaName",
122
+ relname AS "name",
123
+ obj_description(c.oid) AS comment
124
+ FROM pg_class c
125
+ JOIN pg_catalog.pg_namespace n ON n.oid = relnamespace
126
+ WHERE relkind = 'r'
127
+ AND ${filterSchema('nspname')}
128
+ ORDER BY relname`,
110
129
  );
111
130
  return rows;
112
131
  }
@@ -156,7 +175,8 @@ WHERE ${filterSchema('n.nspname')}`,
156
175
 
157
176
  async getColumns() {
158
177
  const { rows } = await this.db.query<DbStructure.Column>(
159
- `SELECT table_schema "schemaName",
178
+ `SELECT
179
+ table_schema "schemaName",
160
180
  table_name "tableName",
161
181
  column_name "name",
162
182
  udt_name "type",
@@ -165,10 +185,22 @@ WHERE ${filterSchema('n.nspname')}`,
165
185
  numeric_scale AS "numericScale",
166
186
  datetime_precision AS "dateTimePrecision",
167
187
  column_default "default",
168
- is_nullable::boolean "isNullable"
169
- FROM information_schema.columns
188
+ is_nullable::boolean "isNullable",
189
+ collation_name AS "collation",
190
+ NULLIF(a.attcompression, '') AS compression,
191
+ pgd.description AS "comment"
192
+ FROM information_schema.columns c
193
+ LEFT JOIN pg_catalog.pg_statio_all_tables AS st
194
+ ON c.table_schema = st.schemaname
195
+ AND c.table_name = st.relname
196
+ LEFT JOIN pg_catalog.pg_description pgd
197
+ ON pgd.objoid = st.relid
198
+ AND pgd.objsubid = c.ordinal_position
199
+ LEFT JOIN pg_catalog.pg_attribute a
200
+ ON a.attrelid = st.relid
201
+ AND a.attnum = c.ordinal_position
170
202
  WHERE ${filterSchema('table_schema')}
171
- ORDER BY ordinal_position`,
203
+ ORDER BY c.ordinal_position`,
172
204
  );
173
205
  return rows;
174
206
  }
@@ -176,64 +208,152 @@ ORDER BY ordinal_position`,
176
208
  async getIndexes() {
177
209
  const { rows } = await this.db.query<DbStructure.Index>(
178
210
  `SELECT
179
- nspname "schemaName",
211
+ n.nspname "schemaName",
180
212
  t.relname "tableName",
181
- json_agg(attname) "columnNames",
182
213
  ic.relname "name",
183
- indisunique "isUnique",
184
- indisprimary "isPrimary"
185
- FROM pg_index
186
- JOIN pg_class t ON t.oid = indrelid
214
+ am.amname AS "using",
215
+ i.indisunique "isUnique",
216
+ (
217
+ SELECT json_agg(
218
+ (
219
+ CASE WHEN t.e = 0
220
+ THEN jsonb_build_object('expression', pg_get_indexdef(i.indexrelid, t.i::int4, false))
221
+ ELSE jsonb_build_object('column', (
222
+ (
223
+ SELECT attname
224
+ FROM pg_catalog.pg_attribute
225
+ WHERE attrelid = i.indrelid
226
+ AND attnum = t.e
227
+ )
228
+ ))
229
+ END
230
+ ) || (
231
+ CASE WHEN i.indcollation[t.i - 1] = 0
232
+ THEN '{}'::jsonb
233
+ ELSE (
234
+ SELECT (
235
+ CASE WHEN collname = 'default'
236
+ THEN '{}'::jsonb
237
+ ELSE jsonb_build_object('collate', collname)
238
+ END
239
+ )
240
+ FROM pg_catalog.pg_collation
241
+ WHERE oid = i.indcollation[t.i - 1]
242
+ )
243
+ END
244
+ ) || (
245
+ SELECT
246
+ CASE WHEN opcdefault AND attoptions IS NULL
247
+ THEN '{}'::jsonb
248
+ ELSE jsonb_build_object(
249
+ 'opclass', opcname || COALESCE('(' || array_to_string(attoptions, ', ') || ')', '')
250
+ )
251
+ END
252
+ FROM pg_opclass
253
+ LEFT JOIN pg_attribute
254
+ ON attrelid = i.indexrelid
255
+ AND attnum = t.i
256
+ WHERE oid = i.indclass[t.i - 1]
257
+ ) || (
258
+ CASE WHEN i.indoption[t.i - 1] = 0
259
+ THEN '{}'::jsonb
260
+ ELSE jsonb_build_object(
261
+ 'order',
262
+ CASE
263
+ WHEN i.indoption[t.i - 1] = 1 THEN 'DESC NULLS LAST'
264
+ WHEN i.indoption[t.i - 1] = 2 THEN 'ASC NULLS FIRST'
265
+ WHEN i.indoption[t.i - 1] = 3 THEN 'DESC'
266
+ ELSE NULL
267
+ END
268
+ )
269
+ END
270
+ )
271
+ )
272
+ FROM unnest(i.indkey[:indnkeyatts - 1]) WITH ORDINALITY AS t(e, i)
273
+ ) "columns",
274
+ (
275
+ SELECT json_agg(
276
+ (
277
+ SELECT attname
278
+ FROM pg_catalog.pg_attribute
279
+ WHERE attrelid = i.indrelid
280
+ AND attnum = j.e
281
+ )
282
+ )
283
+ FROM unnest(i.indkey[indnkeyatts:]) AS j(e)
284
+ ) AS "include",
285
+ NULLIF(pg_catalog.array_to_string(
286
+ ic.reloptions || array(SELECT 'toast.' || x FROM pg_catalog.unnest(tc.reloptions) x),
287
+ ', '
288
+ ), '') AS "with",
289
+ (
290
+ SELECT tablespace
291
+ FROM pg_indexes
292
+ WHERE schemaname = n.nspname
293
+ AND indexname = ic.relname
294
+ ) AS tablespace,
295
+ pg_get_expr(i.indpred, i.indrelid) AS "where"
296
+ FROM pg_index i
297
+ JOIN pg_class t ON t.oid = i.indrelid
187
298
  JOIN pg_namespace n ON n.oid = t.relnamespace
188
- JOIN pg_attribute ON attrelid = t.oid AND attnum = any(indkey)
189
- JOIN pg_class ic ON ic.oid = indexrelid
299
+ JOIN pg_class ic ON ic.oid = i.indexrelid
300
+ JOIN pg_am am ON am.oid = ic.relam
301
+ LEFT JOIN pg_catalog.pg_class tc ON (ic.reltoastrelid = tc.oid)
190
302
  WHERE ${filterSchema('n.nspname')}
191
- GROUP BY "schemaName", "tableName", "name", "isUnique", "isPrimary"
192
- ORDER BY "name"`,
303
+ AND NOT i.indisprimary
304
+ ORDER BY ic.relname`,
193
305
  );
194
306
  return rows;
195
307
  }
196
308
 
197
309
  async getForeignKeys() {
198
310
  const { rows } = await this.db.query<DbStructure.ForeignKey>(
199
- `SELECT tc.table_schema AS "schemaName",
200
- tc.table_name AS "tableName",
201
- ccu.table_schema AS "foreignTableSchemaName",
202
- ccu.table_name AS "foreignTableName",
203
- tc.constraint_name AS "name",
311
+ `SELECT
312
+ s.nspname AS "schemaName",
313
+ t.relname AS "tableName",
314
+ fs.nspname AS "foreignTableSchemaName",
315
+ ft.relname AS "foreignTableName",
316
+ c.conname AS "name",
204
317
  (
205
- SELECT json_agg(kcu.column_name)
206
- FROM information_schema.key_column_usage kcu
207
- WHERE kcu.constraint_name = tc.constraint_name
208
- AND kcu.table_schema = tc.table_schema
318
+ SELECT json_agg(ccu.column_name)
319
+ FROM information_schema.key_column_usage ccu
320
+ WHERE ccu.constraint_name = c.conname
321
+ AND ccu.table_schema = cs.nspname
209
322
  ) AS "columnNames",
210
- json_agg(ccu.column_name) AS "foreignColumnNames"
211
- FROM information_schema.table_constraints tc
212
- JOIN information_schema.constraint_column_usage ccu
213
- ON ccu.constraint_name = tc.constraint_name
214
- AND ccu.table_schema = tc.table_schema
215
- WHERE tc.constraint_type = 'FOREIGN KEY'
216
- AND ${filterSchema('tc.table_schema')}
217
- GROUP BY "schemaName", "tableName", "name", "foreignTableSchemaName", "foreignTableName"
218
- ORDER BY "name"`,
323
+ (
324
+ SELECT json_agg(ccu.column_name)
325
+ FROM information_schema.constraint_column_usage ccu
326
+ WHERE ccu.constraint_name = c.conname
327
+ AND ccu.table_schema = cs.nspname
328
+ ) AS "foreignColumnNames",
329
+ c.confmatchtype AS match,
330
+ c.confupdtype AS "onUpdate",
331
+ c.confdeltype AS "onDelete"
332
+ FROM pg_catalog.pg_constraint c
333
+ JOIN pg_class t ON t.oid = conrelid
334
+ JOIN pg_catalog.pg_namespace s ON s.oid = t.relnamespace
335
+ JOIN pg_class ft ON ft.oid = confrelid
336
+ JOIN pg_catalog.pg_namespace fs ON fs.oid = ft.relnamespace
337
+ JOIN pg_catalog.pg_namespace cs ON cs.oid = c.connamespace
338
+ WHERE contype = 'f'
339
+ ORDER BY c.conname`,
219
340
  );
220
341
  return rows;
221
342
  }
222
343
 
223
- async getConstraints() {
224
- const { rows } = await this.db.query<DbStructure.Constraint>(
344
+ async getPrimaryKeys() {
345
+ const { rows } = await this.db.query<DbStructure.PrimaryKey>(
225
346
  `SELECT tc.table_schema AS "schemaName",
226
347
  tc.table_name AS "tableName",
227
348
  tc.constraint_name AS "name",
228
- tc.constraint_type AS "type",
229
349
  json_agg(ccu.column_name) "columnNames"
230
350
  FROM information_schema.table_constraints tc
231
351
  JOIN information_schema.constraint_column_usage ccu
232
352
  ON ccu.constraint_name = tc.constraint_name
233
353
  AND ccu.table_schema = tc.table_schema
234
- WHERE tc.constraint_type != 'FOREIGN KEY'
354
+ WHERE tc.constraint_type = 'PRIMARY KEY'
235
355
  AND ${filterSchema('tc.table_schema')}
236
- GROUP BY "schemaName", "tableName", "name", "type"
356
+ GROUP BY "schemaName", "tableName", "name"
237
357
  ORDER BY "name"`,
238
358
  );
239
359
  return rows;
@@ -1,5 +1,12 @@
1
1
  import { DbStructure } from './dbStructure';
2
- import { Adapter, IntegerColumn, TextColumn } from 'pqb';
2
+ import {
3
+ Adapter,
4
+ DecimalColumn,
5
+ IntegerColumn,
6
+ TextColumn,
7
+ TimestampColumn,
8
+ VarCharColumn,
9
+ } from 'pqb';
3
10
  import { structureToAst } from './structureToAst';
4
11
  import { RakeDbAst } from '../ast';
5
12
 
@@ -8,7 +15,7 @@ const query = jest.fn().mockImplementation(() => ({ rows: [] }));
8
15
  adapter.query = query;
9
16
  adapter.arrays = query;
10
17
 
11
- const tableColumn: DbStructure.Column = {
18
+ const intColumn: DbStructure.Column = {
12
19
  schemaName: 'public',
13
20
  tableName: 'table',
14
21
  name: 'column',
@@ -17,32 +24,54 @@ const tableColumn: DbStructure.Column = {
17
24
  isNullable: false,
18
25
  };
19
26
 
27
+ const varCharColumn: DbStructure.Column = {
28
+ ...intColumn,
29
+ name: 'varchar',
30
+ type: 'character varying',
31
+ collation: 'en_US',
32
+ maxChars: 10,
33
+ };
34
+
35
+ const decimalColumn: DbStructure.Column = {
36
+ ...intColumn,
37
+ name: 'decimal',
38
+ type: 'decimal',
39
+ numericPrecision: 10,
40
+ numericScale: 2,
41
+ };
42
+
43
+ const timestampColumn: DbStructure.Column = {
44
+ ...intColumn,
45
+ name: 'timestamp',
46
+ type: 'timestamp',
47
+ dateTimePrecision: 10,
48
+ };
49
+
20
50
  const tableColumns = [
21
- { ...tableColumn, name: 'id' },
22
- { ...tableColumn, name: 'name', type: 'text' },
51
+ { ...intColumn, name: 'id' },
52
+ { ...intColumn, name: 'name', type: 'text' },
23
53
  ];
24
54
 
25
- const otherTableColumn = { ...tableColumn, tableName: 'otherTable' };
55
+ const otherTableColumn = { ...intColumn, tableName: 'otherTable' };
26
56
 
27
57
  const table = { schemaName: 'public', name: 'table' };
28
58
 
29
59
  const columns = [...tableColumns, otherTableColumn];
30
60
 
31
- const primaryKey: DbStructure.Constraint = {
61
+ const primaryKey: DbStructure.PrimaryKey = {
32
62
  schemaName: 'public',
33
63
  tableName: 'table',
34
64
  name: 'pkey',
35
- type: 'PRIMARY KEY',
36
65
  columnNames: ['id'],
37
66
  };
38
67
 
39
68
  const index: DbStructure.Index = {
40
69
  schemaName: 'public',
41
70
  tableName: 'table',
42
- columnNames: ['name'],
43
71
  name: 'index',
72
+ using: 'btree',
44
73
  isUnique: false,
45
- isPrimary: false,
74
+ columns: [{ column: 'name' }],
46
75
  };
47
76
 
48
77
  const foreignKey: DbStructure.ForeignKey = {
@@ -53,6 +82,9 @@ const foreignKey: DbStructure.ForeignKey = {
53
82
  name: 'fkey',
54
83
  columnNames: ['otherId'],
55
84
  foreignColumnNames: ['id'],
85
+ match: 'f',
86
+ onUpdate: 'c',
87
+ onDelete: 'c',
56
88
  };
57
89
 
58
90
  const extension: DbStructure.Extension = {
@@ -83,13 +115,16 @@ describe('structureToAst', () => {
83
115
  describe('table', () => {
84
116
  it('should add table', async () => {
85
117
  const db = new DbStructure(adapter);
86
- db.getTables = async () => [{ schemaName: 'public', name: 'table' }];
118
+ db.getTables = async () => [
119
+ { schemaName: 'public', name: 'table', comment: 'comment' },
120
+ ];
87
121
  const ast = await structureToAst(db);
88
122
  expect(ast).toEqual([
89
123
  {
90
124
  type: 'table',
91
125
  action: 'create',
92
126
  name: 'table',
127
+ comment: 'comment',
93
128
  shape: {},
94
129
  noPrimaryKey: 'ignore',
95
130
  indexes: [],
@@ -129,11 +164,50 @@ describe('structureToAst', () => {
129
164
  expect(ast.shape.name).toBeInstanceOf(TextColumn);
130
165
  });
131
166
 
167
+ it('should set maxChars to char column', async () => {
168
+ const db = new DbStructure(adapter);
169
+ db.getTables = async () => [{ schemaName: 'public', name: 'table' }];
170
+ db.getColumns = async () => [varCharColumn];
171
+
172
+ const [ast] = (await structureToAst(db)) as [RakeDbAst.Table];
173
+
174
+ const column = ast.shape[varCharColumn.name];
175
+ expect(column).toBeInstanceOf(VarCharColumn);
176
+ expect(column.data.maxChars).toBe(varCharColumn.maxChars);
177
+ });
178
+
179
+ it('should set numericPrecision and numericScale to decimal column', async () => {
180
+ const db = new DbStructure(adapter);
181
+ db.getTables = async () => [{ schemaName: 'public', name: 'table' }];
182
+ db.getColumns = async () => [decimalColumn];
183
+
184
+ const [ast] = (await structureToAst(db)) as [RakeDbAst.Table];
185
+
186
+ const column = ast.shape[decimalColumn.name];
187
+ expect(column).toBeInstanceOf(DecimalColumn);
188
+ expect(column.data.numericPrecision).toBe(decimalColumn.numericPrecision);
189
+ expect(column.data.numericScale).toBe(decimalColumn.numericScale);
190
+ });
191
+
192
+ it('should set dateTimePrecision to timestamp column', async () => {
193
+ const db = new DbStructure(adapter);
194
+ db.getTables = async () => [{ schemaName: 'public', name: 'table' }];
195
+ db.getColumns = async () => [timestampColumn];
196
+
197
+ const [ast] = (await structureToAst(db)) as [RakeDbAst.Table];
198
+
199
+ const column = ast.shape[timestampColumn.name];
200
+ expect(column).toBeInstanceOf(TimestampColumn);
201
+ expect(column.data.dateTimePrecision).toBe(
202
+ timestampColumn.dateTimePrecision,
203
+ );
204
+ });
205
+
132
206
  it('should set primaryKey to column', async () => {
133
207
  const db = new DbStructure(adapter);
134
208
  db.getTables = async () => [{ schemaName: 'public', name: 'table' }];
135
209
  db.getColumns = async () => columns;
136
- db.getConstraints = async () => [primaryKey];
210
+ db.getPrimaryKeys = async () => [primaryKey];
137
211
 
138
212
  const [ast] = (await structureToAst(db)) as [RakeDbAst.Table];
139
213
 
@@ -146,7 +220,7 @@ describe('structureToAst', () => {
146
220
  const db = new DbStructure(adapter);
147
221
  db.getTables = async () => [{ schemaName: 'public', name: 'table' }];
148
222
  db.getColumns = async () => columns;
149
- db.getConstraints = async () => [
223
+ db.getPrimaryKeys = async () => [
150
224
  { ...primaryKey, columnNames: ['id', 'name'] },
151
225
  ];
152
226
 
@@ -164,7 +238,7 @@ describe('structureToAst', () => {
164
238
  const db = new DbStructure(adapter);
165
239
  db.getTables = async () => [{ schemaName: 'public', name: 'table' }];
166
240
  db.getColumns = async () => columns;
167
- db.getConstraints = async () => [
241
+ db.getPrimaryKeys = async () => [
168
242
  { ...primaryKey, columnNames: ['id', 'name'], name: 'table_pkey' },
169
243
  ];
170
244
 
@@ -177,42 +251,79 @@ describe('structureToAst', () => {
177
251
  });
178
252
  });
179
253
 
180
- it('should ignore primary key indexes', async () => {
254
+ it('should add index to column', async () => {
181
255
  const db = new DbStructure(adapter);
182
- db.getTables = async () => [{ schemaName: 'public', name: 'table' }];
256
+ db.getTables = async () => [table];
183
257
  db.getColumns = async () => columns;
184
- db.getIndexes = async () => [{ ...index, isPrimary: true }];
258
+ db.getIndexes = async () => [index];
185
259
 
186
260
  const [ast] = (await structureToAst(db)) as [RakeDbAst.Table];
187
- expect(ast.shape.name.data.index).toBe(undefined);
261
+ expect(ast.shape.name.data.indexes).toEqual([
262
+ {
263
+ name: 'index',
264
+ unique: false,
265
+ },
266
+ ]);
188
267
  expect(ast.indexes).toHaveLength(0);
189
268
  });
190
269
 
191
- it('should add index to column', async () => {
270
+ it('should set index options to column index', async () => {
192
271
  const db = new DbStructure(adapter);
193
272
  db.getTables = async () => [table];
194
273
  db.getColumns = async () => columns;
195
- db.getIndexes = async () => [index];
274
+ db.getIndexes = async () => [
275
+ {
276
+ ...index,
277
+ using: 'gist',
278
+ isUnique: true,
279
+ columns: [
280
+ {
281
+ column: 'name',
282
+ collate: 'en_US',
283
+ opclass: 'varchar_ops',
284
+ order: 'DESC',
285
+ },
286
+ ],
287
+ include: ['id'],
288
+ with: 'fillfactor=80',
289
+ tablespace: 'tablespace',
290
+ where: 'condition',
291
+ },
292
+ ];
196
293
 
197
294
  const [ast] = (await structureToAst(db)) as [RakeDbAst.Table];
198
- expect(ast.shape.name.data.index).toEqual({
199
- name: 'index',
200
- unique: false,
201
- });
295
+ expect(ast.shape.name.data.indexes).toEqual([
296
+ {
297
+ name: 'index',
298
+ using: 'gist',
299
+ unique: true,
300
+ collate: 'en_US',
301
+ opclass: 'varchar_ops',
302
+ order: 'DESC',
303
+ include: ['id'],
304
+ with: 'fillfactor=80',
305
+ tablespace: 'tablespace',
306
+ where: 'condition',
307
+ },
308
+ ]);
202
309
  expect(ast.indexes).toHaveLength(0);
203
310
  });
204
311
 
205
- it('should add composite indexes to table', async () => {
312
+ it('should add composite indexes to the table', async () => {
206
313
  const db = new DbStructure(adapter);
207
314
  db.getTables = async () => [table];
208
315
  db.getColumns = async () => columns;
209
316
  db.getIndexes = async () => [
210
- { ...index, columnNames: ['id', 'name'] },
211
- { ...index, columnNames: ['id', 'name'], isUnique: true },
317
+ { ...index, columns: [{ column: 'id' }, { column: 'name' }] },
318
+ {
319
+ ...index,
320
+ columns: [{ column: 'id' }, { column: 'name' }],
321
+ isUnique: true,
322
+ },
212
323
  ];
213
324
 
214
325
  const [ast] = (await structureToAst(db)) as [RakeDbAst.Table];
215
- expect(ast.shape.name.data.index).toBe(undefined);
326
+ expect(ast.shape.name.data.indexes).toBe(undefined);
216
327
  expect(ast.indexes).toEqual([
217
328
  {
218
329
  columns: [{ column: 'id' }, { column: 'name' }],
@@ -225,22 +336,76 @@ describe('structureToAst', () => {
225
336
  ]);
226
337
  });
227
338
 
339
+ it('should add index with expression and options to the table', async () => {
340
+ const db = new DbStructure(adapter);
341
+ db.getTables = async () => [table];
342
+ db.getColumns = async () => columns;
343
+ db.getIndexes = async () => [
344
+ {
345
+ ...index,
346
+ using: 'gist',
347
+ isUnique: true,
348
+ columns: [
349
+ {
350
+ expression: 'lower(name)',
351
+ collate: 'en_US',
352
+ opclass: 'varchar_ops',
353
+ order: 'DESC',
354
+ },
355
+ ],
356
+ include: ['id'],
357
+ with: 'fillfactor=80',
358
+ tablespace: 'tablespace',
359
+ where: 'condition',
360
+ },
361
+ ];
362
+
363
+ const [ast] = (await structureToAst(db)) as [RakeDbAst.Table];
364
+ expect(ast.shape.name.data.indexes).toBe(undefined);
365
+ expect(ast.indexes).toEqual([
366
+ {
367
+ columns: [
368
+ {
369
+ expression: 'lower(name)',
370
+ collate: 'en_US',
371
+ opclass: 'varchar_ops',
372
+ order: 'DESC',
373
+ },
374
+ ],
375
+ options: {
376
+ name: 'index',
377
+ using: 'gist',
378
+ unique: true,
379
+ include: ['id'],
380
+ with: 'fillfactor=80',
381
+ tablespace: 'tablespace',
382
+ where: 'condition',
383
+ },
384
+ },
385
+ ]);
386
+ });
387
+
228
388
  it('should add foreign key to the column', async () => {
229
389
  const db = new DbStructure(adapter);
230
390
  db.getTables = async () => [table];
231
391
  db.getColumns = async () => [
232
392
  ...columns,
233
- { ...tableColumn, name: 'otherId' },
393
+ { ...intColumn, name: 'otherId' },
234
394
  ];
235
395
  db.getForeignKeys = async () => [foreignKey];
236
396
 
237
397
  const [ast] = (await structureToAst(db)) as [RakeDbAst.Table];
238
398
 
239
- expect(ast.shape.otherId.data.foreignKey).toEqual({
240
- columns: ['id'],
241
- name: 'fkey',
242
- table: 'otherTable',
243
- });
399
+ expect(ast.shape.otherId.data.foreignKeys).toEqual([
400
+ {
401
+ columns: ['id'],
402
+ name: 'fkey',
403
+ table: 'otherTable',
404
+ match: 'FULL',
405
+ onUpdate: 'CASCADE',
406
+ onDelete: 'CASCADE',
407
+ },
408
+ ]);
244
409
  expect(ast.foreignKeys).toHaveLength(0);
245
410
  });
246
411
 
@@ -249,7 +414,7 @@ describe('structureToAst', () => {
249
414
  db.getTables = async () => [table];
250
415
  db.getColumns = async () => [
251
416
  ...columns,
252
- { ...tableColumn, name: 'otherId' },
417
+ { ...intColumn, name: 'otherId' },
253
418
  ];
254
419
  db.getForeignKeys = async () => [
255
420
  {
@@ -261,7 +426,7 @@ describe('structureToAst', () => {
261
426
 
262
427
  const [ast] = (await structureToAst(db)) as [RakeDbAst.Table];
263
428
 
264
- expect(ast.shape.otherId.data.foreignKey).toBe(undefined);
429
+ expect(ast.shape.otherId.data.foreignKeys).toBe(undefined);
265
430
  expect(ast.foreignKeys).toEqual([
266
431
  {
267
432
  columns: ['name', 'otherId'],
@@ -269,6 +434,9 @@ describe('structureToAst', () => {
269
434
  foreignColumns: ['name', 'id'],
270
435
  options: {
271
436
  name: 'fkey',
437
+ match: 'FULL',
438
+ onUpdate: 'CASCADE',
439
+ onDelete: 'CASCADE',
272
440
  },
273
441
  },
274
442
  ]);