leoric 2.3.2 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/History.md CHANGED
@@ -1,3 +1,34 @@
1
+ 2.5.0 / 2022-05-13
2
+ ==================
3
+
4
+ ## What's Changed
5
+ * feat: support disconnect and fix timestamps init by @JimmyDaddy in https://github.com/cyjake/leoric/pull/313
6
+
7
+
8
+ **Full Changelog**: https://github.com/cyjake/leoric/compare/v2.4.1...v2.5.0
9
+
10
+ 2.4.1 / 2022-04-27
11
+ ==================
12
+
13
+ ## What's Changed
14
+ * fix: realm.Bone.DataTypes should be invokable, Invokable.TYPE.toSqlString() get wrong default length(1), DataType definitions by @JimmyDaddy in https://github.com/cyjake/leoric/pull/307
15
+
16
+
17
+ **Full Changelog**: https://github.com/cyjake/leoric/compare/v2.4.0...v2.4.1
18
+
19
+ 2.4.0 / 2022-04-24
20
+ ==================
21
+
22
+ ## What's Changed
23
+ * feat: support custom driver by @JimmyDaddy in https://github.com/cyjake/leoric/pull/304
24
+ * chore: update build status badge by @snapre in https://github.com/cyjake/leoric/pull/305
25
+ * feat: export more ts type definitions and use deep-equal module by @JimmyDaddy in https://github.com/cyjake/leoric/pull/306
26
+
27
+ ## New Contributors
28
+ * @snapre made their first contribution in https://github.com/cyjake/leoric/pull/305
29
+
30
+ **Full Changelog**: https://github.com/cyjake/leoric/compare/v2.3.2...v2.4.0
31
+
1
32
  2.3.2 / 2022-04-15
2
33
  ==================
3
34
 
package/Readme.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [![Package Quality](https://packagequality.com/shield/leoric.svg)](https://packagequality.com/#?package=leoric)
4
4
  [![NPM Downloads](https://img.shields.io/npm/dm/leoric.svg?style=flat)](https://www.npmjs.com/package/leoric)
5
5
  [![NPM Version](http://img.shields.io/npm/v/leoric.svg?style=flat)](https://www.npmjs.com/package/leoric)
6
- [![Build Status](https://travis-ci.org/cyjake/leoric.svg)](https://travis-ci.org/cyjake/leoric)
6
+ [![Build Status](https://github.com/cyjake/leoric/actions/workflows/nodejs.yml/badge.svg)](https://github.com/cyjake/leoric/actions/workflows/nodejs.yml)
7
7
  [![codecov](https://codecov.io/gh/cyjake/leoric/branch/master/graph/badge.svg?token=OZZWTZTDS1)](https://codecov.io/gh/cyjake/leoric)
8
8
 
9
9
  Leoric is an object-relational mapping for Node.js, which is heavily influenced by Active Record of Ruby on Rails. See the [documentation](https://leoric.js.org) for detail.
package/index.js CHANGED
@@ -12,6 +12,8 @@ const { heresql } = require('./src/utils/string');
12
12
  const Hint = require('./src/hint');
13
13
  const Realm = require('./src/realm');
14
14
  const Decorators = require('./src/decorators');
15
+ const Raw = require('./src/raw');
16
+ const { MysqlDriver, PostgresDriver, SqliteDriver, AbstractDriver } = require('./src/drivers');
15
17
 
16
18
  /**
17
19
  * @typedef {Object} RawSql
@@ -37,10 +39,17 @@ const connect = async function connect(opts) {
37
39
  return realm;
38
40
  };
39
41
 
42
+ const disconnect = async function disconnect(realm, ...args) {
43
+ if (realm instanceof Realm && realm.connected) {
44
+ return await realm.disconnect(...args);
45
+ }
46
+ };
47
+
40
48
  Object.assign(Realm.prototype, migrations, { DataTypes });
41
49
  Object.assign(Realm, {
42
50
  default: Realm,
43
51
  connect,
52
+ disconnect,
44
53
  Bone,
45
54
  Collection,
46
55
  DataTypes,
@@ -50,6 +59,11 @@ Object.assign(Realm, {
50
59
  heresql,
51
60
  ...Hint,
52
61
  ...Decorators,
62
+ MysqlDriver,
63
+ PostgresDriver,
64
+ SqliteDriver,
65
+ AbstractDriver,
66
+ Raw,
53
67
  });
54
68
 
55
69
  module.exports = Realm;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leoric",
3
- "version": "2.3.2",
3
+ "version": "2.5.0",
4
4
  "description": "JavaScript Object-relational mapping alchemy",
5
5
  "main": "index.js",
6
6
  "types": "types/index.d.ts",
@@ -21,6 +21,7 @@
21
21
  "test:mysql2": "./test/start.sh test/integration/mysql2.test.js",
22
22
  "test:postgres": "./test/start.sh test/integration/postgres.test.js",
23
23
  "test:sqlite": "./test/start.sh test/integration/sqlite.test.js",
24
+ "test:custom": "./test/start.sh test/integration/custom.test.js",
24
25
  "test:sqlcipher": "./test/start.sh test/integration/sqlcipher.test.js",
25
26
  "test:dts": "./test/start.sh dts",
26
27
  "test:coverage": "nyc ./test/start.sh && nyc report --reporter=lcov",
@@ -49,6 +50,7 @@
49
50
  },
50
51
  "dependencies": {
51
52
  "debug": "^3.1.0",
53
+ "deep-equal": "^2.0.5",
52
54
  "heredoc": "^1.3.1",
53
55
  "pluralize": "^7.0.0",
54
56
  "reflect-metadata": "^0.1.13",
package/src/bone.js CHANGED
@@ -5,6 +5,7 @@
5
5
  * @module
6
6
  */
7
7
  const util = require('util');
8
+ const deepEqual = require('deep-equal');
8
9
  const pluralize = require('pluralize');
9
10
  const { executeValidator, LeoricValidateError } = require('./validator');
10
11
  require('reflect-metadata');
@@ -19,6 +20,7 @@ const {
19
20
  TIMESTAMP_NAMES,
20
21
  LEGACY_TIMESTAMP_COLUMN_MAP,
21
22
  ASSOCIATE_METADATA_MAP,
23
+ TIMESTAMP_ATTRIBUTE_NAMES,
22
24
  } = require('./constants');
23
25
 
24
26
  const columnAttributesKey = Symbol('leoric#columns');
@@ -137,6 +139,8 @@ function valuesValidate(values, attributes, ctx) {
137
139
  */
138
140
  class Bone {
139
141
 
142
+ static DataTypes = DataTypes.invokable;
143
+
140
144
  // private variables
141
145
  #raw = {};
142
146
  #rawSaved = {};
@@ -380,7 +384,7 @@ class Bone {
380
384
  if (this.#rawUnset.has(name) || !this.hasAttribute(name)) return false;
381
385
  const value = this.attribute(name);
382
386
  const valueWas = this.attributeWas(name);
383
- return !util.isDeepStrictEqual(value, valueWas);
387
+ return !deepEqual(value, valueWas);
384
388
  }
385
389
 
386
390
  /**
@@ -412,7 +416,7 @@ class Bone {
412
416
  if (this.#rawUnset.has(name) || this.#rawPrevious[name] === undefined || !this.hasAttribute(name)) return {};
413
417
  const value = this.attribute(name);
414
418
  const valueWas = this.#rawPrevious[name] == null ? null : this.#rawPrevious[name];
415
- if (util.isDeepStrictEqual(value, valueWas)) return {};
419
+ if (deepEqual(value, valueWas)) return {};
416
420
  return { [name]: [ valueWas, value ] };
417
421
  }
418
422
  const result = {};
@@ -420,7 +424,7 @@ class Bone {
420
424
  if (this.#rawUnset.has(attrKey) || this.#rawPrevious[attrKey] === undefined) continue;
421
425
  const value = this.attribute(attrKey);
422
426
  const valueWas = this.#rawPrevious[attrKey] == null ? null : this.#rawPrevious[attrKey];
423
- if (!util.isDeepStrictEqual(value, valueWas)) result[attrKey] = [ valueWas, value ];
427
+ if (!deepEqual(value, valueWas)) result[attrKey] = [ valueWas, value ];
424
428
  }
425
429
  return result;
426
430
  }
@@ -439,7 +443,7 @@ class Bone {
439
443
  if (this.#rawUnset.has(name) || !this.hasAttribute(name)) return {};
440
444
  const value = this.attribute(name);
441
445
  const valueWas = this.attributeWas(name);
442
- if (util.isDeepStrictEqual(value, valueWas)) return {};
446
+ if (deepEqual(value, valueWas)) return {};
443
447
  return { [name]: [ valueWas, value ] };
444
448
  }
445
449
  const result = {};
@@ -448,7 +452,7 @@ class Bone {
448
452
  const value = this.attribute(attrKey);
449
453
  const valueWas = this.attributeWas(attrKey);
450
454
 
451
- if (!util.isDeepStrictEqual(value, valueWas)) {
455
+ if (!deepEqual(value, valueWas)) {
452
456
  result[attrKey] = [ valueWas, value ];
453
457
  }
454
458
  }
@@ -996,7 +1000,7 @@ class Bone {
996
1000
  const attribute = new Attribute(name, attributes[name], options.define);
997
1001
  attributeMap[attribute.columnName] = attribute;
998
1002
  attributes[name] = attribute;
999
- if (TIMESTAMP_NAMES.includes(name)) {
1003
+ if (TIMESTAMP_ATTRIBUTE_NAMES.includes(name)) {
1000
1004
  const { columnName } = attribute;
1001
1005
  const legacyColumnName = LEGACY_TIMESTAMP_COLUMN_MAP[columnName];
1002
1006
  if (!columnMap[columnName] && legacyColumnName && columnMap[legacyColumnName]) {
@@ -1560,9 +1564,7 @@ class Bone {
1560
1564
  }
1561
1565
 
1562
1566
  static query(spell) {
1563
- const { sql, values } = this.driver.format(spell);
1564
- const query = { sql, nestTables: spell.command === 'select' };
1565
- return this.driver.query(query, values, spell);
1567
+ return this.driver.cast(spell);
1566
1568
  }
1567
1569
 
1568
1570
  static async transaction(callback) {
@@ -1713,6 +1715,4 @@ for (const getter of Spell_getters) {
1713
1715
  });
1714
1716
  }
1715
1717
 
1716
- Object.assign(Bone, { DataTypes });
1717
-
1718
1718
  module.exports = Bone;
package/src/constants.js CHANGED
@@ -20,6 +20,12 @@ const LEGACY_TIMESTAMP_COLUMN_MAP = {
20
20
  deleted_at: 'gmt_deleted',
21
21
  };
22
22
 
23
+ const TIMESTAMP_ATTRIBUTE_NAMES = [
24
+ 'createdAt', 'updatedAt', 'deletedAt',
25
+ 'gmtCreate', 'gmtModified', 'gmtDeleted',
26
+ 'created_at', 'updated_at', 'deleted_at',
27
+ 'gmt_create', 'gmt_modified', 'gmt_deleted',
28
+ ];
23
29
  const TIMESTAMP_NAMES = [ 'createdAt', 'updatedAt', 'deletedAt' ];
24
30
 
25
31
  const ASSOCIATE_METADATA_MAP = {
@@ -33,5 +39,6 @@ module.exports = {
33
39
  LEGACY_TIMESTAMP_MAP,
34
40
  TIMESTAMP_NAMES,
35
41
  LEGACY_TIMESTAMP_COLUMN_MAP,
36
- ASSOCIATE_METADATA_MAP
42
+ ASSOCIATE_METADATA_MAP,
43
+ TIMESTAMP_ATTRIBUTE_NAMES
37
44
  };
package/src/data_types.js CHANGED
@@ -110,20 +110,20 @@ class DataType {
110
110
  * STRING
111
111
  * STRING(127)
112
112
  * STRING.BINARY
113
- * @param {number} length
113
+ * @param {number} dataLength
114
114
  */
115
115
  class STRING extends DataType {
116
- constructor(length = 255) {
116
+ constructor(dataLength = 255) {
117
117
  super();
118
118
  this.dataType = 'varchar';
119
- this.length = length;
119
+ this.dataLength = dataLength;
120
120
  }
121
121
 
122
122
  toSqlString() {
123
- const { length } = this;
123
+ const { dataLength } = this;
124
124
  const dataType = this.dataType.toUpperCase();
125
125
  const chunks = [];
126
- chunks.push(length > 0 ? `${dataType}(${length})` : dataType);
126
+ chunks.push(dataLength > 0 ? `${dataType}(${dataLength})` : dataType);
127
127
  return chunks.join(' ');
128
128
  }
129
129
 
@@ -134,17 +134,17 @@ class STRING extends DataType {
134
134
  }
135
135
 
136
136
  class BINARY extends DataType {
137
- constructor(length = 255) {
137
+ constructor(dataLength = 255) {
138
138
  super();
139
- this.length = length;
139
+ this.dataLength = dataLength;
140
140
  this.dataType = 'binary';
141
141
  }
142
142
 
143
143
  toSqlString() {
144
- const { length } = this;
144
+ const { dataLength } = this;
145
145
  const dataType = this.dataType.toUpperCase();
146
146
  const chunks = [];
147
- chunks.push(length > 0 ? `${dataType}(${length})` : dataType);
147
+ chunks.push(dataLength > 0 ? `${dataType}(${dataLength})` : dataType);
148
148
  return chunks.join(' ');
149
149
  }
150
150
 
@@ -156,8 +156,8 @@ class BINARY extends DataType {
156
156
  }
157
157
 
158
158
  class VARBINARY extends BINARY {
159
- constructor(length) {
160
- super(length);
159
+ constructor(dataLength) {
160
+ super(dataLength);
161
161
  this.dataType = 'varbinary';
162
162
  }
163
163
  }
@@ -170,12 +170,12 @@ class VARBINARY extends BINARY {
170
170
  * INTEGER.UNSIGNED
171
171
  * INTEGER.UNSIGNED.ZEROFILL
172
172
  * INTEGER(10)
173
- * @param {number} length
173
+ * @param {number} dataLength
174
174
  */
175
175
  class INTEGER extends DataType {
176
- constructor(length) {
176
+ constructor(dataLength) {
177
177
  super();
178
- this.length = length;
178
+ this.dataLength = dataLength;
179
179
  this.dataType = 'integer';
180
180
  }
181
181
 
@@ -190,10 +190,10 @@ class INTEGER extends DataType {
190
190
  }
191
191
 
192
192
  toSqlString() {
193
- const { length, unsigned, zerofill } = this;
193
+ const { dataLength, unsigned, zerofill } = this;
194
194
  const dataType = this.dataType.toUpperCase();
195
195
  const chunks = [];
196
- chunks.push(length > 0 ? `${dataType}(${length})` : dataType);
196
+ chunks.push(dataLength > 0 ? `${dataType}(${dataLength})` : dataType);
197
197
  if (unsigned) chunks.push('UNSIGNED');
198
198
  if (zerofill) chunks.push('ZEROFILL');
199
199
  return chunks.join(' ');
@@ -222,11 +222,11 @@ class INTEGER extends DataType {
222
222
  * TINYINT
223
223
  * TINYINT.UNSIGNED
224
224
  * TINYINT(1)
225
- * @param {number} length
225
+ * @param {number} dataLength
226
226
  */
227
227
  class TINYINT extends INTEGER {
228
- constructor(length) {
229
- super(length);
228
+ constructor(dataLength) {
229
+ super(dataLength);
230
230
  this.dataType = 'tinyint';
231
231
  }
232
232
  }
@@ -237,11 +237,11 @@ class TINYINT extends INTEGER {
237
237
  * SMALLINT
238
238
  * SMALLINT.UNSIGNED
239
239
  * SMALLINT(2)
240
- * @param {number} length
240
+ * @param {number} dataLength
241
241
  */
242
242
  class SMALLINT extends INTEGER {
243
- constructor(length) {
244
- super(length);
243
+ constructor(dataLength) {
244
+ super(dataLength);
245
245
  this.dataType = 'smallint';
246
246
  }
247
247
  }
@@ -252,11 +252,11 @@ class SMALLINT extends INTEGER {
252
252
  * MEDIUMINT
253
253
  * MEDIUMINT.UNSIGNED
254
254
  * MEDIUMINT(3)
255
- * @param {number} length
255
+ * @param {number} dataLength
256
256
  */
257
257
  class MEDIUMINT extends INTEGER {
258
- constructor(length) {
259
- super(length);
258
+ constructor(dataLength) {
259
+ super(dataLength);
260
260
  this.dataType = 'mediumint';
261
261
  }
262
262
  }
@@ -268,11 +268,11 @@ class MEDIUMINT extends INTEGER {
268
268
  * BIGINT
269
269
  * BIGINT.UNSIGNED
270
270
  * BIGINT(8)
271
- * @param {number} length
271
+ * @param {number} dataLength
272
272
  */
273
273
  class BIGINT extends INTEGER {
274
- constructor(length) {
275
- super(length);
274
+ constructor(dataLength) {
275
+ super(dataLength);
276
276
  this.dataType = 'bigint';
277
277
  }
278
278
  }
@@ -442,11 +442,11 @@ class TEXT extends DataType {
442
442
  }
443
443
  super();
444
444
  this.dataType = 'text';
445
- this.length = length;
445
+ this.dataLength = length;
446
446
  }
447
447
 
448
448
  toSqlString() {
449
- return [ this.length, this.dataType ].join('').toUpperCase();
449
+ return [ this.dataLength, this.dataType ].join('').toUpperCase();
450
450
  }
451
451
  }
452
452
 
@@ -457,11 +457,11 @@ class BLOB extends DataType {
457
457
  }
458
458
  super();
459
459
  this.dataType = 'blob';
460
- this.length = length;
460
+ this.dataLength = length;
461
461
  }
462
462
 
463
463
  toSqlString() {
464
- return [ this.length, this.dataType ].join('').toUpperCase();
464
+ return [ this.dataLength, this.dataType ].join('').toUpperCase();
465
465
  }
466
466
 
467
467
  cast(value) {
@@ -77,7 +77,7 @@ function createType(DataTypes, params) {
77
77
  case 'VARBINARY':
78
78
  case 'CHAR':
79
79
  case 'VARCHAR':
80
- return new DataType(type.length);
80
+ return new DataType(type.dataLength);
81
81
  default:
82
82
  return new DataType();
83
83
  }
@@ -1,6 +1,13 @@
1
1
  'use strict';
2
2
 
3
+ const SqlString = require('sqlstring');
4
+ const debug = require('debug')('leoric');
5
+
3
6
  const Logger = require('./logger');
7
+ const Attribute = require('./attribute');
8
+ const DataTypes = require('../../data_types');
9
+ const Spellbook = require('./spellbook');
10
+ const { heresql, camelCase } = require('../../utils/string');
4
11
 
5
12
  /**
6
13
  * Migration methods
@@ -8,11 +15,204 @@ const Logger = require('./logger');
8
15
  * - https://dev.mysql.com/doc/refman/8.0/en/alter-table.html
9
16
  */
10
17
 
11
- module.exports = class AbstractDriver {
18
+ class AbstractDriver {
19
+
20
+ // define static properties as this way IDE will prompt
21
+ static Spellbook = Spellbook;
22
+ static Attribute = Attribute;
23
+ static DataTypes = DataTypes;
24
+
12
25
  constructor(opts = {}) {
13
26
  const { logger } = opts;
14
27
  this.logger = logger instanceof Logger ? logger : new Logger(logger);
15
28
  this.idleTimeout = opts.idleTimeout || 60;
16
29
  this.options = opts;
30
+ this.Attribute = this.constructor.Attribute;
31
+ this.DataTypes = this.constructor.DataTypes;
32
+ this.spellbook = new this.constructor.Spellbook();
33
+ this.escape = SqlString.escape;
34
+ this.escapeId = SqlString.escapeId;
35
+ }
36
+
37
+ /**
38
+ * query with spell
39
+ * @param {Spell} spell
40
+ * @returns
41
+ */
42
+ async cast(spell) {
43
+ const { sql, values } = this.format(spell);
44
+ const query = { sql, nestTables: spell.command === 'select' };
45
+ return await this.query(query, values, spell);
46
+ }
47
+
48
+ /**
49
+ * raw query
50
+ * @param {object|string} query
51
+ * @param {object | array} values
52
+ * @param {object} opts
53
+ */
54
+ async query(query, values, opts) {
55
+ throw new Error('unimplemented!');
56
+ }
57
+
58
+ /**
59
+ * disconnect manually
60
+ * @param {Function} callback
61
+ */
62
+ async disconnect(callback) {
63
+ debug('[disconnect] called');
64
+ }
65
+
66
+ get dialect() {
67
+ return camelCase(this.constructor.name.replace('Driver', ''));
68
+ }
69
+
70
+ /**
71
+ * use spellbook to format spell
72
+ * @param {Spell} spell
73
+ * @returns
74
+ */
75
+ format(spell) {
76
+ return this.spellbook.format(spell);
77
+ }
78
+
79
+ async createTable(table, attributes) {
80
+ const { escapeId } = this;
81
+ const chunks = [ `CREATE TABLE ${escapeId(table)}` ];
82
+ const columns = Object.keys(attributes).map(name => {
83
+ const attribute = new this.Attribute(name, attributes[name]);
84
+ return attribute.toSqlString();
85
+ });
86
+ chunks.push(`(${columns.join(', ')})`);
87
+ await this.query(chunks.join(' '));
88
+ }
89
+
90
+ async alterTable(table, attributes) {
91
+ const { escapeId } = this;
92
+ const chunks = [ `ALTER TABLE ${escapeId(table)}` ];
93
+
94
+ const actions = Object.keys(attributes).map(name => {
95
+ const options = attributes[name];
96
+ // { [columnName]: { remove: true } }
97
+ if (options.remove) return `DROP COLUMN ${escapeId(name)}`;
98
+ const attribute = new this.Attribute(name, options);
99
+ return [
100
+ options.modify ? 'MODIFY COLUMN' : 'ADD COLUMN',
101
+ attribute.toSqlString(),
102
+ ].join(' ');
103
+ });
104
+ chunks.push(actions.join(', '));
105
+ await this.query(chunks.join(' '));
106
+ }
107
+
108
+ async describeTable(table) {
109
+ const { database } = this.options;
110
+ const schemaInfo = await this.querySchemaInfo(database, table);
111
+ return schemaInfo[table].reduce(function(result, column) {
112
+ result[column.columnName] = column;
113
+ return result;
114
+ }, {});
115
+ }
116
+
117
+ async addColumn(table, name, params) {
118
+ const { escapeId } = this;
119
+ const attribute = new this.Attribute(name, params);
120
+ const sql = heresql(`
121
+ ALTER TABLE ${escapeId(table)}
122
+ ADD COLUMN ${attribute.toSqlString()}
123
+ `);
124
+ await this.query(sql);
17
125
  }
126
+
127
+ async changeColumn(table, name, params) {
128
+ const { escapeId } = this;
129
+ const attribute = new this.Attribute(name, params);
130
+ const sql = heresql(`
131
+ ALTER TABLE ${escapeId(table)}
132
+ MODIFY COLUMN ${attribute.toSqlString()}
133
+ `);
134
+ await this.query(sql);
135
+ }
136
+
137
+ async removeColumn(table, name) {
138
+ const { escapeId } = this;
139
+ const { columnName } = new this.Attribute(name);
140
+ const sql = heresql(`
141
+ ALTER TABLE ${escapeId(table)} DROP COLUMN ${escapeId(columnName)}
142
+ `);
143
+ await this.query(sql);
144
+ }
145
+
146
+ async renameColumn(table, name, newName) {
147
+ const { escapeId } = this;
148
+ const { columnName } = new this.Attribute(name);
149
+ const attribute = new this.Attribute(newName);
150
+ const sql = heresql(`
151
+ ALTER TABLE ${escapeId(table)}
152
+ RENAME COLUMN ${escapeId(columnName)} TO ${escapeId(attribute.columnName)}
153
+ `);
154
+ await this.query(sql);
155
+ }
156
+
157
+ async renameTable(table, newTable) {
158
+ const { escapeId } = this;
159
+ const sql = heresql(`
160
+ ALTER TABLE ${escapeId(table)} RENAME TO ${escapeId(newTable)}
161
+ `);
162
+ await this.query(sql);
163
+ }
164
+
165
+ async dropTable(table) {
166
+ const { escapeId } = this;
167
+ await this.query(`DROP TABLE IF EXISTS ${escapeId(table)}`);
168
+ }
169
+
170
+ async truncateTable(table) {
171
+ const { escapeId } = this;
172
+ await this.query(`TRUNCATE TABLE ${escapeId(table)}`);
173
+ }
174
+
175
+ async addIndex(table, attributes, opts = {}) {
176
+ const { escapeId } = this;
177
+ const columns = attributes.map(name => new this.Attribute(name).columnName);
178
+ const type = opts.unique ? 'UNIQUE' : opts.type;
179
+ const prefix = type === 'UNIQUE' ? 'uk' : 'idx';
180
+ const { name } = {
181
+ name: [ prefix, table ].concat(columns).join('_'),
182
+ ...opts,
183
+ };
184
+
185
+ if (type != null && ![ 'UNIQUE', 'FULLTEXT', 'SPATIAL' ].includes(type)) {
186
+ throw new Error(`Unexpected index type: ${type}`);
187
+ }
188
+
189
+ const sql = heresql(`
190
+ CREATE ${type ? `${type} INDEX` : 'INDEX'} ${escapeId(name)}
191
+ ON ${escapeId(table)} (${columns.map(escapeId).join(', ')})
192
+ `);
193
+ await this.query(sql);
194
+ }
195
+
196
+ async removeIndex(table, attributes, opts = {}) {
197
+ const { escapeId } = this;
198
+ let name;
199
+ if (Array.isArray(attributes)) {
200
+ const columns = attributes.map(entry => new this.Attribute(entry).columnName);
201
+ const type = opts.unique ? 'UNIQUE' : opts.type;
202
+ const prefix = type === 'UNIQUE' ? 'uk' : 'idx';
203
+ name = [ prefix, table ].concat(columns).join('_');
204
+ } else if (typeof attributes === 'string') {
205
+ name = attributes;
206
+ } else {
207
+ throw new Error(`Unexpected index name: ${attributes}`);
208
+ }
209
+
210
+ const sql = this.type === 'mysql'
211
+ ? `DROP INDEX ${escapeId(name)} ON ${escapeId(table)}`
212
+ : `DROP INDEX IF EXISTS ${escapeId(name)}`;
213
+ await this.query(sql);
214
+ }
215
+
18
216
  };
217
+
218
+ module.exports = AbstractDriver;