knex 0.21.20 → 0.21.21

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.
Files changed (141) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/CONTRIBUTING.md +184 -184
  3. package/LICENSE +22 -22
  4. package/README.md +95 -95
  5. package/bin/cli.js +414 -414
  6. package/bin/utils/cli-config-utils.js +151 -151
  7. package/bin/utils/constants.js +7 -7
  8. package/bin/utils/migrationsLister.js +37 -37
  9. package/knex.js +8 -8
  10. package/lib/client.js +413 -413
  11. package/lib/config-resolver.js +61 -61
  12. package/lib/constants.js +44 -44
  13. package/lib/dialects/mssql/index.js +390 -390
  14. package/lib/dialects/mssql/query/compiler.js +444 -444
  15. package/lib/dialects/mssql/schema/columncompiler.js +103 -103
  16. package/lib/dialects/mssql/schema/compiler.js +59 -59
  17. package/lib/dialects/mssql/schema/tablecompiler.js +245 -245
  18. package/lib/dialects/mssql/transaction.js +97 -97
  19. package/lib/dialects/mysql/index.js +191 -191
  20. package/lib/dialects/mysql/query/compiler.js +142 -142
  21. package/lib/dialects/mysql/schema/columncompiler.js +171 -171
  22. package/lib/dialects/mysql/schema/compiler.js +60 -60
  23. package/lib/dialects/mysql/schema/tablecompiler.js +262 -262
  24. package/lib/dialects/mysql/transaction.js +48 -48
  25. package/lib/dialects/mysql2/index.js +35 -35
  26. package/lib/dialects/mysql2/transaction.js +46 -46
  27. package/lib/dialects/oracle/DEAD_CODE.md +5 -5
  28. package/lib/dialects/oracle/formatter.js +20 -20
  29. package/lib/dialects/oracle/index.js +79 -79
  30. package/lib/dialects/oracle/query/compiler.js +327 -327
  31. package/lib/dialects/oracle/schema/columnbuilder.js +18 -18
  32. package/lib/dialects/oracle/schema/columncompiler.js +139 -139
  33. package/lib/dialects/oracle/schema/compiler.js +81 -81
  34. package/lib/dialects/oracle/schema/tablecompiler.js +165 -165
  35. package/lib/dialects/oracle/schema/trigger.js +126 -126
  36. package/lib/dialects/oracle/utils.js +86 -86
  37. package/lib/dialects/oracledb/index.js +489 -489
  38. package/lib/dialects/oracledb/query/compiler.js +363 -363
  39. package/lib/dialects/oracledb/schema/columncompiler.js +35 -35
  40. package/lib/dialects/oracledb/transaction.js +76 -76
  41. package/lib/dialects/oracledb/utils.js +14 -14
  42. package/lib/dialects/postgres/index.js +319 -319
  43. package/lib/dialects/postgres/query/compiler.js +206 -206
  44. package/lib/dialects/postgres/schema/columncompiler.js +125 -125
  45. package/lib/dialects/postgres/schema/compiler.js +109 -109
  46. package/lib/dialects/postgres/schema/tablecompiler.js +183 -183
  47. package/lib/dialects/redshift/index.js +73 -73
  48. package/lib/dialects/redshift/query/compiler.js +119 -119
  49. package/lib/dialects/redshift/schema/columnbuilder.js +20 -20
  50. package/lib/dialects/redshift/schema/columncompiler.js +60 -60
  51. package/lib/dialects/redshift/schema/compiler.js +14 -14
  52. package/lib/dialects/redshift/schema/tablecompiler.js +123 -123
  53. package/lib/dialects/redshift/transaction.js +18 -18
  54. package/lib/dialects/sqlite3/formatter.js +21 -21
  55. package/lib/dialects/sqlite3/index.js +169 -169
  56. package/lib/dialects/sqlite3/query/compiler.js +222 -222
  57. package/lib/dialects/sqlite3/schema/columncompiler.js +27 -27
  58. package/lib/dialects/sqlite3/schema/compiler.js +49 -49
  59. package/lib/dialects/sqlite3/schema/ddl.js +525 -525
  60. package/lib/dialects/sqlite3/schema/tablecompiler.js +238 -238
  61. package/lib/formatter.js +295 -295
  62. package/lib/functionhelper.js +14 -14
  63. package/lib/helpers.js +92 -92
  64. package/lib/index.js +3 -3
  65. package/lib/interface.js +115 -115
  66. package/lib/knex.js +42 -42
  67. package/lib/logger.js +76 -76
  68. package/lib/migrate/MigrationGenerator.js +82 -82
  69. package/lib/migrate/Migrator.js +611 -611
  70. package/lib/migrate/configuration-merger.js +60 -60
  71. package/lib/migrate/migrate-stub.js +17 -17
  72. package/lib/migrate/migration-list-resolver.js +36 -36
  73. package/lib/migrate/sources/fs-migrations.js +99 -99
  74. package/lib/migrate/stub/cjs.stub +15 -15
  75. package/lib/migrate/stub/coffee.stub +13 -13
  76. package/lib/migrate/stub/eg.stub +14 -14
  77. package/lib/migrate/stub/js.stub +15 -15
  78. package/lib/migrate/stub/knexfile-coffee.stub +34 -34
  79. package/lib/migrate/stub/knexfile-eg.stub +43 -43
  80. package/lib/migrate/stub/knexfile-js.stub +44 -44
  81. package/lib/migrate/stub/knexfile-ls.stub +35 -35
  82. package/lib/migrate/stub/knexfile-ts.stub +44 -44
  83. package/lib/migrate/stub/ls.stub +14 -14
  84. package/lib/migrate/stub/ts.stub +21 -21
  85. package/lib/migrate/table-creator.js +67 -67
  86. package/lib/migrate/table-resolver.js +27 -27
  87. package/lib/query/builder.js +1372 -1372
  88. package/lib/query/compiler.js +889 -889
  89. package/lib/query/constants.js +13 -13
  90. package/lib/query/joinclause.js +263 -263
  91. package/lib/query/methods.js +92 -92
  92. package/lib/query/string.js +190 -190
  93. package/lib/raw.js +188 -188
  94. package/lib/ref.js +39 -39
  95. package/lib/runner.js +285 -285
  96. package/lib/schema/builder.js +82 -82
  97. package/lib/schema/columnbuilder.js +117 -117
  98. package/lib/schema/columncompiler.js +177 -177
  99. package/lib/schema/compiler.js +101 -101
  100. package/lib/schema/helpers.js +51 -51
  101. package/lib/schema/tablebuilder.js +288 -288
  102. package/lib/schema/tablecompiler.js +296 -296
  103. package/lib/seed/Seeder.js +203 -203
  104. package/lib/seed/seed-stub.js +13 -13
  105. package/lib/seed/stub/coffee.stub +9 -9
  106. package/lib/seed/stub/eg.stub +11 -11
  107. package/lib/seed/stub/js.stub +13 -13
  108. package/lib/seed/stub/ls.stub +11 -11
  109. package/lib/seed/stub/ts.stub +13 -13
  110. package/lib/transaction.js +363 -363
  111. package/lib/util/batchInsert.js +59 -59
  112. package/lib/util/delay.js +6 -6
  113. package/lib/util/fake-client.js +9 -9
  114. package/lib/util/finally-mixin.js +13 -13
  115. package/lib/util/fs.js +76 -76
  116. package/lib/util/import-file.js +13 -13
  117. package/lib/util/is-module-type.js +14 -14
  118. package/lib/util/is.js +32 -32
  119. package/lib/util/make-knex.js +338 -338
  120. package/lib/util/nanoid.js +29 -29
  121. package/lib/util/noop.js +1 -1
  122. package/lib/util/parse-connection.js +66 -66
  123. package/lib/util/save-async-stack.js +14 -14
  124. package/lib/util/template.js +52 -52
  125. package/lib/util/timeout.js +29 -29
  126. package/lib/util/timestamp.js +16 -16
  127. package/package.json +1 -1
  128. package/scripts/build.js +125 -125
  129. package/scripts/docker-compose.yml +111 -111
  130. package/scripts/next-release-howto.md +24 -24
  131. package/scripts/release.sh +34 -34
  132. package/scripts/runkit-example.js +34 -34
  133. package/scripts/stress-test/README.txt +18 -18
  134. package/scripts/stress-test/docker-compose.yml +47 -47
  135. package/scripts/stress-test/knex-stress-test.js +196 -196
  136. package/scripts/stress-test/mysql2-random-hanging-every-now-and-then.js +145 -145
  137. package/scripts/stress-test/mysql2-sudden-exit-without-error.js +100 -100
  138. package/scripts/stress-test/reconnect-test-mysql-based-drivers.js +184 -184
  139. package/types/index.d.ts +2249 -2249
  140. package/types/result.d.ts +27 -27
  141. package/types/tables.d.ts +4 -4
@@ -1,525 +1,525 @@
1
- // SQLite3_DDL
2
- //
3
- // All of the SQLite3 specific DDL helpers for renaming/dropping
4
- // columns and changing datatypes.
5
- // -------
6
-
7
- const assign = require('lodash/assign');
8
- const chunk = require('lodash/chunk');
9
- const find = require('lodash/find');
10
- const fromPairs = require('lodash/fromPairs');
11
- const identity = require('lodash/identity');
12
- const invert = require('lodash/invert');
13
- const isEmpty = require('lodash/isEmpty');
14
- const negate = require('lodash/negate');
15
- const omit = require('lodash/omit');
16
- const uniqueId = require('lodash/uniqueId');
17
- const { COMMA_NO_PAREN_REGEX } = require('../../../constants');
18
-
19
- // So altering the schema in SQLite3 is a major pain.
20
- // We have our own object to deal with the renaming and altering the types
21
- // for sqlite3 things.
22
- function SQLite3_DDL(client, tableCompiler, pragma, connection) {
23
- this.client = client;
24
- this.tableCompiler = tableCompiler;
25
- this.pragma = pragma;
26
- this.tableNameRaw = this.tableCompiler.tableNameRaw;
27
- this.alteredName = uniqueId('_knex_temp_alter');
28
- this.connection = connection;
29
- this.formatter =
30
- client && client.config && client.config.wrapIdentifier
31
- ? client.config.wrapIdentifier
32
- : (value) => value;
33
- }
34
-
35
- assign(SQLite3_DDL.prototype, {
36
- tableName() {
37
- return this.formatter(this.tableNameRaw, (value) => value);
38
- },
39
-
40
- getColumn: async function (column) {
41
- const currentCol = find(this.pragma, (col) => {
42
- return (
43
- this.client.wrapIdentifier(col.name).toLowerCase() ===
44
- this.client.wrapIdentifier(column).toLowerCase()
45
- );
46
- });
47
- if (!currentCol)
48
- throw new Error(
49
- `The column ${column} is not in the ${this.tableName()} table`
50
- );
51
- return currentCol;
52
- },
53
-
54
- getTableSql() {
55
- this.trx.disableProcessing();
56
- return this.trx
57
- .raw(
58
- `SELECT name, sql FROM sqlite_master WHERE type="table" AND name="${this.tableName()}"`
59
- )
60
- .then((result) => {
61
- this.trx.enableProcessing();
62
- return result;
63
- });
64
- },
65
-
66
- renameTable: async function () {
67
- return this.trx.raw(
68
- `ALTER TABLE "${this.tableName()}" RENAME TO "${this.alteredName}"`
69
- );
70
- },
71
-
72
- dropOriginal() {
73
- return this.trx.raw(`DROP TABLE "${this.tableName()}"`);
74
- },
75
-
76
- dropTempTable() {
77
- return this.trx.raw(`DROP TABLE "${this.alteredName}"`);
78
- },
79
-
80
- copyData() {
81
- return this.trx
82
- .raw(`SELECT * FROM "${this.tableName()}"`)
83
- .then((result) =>
84
- this.insertChunked(20, this.alteredName, identity, result)
85
- );
86
- },
87
-
88
- reinsertData(iterator) {
89
- return this.trx
90
- .raw(`SELECT * FROM "${this.alteredName}"`)
91
- .then((result) =>
92
- this.insertChunked(20, this.tableName(), iterator, result)
93
- );
94
- },
95
-
96
- async insertChunked(chunkSize, target, iterator, result) {
97
- iterator = iterator || identity;
98
- const chunked = chunk(result, chunkSize);
99
- for (const batch of chunked) {
100
- await this.trx.queryBuilder().table(target).insert(batch.map(iterator));
101
- }
102
- },
103
-
104
- createTempTable(createTable) {
105
- return this.trx.raw(
106
- createTable.sql.replace(this.tableName(), this.alteredName)
107
- );
108
- },
109
-
110
- _doReplace(sql, from, to) {
111
- const oneLineSql = sql.replace(/\s+/g, ' ');
112
- const matched = oneLineSql.match(/^CREATE TABLE\s+(\S+)\s*\((.*)\)/);
113
-
114
- const tableName = matched[1];
115
- const defs = matched[2];
116
-
117
- if (!defs) {
118
- throw new Error('No column definitions in this statement!');
119
- }
120
-
121
- let parens = 0,
122
- args = [],
123
- ptr = 0;
124
- let i = 0;
125
- const x = defs.length;
126
- for (i = 0; i < x; i++) {
127
- switch (defs[i]) {
128
- case '(':
129
- parens++;
130
- break;
131
- case ')':
132
- parens--;
133
- break;
134
- case ',':
135
- if (parens === 0) {
136
- args.push(defs.slice(ptr, i));
137
- ptr = i + 1;
138
- }
139
- break;
140
- case ' ':
141
- if (ptr === i) {
142
- ptr = i + 1;
143
- }
144
- break;
145
- }
146
- }
147
- args.push(defs.slice(ptr, i));
148
-
149
- const fromIdentifier = from.replace(/[`"'[\]]/g, '');
150
-
151
- args = args.map((item) => {
152
- let split = item.trim().split(' ');
153
-
154
- // SQLite supports all quoting mechanisms prevalent in all major dialects of SQL
155
- // and preserves the original quoting in sqlite_master.
156
- //
157
- // Also, identifiers are never case sensitive, not even when quoted.
158
- //
159
- // Ref: https://www.sqlite.org/lang_keywords.html
160
- const fromMatchCandidates = [
161
- new RegExp(`\`${fromIdentifier}\``, 'i'),
162
- new RegExp(`"${fromIdentifier}"`, 'i'),
163
- new RegExp(`'${fromIdentifier}'`, 'i'),
164
- new RegExp(`\\[${fromIdentifier}\\]`, 'i'),
165
- ];
166
- if (fromIdentifier.match(/^\S+$/)) {
167
- fromMatchCandidates.push(new RegExp(`\\b${fromIdentifier}\\b`, 'i'));
168
- }
169
-
170
- const doesMatchFromIdentifier = (target) =>
171
- fromMatchCandidates.some((c) => target.match(c));
172
-
173
- const replaceFromIdentifier = (target) =>
174
- fromMatchCandidates.reduce(
175
- (result, candidate) => result.replace(candidate, to),
176
- target
177
- );
178
-
179
- if (doesMatchFromIdentifier(split[0])) {
180
- // column definition
181
- if (to) {
182
- split[0] = to;
183
- return split.join(' ');
184
- }
185
- return ''; // for deletions
186
- }
187
-
188
- // skip constraint name
189
- const idx = /constraint/i.test(split[0]) ? 2 : 0;
190
-
191
- // primary key and unique constraints have one or more
192
- // columns from this table listed between (); replace
193
- // one if it matches
194
- if (/primary|unique/i.test(split[idx])) {
195
- const ret = item.replace(/\(.*\)/, replaceFromIdentifier);
196
- // If any member columns are dropped then uniqueness/pk constraint
197
- // can not be retained
198
- if (ret !== item && isEmpty(to)) return '';
199
- return ret;
200
- }
201
-
202
- // foreign keys have one or more columns from this table
203
- // listed between (); replace one if it matches
204
- // foreign keys also have a 'references' clause
205
- // which may reference THIS table; if it does, replace
206
- // column references in that too!
207
- if (/foreign/.test(split[idx])) {
208
- split = item.split(/ references /i);
209
- // the quoted column names save us from having to do anything
210
- // other than a straight replace here
211
- const replacedKeySpec = replaceFromIdentifier(split[0]);
212
-
213
- if (split[0] !== replacedKeySpec) {
214
- // If we are removing one or more columns of a foreign
215
- // key, then we should not retain the key at all
216
- if (isEmpty(to)) return '';
217
- else split[0] = replacedKeySpec;
218
- }
219
-
220
- if (split[1].slice(0, tableName.length) === tableName) {
221
- // self-referential foreign key
222
- const replacedKeyTargetSpec = split[1].replace(
223
- /\(.*\)/,
224
- replaceFromIdentifier
225
- );
226
- if (split[1] !== replacedKeyTargetSpec) {
227
- // If we are removing one or more columns of a foreign
228
- // key, then we should not retain the key at all
229
- if (isEmpty(to)) return '';
230
- else split[1] = replacedKeyTargetSpec;
231
- }
232
- }
233
- return split.join(' references ');
234
- }
235
-
236
- return item;
237
- });
238
-
239
- args = args.filter(negate(isEmpty));
240
-
241
- if (args.length === 0) {
242
- throw new Error('Unable to drop last column from table');
243
- }
244
-
245
- return oneLineSql
246
- .replace(/\(.*\)/, () => `(${args.join(', ')})`)
247
- .replace(/,\s*([,)])/, '$1');
248
- },
249
-
250
- // Boy, this is quite a method.
251
- renameColumn: async function (from, to) {
252
- return this.client.transaction(
253
- async (trx) => {
254
- this.trx = trx;
255
- const column = await this.getColumn(from);
256
- const sql = await this.getTableSql(column);
257
- const a = this.client.wrapIdentifier(from);
258
- const b = this.client.wrapIdentifier(to);
259
- const createTable = sql[0];
260
- const newSql = this._doReplace(createTable.sql, a, b);
261
- if (sql === newSql) {
262
- throw new Error('Unable to find the column to change');
263
- }
264
-
265
- const { from: mappedFrom, to: mappedTo } = invert(
266
- this.client.postProcessResponse(
267
- invert({
268
- from,
269
- to,
270
- })
271
- )
272
- );
273
-
274
- return this.reinsertMapped(createTable, newSql, (row) => {
275
- row[mappedTo] = row[mappedFrom];
276
- return omit(row, mappedFrom);
277
- });
278
- },
279
- { connection: this.connection }
280
- );
281
- },
282
-
283
- dropColumn: async function (columns) {
284
- return this.client.transaction(
285
- (trx) => {
286
- this.trx = trx;
287
- return Promise.all(columns.map((column) => this.getColumn(column)))
288
- .then(() => this.getTableSql())
289
- .then((sql) => {
290
- const createTable = sql[0];
291
- let newSql = createTable.sql;
292
- columns.forEach((column) => {
293
- const a = this.client.wrapIdentifier(column);
294
- newSql = this._doReplace(newSql, a, '');
295
- });
296
- if (sql === newSql) {
297
- throw new Error('Unable to find the column to change');
298
- }
299
- const mappedColumns = Object.keys(
300
- this.client.postProcessResponse(
301
- fromPairs(columns.map((column) => [column, column]))
302
- )
303
- );
304
- return this.reinsertMapped(createTable, newSql, (row) =>
305
- omit(row, ...mappedColumns)
306
- );
307
- });
308
- },
309
- { connection: this.connection }
310
- );
311
- },
312
-
313
- dropForeign: async function (columns, indexName) {
314
- return this.client.transaction(
315
- async (trx) => {
316
- this.trx = trx;
317
-
318
- const sql = await this.getTableSql();
319
-
320
- const createTable = sql[0];
321
-
322
- const oneLineSql = createTable.sql.replace(/\s+/g, ' ');
323
- const matched = oneLineSql.match(/^CREATE TABLE\s+(\S+)\s*\((.*)\)/);
324
-
325
- const defs = matched[2];
326
-
327
- if (!defs) {
328
- throw new Error('No column definitions in this statement!');
329
- }
330
-
331
- const updatedDefs = defs
332
- .split(COMMA_NO_PAREN_REGEX)
333
- .map((line) => line.trim())
334
- .filter((defLine) => {
335
- if (
336
- defLine.toLowerCase().startsWith('constraint') === false &&
337
- defLine.toLowerCase().includes('foreign key') === false
338
- )
339
- return true;
340
-
341
- if (indexName) {
342
- if (defLine.includes(indexName)) return false;
343
- return true;
344
- } else {
345
- const matched = defLine.match(/\(`(\S+)`\)/);
346
- const columnName = matched[1];
347
-
348
- return columns.includes(columnName) === false;
349
- }
350
- })
351
- .join(', ');
352
-
353
- const newSql = oneLineSql.replace(defs, updatedDefs);
354
-
355
- return this.reinsertMapped(createTable, newSql, (row) => {
356
- return row;
357
- });
358
- },
359
- { connection: this.connection }
360
- );
361
- },
362
-
363
- dropPrimary: async function (constraintName) {
364
- return this.client.transaction(
365
- async (trx) => {
366
- this.trx = trx;
367
-
368
- const sql = await this.getTableSql();
369
-
370
- const createTable = sql[0];
371
-
372
- const oneLineSql = createTable.sql.replace(/\s+/g, ' ');
373
- const matched = oneLineSql.match(/^CREATE TABLE\s+(\S+)\s*\((.*)\)/);
374
-
375
- const defs = matched[2];
376
-
377
- if (!defs) {
378
- throw new Error('No column definitions in this statement!');
379
- }
380
-
381
- const updatedDefs = defs
382
- .split(COMMA_NO_PAREN_REGEX)
383
- .map((line) => line.trim())
384
- .filter((defLine) => {
385
- if (
386
- defLine.startsWith('constraint') === false &&
387
- defLine.includes('primary key') === false
388
- )
389
- return true;
390
-
391
- if (constraintName) {
392
- if (defLine.includes(constraintName)) return false;
393
- return true;
394
- } else {
395
- return true;
396
- }
397
- })
398
- .join(', ');
399
-
400
- const newSql = oneLineSql.replace(defs, updatedDefs);
401
-
402
- return this.reinsertMapped(createTable, newSql, (row) => {
403
- return row;
404
- });
405
- },
406
- { connection: this.connection }
407
- );
408
- },
409
-
410
- primary: async function (columns, constraintName) {
411
- return this.client.transaction(
412
- async (trx) => {
413
- this.trx = trx;
414
-
415
- const tableInfo = (await this.getTableSql())[0];
416
- const currentSQL = tableInfo.sql;
417
-
418
- const oneLineSQL = currentSQL.replace(/\s+/g, ' ');
419
- const matched = oneLineSQL.match(/^CREATE TABLE\s+(\S+)\s*\((.*)\)/);
420
-
421
- const columnDefinitions = matched[2];
422
-
423
- if (!columnDefinitions) {
424
- throw new Error('No column definitions in this statement!');
425
- }
426
-
427
- const primaryKeyDef = `primary key(${columns.join(',')})`;
428
- const constraintDef = constraintName
429
- ? `constraint ${constraintName} ${primaryKeyDef}`
430
- : primaryKeyDef;
431
-
432
- const newColumnDefinitions = [
433
- ...columnDefinitions
434
- .split(COMMA_NO_PAREN_REGEX)
435
- .map((line) => line.trim())
436
- .filter((line) => line.startsWith('primary') === false)
437
- .map((line) => line.replace(/primary key/i, '')),
438
- constraintDef,
439
- ].join(', ');
440
-
441
- const newSQL = oneLineSQL.replace(
442
- columnDefinitions,
443
- newColumnDefinitions
444
- );
445
-
446
- return this.reinsertMapped(tableInfo, newSQL, (row) => {
447
- return row;
448
- });
449
- },
450
- { connection: this.connection }
451
- );
452
- },
453
-
454
- foreign: async function (foreignInfo) {
455
- return this.client.transaction(
456
- async (trx) => {
457
- this.trx = trx;
458
-
459
- const tableInfo = (await this.getTableSql())[0];
460
- const currentSQL = tableInfo.sql;
461
-
462
- const oneLineSQL = currentSQL.replace(/\s+/g, ' ');
463
- const matched = oneLineSQL.match(/^CREATE TABLE\s+(\S+)\s*\((.*)\)/);
464
-
465
- const columnDefinitions = matched[2];
466
-
467
- if (!columnDefinitions) {
468
- throw new Error('No column definitions in this statement!');
469
- }
470
-
471
- const newColumnDefinitions = columnDefinitions
472
- .split(COMMA_NO_PAREN_REGEX)
473
- .map((line) => line.trim());
474
-
475
- let newForeignSQL = '';
476
-
477
- if (foreignInfo.keyName) {
478
- newForeignSQL += `CONSTRAINT ${foreignInfo.keyName}`;
479
- }
480
-
481
- newForeignSQL += ` FOREIGN KEY (${foreignInfo.column.join(', ')}) `;
482
- newForeignSQL += ` REFERENCES ${foreignInfo.inTable} (${foreignInfo.references})`;
483
-
484
- if (foreignInfo.onUpdate) {
485
- newForeignSQL += ` ON UPDATE ${foreignInfo.onUpdate}`;
486
- }
487
-
488
- if (foreignInfo.onDelete) {
489
- newForeignSQL += ` ON DELETE ${foreignInfo.onDelete}`;
490
- }
491
-
492
- newColumnDefinitions.push(newForeignSQL);
493
-
494
- const newSQL = oneLineSQL.replace(
495
- columnDefinitions,
496
- newColumnDefinitions.join(', ')
497
- );
498
-
499
- return this.reinsertMapped(tableInfo, newSQL, (row) => {
500
- return row;
501
- });
502
- },
503
- { connection: this.connection }
504
- );
505
- },
506
-
507
- /**
508
- * @fixme
509
- *
510
- * There's a bunch of overlap between renameColumn/dropColumn/dropForeign/primary/foreign.
511
- * It'll be helpful to refactor this file heavily to combine/optimize some of these calls
512
- */
513
-
514
- reinsertMapped(createTable, newSql, mapRow) {
515
- return Promise.resolve()
516
- .then(() => this.createTempTable(createTable))
517
- .then(() => this.copyData())
518
- .then(() => this.dropOriginal())
519
- .then(() => this.trx.raw(newSql))
520
- .then(() => this.reinsertData(mapRow))
521
- .then(() => this.dropTempTable());
522
- },
523
- });
524
-
525
- module.exports = SQLite3_DDL;
1
+ // SQLite3_DDL
2
+ //
3
+ // All of the SQLite3 specific DDL helpers for renaming/dropping
4
+ // columns and changing datatypes.
5
+ // -------
6
+
7
+ const assign = require('lodash/assign');
8
+ const chunk = require('lodash/chunk');
9
+ const find = require('lodash/find');
10
+ const fromPairs = require('lodash/fromPairs');
11
+ const identity = require('lodash/identity');
12
+ const invert = require('lodash/invert');
13
+ const isEmpty = require('lodash/isEmpty');
14
+ const negate = require('lodash/negate');
15
+ const omit = require('lodash/omit');
16
+ const uniqueId = require('lodash/uniqueId');
17
+ const { COMMA_NO_PAREN_REGEX } = require('../../../constants');
18
+
19
+ // So altering the schema in SQLite3 is a major pain.
20
+ // We have our own object to deal with the renaming and altering the types
21
+ // for sqlite3 things.
22
+ function SQLite3_DDL(client, tableCompiler, pragma, connection) {
23
+ this.client = client;
24
+ this.tableCompiler = tableCompiler;
25
+ this.pragma = pragma;
26
+ this.tableNameRaw = this.tableCompiler.tableNameRaw;
27
+ this.alteredName = uniqueId('_knex_temp_alter');
28
+ this.connection = connection;
29
+ this.formatter =
30
+ client && client.config && client.config.wrapIdentifier
31
+ ? client.config.wrapIdentifier
32
+ : (value) => value;
33
+ }
34
+
35
+ assign(SQLite3_DDL.prototype, {
36
+ tableName() {
37
+ return this.formatter(this.tableNameRaw, (value) => value);
38
+ },
39
+
40
+ getColumn: async function (column) {
41
+ const currentCol = find(this.pragma, (col) => {
42
+ return (
43
+ this.client.wrapIdentifier(col.name).toLowerCase() ===
44
+ this.client.wrapIdentifier(column).toLowerCase()
45
+ );
46
+ });
47
+ if (!currentCol)
48
+ throw new Error(
49
+ `The column ${column} is not in the ${this.tableName()} table`
50
+ );
51
+ return currentCol;
52
+ },
53
+
54
+ getTableSql() {
55
+ this.trx.disableProcessing();
56
+ return this.trx
57
+ .raw(
58
+ `SELECT name, sql FROM sqlite_master WHERE type="table" AND name="${this.tableName()}"`
59
+ )
60
+ .then((result) => {
61
+ this.trx.enableProcessing();
62
+ return result;
63
+ });
64
+ },
65
+
66
+ renameTable: async function () {
67
+ return this.trx.raw(
68
+ `ALTER TABLE "${this.tableName()}" RENAME TO "${this.alteredName}"`
69
+ );
70
+ },
71
+
72
+ dropOriginal() {
73
+ return this.trx.raw(`DROP TABLE "${this.tableName()}"`);
74
+ },
75
+
76
+ dropTempTable() {
77
+ return this.trx.raw(`DROP TABLE "${this.alteredName}"`);
78
+ },
79
+
80
+ copyData() {
81
+ return this.trx
82
+ .raw(`SELECT * FROM "${this.tableName()}"`)
83
+ .then((result) =>
84
+ this.insertChunked(20, this.alteredName, identity, result)
85
+ );
86
+ },
87
+
88
+ reinsertData(iterator) {
89
+ return this.trx
90
+ .raw(`SELECT * FROM "${this.alteredName}"`)
91
+ .then((result) =>
92
+ this.insertChunked(20, this.tableName(), iterator, result)
93
+ );
94
+ },
95
+
96
+ async insertChunked(chunkSize, target, iterator, result) {
97
+ iterator = iterator || identity;
98
+ const chunked = chunk(result, chunkSize);
99
+ for (const batch of chunked) {
100
+ await this.trx.queryBuilder().table(target).insert(batch.map(iterator));
101
+ }
102
+ },
103
+
104
+ createTempTable(createTable) {
105
+ return this.trx.raw(
106
+ createTable.sql.replace(this.tableName(), this.alteredName)
107
+ );
108
+ },
109
+
110
+ _doReplace(sql, from, to) {
111
+ const oneLineSql = sql.replace(/\s+/g, ' ');
112
+ const matched = oneLineSql.match(/^CREATE TABLE\s+(\S+)\s*\((.*)\)/);
113
+
114
+ const tableName = matched[1];
115
+ const defs = matched[2];
116
+
117
+ if (!defs) {
118
+ throw new Error('No column definitions in this statement!');
119
+ }
120
+
121
+ let parens = 0,
122
+ args = [],
123
+ ptr = 0;
124
+ let i = 0;
125
+ const x = defs.length;
126
+ for (i = 0; i < x; i++) {
127
+ switch (defs[i]) {
128
+ case '(':
129
+ parens++;
130
+ break;
131
+ case ')':
132
+ parens--;
133
+ break;
134
+ case ',':
135
+ if (parens === 0) {
136
+ args.push(defs.slice(ptr, i));
137
+ ptr = i + 1;
138
+ }
139
+ break;
140
+ case ' ':
141
+ if (ptr === i) {
142
+ ptr = i + 1;
143
+ }
144
+ break;
145
+ }
146
+ }
147
+ args.push(defs.slice(ptr, i));
148
+
149
+ const fromIdentifier = from.replace(/[`"'[\]]/g, '');
150
+
151
+ args = args.map((item) => {
152
+ let split = item.trim().split(' ');
153
+
154
+ // SQLite supports all quoting mechanisms prevalent in all major dialects of SQL
155
+ // and preserves the original quoting in sqlite_master.
156
+ //
157
+ // Also, identifiers are never case sensitive, not even when quoted.
158
+ //
159
+ // Ref: https://www.sqlite.org/lang_keywords.html
160
+ const fromMatchCandidates = [
161
+ new RegExp(`\`${fromIdentifier}\``, 'i'),
162
+ new RegExp(`"${fromIdentifier}"`, 'i'),
163
+ new RegExp(`'${fromIdentifier}'`, 'i'),
164
+ new RegExp(`\\[${fromIdentifier}\\]`, 'i'),
165
+ ];
166
+ if (fromIdentifier.match(/^\S+$/)) {
167
+ fromMatchCandidates.push(new RegExp(`\\b${fromIdentifier}\\b`, 'i'));
168
+ }
169
+
170
+ const doesMatchFromIdentifier = (target) =>
171
+ fromMatchCandidates.some((c) => target.match(c));
172
+
173
+ const replaceFromIdentifier = (target) =>
174
+ fromMatchCandidates.reduce(
175
+ (result, candidate) => result.replace(candidate, to),
176
+ target
177
+ );
178
+
179
+ if (doesMatchFromIdentifier(split[0])) {
180
+ // column definition
181
+ if (to) {
182
+ split[0] = to;
183
+ return split.join(' ');
184
+ }
185
+ return ''; // for deletions
186
+ }
187
+
188
+ // skip constraint name
189
+ const idx = /constraint/i.test(split[0]) ? 2 : 0;
190
+
191
+ // primary key and unique constraints have one or more
192
+ // columns from this table listed between (); replace
193
+ // one if it matches
194
+ if (/primary|unique/i.test(split[idx])) {
195
+ const ret = item.replace(/\(.*\)/, replaceFromIdentifier);
196
+ // If any member columns are dropped then uniqueness/pk constraint
197
+ // can not be retained
198
+ if (ret !== item && isEmpty(to)) return '';
199
+ return ret;
200
+ }
201
+
202
+ // foreign keys have one or more columns from this table
203
+ // listed between (); replace one if it matches
204
+ // foreign keys also have a 'references' clause
205
+ // which may reference THIS table; if it does, replace
206
+ // column references in that too!
207
+ if (/foreign/.test(split[idx])) {
208
+ split = item.split(/ references /i);
209
+ // the quoted column names save us from having to do anything
210
+ // other than a straight replace here
211
+ const replacedKeySpec = replaceFromIdentifier(split[0]);
212
+
213
+ if (split[0] !== replacedKeySpec) {
214
+ // If we are removing one or more columns of a foreign
215
+ // key, then we should not retain the key at all
216
+ if (isEmpty(to)) return '';
217
+ else split[0] = replacedKeySpec;
218
+ }
219
+
220
+ if (split[1].slice(0, tableName.length) === tableName) {
221
+ // self-referential foreign key
222
+ const replacedKeyTargetSpec = split[1].replace(
223
+ /\(.*\)/,
224
+ replaceFromIdentifier
225
+ );
226
+ if (split[1] !== replacedKeyTargetSpec) {
227
+ // If we are removing one or more columns of a foreign
228
+ // key, then we should not retain the key at all
229
+ if (isEmpty(to)) return '';
230
+ else split[1] = replacedKeyTargetSpec;
231
+ }
232
+ }
233
+ return split.join(' references ');
234
+ }
235
+
236
+ return item;
237
+ });
238
+
239
+ args = args.filter(negate(isEmpty));
240
+
241
+ if (args.length === 0) {
242
+ throw new Error('Unable to drop last column from table');
243
+ }
244
+
245
+ return oneLineSql
246
+ .replace(/\(.*\)/, () => `(${args.join(', ')})`)
247
+ .replace(/,\s*([,)])/, '$1');
248
+ },
249
+
250
+ // Boy, this is quite a method.
251
+ renameColumn: async function (from, to) {
252
+ return this.client.transaction(
253
+ async (trx) => {
254
+ this.trx = trx;
255
+ const column = await this.getColumn(from);
256
+ const sql = await this.getTableSql(column);
257
+ const a = this.client.wrapIdentifier(from);
258
+ const b = this.client.wrapIdentifier(to);
259
+ const createTable = sql[0];
260
+ const newSql = this._doReplace(createTable.sql, a, b);
261
+ if (sql === newSql) {
262
+ throw new Error('Unable to find the column to change');
263
+ }
264
+
265
+ const { from: mappedFrom, to: mappedTo } = invert(
266
+ this.client.postProcessResponse(
267
+ invert({
268
+ from,
269
+ to,
270
+ })
271
+ )
272
+ );
273
+
274
+ return this.reinsertMapped(createTable, newSql, (row) => {
275
+ row[mappedTo] = row[mappedFrom];
276
+ return omit(row, mappedFrom);
277
+ });
278
+ },
279
+ { connection: this.connection }
280
+ );
281
+ },
282
+
283
+ dropColumn: async function (columns) {
284
+ return this.client.transaction(
285
+ (trx) => {
286
+ this.trx = trx;
287
+ return Promise.all(columns.map((column) => this.getColumn(column)))
288
+ .then(() => this.getTableSql())
289
+ .then((sql) => {
290
+ const createTable = sql[0];
291
+ let newSql = createTable.sql;
292
+ columns.forEach((column) => {
293
+ const a = this.client.wrapIdentifier(column);
294
+ newSql = this._doReplace(newSql, a, '');
295
+ });
296
+ if (sql === newSql) {
297
+ throw new Error('Unable to find the column to change');
298
+ }
299
+ const mappedColumns = Object.keys(
300
+ this.client.postProcessResponse(
301
+ fromPairs(columns.map((column) => [column, column]))
302
+ )
303
+ );
304
+ return this.reinsertMapped(createTable, newSql, (row) =>
305
+ omit(row, ...mappedColumns)
306
+ );
307
+ });
308
+ },
309
+ { connection: this.connection }
310
+ );
311
+ },
312
+
313
+ dropForeign: async function (columns, indexName) {
314
+ return this.client.transaction(
315
+ async (trx) => {
316
+ this.trx = trx;
317
+
318
+ const sql = await this.getTableSql();
319
+
320
+ const createTable = sql[0];
321
+
322
+ const oneLineSql = createTable.sql.replace(/\s+/g, ' ');
323
+ const matched = oneLineSql.match(/^CREATE TABLE\s+(\S+)\s*\((.*)\)/);
324
+
325
+ const defs = matched[2];
326
+
327
+ if (!defs) {
328
+ throw new Error('No column definitions in this statement!');
329
+ }
330
+
331
+ const updatedDefs = defs
332
+ .split(COMMA_NO_PAREN_REGEX)
333
+ .map((line) => line.trim())
334
+ .filter((defLine) => {
335
+ if (
336
+ defLine.toLowerCase().startsWith('constraint') === false &&
337
+ defLine.toLowerCase().includes('foreign key') === false
338
+ )
339
+ return true;
340
+
341
+ if (indexName) {
342
+ if (defLine.includes(indexName)) return false;
343
+ return true;
344
+ } else {
345
+ const matched = defLine.match(/\(`(\S+)`\)/);
346
+ const columnName = matched[1];
347
+
348
+ return columns.includes(columnName) === false;
349
+ }
350
+ })
351
+ .join(', ');
352
+
353
+ const newSql = oneLineSql.replace(defs, updatedDefs);
354
+
355
+ return this.reinsertMapped(createTable, newSql, (row) => {
356
+ return row;
357
+ });
358
+ },
359
+ { connection: this.connection }
360
+ );
361
+ },
362
+
363
+ dropPrimary: async function (constraintName) {
364
+ return this.client.transaction(
365
+ async (trx) => {
366
+ this.trx = trx;
367
+
368
+ const sql = await this.getTableSql();
369
+
370
+ const createTable = sql[0];
371
+
372
+ const oneLineSql = createTable.sql.replace(/\s+/g, ' ');
373
+ const matched = oneLineSql.match(/^CREATE TABLE\s+(\S+)\s*\((.*)\)/);
374
+
375
+ const defs = matched[2];
376
+
377
+ if (!defs) {
378
+ throw new Error('No column definitions in this statement!');
379
+ }
380
+
381
+ const updatedDefs = defs
382
+ .split(COMMA_NO_PAREN_REGEX)
383
+ .map((line) => line.trim())
384
+ .filter((defLine) => {
385
+ if (
386
+ defLine.startsWith('constraint') === false &&
387
+ defLine.includes('primary key') === false
388
+ )
389
+ return true;
390
+
391
+ if (constraintName) {
392
+ if (defLine.includes(constraintName)) return false;
393
+ return true;
394
+ } else {
395
+ return true;
396
+ }
397
+ })
398
+ .join(', ');
399
+
400
+ const newSql = oneLineSql.replace(defs, updatedDefs);
401
+
402
+ return this.reinsertMapped(createTable, newSql, (row) => {
403
+ return row;
404
+ });
405
+ },
406
+ { connection: this.connection }
407
+ );
408
+ },
409
+
410
+ primary: async function (columns, constraintName) {
411
+ return this.client.transaction(
412
+ async (trx) => {
413
+ this.trx = trx;
414
+
415
+ const tableInfo = (await this.getTableSql())[0];
416
+ const currentSQL = tableInfo.sql;
417
+
418
+ const oneLineSQL = currentSQL.replace(/\s+/g, ' ');
419
+ const matched = oneLineSQL.match(/^CREATE TABLE\s+(\S+)\s*\((.*)\)/);
420
+
421
+ const columnDefinitions = matched[2];
422
+
423
+ if (!columnDefinitions) {
424
+ throw new Error('No column definitions in this statement!');
425
+ }
426
+
427
+ const primaryKeyDef = `primary key(${columns.join(',')})`;
428
+ const constraintDef = constraintName
429
+ ? `constraint ${constraintName} ${primaryKeyDef}`
430
+ : primaryKeyDef;
431
+
432
+ const newColumnDefinitions = [
433
+ ...columnDefinitions
434
+ .split(COMMA_NO_PAREN_REGEX)
435
+ .map((line) => line.trim())
436
+ .filter((line) => line.startsWith('primary') === false)
437
+ .map((line) => line.replace(/primary key/i, '')),
438
+ constraintDef,
439
+ ].join(', ');
440
+
441
+ const newSQL = oneLineSQL.replace(
442
+ columnDefinitions,
443
+ newColumnDefinitions
444
+ );
445
+
446
+ return this.reinsertMapped(tableInfo, newSQL, (row) => {
447
+ return row;
448
+ });
449
+ },
450
+ { connection: this.connection }
451
+ );
452
+ },
453
+
454
+ foreign: async function (foreignInfo) {
455
+ return this.client.transaction(
456
+ async (trx) => {
457
+ this.trx = trx;
458
+
459
+ const tableInfo = (await this.getTableSql())[0];
460
+ const currentSQL = tableInfo.sql;
461
+
462
+ const oneLineSQL = currentSQL.replace(/\s+/g, ' ');
463
+ const matched = oneLineSQL.match(/^CREATE TABLE\s+(\S+)\s*\((.*)\)/);
464
+
465
+ const columnDefinitions = matched[2];
466
+
467
+ if (!columnDefinitions) {
468
+ throw new Error('No column definitions in this statement!');
469
+ }
470
+
471
+ const newColumnDefinitions = columnDefinitions
472
+ .split(COMMA_NO_PAREN_REGEX)
473
+ .map((line) => line.trim());
474
+
475
+ let newForeignSQL = '';
476
+
477
+ if (foreignInfo.keyName) {
478
+ newForeignSQL += `CONSTRAINT ${foreignInfo.keyName}`;
479
+ }
480
+
481
+ newForeignSQL += ` FOREIGN KEY (${foreignInfo.column.join(', ')}) `;
482
+ newForeignSQL += ` REFERENCES ${foreignInfo.inTable} (${foreignInfo.references})`;
483
+
484
+ if (foreignInfo.onUpdate) {
485
+ newForeignSQL += ` ON UPDATE ${foreignInfo.onUpdate}`;
486
+ }
487
+
488
+ if (foreignInfo.onDelete) {
489
+ newForeignSQL += ` ON DELETE ${foreignInfo.onDelete}`;
490
+ }
491
+
492
+ newColumnDefinitions.push(newForeignSQL);
493
+
494
+ const newSQL = oneLineSQL.replace(
495
+ columnDefinitions,
496
+ newColumnDefinitions.join(', ')
497
+ );
498
+
499
+ return this.reinsertMapped(tableInfo, newSQL, (row) => {
500
+ return row;
501
+ });
502
+ },
503
+ { connection: this.connection }
504
+ );
505
+ },
506
+
507
+ /**
508
+ * @fixme
509
+ *
510
+ * There's a bunch of overlap between renameColumn/dropColumn/dropForeign/primary/foreign.
511
+ * It'll be helpful to refactor this file heavily to combine/optimize some of these calls
512
+ */
513
+
514
+ reinsertMapped(createTable, newSql, mapRow) {
515
+ return Promise.resolve()
516
+ .then(() => this.createTempTable(createTable))
517
+ .then(() => this.copyData())
518
+ .then(() => this.dropOriginal())
519
+ .then(() => this.trx.raw(newSql))
520
+ .then(() => this.reinsertData(mapRow))
521
+ .then(() => this.dropTempTable());
522
+ },
523
+ });
524
+
525
+ module.exports = SQLite3_DDL;