leoric 2.3.0 → 2.3.1

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,16 @@
1
+ 2.3.1 / 2022-03-22
2
+ ==================
3
+
4
+ ## What's Changed
5
+ * fix: mysql2 Invalid Date compatible by @JimmyDaddy in https://github.com/cyjake/leoric/pull/291
6
+ * fix: order by raw in sequelize mode by @JimmyDaddy in https://github.com/cyjake/leoric/pull/292
7
+ * fix: bulk update query conditions duplicated in sequelize mode by @JimmyDaddy in https://github.com/cyjake/leoric/pull/293
8
+ * fix: bulk destroy query conditions duplicated in sequelize mode by @JimmyDaddy in https://github.com/cyjake/leoric/pull/295
9
+ * fix: drop column if not defined in attributes when alter table by @cyjake in https://github.com/cyjake/leoric/pull/296
10
+
11
+
12
+ **Full Changelog**: https://github.com/cyjake/leoric/compare/v2.3.0...v2.3.1
13
+
1
14
  2.3.0 / 2022-03-10
2
15
  ==================
3
16
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leoric",
3
- "version": "2.3.0",
3
+ "version": "2.3.1",
4
4
  "description": "JavaScript Object-relational mapping alchemy",
5
5
  "main": "index.js",
6
6
  "types": "types/index.d.ts",
@@ -2,6 +2,7 @@
2
2
 
3
3
  const { setupSingleHook } = require('../setup_hooks');
4
4
  const { compose, isPlainObject } = require('../utils');
5
+ const Raw = require('../raw');
5
6
 
6
7
  function translateOptions(spell, options) {
7
8
  const { attributes, where, group, order, offset, limit, include, having } = options;
@@ -18,7 +19,7 @@ function translateOptions(spell, options) {
18
19
  if (having) spell.$having(having);
19
20
 
20
21
  if (order) {
21
- if (typeof order === 'string') {
22
+ if (typeof order === 'string' || order instanceof Raw) {
22
23
  spell.$order(order);
23
24
  } else if (Array.isArray(order) && order.length) {
24
25
  if (order.some(item => Array.isArray(item))) {
@@ -317,10 +318,12 @@ module.exports = Bone => {
317
318
  }
318
319
 
319
320
  // proxy to class.destroy({ individualHooks=false }) see https://github.com/sequelize/sequelize/blob/4063c2ab627ad57919d5b45cc7755f077a69fa5e/lib/model.js#L2895 before(after)BulkDestroy
320
- static async bulkDestroy(options = {}) {
321
+ static bulkDestroy(options = {}) {
321
322
  const { where, force } = options;
322
323
  const spell = this._remove(where || {}, force, { ...options });
323
- translateOptions(spell, options);
324
+ const transOptions = { ...options };
325
+ delete transOptions.where;
326
+ translateOptions(spell, transOptions);
324
327
  return spell;
325
328
  }
326
329
 
@@ -496,7 +499,9 @@ module.exports = Bone => {
496
499
  const { where, paranoid = false, validate } = options;
497
500
  const whereConditions = where || {};
498
501
  const spell = super.update(whereConditions, values, { validate, hooks: false, ...options });
499
- translateOptions(spell, options);
502
+ const transOptions = { ...options };
503
+ delete transOptions.where;
504
+ translateOptions(spell, transOptions);
500
505
  if (!paranoid) return spell.unparanoid;
501
506
  return spell;
502
507
  }
package/src/bone.js CHANGED
@@ -37,10 +37,12 @@ function looseReadonly(props) {
37
37
 
38
38
  function compare(attributes, columnMap) {
39
39
  const diff = {};
40
+ const columnNames = new Set();
40
41
 
41
42
  for (const name in attributes) {
42
43
  const attribute = attributes[name];
43
44
  const { columnName } = attribute;
45
+ columnNames.add(columnName);
44
46
 
45
47
  if (!attribute.equals(columnMap[columnName])) {
46
48
  diff[name] = {
@@ -50,6 +52,12 @@ function compare(attributes, columnMap) {
50
52
  }
51
53
  }
52
54
 
55
+ for (const columnName in columnMap) {
56
+ if (!columnNames.has(columnName)) {
57
+ diff[columnName] = { remove: true };
58
+ }
59
+ }
60
+
53
61
  return diff;
54
62
  }
55
63
 
@@ -254,8 +262,8 @@ class Bone {
254
262
 
255
263
  /**
256
264
  * get actual update/insert columns to avoid empty insert or update
257
- * @param {Object} data
258
- * @returns
265
+ * @param {Object} data
266
+ * @returns
259
267
  */
260
268
  static _getColumns(data) {
261
269
  if (!Object.keys(data).length) return data;
@@ -573,7 +581,14 @@ class Bone {
573
581
  for (const name of Object.keys(attributes)) {
574
582
  const attribute = attributes[name];
575
583
  // Take advantage of uncast/cast to create new copy of value
576
- const value = attribute.uncast(this.#raw[name]);
584
+ let value;
585
+ try {
586
+ value = attribute.uncast(this.#raw[name]);
587
+ } catch (error) {
588
+ console.error(error);
589
+ // do not interrupt sync raw
590
+ value = this.#raw[name];
591
+ }
577
592
  if (this.#rawSaved[name] !== undefined) {
578
593
  this.#rawPrevious[name] = this.#rawSaved[name];
579
594
  } else if (this.#rawPrevious[name] === undefined && this.#raw[name] != null) {
package/src/data_types.js CHANGED
@@ -17,17 +17,20 @@ class DataType {
17
17
  const {
18
18
  STRING, TEXT,
19
19
  DATE, DATEONLY,
20
- TINYINT, SMALLINT, MEDIUMINT, INTEGER, BIGINT,
20
+ TINYINT, SMALLINT, MEDIUMINT, INTEGER, BIGINT, DECIMAL,
21
21
  BOOLEAN,
22
22
  BINARY, VARBINARY, BLOB,
23
23
  } = this;
24
- const [ , dataType, appendix ] = columnType.match(/(\w+)(?:\((\d+)\))?/);
25
- const length = appendix && parseInt(appendix, 10);
24
+ const [ , dataType, ...matches ] = columnType.match(/(\w+)(?:\((\d+)(?:,(\d+))?\))?/);
25
+ const params = [];
26
+ for (let i = 0; i < matches.length; i++) {
27
+ if (matches[i] != null) params[i] = parseInt(matches[i], 10);
28
+ }
26
29
 
27
30
  switch (dataType) {
28
31
  case 'varchar':
29
32
  case 'char':
30
- return new STRING(length);
33
+ return new STRING(...params);
31
34
  // longtext is only for MySQL
32
35
  case 'longtext':
33
36
  return new TEXT('long');
@@ -40,30 +43,31 @@ class DataType {
40
43
  case 'datetime':
41
44
  case 'timestamp':
42
45
  // new DATE(precision)
43
- return new DATE(length);
46
+ return new DATE(...params);
44
47
  case 'decimal':
48
+ return new DECIMAL(...params);
45
49
  case 'int':
46
50
  case 'integer':
47
51
  case 'numeric':
48
- return new INTEGER(length);
52
+ return new INTEGER(...params);
49
53
  case 'mediumint':
50
- return new MEDIUMINT(length);
54
+ return new MEDIUMINT(...params);
51
55
  case 'smallint':
52
- return new SMALLINT(length);
56
+ return new SMALLINT(...params);
53
57
  case 'tinyint':
54
- return new TINYINT(length);
58
+ return new TINYINT(...params);
55
59
  case 'bigint':
56
- return new BIGINT(length);
60
+ return new BIGINT(...params);
57
61
  case 'boolean':
58
62
  return new BOOLEAN();
59
63
  // mysql only
60
64
  case 'binary':
61
65
  // postgres only
62
66
  case 'bytea':
63
- return new BINARY(length);
67
+ return new BINARY(...params);
64
68
  // mysql only
65
69
  case 'varbinary':
66
- return new VARBINARY(length);
70
+ return new VARBINARY(...params);
67
71
  case 'longblob':
68
72
  return new BLOB('long');
69
73
  case 'mediumblob':
@@ -273,6 +277,41 @@ class BIGINT extends INTEGER {
273
277
  }
274
278
  }
275
279
 
280
+ /**
281
+ * fixed-point decimal types
282
+ * @example
283
+ * DECIMAL
284
+ * DECIMAL.UNSIGNED
285
+ * DECIMAL(5, 2)
286
+ * @param {number} precision
287
+ * @param {number} scale
288
+ * - https://dev.mysql.com/doc/refman/8.0/en/fixed-point-types.html
289
+ */
290
+ class DECIMAL extends INTEGER {
291
+ constructor(precision, scale) {
292
+ super();
293
+ this.dataType = 'decimal';
294
+ this.precision = precision;
295
+ this.scale = scale;
296
+ }
297
+
298
+ toSqlString() {
299
+ const { precision, scale, unsigned, zerofill } = this;
300
+ const dataType = this.dataType.toUpperCase();
301
+ const chunks = [];
302
+ if (precision > 0 && scale >= 0) {
303
+ chunks.push(`${dataType}(${precision},${scale})`);
304
+ } else if (precision > 0) {
305
+ chunks.push(`${dataType}(${precision})`);
306
+ } else {
307
+ chunks.push(dataType);
308
+ }
309
+ if (unsigned) chunks.push('UNSIGNED');
310
+ if (zerofill) chunks.push('ZEROFILL');
311
+ return chunks.join(' ');
312
+ }
313
+ }
314
+
276
315
  const rDateFormat = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:[,.]\d{3,6})$/;
277
316
 
278
317
  class DATE extends DataType {
@@ -494,6 +533,7 @@ const DataTypes = {
494
533
  MEDIUMINT,
495
534
  INTEGER,
496
535
  BIGINT,
536
+ DECIMAL,
497
537
  DATE,
498
538
  DATEONLY,
499
539
  BOOLEAN,
@@ -66,6 +66,8 @@ function createType(DataTypes, params) {
66
66
  switch (type.constructor.name) {
67
67
  case 'DATE':
68
68
  return new DataType(type.precision, type.timezone);
69
+ case 'DECIMAL':
70
+ return new DataType(type.precision, type.scale);
69
71
  case 'TINYINT':
70
72
  case 'SMALLINT':
71
73
  case 'MEDIUMINT':
@@ -73,6 +75,8 @@ function createType(DataTypes, params) {
73
75
  case 'BIGINT':
74
76
  case 'BINARY':
75
77
  case 'VARBINARY':
78
+ case 'CHAR':
79
+ case 'VARCHAR':
76
80
  return new DataType(type.length);
77
81
  default:
78
82
  return new DataType();
@@ -19,9 +19,12 @@ module.exports = {
19
19
  const chunks = [ `ALTER TABLE ${escapeId(table)}` ];
20
20
 
21
21
  const actions = Object.keys(attributes).map(name => {
22
- const attribute = new Attribute(name, attributes[name]);
22
+ const options = attributes[name];
23
+ // { [columnName]: { remove: true } }
24
+ if (options.remove) return `DROP COLUMN ${escapeId(name)}`;
25
+ const attribute = new Attribute(name, options);
23
26
  return [
24
- attribute.modify ? 'MODIFY COLUMN' : 'ADD COLUMN',
27
+ options.modify ? 'MODIFY COLUMN' : 'ADD COLUMN',
25
28
  attribute.toSqlString(),
26
29
  ].join(' ');
27
30
  });
@@ -15,8 +15,13 @@ class MysqlAttribute extends Attribute {
15
15
  }
16
16
 
17
17
  toSqlString() {
18
- const { allowNull, defaultValue, primaryKey } = this;
19
- const { columnName, type, columnType } = this;
18
+ const {
19
+ columnName,
20
+ type, columnType,
21
+ allowNull, defaultValue,
22
+ primaryKey,
23
+ comment,
24
+ } = this;
20
25
 
21
26
  const chunks = [
22
27
  escapeId(columnName),
@@ -33,6 +38,10 @@ class MysqlAttribute extends Attribute {
33
38
  chunks.push(`DEFAULT ${escape(defaultValue)}`);
34
39
  }
35
40
 
41
+ if (typeof comment === 'string') {
42
+ chunks.push(`COMMENT ${escape(comment)}`);
43
+ }
44
+
36
45
  return chunks.join(' ');
37
46
  }
38
47
  }
@@ -38,7 +38,7 @@ module.exports = {
38
38
  columns.push({
39
39
  columnName: row.column_name,
40
40
  columnType: row.column_type,
41
- columnComment: row.column_comment,
41
+ comment: row.column_comment,
42
42
  defaultValue: row.column_default,
43
43
  dataType: row.data_type,
44
44
  allowNull: row.is_nullable === 'YES',
@@ -28,6 +28,10 @@ function formatAddColumn(driver, columnName, attribute) {
28
28
  return `ADD COLUMN ${attribute.toSqlString()}`;
29
29
  }
30
30
 
31
+ function formatDropColumn(driver, columnName) {
32
+ return `DROP COLUMN ${driver.escapeId(columnName)}`;
33
+ }
34
+
31
35
  module.exports = {
32
36
  ...schema,
33
37
 
@@ -85,7 +89,9 @@ module.exports = {
85
89
  const { escapeId } = this;
86
90
  const chunks = [ `ALTER TABLE ${escapeId(table)}` ];
87
91
  const actions = Object.keys(changes).map(name => {
88
- const attribute = new Attribute(name, changes[name]);
92
+ const options = changes[name];
93
+ if (options.remove) return formatDropColumn(this, name);
94
+ const attribute = new Attribute(name, options);
89
95
  const { columnName } = attribute;;
90
96
  return attribute.modify
91
97
  ? formatAlterColumns(this, columnName, attribute).join(', ')
@@ -108,7 +108,8 @@ module.exports = {
108
108
  for (let i = 0; i < tables.length; i++) {
109
109
  const table = tables[i];
110
110
  const { rows } = results[i];
111
- const columns = rows.map(({ name, type, notnull, dflt_value, pk }) => {
111
+ const columns = rows.map(row => {
112
+ const { name, type, notnull, dflt_value, pk } = row;
112
113
  const columnType = type.toLowerCase();
113
114
  const [, dataType, precision ] = columnType.match(rColumnType);
114
115
  const primaryKey = pk === 1;
@@ -145,6 +146,8 @@ module.exports = {
145
146
  const { escapeId } = this;
146
147
  const chunks = [ `ALTER TABLE ${escapeId(table)}` ];
147
148
  const attributes = Object.keys(changes).map(name => {
149
+ const options = changes[name];
150
+ if (options.remove) return { columnName: name, remove: true };
148
151
  return new Attribute(name, changes[name]);
149
152
  });
150
153
 
@@ -157,7 +160,12 @@ module.exports = {
157
160
  // SQLite can only add one column a time
158
161
  // - https://www.sqlite.org/lang_altertable.html
159
162
  for (const attribute of attributes) {
160
- await this.query(chunks.concat(`ADD COLUMN ${attribute.toSqlString()}`).join(' '));
163
+ if (attribute.remove) {
164
+ const { columnName } = attribute;
165
+ await this.query(chunks.concat(`DROP COLUMN ${this.escapeId(columnName)}`).join(' '));
166
+ } else {
167
+ await this.query(chunks.concat(`ADD COLUMN ${attribute.toSqlString()}`).join(' '));
168
+ }
161
169
  }
162
170
  },
163
171
 
@@ -11,6 +11,7 @@ export default class DataType {
11
11
  static STRING: typeof STRING & INVOKABLE<STRING>;
12
12
  static INTEGER: typeof INTEGER & INVOKABLE<INTEGER>;
13
13
  static BIGINT: typeof BIGINT & INVOKABLE<BIGINT>;
14
+ static DECIMAL: typeof DECIMAL & INVOKABLE<DECIMAL>;
14
15
  static TEXT: typeof TEXT & INVOKABLE<TEXT>;
15
16
  static BLOB: typeof BLOB & INVOKABLE<BLOB>;
16
17
  static JSON: typeof JSON & INVOKABLE<JSON>;
@@ -31,7 +32,7 @@ declare class STRING extends DataType {
31
32
  }
32
33
 
33
34
  declare class INTEGER extends DataType {
34
- dataType: 'integer' | 'bigint';
35
+ dataType: 'integer' | 'bigint' | 'decimal';
35
36
  length: number;
36
37
  constructor(length: number);
37
38
  get UNSIGNED(): this;
@@ -42,6 +43,13 @@ declare class BIGINT extends INTEGER {
42
43
  dataType: 'bigint';
43
44
  }
44
45
 
46
+ declare class DECIMAL extends INTEGER {
47
+ dataType: 'decimal';
48
+ precision: number;
49
+ scale: number;
50
+ constructor(precision: number, scale: number);
51
+ }
52
+
45
53
  declare class TEXT extends DataType {
46
54
  dataType: 'text';
47
55
  length: LENGTH_VARIANTS;