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.
- package/CHANGELOG.md +6 -0
- package/CONTRIBUTING.md +184 -184
- package/LICENSE +22 -22
- package/README.md +95 -95
- package/bin/cli.js +414 -414
- package/bin/utils/cli-config-utils.js +151 -151
- package/bin/utils/constants.js +7 -7
- package/bin/utils/migrationsLister.js +37 -37
- package/knex.js +8 -8
- package/lib/client.js +413 -413
- package/lib/config-resolver.js +61 -61
- package/lib/constants.js +44 -44
- package/lib/dialects/mssql/index.js +390 -390
- package/lib/dialects/mssql/query/compiler.js +444 -444
- package/lib/dialects/mssql/schema/columncompiler.js +103 -103
- package/lib/dialects/mssql/schema/compiler.js +59 -59
- package/lib/dialects/mssql/schema/tablecompiler.js +245 -245
- package/lib/dialects/mssql/transaction.js +97 -97
- package/lib/dialects/mysql/index.js +191 -191
- package/lib/dialects/mysql/query/compiler.js +142 -142
- package/lib/dialects/mysql/schema/columncompiler.js +171 -171
- package/lib/dialects/mysql/schema/compiler.js +60 -60
- package/lib/dialects/mysql/schema/tablecompiler.js +262 -262
- package/lib/dialects/mysql/transaction.js +48 -48
- package/lib/dialects/mysql2/index.js +35 -35
- package/lib/dialects/mysql2/transaction.js +46 -46
- package/lib/dialects/oracle/DEAD_CODE.md +5 -5
- package/lib/dialects/oracle/formatter.js +20 -20
- package/lib/dialects/oracle/index.js +79 -79
- package/lib/dialects/oracle/query/compiler.js +327 -327
- package/lib/dialects/oracle/schema/columnbuilder.js +18 -18
- package/lib/dialects/oracle/schema/columncompiler.js +139 -139
- package/lib/dialects/oracle/schema/compiler.js +81 -81
- package/lib/dialects/oracle/schema/tablecompiler.js +165 -165
- package/lib/dialects/oracle/schema/trigger.js +126 -126
- package/lib/dialects/oracle/utils.js +86 -86
- package/lib/dialects/oracledb/index.js +489 -489
- package/lib/dialects/oracledb/query/compiler.js +363 -363
- package/lib/dialects/oracledb/schema/columncompiler.js +35 -35
- package/lib/dialects/oracledb/transaction.js +76 -76
- package/lib/dialects/oracledb/utils.js +14 -14
- package/lib/dialects/postgres/index.js +319 -319
- package/lib/dialects/postgres/query/compiler.js +206 -206
- package/lib/dialects/postgres/schema/columncompiler.js +125 -125
- package/lib/dialects/postgres/schema/compiler.js +109 -109
- package/lib/dialects/postgres/schema/tablecompiler.js +183 -183
- package/lib/dialects/redshift/index.js +73 -73
- package/lib/dialects/redshift/query/compiler.js +119 -119
- package/lib/dialects/redshift/schema/columnbuilder.js +20 -20
- package/lib/dialects/redshift/schema/columncompiler.js +60 -60
- package/lib/dialects/redshift/schema/compiler.js +14 -14
- package/lib/dialects/redshift/schema/tablecompiler.js +123 -123
- package/lib/dialects/redshift/transaction.js +18 -18
- package/lib/dialects/sqlite3/formatter.js +21 -21
- package/lib/dialects/sqlite3/index.js +169 -169
- package/lib/dialects/sqlite3/query/compiler.js +222 -222
- package/lib/dialects/sqlite3/schema/columncompiler.js +27 -27
- package/lib/dialects/sqlite3/schema/compiler.js +49 -49
- package/lib/dialects/sqlite3/schema/ddl.js +525 -525
- package/lib/dialects/sqlite3/schema/tablecompiler.js +238 -238
- package/lib/formatter.js +295 -295
- package/lib/functionhelper.js +14 -14
- package/lib/helpers.js +92 -92
- package/lib/index.js +3 -3
- package/lib/interface.js +115 -115
- package/lib/knex.js +42 -42
- package/lib/logger.js +76 -76
- package/lib/migrate/MigrationGenerator.js +82 -82
- package/lib/migrate/Migrator.js +611 -611
- package/lib/migrate/configuration-merger.js +60 -60
- package/lib/migrate/migrate-stub.js +17 -17
- package/lib/migrate/migration-list-resolver.js +36 -36
- package/lib/migrate/sources/fs-migrations.js +99 -99
- package/lib/migrate/stub/cjs.stub +15 -15
- package/lib/migrate/stub/coffee.stub +13 -13
- package/lib/migrate/stub/eg.stub +14 -14
- package/lib/migrate/stub/js.stub +15 -15
- package/lib/migrate/stub/knexfile-coffee.stub +34 -34
- package/lib/migrate/stub/knexfile-eg.stub +43 -43
- package/lib/migrate/stub/knexfile-js.stub +44 -44
- package/lib/migrate/stub/knexfile-ls.stub +35 -35
- package/lib/migrate/stub/knexfile-ts.stub +44 -44
- package/lib/migrate/stub/ls.stub +14 -14
- package/lib/migrate/stub/ts.stub +21 -21
- package/lib/migrate/table-creator.js +67 -67
- package/lib/migrate/table-resolver.js +27 -27
- package/lib/query/builder.js +1372 -1372
- package/lib/query/compiler.js +889 -889
- package/lib/query/constants.js +13 -13
- package/lib/query/joinclause.js +263 -263
- package/lib/query/methods.js +92 -92
- package/lib/query/string.js +190 -190
- package/lib/raw.js +188 -188
- package/lib/ref.js +39 -39
- package/lib/runner.js +285 -285
- package/lib/schema/builder.js +82 -82
- package/lib/schema/columnbuilder.js +117 -117
- package/lib/schema/columncompiler.js +177 -177
- package/lib/schema/compiler.js +101 -101
- package/lib/schema/helpers.js +51 -51
- package/lib/schema/tablebuilder.js +288 -288
- package/lib/schema/tablecompiler.js +296 -296
- package/lib/seed/Seeder.js +203 -203
- package/lib/seed/seed-stub.js +13 -13
- package/lib/seed/stub/coffee.stub +9 -9
- package/lib/seed/stub/eg.stub +11 -11
- package/lib/seed/stub/js.stub +13 -13
- package/lib/seed/stub/ls.stub +11 -11
- package/lib/seed/stub/ts.stub +13 -13
- package/lib/transaction.js +363 -363
- package/lib/util/batchInsert.js +59 -59
- package/lib/util/delay.js +6 -6
- package/lib/util/fake-client.js +9 -9
- package/lib/util/finally-mixin.js +13 -13
- package/lib/util/fs.js +76 -76
- package/lib/util/import-file.js +13 -13
- package/lib/util/is-module-type.js +14 -14
- package/lib/util/is.js +32 -32
- package/lib/util/make-knex.js +338 -338
- package/lib/util/nanoid.js +29 -29
- package/lib/util/noop.js +1 -1
- package/lib/util/parse-connection.js +66 -66
- package/lib/util/save-async-stack.js +14 -14
- package/lib/util/template.js +52 -52
- package/lib/util/timeout.js +29 -29
- package/lib/util/timestamp.js +16 -16
- package/package.json +1 -1
- package/scripts/build.js +125 -125
- package/scripts/docker-compose.yml +111 -111
- package/scripts/next-release-howto.md +24 -24
- package/scripts/release.sh +34 -34
- package/scripts/runkit-example.js +34 -34
- package/scripts/stress-test/README.txt +18 -18
- package/scripts/stress-test/docker-compose.yml +47 -47
- package/scripts/stress-test/knex-stress-test.js +196 -196
- package/scripts/stress-test/mysql2-random-hanging-every-now-and-then.js +145 -145
- package/scripts/stress-test/mysql2-sudden-exit-without-error.js +100 -100
- package/scripts/stress-test/reconnect-test-mysql-based-drivers.js +184 -184
- package/types/index.d.ts +2249 -2249
- package/types/result.d.ts +27 -27
- 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;
|