knex 0.21.13 → 0.21.17

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 CHANGED
@@ -1,5 +1,53 @@
1
1
  # Master (Unreleased)
2
2
 
3
+ # 0.21.17 - 30 January, 2021
4
+
5
+ ### Bug fixes:
6
+
7
+ - SQLite: Fix SQLite foreign on delete when altering a table #4261
8
+
9
+ ### New features:
10
+
11
+ - Add support for optimizer hints (see https://github.com/knex/documentation/pull/306 for documentation) #4243
12
+
13
+ # 0.21.16 - 17 January, 2021
14
+
15
+ ### Bug fixes:
16
+
17
+ - MSSQL: Avoid passing unsupported pool param. Fixes node-mssql 7+ support #4236
18
+
19
+ # 0.21.15 - 26 December, 2020
20
+
21
+ ### New features:
22
+
23
+ - SQLite: Add primary/foreign support on alterTable #4162
24
+ - SQLite: Add dropPrimary/dropForeign support on alterTable #4162
25
+
26
+ ### Typings:
27
+
28
+ - Add "after" and "first" to columnBuilder types #3549 #4169
29
+
30
+ ### Test / internal changes:
31
+
32
+ - Extract knex config resolution logic #4166
33
+ - Run CI using GitHub Actions #4168
34
+ - Add Node.js 15 to CI matrix #4173
35
+
36
+ # 0.21.14 - 18 December, 2020
37
+
38
+ ### New features:
39
+
40
+ - MSSQL: support "returning" on inserts, updates and deletes on tables with triggers #4152
41
+ - Use esm import if package.json type is "module" #4158
42
+
43
+ ### Bug fixes:
44
+
45
+ - Make sure query-response and query-error events contain _knexTxId #4160
46
+
47
+ ### Test / internal changes:
48
+
49
+ - Improved integration test framework #4161
50
+
3
51
  # 0.21.13 - 12 December, 2020
4
52
 
5
53
  ### New features:
package/README.md CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  [![npm version](http://img.shields.io/npm/v/knex.svg)](https://npmjs.org/package/knex)
4
4
  [![npm downloads](https://img.shields.io/npm/dm/knex.svg)](https://npmjs.org/package/knex)
5
- [![Build Status](https://travis-ci.org/knex/knex.svg?branch=master)](https://travis-ci.org/knex/knex)
6
- [![Coverage Status](https://coveralls.io/repos/tgriesser/knex/badge.svg?branch=master)](https://coveralls.io/r/tgriesser/knex?branch=master)
5
+ ![](https://github.com/knex/knex/workflows/CI/badge.svg)
6
+ [![Coverage Status](https://coveralls.io/repos/knex/knex/badge.svg?branch=master)](https://coveralls.io/r/knex/knex?branch=master)
7
7
  [![Dependencies Status](https://david-dm.org/knex/knex.svg)](https://david-dm.org/knex/knex)
8
8
  [![Gitter chat](https://badges.gitter.im/tgriesser/knex.svg)](https://gitter.im/tgriesser/knex)
9
9
  [![Language Grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/knex/knex.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/knex/knex/context:javascript)
package/bin/cli.js CHANGED
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- /* eslint no-console:0, no-var:0 */
3
2
  const Liftoff = require('liftoff');
4
3
  const interpret = require('interpret');
5
4
  const path = require('path');
@@ -0,0 +1,61 @@
1
+ const Client = require('./client');
2
+ const { SUPPORTED_CLIENTS } = require('./constants');
3
+
4
+ const parseConnection = require('./util/parse-connection');
5
+ const { resolveClientNameWithAliases } = require('./helpers');
6
+
7
+ function resolveConfig(config) {
8
+ let Dialect;
9
+ let resolvedConfig;
10
+
11
+ // If config is a string, try to parse it
12
+ const parsedConfig =
13
+ typeof config === 'string'
14
+ ? Object.assign(parseConnection(config), arguments[2])
15
+ : config;
16
+
17
+ // If user provided no relevant parameters, use generic client
18
+ if (
19
+ arguments.length === 0 ||
20
+ (!parsedConfig.client && !parsedConfig.dialect)
21
+ ) {
22
+ Dialect = Client;
23
+ }
24
+ // If user provided Client constructor as a parameter, use it
25
+ else if (
26
+ typeof parsedConfig.client === 'function' &&
27
+ parsedConfig.client.prototype instanceof Client
28
+ ) {
29
+ Dialect = parsedConfig.client;
30
+ }
31
+ // If neither applies, let's assume user specified name of a client or dialect as a string
32
+ else {
33
+ const clientName = parsedConfig.client || parsedConfig.dialect;
34
+ if (!SUPPORTED_CLIENTS.includes(clientName)) {
35
+ throw new Error(
36
+ `knex: Unknown configuration option 'client' value ${clientName}. Note that it is case-sensitive, check documentation for supported values.`
37
+ );
38
+ }
39
+
40
+ const resolvedClientName = resolveClientNameWithAliases(clientName);
41
+ Dialect = require(`./dialects/${resolvedClientName}/index.js`);
42
+ }
43
+
44
+ // If config connection parameter is passed as string, try to parse it
45
+ if (typeof parsedConfig.connection === 'string') {
46
+ resolvedConfig = Object.assign({}, parsedConfig, {
47
+ connection: parseConnection(parsedConfig.connection).connection,
48
+ });
49
+ } else {
50
+ resolvedConfig = Object.assign({}, parsedConfig);
51
+ }
52
+
53
+ return {
54
+ resolvedConfig,
55
+ Dialect,
56
+ };
57
+ }
58
+
59
+ module.exports = {
60
+ resolveConfig,
61
+ };
package/lib/constants.js CHANGED
@@ -29,8 +29,16 @@ const POOL_CONFIG_OPTIONS = Object.freeze([
29
29
  'Promise',
30
30
  ]);
31
31
 
32
+ /**
33
+ * Regex that only matches comma's in strings that aren't wrapped in parentheses. Can be used to
34
+ * safely split strings like `id int, name string, body text, primary key (id, name)` into definition
35
+ * rows
36
+ */
37
+ const COMMA_NO_PAREN_REGEX = /,[\s](?![^(]*\))/g;
38
+
32
39
  module.exports = {
33
40
  CLIENT_ALIASES,
34
41
  SUPPORTED_CLIENTS,
35
42
  POOL_CONFIG_OPTIONS,
43
+ COMMA_NO_PAREN_REGEX,
36
44
  };
@@ -31,7 +31,6 @@ function Client_MSSQL(config = {}) {
31
31
  min: 1,
32
32
  max: 1,
33
33
  idleTimeoutMillis: Number.MAX_SAFE_INTEGER,
34
- evictionRunIntervalMillis: 0,
35
34
  };
36
35
 
37
36
  Client.call(this, config);
@@ -37,9 +37,70 @@ class QueryCompiler_MSSQL extends QueryCompiler {
37
37
  return sql + compact(statements).join(' ');
38
38
  }
39
39
 
40
+ //#region Insert
40
41
  // Compiles an "insert" query, allowing for multiple
41
42
  // inserts using a single query statement.
42
43
  insert() {
44
+ if (this.single.options && this.single.options.includeTriggerModifications) {
45
+ return this.insertWithTriggers();
46
+ } else {
47
+ return this.standardInsert();
48
+ }
49
+ }
50
+
51
+ insertWithTriggers() {
52
+ const insertValues = this.single.insert || [];
53
+ const { returning } = this.single;
54
+ let sql = this.with() + `${this._buildTempTable(returning)}insert into ${this.tableName} `;
55
+ const returningSql = returning
56
+ ? this._returning('insert', returning, true) + ' '
57
+ : '';
58
+
59
+ if (Array.isArray(insertValues)) {
60
+ if (insertValues.length === 0) {
61
+ return '';
62
+ }
63
+ } else if (typeof insertValues === 'object' && isEmpty(insertValues)) {
64
+ return {
65
+ sql: sql + returningSql + this._emptyInsertValue + this._buildReturningSelect(returning),
66
+ returning,
67
+ };
68
+ }
69
+
70
+ const insertData = this._prepInsert(insertValues);
71
+ if (typeof insertData === 'string') {
72
+ sql += insertData;
73
+ } else {
74
+ if (insertData.columns.length) {
75
+ sql += `(${this.formatter.columnize(insertData.columns)}`;
76
+ sql += `) ${returningSql}values (`;
77
+ let i = -1;
78
+ while (++i < insertData.values.length) {
79
+ if (i !== 0) sql += '), (';
80
+ sql += this.formatter.parameterize(
81
+ insertData.values[i],
82
+ this.client.valueForUndefined
83
+ );
84
+ }
85
+ sql += ')';
86
+ } else if (insertValues.length === 1 && insertValues[0]) {
87
+ sql += returningSql + this._emptyInsertValue;
88
+ } else {
89
+ sql = '';
90
+ }
91
+ }
92
+
93
+ if (returning) {
94
+ sql += this._buildReturningSelect(returning);
95
+ }
96
+
97
+ return {
98
+ sql,
99
+ returning,
100
+ };
101
+ }
102
+
103
+ standardInsert() {
43
104
  const insertValues = this.single.insert || [];
44
105
  let sql = this.with() + `insert into ${this.tableName} `;
45
106
  const { returning } = this.single;
@@ -80,14 +141,52 @@ class QueryCompiler_MSSQL extends QueryCompiler {
80
141
  sql = '';
81
142
  }
82
143
  }
144
+
83
145
  return {
84
146
  sql,
85
147
  returning,
86
148
  };
87
149
  }
150
+ //#endregion
88
151
 
152
+ //#region Update
89
153
  // Compiles an `update` query, allowing for a return value.
90
154
  update() {
155
+ if (this.single.options && this.single.options.includeTriggerModifications) {
156
+ return this.updateWithTriggers();
157
+ } else {
158
+ return this.standardUpdate();
159
+ }
160
+ }
161
+
162
+ updateWithTriggers() {
163
+ const top = this.top();
164
+ const withSQL = this.with();
165
+ const updates = this._prepUpdate(this.single.update);
166
+ const join = this.join();
167
+ const where = this.where();
168
+ const order = this.order();
169
+ const { returning } = this.single;
170
+ const declaredTemp = this._buildTempTable(returning);
171
+ return {
172
+ sql:
173
+ withSQL +
174
+ declaredTemp +
175
+ `update ${top ? top + ' ' : ''}${this.tableName}` +
176
+ ' set ' +
177
+ updates.join(', ') +
178
+ (returning ? ` ${this._returning('update', returning, true)}` : '') +
179
+ (join ? ` from ${this.tableName} ${join}` : '') +
180
+ (where ? ` ${where}` : '') +
181
+ (order ? ` ${order}` : '') +
182
+ (!returning
183
+ ? this._returning('rowcount', '@@rowcount')
184
+ : this._buildReturningSelect(returning)),
185
+ returning: returning || '@@rowcount',
186
+ };
187
+ }
188
+
189
+ standardUpdate() {
91
190
  const top = this.top();
92
191
  const withSQL = this.with();
93
192
  const updates = this._prepUpdate(this.single.update);
@@ -105,13 +204,42 @@ class QueryCompiler_MSSQL extends QueryCompiler {
105
204
  (join ? ` from ${this.tableName} ${join}` : '') +
106
205
  (where ? ` ${where}` : '') +
107
206
  (order ? ` ${order}` : '') +
108
- (!returning ? this._returning('rowcount', '@@rowcount') : ''),
207
+ (!returning
208
+ ? this._returning('rowcount', '@@rowcount')
209
+ : ""),
109
210
  returning: returning || '@@rowcount',
110
211
  };
111
212
  }
213
+ //#endregion
112
214
 
215
+ //#region Delete
113
216
  // Compiles a `delete` query.
114
217
  del() {
218
+ if (this.single.options && this.single.options.includeTriggerModifications) {
219
+ return this.deleteWithTriggers();
220
+ } else {
221
+ return this.standardDelete();
222
+ }
223
+ }
224
+
225
+ deleteWithTriggers() {
226
+ // Make sure tableName is processed by the formatter first.
227
+ const withSQL = this.with();
228
+ const { tableName } = this;
229
+ const wheres = this.where();
230
+ const { returning } = this.single;
231
+ return {
232
+ sql:
233
+ withSQL +
234
+ `${this._buildTempTable(returning)}delete from ${tableName}` +
235
+ (returning ? ` ${this._returning('del', returning, true)}` : '') +
236
+ (wheres ? ` ${wheres}` : '') +
237
+ (!returning ? this._returning('rowcount', '@@rowcount') : this._buildReturningSelect(returning)),
238
+ returning: returning || '@@rowcount',
239
+ };
240
+ }
241
+
242
+ standardDelete() {
115
243
  // Make sure tableName is processed by the formatter first.
116
244
  const withSQL = this.with();
117
245
  const { tableName } = this;
@@ -123,16 +251,18 @@ class QueryCompiler_MSSQL extends QueryCompiler {
123
251
  `delete from ${tableName}` +
124
252
  (returning ? ` ${this._returning('del', returning)}` : '') +
125
253
  (wheres ? ` ${wheres}` : '') +
126
- (!returning ? this._returning('rowcount', '@@rowcount') : ''),
254
+ (!returning ? this._returning('rowcount', '@@rowcount') : ""),
127
255
  returning: returning || '@@rowcount',
128
256
  };
129
257
  }
258
+ //#endregion
130
259
 
131
260
  // Compiles the columns in the query, specifying if an item was distinct.
132
261
  columns() {
133
262
  let distinctClause = '';
134
263
  if (this.onlyUnions()) return '';
135
264
  const top = this.top();
265
+ const hints = this._hintComments()
136
266
  const columns = this.grouped.columns || [];
137
267
  let i = -1,
138
268
  sql = [];
@@ -156,29 +286,75 @@ class QueryCompiler_MSSQL extends QueryCompiler {
156
286
  if (sql.length === 0) sql = ['*'];
157
287
 
158
288
  return (
159
- `select ${distinctClause}` +
289
+ `select ${hints}${distinctClause}` +
160
290
  (top ? top + ' ' : '') +
161
291
  sql.join(', ') +
162
292
  (this.tableName ? ` from ${this.tableName}` : '')
163
293
  );
164
294
  }
165
295
 
166
- _returning(method, value) {
296
+ _returning(method, value, withTrigger) {
167
297
  switch (method) {
168
298
  case 'update':
169
299
  case 'insert':
170
300
  return value
171
- ? `output ${this.formatter.columnizeWithPrefix('inserted.', value)}`
301
+ ? `output ${this.formatter.columnizeWithPrefix('inserted.', value)}${withTrigger ? " into #out" : ""}`
172
302
  : '';
173
303
  case 'del':
174
304
  return value
175
- ? `output ${this.formatter.columnizeWithPrefix('deleted.', value)}`
305
+ ? `output ${this.formatter.columnizeWithPrefix('deleted.', value)}${withTrigger ? " into #out" : ""}`
176
306
  : '';
177
307
  case 'rowcount':
178
308
  return value ? ';select @@rowcount' : '';
179
309
  }
180
310
  }
181
311
 
312
+ _buildTempTable(values) {
313
+ // If value is nothing then return an empty string
314
+ if (values && values.length > 0) {
315
+ let selections = "";
316
+
317
+ // Build values that will be returned from this procedure
318
+ if (Array.isArray(values)) {
319
+ selections = values.map(value => `[t].${this.formatter.columnize(value)}`).join(",");
320
+ } else {
321
+ selections = `[t].${this.formatter.columnize(values)}`;
322
+ }
323
+
324
+ // Force #out to be correctly populated with the correct column structure.
325
+ let sql = `select top(0) ${selections} into #out `;
326
+ sql += `from ${this.tableName} as t `;
327
+ sql += `left join ${this.tableName} on 0=1;`;
328
+
329
+ return sql;
330
+ }
331
+
332
+ return '';
333
+ }
334
+
335
+ _buildReturningSelect(values) {
336
+ // If value is nothing then return an empty string
337
+ if (values && values.length > 0) {
338
+ let selections = "";
339
+
340
+ // Build columns to return
341
+ if (Array.isArray(values)) {
342
+ selections = values.map(value => `${this.formatter.columnize(value)}`).join(",");
343
+ } else {
344
+ selections = this.formatter.columnize(values);
345
+ }
346
+
347
+ // Get the returned values
348
+ let sql = `; select ${selections} from #out; `
349
+ // Drop the temp table to prevent memory leaks
350
+ sql += `drop table #out;`;
351
+
352
+ return sql;
353
+ }
354
+
355
+ return '';
356
+ }
357
+
182
358
  // Compiles a `truncate` query.
183
359
  truncate() {
184
360
  return `truncate table ${this.tableName}`;
@@ -252,9 +428,8 @@ class QueryCompiler_MSSQL extends QueryCompiler {
252
428
  const noLimit = !this.single.limit && this.single.limit !== 0;
253
429
  const noOffset = !this.single.offset;
254
430
  if (noOffset) return '';
255
- let offset = `offset ${
256
- noOffset ? '0' : this.formatter.parameter(this.single.offset)
257
- } rows`;
431
+ let offset = `offset ${noOffset ? '0' : this.formatter.parameter(this.single.offset)
432
+ } rows`;
258
433
  if (!noLimit) {
259
434
  offset += ` fetch next ${this.formatter.parameter(
260
435
  this.single.limit
@@ -1,4 +1,4 @@
1
- /* eslint max-len:0 no-console:0*/
1
+ /* eslint max-len:0*/
2
2
 
3
3
  // MySQL Table Builder & Compiler
4
4
  // -------
@@ -14,6 +14,7 @@ const isEmpty = require('lodash/isEmpty');
14
14
  const negate = require('lodash/negate');
15
15
  const omit = require('lodash/omit');
16
16
  const uniqueId = require('lodash/uniqueId');
17
+ const { COMMA_NO_PAREN_REGEX } = require('../../../constants');
17
18
 
18
19
  // So altering the schema in SQLite3 is a major pain.
19
20
  // We have our own object to deal with the renaming and altering the types
@@ -328,7 +329,7 @@ assign(SQLite3_DDL.prototype, {
328
329
  }
329
330
 
330
331
  const updatedDefs = defs
331
- .split(',')
332
+ .split(COMMA_NO_PAREN_REGEX)
332
333
  .map((line) => line.trim())
333
334
  .filter((defLine) => {
334
335
  if (
@@ -359,6 +360,157 @@ assign(SQLite3_DDL.prototype, {
359
360
  );
360
361
  },
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
+
362
514
  reinsertMapped(createTable, newSql, mapRow) {
363
515
  return Promise.resolve()
364
516
  .then(() => this.createTempTable(createTable))