leoric 1.15.0 → 2.0.2
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 +53 -0
- package/package.json +1 -1
- package/src/adapters/sequelize.js +3 -3
- package/src/bone.js +77 -58
- package/src/collection.js +11 -2
- package/src/constants.js +30 -0
- package/src/data_types.js +47 -24
- package/src/drivers/abstract/attribute.js +3 -3
- package/src/drivers/abstract/spellbook.js +16 -11
- package/src/drivers/mysql/index.js +1 -1
- package/src/drivers/mysql/schema.js +3 -1
- package/src/drivers/mysql/spellbook.js +17 -0
- package/src/drivers/postgres/attribute.js +0 -1
- package/src/drivers/postgres/data_types.js +26 -0
- package/src/drivers/postgres/index.js +1 -1
- package/src/drivers/postgres/schema.js +3 -0
- package/src/drivers/sqlite/data_types.js +42 -4
- package/src/drivers/sqlite/index.js +1 -1
- package/src/drivers/sqlite/schema.js +6 -1
- package/src/expr_formatter.js +1 -1
- package/src/query_object.js +2 -1
- package/src/raw.js +9 -0
- package/src/realm.js +5 -13
- package/src/spell.js +10 -22
- package/types/index.d.ts +6 -6
package/History.md
CHANGED
|
@@ -1,3 +1,56 @@
|
|
|
1
|
+
2.0.2 / 2021-02-10
|
|
2
|
+
==================
|
|
3
|
+
|
|
4
|
+
## What's Changed
|
|
5
|
+
* fix: order by alias should not throw by @cyjake in https://github.com/cyjake/leoric/pull/255
|
|
6
|
+
* fix: fix #257 DataType.uncast should skip Raw type at type checking by @JimmyDaddy in https://github.com/cyjake/leoric/pull/258
|
|
7
|
+
* docs: async function in transaction by @cyjake in https://github.com/cyjake/leoric/pull/259
|
|
8
|
+
* fix: fixed #256 static create instance should check all default attri… by @JimmyDaddy in https://github.com/cyjake/leoric/pull/262
|
|
9
|
+
* fix: fix #260 UPDATE with LIMIT and ORDER should be formatted(mysql only) by @JimmyDaddy in https://github.com/cyjake/leoric/pull/261
|
|
10
|
+
* refactor: keep the UPDATE ... ORDER BY ... LIMIT to mysql driver by @cyjake in https://github.com/cyjake/leoric/pull/264
|
|
11
|
+
* fix: fix #263 upsert attributes should use defaultValue while there i… by @JimmyDaddy in https://github.com/cyjake/leoric/pull/265
|
|
12
|
+
* fix: fix restore Error `Undefined attribute "deletedAt"` by @JimmyDaddy in https://github.com/cyjake/leoric/pull/267
|
|
13
|
+
* fix: type checking adaption by @JimmyDaddy in https://github.com/cyjake/leoric/pull/266
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
**Full Changelog**: https://github.com/cyjake/leoric/compare/v2.0.1...v2.0.2
|
|
17
|
+
|
|
18
|
+
2.0.1 / 2022-01-05
|
|
19
|
+
==================
|
|
20
|
+
|
|
21
|
+
## What's Changed
|
|
22
|
+
* fix: format numeric result by @JimmyDaddy in https://github.com/cyjake/leoric/pull/253
|
|
23
|
+
* fix: should still return number if value is '0.000' by @cyjake in https://github.com/cyjake/leoric/pull/254
|
|
24
|
+
|
|
25
|
+
**Full Changelog**: https://github.com/cyjake/leoric/compare/v1.15.1...v2.0.1
|
|
26
|
+
|
|
27
|
+
2.0.0 / 2021-12-28
|
|
28
|
+
==================
|
|
29
|
+
|
|
30
|
+
## What's Changed
|
|
31
|
+
* breaking: model.sync add force/alter option by @SmartOrange in https://github.com/cyjake/leoric/pull/224
|
|
32
|
+
* breaking: logQueryError(err, sql, duration, options) by @cyjake in https://github.com/cyjake/leoric/pull/237
|
|
33
|
+
* test: add utf8mb4 test cases by @fengmk2 in https://github.com/cyjake/leoric/pull/239
|
|
34
|
+
* Merge 1.x changes by @cyjake in https://github.com/cyjake/leoric/pull/249
|
|
35
|
+
|
|
36
|
+
## New Contributors
|
|
37
|
+
* @SmartOrange made their first contribution in https://github.com/cyjake/leoric/pull/222
|
|
38
|
+
|
|
39
|
+
**Full Changelog**: https://github.com/cyjake/leoric/compare/v1.15.1...v2.0.0
|
|
40
|
+
|
|
41
|
+
1.15.1 / 2021-12-28
|
|
42
|
+
===================
|
|
43
|
+
|
|
44
|
+
## What's Changed
|
|
45
|
+
* fix: fix #242 date string format by @JimmyDaddy in https://github.com/cyjake/leoric/pull/243
|
|
46
|
+
* fix: update with empty conditions by @JimmyDaddy in https://github.com/cyjake/leoric/pull/241
|
|
47
|
+
* fix: silent option's priority should be lower than valueSet by @JimmyDaddy in https://github.com/cyjake/leoric/pull/244
|
|
48
|
+
* fix: information_schema.columns.datetime_precision by @cyjake in https://github.com/cyjake/leoric/pull/246
|
|
49
|
+
* fix: should not hoist subquery if query is ordered by external columns by @cyjake in https://github.com/cyjake/leoric/pull/247
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
**Full Changelog**: https://github.com/cyjake/leoric/compare/v1.15.0...v1.15.1
|
|
53
|
+
|
|
1
54
|
1.15.0 / 2021-11-22
|
|
2
55
|
===================
|
|
3
56
|
|
package/package.json
CHANGED
|
@@ -238,9 +238,6 @@ module.exports = Bone => {
|
|
|
238
238
|
}
|
|
239
239
|
|
|
240
240
|
static build(values, options = {}) {
|
|
241
|
-
if (options.validate !== false) {
|
|
242
|
-
this._validateAttributes(values);
|
|
243
|
-
}
|
|
244
241
|
const { raw } = Object.assign({ raw: false, isNewRecord: true }, options);
|
|
245
242
|
const { attributes } = this;
|
|
246
243
|
|
|
@@ -256,6 +253,7 @@ module.exports = Bone => {
|
|
|
256
253
|
} else {
|
|
257
254
|
instance = new this(values, options);
|
|
258
255
|
}
|
|
256
|
+
|
|
259
257
|
return instance;
|
|
260
258
|
}
|
|
261
259
|
|
|
@@ -469,6 +467,7 @@ module.exports = Bone => {
|
|
|
469
467
|
const { where, paranoid, individualHooks } = options;
|
|
470
468
|
if (individualHooks) {
|
|
471
469
|
let findSpell = this._find(where, options);
|
|
470
|
+
translateOptions(findSpell, options);
|
|
472
471
|
if (paranoid === false) findSpell = findSpell.unparanoid;
|
|
473
472
|
const instances = await findSpell;
|
|
474
473
|
if (instances.length) {
|
|
@@ -484,6 +483,7 @@ module.exports = Bone => {
|
|
|
484
483
|
const { where, paranoid = false, validate } = options;
|
|
485
484
|
const whereConditions = where || {};
|
|
486
485
|
const spell = super.update(whereConditions, values, { validate, hooks: false, ...options });
|
|
486
|
+
translateOptions(spell, options);
|
|
487
487
|
if (!paranoid) return spell.unparanoid;
|
|
488
488
|
return spell;
|
|
489
489
|
}
|
package/src/bone.js
CHANGED
|
@@ -11,9 +11,11 @@ const { executeValidator, LeoricValidateError } = require('./validator');
|
|
|
11
11
|
const DataTypes = require('./data_types');
|
|
12
12
|
const Collection = require('./collection');
|
|
13
13
|
const Spell = require('./spell');
|
|
14
|
+
const Raw = require('./raw');
|
|
14
15
|
const { capitalize, camelCase, snakeCase } = require('./utils/string');
|
|
15
16
|
const { hookNames, setupSingleHook } = require('./setup_hooks');
|
|
16
17
|
const { logger } = require('./utils/index');
|
|
18
|
+
const { TIMESTAMP_NAMES, LEGACY_TIMESTAMP_COLUMN_MAP } = require('./constants');
|
|
17
19
|
|
|
18
20
|
function looseReadonly(props) {
|
|
19
21
|
return Object.keys(props).reduce((result, name) => {
|
|
@@ -27,12 +29,8 @@ function looseReadonly(props) {
|
|
|
27
29
|
}, {});
|
|
28
30
|
}
|
|
29
31
|
|
|
30
|
-
function compare(attributes,
|
|
32
|
+
function compare(attributes, columnMap) {
|
|
31
33
|
const diff = {};
|
|
32
|
-
const columnMap = columns.reduce((result, entry) => {
|
|
33
|
-
result[entry.columnName] = entry;
|
|
34
|
-
return result;
|
|
35
|
-
}, {});
|
|
36
34
|
|
|
37
35
|
for (const name in attributes) {
|
|
38
36
|
const attribute = attributes[name];
|
|
@@ -71,7 +69,7 @@ function copyValues(values) {
|
|
|
71
69
|
for (const key in values) {
|
|
72
70
|
if (Object.hasOwnProperty.call(values, key)) {
|
|
73
71
|
const v = values[key];
|
|
74
|
-
if (v && (v
|
|
72
|
+
if (v && ((v instanceof Raw) || v.__expr || (v instanceof Spell))) continue;
|
|
75
73
|
copyValue[key] = v;
|
|
76
74
|
}
|
|
77
75
|
}
|
|
@@ -79,6 +77,23 @@ function copyValues(values) {
|
|
|
79
77
|
return copyValue;
|
|
80
78
|
}
|
|
81
79
|
|
|
80
|
+
function valuesValidate(values, attributes, ctx) {
|
|
81
|
+
for (const valueKey in values) {
|
|
82
|
+
const attribute = attributes[valueKey];
|
|
83
|
+
if (!attribute) continue;
|
|
84
|
+
const { validate = {}, name, allowNull, defaultValue } = attribute;
|
|
85
|
+
const value = values[valueKey];
|
|
86
|
+
if (value == null && defaultValue == null) {
|
|
87
|
+
if (allowNull === false) throw new LeoricValidateError('notNull', name);
|
|
88
|
+
if ((allowNull === true || allowNull === undefined) && validate.notNull === undefined ) continue;
|
|
89
|
+
}
|
|
90
|
+
if (!validate) continue;
|
|
91
|
+
for (const key in validate) {
|
|
92
|
+
if (validate.hasOwnProperty(key)) executeValidator(ctx, key, attribute, value);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
82
97
|
/**
|
|
83
98
|
* The base class that provides Object-relational mapping. This class is never intended to be used directly. We need to create models that extends from Bone. Most of the query features of Bone is implemented by {@link Spell} such as {@link Spell#$group} and {@link Spell#$join}. With Bone, you can create models like this:
|
|
84
99
|
*
|
|
@@ -162,14 +177,15 @@ class Bone {
|
|
|
162
177
|
attribute(...args) {
|
|
163
178
|
const [ name, value ] = args;
|
|
164
179
|
const { attributes } = this.constructor;
|
|
180
|
+
const attribute = attributes[name];
|
|
165
181
|
|
|
166
|
-
if (!
|
|
182
|
+
if (!attribute) {
|
|
167
183
|
throw new Error(`${this.constructor.name} has no attribute "${name}"`);
|
|
168
184
|
}
|
|
169
185
|
|
|
170
186
|
if (args.length > 1) {
|
|
171
187
|
// execute validators
|
|
172
|
-
this.#raw[name] = value;
|
|
188
|
+
this.#raw[name] = value instanceof Raw ? value : attribute.cast(value);
|
|
173
189
|
this.#rawUnset.delete(name);
|
|
174
190
|
return this;
|
|
175
191
|
}
|
|
@@ -282,21 +298,7 @@ class Bone {
|
|
|
282
298
|
|
|
283
299
|
// merge all changed values
|
|
284
300
|
changedValues = Object.assign(changedValues, values);
|
|
285
|
-
|
|
286
|
-
for (const valueKey in changedValues) {
|
|
287
|
-
const attribute = attributes[valueKey];
|
|
288
|
-
if (!attribute) continue;
|
|
289
|
-
const { validate = {}, name, allowNull, defaultValue } = attribute;
|
|
290
|
-
const value = changedValues[valueKey];
|
|
291
|
-
if (value == null && defaultValue == null) {
|
|
292
|
-
if (allowNull === false) throw new LeoricValidateError('notNull', name);
|
|
293
|
-
if ((allowNull === true || allowNull === undefined) && validate.notNull === undefined ) return;
|
|
294
|
-
}
|
|
295
|
-
if (!validate) return;
|
|
296
|
-
for (const key in validate) {
|
|
297
|
-
if (validate.hasOwnProperty(key)) executeValidator(this, key, attribute, value);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
301
|
+
valuesValidate(changedValues, attributes, this);
|
|
300
302
|
}
|
|
301
303
|
|
|
302
304
|
/**
|
|
@@ -308,22 +310,7 @@ class Bone {
|
|
|
308
310
|
*/
|
|
309
311
|
static _validateAttributes(values = {}) {
|
|
310
312
|
const { attributes } = this;
|
|
311
|
-
|
|
312
|
-
const attribute = attributes[valueKey];
|
|
313
|
-
// If valueKey is not an attribute of the Model, go to the next loop instead of throw 'No Such Attribute' Error,
|
|
314
|
-
// in case it is a custom property of the Model which defined by custom setters/getters.
|
|
315
|
-
if (!attribute) return;
|
|
316
|
-
const { validate = {}, name, allowNull, defaultValue } = attribute;
|
|
317
|
-
const value = values[valueKey];
|
|
318
|
-
if (value == null && defaultValue == null) {
|
|
319
|
-
if (allowNull === false) throw new LeoricValidateError('notNull', name);
|
|
320
|
-
if ((allowNull === true || allowNull === undefined) && validate.notNull === undefined) return;
|
|
321
|
-
}
|
|
322
|
-
if (!validate) return;
|
|
323
|
-
for (const key in validate) {
|
|
324
|
-
if (validate.hasOwnProperty(key)) executeValidator(this, key, attribute, value);
|
|
325
|
-
}
|
|
326
|
-
}
|
|
313
|
+
valuesValidate(values, attributes, this);
|
|
327
314
|
}
|
|
328
315
|
|
|
329
316
|
/**
|
|
@@ -682,7 +669,7 @@ class Bone {
|
|
|
682
669
|
if (shardingKey) where[shardingKey] = this[shardingKey];
|
|
683
670
|
|
|
684
671
|
const { updatedAt, deletedAt } = Model.timestamps;
|
|
685
|
-
if (attributes[updatedAt] && !changes[updatedAt] && !changes[deletedAt]) {
|
|
672
|
+
if (attributes[updatedAt] && !changes[updatedAt] && !changes[deletedAt] && !options.silent) {
|
|
686
673
|
changes[updatedAt] = new Date();
|
|
687
674
|
}
|
|
688
675
|
if (options.validate !== false ) {
|
|
@@ -727,6 +714,7 @@ class Bone {
|
|
|
727
714
|
this[updatedAt] = this[createdAt];
|
|
728
715
|
}
|
|
729
716
|
|
|
717
|
+
const validateValues = {};
|
|
730
718
|
for (const name in attributes) {
|
|
731
719
|
const value = this.attribute(name);
|
|
732
720
|
const { defaultValue } = attributes[name];
|
|
@@ -735,10 +723,12 @@ class Bone {
|
|
|
735
723
|
} else if (value === undefined && defaultValue != null) {
|
|
736
724
|
data[name] = defaultValue;
|
|
737
725
|
}
|
|
726
|
+
if (attributes[name].primaryKey) continue;
|
|
727
|
+
validateValues[name] = data[name];
|
|
738
728
|
}
|
|
739
729
|
|
|
740
730
|
if (opts.validate !== false) {
|
|
741
|
-
this._validateAttributes();
|
|
731
|
+
this._validateAttributes(validateValues);
|
|
742
732
|
}
|
|
743
733
|
|
|
744
734
|
const spell = new Spell(Model, opts).$insert(data);
|
|
@@ -824,10 +814,10 @@ class Bone {
|
|
|
824
814
|
|
|
825
815
|
const conditions = {
|
|
826
816
|
[primaryKey]: this[primaryKey],
|
|
827
|
-
deletedAt: { $ne: null },
|
|
817
|
+
[deletedAt]: { $ne: null },
|
|
828
818
|
};
|
|
829
819
|
if (shardingKey) conditions[shardingKey] = this[shardingKey];
|
|
830
|
-
await this.update({ deletedAt: null }, { ...opts, paranoid: false });
|
|
820
|
+
await this.update({ [deletedAt]: null }, { ...opts, paranoid: false });
|
|
831
821
|
return this;
|
|
832
822
|
}
|
|
833
823
|
|
|
@@ -842,7 +832,7 @@ class Bone {
|
|
|
842
832
|
if (deletedAt == null) {
|
|
843
833
|
throw new Error('Model is not paranoid');
|
|
844
834
|
}
|
|
845
|
-
return Bone.update.call(this, conditions, { deletedAt: null }, { ...opts, paranoid: false });
|
|
835
|
+
return Bone.update.call(this, conditions, { [deletedAt]: null }, { ...opts, paranoid: false });
|
|
846
836
|
}
|
|
847
837
|
|
|
848
838
|
/**
|
|
@@ -857,9 +847,13 @@ class Bone {
|
|
|
857
847
|
const data = {};
|
|
858
848
|
const Model = this;
|
|
859
849
|
const { attributes } = Model;
|
|
860
|
-
for (const
|
|
861
|
-
|
|
862
|
-
|
|
850
|
+
for (const key in attributes) {
|
|
851
|
+
const attribute = attributes[key];
|
|
852
|
+
if (attribute.primaryKey) continue;
|
|
853
|
+
if (values[key] == null && attribute.defaultValue != null) {
|
|
854
|
+
data[key] = attribute.defaultValue;
|
|
855
|
+
} else if (values[key] !== undefined){
|
|
856
|
+
data[key] = values[key];
|
|
863
857
|
}
|
|
864
858
|
}
|
|
865
859
|
|
|
@@ -933,20 +927,38 @@ class Bone {
|
|
|
933
927
|
};
|
|
934
928
|
}
|
|
935
929
|
|
|
930
|
+
const columnMap = columns.reduce((result, entry) => {
|
|
931
|
+
result[entry.columnName] = entry;
|
|
932
|
+
return result;
|
|
933
|
+
}, {});
|
|
934
|
+
|
|
936
935
|
for (const name of Object.keys(attributes)) {
|
|
937
936
|
const attribute = new Attribute(name, attributes[name], options.define);
|
|
938
937
|
attributeMap[attribute.columnName] = attribute;
|
|
939
938
|
attributes[name] = attribute;
|
|
939
|
+
if (TIMESTAMP_NAMES.includes(name)) {
|
|
940
|
+
const { columnName } = attribute;
|
|
941
|
+
const legacyColumnName = LEGACY_TIMESTAMP_COLUMN_MAP[columnName];
|
|
942
|
+
if (!columnMap[columnName] && legacyColumnName && columnMap[legacyColumnName]) {
|
|
943
|
+
// correct columname
|
|
944
|
+
attribute.columnName = legacyColumnName;
|
|
945
|
+
attributeMap[attribute.columnName] = attribute;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
const columnInfo = columnMap[attribute.columnName];
|
|
949
|
+
// if datetime or timestamp precision not defined, default to column info
|
|
950
|
+
if (columnInfo && attribute.type instanceof DataTypes.DATE && attribute.type.precision == null) {
|
|
951
|
+
attribute.type.precision = columnInfo.datetimePrecision;
|
|
952
|
+
}
|
|
940
953
|
}
|
|
941
954
|
|
|
942
955
|
const primaryKey = Object.keys(attributes).find(key => attributes[key].primaryKey);
|
|
943
956
|
const timestamps = {};
|
|
944
|
-
for (const key of
|
|
957
|
+
for (const key of TIMESTAMP_NAMES) {
|
|
945
958
|
const name = attributes.hasOwnProperty(key) ? key : snakeCase(key);
|
|
946
959
|
const attribute = attributes[name];
|
|
947
960
|
|
|
948
|
-
if (
|
|
949
|
-
if (columns.some(column => column.columnName === attribute.columnName)) {
|
|
961
|
+
if (attribute && columnMap[attribute.columnName]) {
|
|
950
962
|
timestamps[key] = name;
|
|
951
963
|
}
|
|
952
964
|
}
|
|
@@ -960,7 +972,7 @@ class Bone {
|
|
|
960
972
|
attributeMap,
|
|
961
973
|
associations,
|
|
962
974
|
tableAlias,
|
|
963
|
-
synchronized: Object.keys(compare(attributes,
|
|
975
|
+
synchronized: Object.keys(compare(attributes, columnMap)).length === 0,
|
|
964
976
|
}));
|
|
965
977
|
|
|
966
978
|
for (const hookName of hookNames) {
|
|
@@ -1311,13 +1323,9 @@ class Bone {
|
|
|
1311
1323
|
static create(values, opts = {}) {
|
|
1312
1324
|
const data = Object.assign({}, values);
|
|
1313
1325
|
const instance = new this(data);
|
|
1314
|
-
if (opts.validate !== false) {
|
|
1315
|
-
instance._validateAttributes(data); // call instance._validateAttributes manually to validate the raw value
|
|
1316
|
-
}
|
|
1317
1326
|
// static create proxy to instance.create
|
|
1318
1327
|
return instance.create({
|
|
1319
1328
|
...opts,
|
|
1320
|
-
validate: false, // should not validate again
|
|
1321
1329
|
});
|
|
1322
1330
|
}
|
|
1323
1331
|
|
|
@@ -1416,7 +1424,7 @@ class Bone {
|
|
|
1416
1424
|
// values should be immutable
|
|
1417
1425
|
const data = Object.assign({}, values);
|
|
1418
1426
|
const { updatedAt, deletedAt } = this.timestamps;
|
|
1419
|
-
if (attributes[updatedAt] && !data[updatedAt] && !data[deletedAt]) {
|
|
1427
|
+
if (attributes[updatedAt] && !data[updatedAt] && !data[deletedAt] && !options.silent) {
|
|
1420
1428
|
data[updatedAt] = new Date();
|
|
1421
1429
|
}
|
|
1422
1430
|
|
|
@@ -1537,7 +1545,7 @@ class Bone {
|
|
|
1537
1545
|
Object.defineProperties(this, looseReadonly({ ...hookMethods, attributes, table }));
|
|
1538
1546
|
}
|
|
1539
1547
|
|
|
1540
|
-
static async sync() {
|
|
1548
|
+
static async sync({ force = false, alter = false } = {}) {
|
|
1541
1549
|
const { driver, physicTable: table } = this;
|
|
1542
1550
|
const { database } = this.options;
|
|
1543
1551
|
|
|
@@ -1554,11 +1562,22 @@ class Bone {
|
|
|
1554
1562
|
}
|
|
1555
1563
|
|
|
1556
1564
|
const { attributes, columns } = this;
|
|
1565
|
+
const columnMap = columns.reduce((result, entry) => {
|
|
1566
|
+
result[entry.columnName] = entry;
|
|
1567
|
+
return result;
|
|
1568
|
+
}, {});
|
|
1557
1569
|
|
|
1558
1570
|
if (columns.length === 0) {
|
|
1559
1571
|
await driver.createTable(table, attributes);
|
|
1560
1572
|
} else {
|
|
1561
|
-
|
|
1573
|
+
if (force) {
|
|
1574
|
+
await driver.dropTable(table);
|
|
1575
|
+
await driver.createTable(table, attributes);
|
|
1576
|
+
} else if (alter){
|
|
1577
|
+
await driver.alterTable(table, compare(attributes, columnMap));
|
|
1578
|
+
} else {
|
|
1579
|
+
console.warn('[synchronize_fail] %s couldn\'t be synchronized, please use force or alter to specify execution', this.name);
|
|
1580
|
+
}
|
|
1562
1581
|
}
|
|
1563
1582
|
|
|
1564
1583
|
const schemaInfo = await driver.querySchemaInfo(database, table);
|
package/src/collection.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { AGGREGATOR_MAP } = require('./constants');
|
|
4
|
+
|
|
5
|
+
const AGGREGATORS = Object.values(AGGREGATOR_MAP);
|
|
3
6
|
/**
|
|
4
7
|
* An extended Array to represent collections of models.
|
|
5
8
|
*/
|
|
@@ -87,8 +90,14 @@ function dispatch(spell, rows, fields) {
|
|
|
87
90
|
const { type, value, args } = columns[0];
|
|
88
91
|
if (type === 'alias' && args && args[0].type === 'func') {
|
|
89
92
|
const row = rows[0];
|
|
90
|
-
const
|
|
91
|
-
|
|
93
|
+
const record = row && (row[''] || row[table]);
|
|
94
|
+
const result = record && record[value];
|
|
95
|
+
// see https://www.w3schools.com/mysql/mysql_ref_functions.asp
|
|
96
|
+
if (AGGREGATORS.includes(args[0].name)) {
|
|
97
|
+
const num = Number(result);
|
|
98
|
+
return isNaN(num) ? result : num;
|
|
99
|
+
}
|
|
100
|
+
return result;
|
|
92
101
|
}
|
|
93
102
|
}
|
|
94
103
|
|
package/src/constants.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const AGGREGATOR_MAP = {
|
|
4
|
+
count: 'count',
|
|
5
|
+
average: 'avg',
|
|
6
|
+
minimum: 'min',
|
|
7
|
+
maximum: 'max',
|
|
8
|
+
sum: 'sum'
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const LEGACY_TIMESTAMP_MAP = {
|
|
12
|
+
gmtCreate: 'createdAt',
|
|
13
|
+
gmtModified: 'updatedAt',
|
|
14
|
+
gmtDeleted: 'deletedAt',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const LEGACY_TIMESTAMP_COLUMN_MAP = {
|
|
18
|
+
created_at: 'gmt_create',
|
|
19
|
+
updated_at: 'gmt_modified',
|
|
20
|
+
deleted_at: 'gmt_deleted',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const TIMESTAMP_NAMES = [ 'createdAt', 'updatedAt', 'deletedAt' ];
|
|
24
|
+
|
|
25
|
+
module.exports = {
|
|
26
|
+
AGGREGATOR_MAP,
|
|
27
|
+
LEGACY_TIMESTAMP_MAP,
|
|
28
|
+
TIMESTAMP_NAMES,
|
|
29
|
+
LEGACY_TIMESTAMP_COLUMN_MAP
|
|
30
|
+
};
|
package/src/data_types.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const util = require('util');
|
|
4
4
|
const invokable = require('./utils/invokable');
|
|
5
|
+
const Raw = require('./raw');
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* @example
|
|
@@ -114,7 +115,7 @@ class STRING extends DataType {
|
|
|
114
115
|
}
|
|
115
116
|
|
|
116
117
|
uncast(value) {
|
|
117
|
-
if (value == null) return value;
|
|
118
|
+
if (value == null || value instanceof Raw) return value;
|
|
118
119
|
return '' + value;
|
|
119
120
|
}
|
|
120
121
|
}
|
|
@@ -186,15 +187,18 @@ class INTEGER extends DataType {
|
|
|
186
187
|
}
|
|
187
188
|
|
|
188
189
|
cast(value) {
|
|
189
|
-
if (value == null) return value;
|
|
190
|
+
if (value == null || isNaN(value)) return value;
|
|
190
191
|
return Number(value);
|
|
191
192
|
}
|
|
192
193
|
|
|
193
|
-
uncast(value) {
|
|
194
|
+
uncast(value, strict = true) {
|
|
194
195
|
const originValue = value;
|
|
195
|
-
if (value == null) return value;
|
|
196
|
+
if (value == null || value instanceof Raw) return value;
|
|
196
197
|
if (typeof value === 'string') value = parseInt(value, 10);
|
|
197
|
-
if (isNaN(value))
|
|
198
|
+
if (isNaN(value)) {
|
|
199
|
+
if (strict) throw new Error(util.format('invalid integer: %s', originValue));
|
|
200
|
+
return originValue;
|
|
201
|
+
}
|
|
198
202
|
return value;
|
|
199
203
|
}
|
|
200
204
|
}
|
|
@@ -214,7 +218,7 @@ class BIGINT extends INTEGER {
|
|
|
214
218
|
}
|
|
215
219
|
}
|
|
216
220
|
|
|
217
|
-
const rDateFormat = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:[,.]\d{3,6})
|
|
221
|
+
const rDateFormat = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:[,.]\d{3,6})$/;
|
|
218
222
|
|
|
219
223
|
class DATE extends DataType {
|
|
220
224
|
constructor(precision, timezone = true) {
|
|
@@ -229,20 +233,31 @@ class DATE extends DataType {
|
|
|
229
233
|
toSqlString() {
|
|
230
234
|
const { precision } = this;
|
|
231
235
|
const dataType = this.dataType.toUpperCase();
|
|
232
|
-
if (precision
|
|
236
|
+
if (precision != null && precision >= 0) return `${dataType}(${precision})`;
|
|
233
237
|
return dataType;
|
|
234
238
|
}
|
|
235
239
|
|
|
240
|
+
_round(value) {
|
|
241
|
+
const { precision } = this;
|
|
242
|
+
if (precision != null && precision < 3 && value instanceof Date) {
|
|
243
|
+
const divider = 10 ** (3 - precision);
|
|
244
|
+
return new Date(Math.round(value.getTime() / divider) * divider);
|
|
245
|
+
}
|
|
246
|
+
return value;
|
|
247
|
+
}
|
|
248
|
+
|
|
236
249
|
cast(value) {
|
|
250
|
+
const original = value;
|
|
237
251
|
if (value == null) return value;
|
|
238
|
-
if (value instanceof Date)
|
|
239
|
-
|
|
252
|
+
if (!(value instanceof Date)) value = new Date(value);
|
|
253
|
+
if (isNaN(value.getTime())) return original;
|
|
254
|
+
return this._round(value);
|
|
240
255
|
}
|
|
241
256
|
|
|
242
257
|
uncast(value) {
|
|
243
258
|
const originValue = value;
|
|
244
259
|
|
|
245
|
-
if (value == null) return value;
|
|
260
|
+
if (value == null || value instanceof Raw) return value;
|
|
246
261
|
if (typeof value.toDate === 'function') {
|
|
247
262
|
value = value.toDate();
|
|
248
263
|
}
|
|
@@ -258,13 +273,7 @@ class DATE extends DataType {
|
|
|
258
273
|
if (!(value instanceof Date)) value = new Date(value);
|
|
259
274
|
if (isNaN(value)) throw new Error(util.format('invalid date: %s', originValue));
|
|
260
275
|
|
|
261
|
-
|
|
262
|
-
if (precision < 3) {
|
|
263
|
-
const result = new Date(value);
|
|
264
|
-
result.setMilliseconds(result.getMilliseconds() % (10 ** precision));
|
|
265
|
-
return result;
|
|
266
|
-
}
|
|
267
|
-
return value;
|
|
276
|
+
return this._round(value);
|
|
268
277
|
}
|
|
269
278
|
}
|
|
270
279
|
|
|
@@ -278,16 +287,25 @@ class DATEONLY extends DataType {
|
|
|
278
287
|
return this.dataType.toUpperCase();
|
|
279
288
|
}
|
|
280
289
|
|
|
290
|
+
_round(value) {
|
|
291
|
+
if (value instanceof Date) {
|
|
292
|
+
return new Date(value.getFullYear(), value.getMonth(), value.getDate());
|
|
293
|
+
}
|
|
294
|
+
return value;
|
|
295
|
+
}
|
|
296
|
+
|
|
281
297
|
cast(value) {
|
|
298
|
+
const original = value;
|
|
282
299
|
if (value == null) return value;
|
|
283
|
-
if (value instanceof Date)
|
|
284
|
-
|
|
300
|
+
if (!(value instanceof Date)) value = new Date(value);
|
|
301
|
+
if (isNaN(value.getTime())) return original;
|
|
302
|
+
return this._round(value);
|
|
285
303
|
}
|
|
286
304
|
|
|
287
305
|
uncast(value) {
|
|
288
306
|
const originValue = value;
|
|
289
307
|
|
|
290
|
-
if (value == null) return value;
|
|
308
|
+
if (value == null || value instanceof Raw) return value;
|
|
291
309
|
if (typeof value.toDate === 'function') {
|
|
292
310
|
value = value.toDate();
|
|
293
311
|
}
|
|
@@ -299,9 +317,9 @@ class DATEONLY extends DataType {
|
|
|
299
317
|
}
|
|
300
318
|
|
|
301
319
|
if (!(value instanceof Date)) value = new Date(value);
|
|
302
|
-
if (isNaN(value)) throw new Error(util.format('invalid date: %s', originValue))
|
|
320
|
+
if (isNaN(value)) throw new Error(util.format('invalid date: %s', originValue));
|
|
303
321
|
|
|
304
|
-
return
|
|
322
|
+
return this._round(value);
|
|
305
323
|
}
|
|
306
324
|
}
|
|
307
325
|
|
|
@@ -374,11 +392,16 @@ class JSON extends DataType {
|
|
|
374
392
|
if (!value) return value;
|
|
375
393
|
// type === JSONB
|
|
376
394
|
if (typeof value === 'object') return value;
|
|
377
|
-
|
|
395
|
+
try {
|
|
396
|
+
return global.JSON.parse(value);
|
|
397
|
+
} catch (err) {
|
|
398
|
+
console.error(new Error(`unable to cast ${value} to JSON`));
|
|
399
|
+
return value;
|
|
400
|
+
}
|
|
378
401
|
}
|
|
379
402
|
|
|
380
403
|
uncast(value) {
|
|
381
|
-
if (value == null) return value;
|
|
404
|
+
if (value == null || value instanceof Raw) return value;
|
|
382
405
|
return global.JSON.stringify(value);
|
|
383
406
|
}
|
|
384
407
|
}
|
|
@@ -126,11 +126,11 @@ class Attribute {
|
|
|
126
126
|
return this.type.cast(value);
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
uncast(value) {
|
|
129
|
+
uncast(value, strict = true) {
|
|
130
130
|
if (Array.isArray(value) && this.jsType !== JSON) {
|
|
131
|
-
return value.map(entry => this.type.uncast(entry));
|
|
131
|
+
return value.map(entry => this.type.uncast(entry, strict));
|
|
132
132
|
}
|
|
133
|
-
return this.type.uncast(value);
|
|
133
|
+
return this.type.uncast(value, strict);
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
|
|
@@ -4,6 +4,7 @@ const SqlString = require('sqlstring');
|
|
|
4
4
|
|
|
5
5
|
const { copyExpr, findExpr, walkExpr } = require('../../expr');
|
|
6
6
|
const { formatExpr, formatConditions, collectLiteral } = require('../../expr_formatter');
|
|
7
|
+
const Raw = require('../../raw');
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Format orders into ORDER BY clause in SQL
|
|
@@ -128,7 +129,7 @@ function createSubspell(spell) {
|
|
|
128
129
|
subspell.orders = [];
|
|
129
130
|
for (const order of orders) {
|
|
130
131
|
const [token, direction] = order;
|
|
131
|
-
const { type, qualifiers, value } = token;
|
|
132
|
+
const { type, qualifiers = [], value } = token;
|
|
132
133
|
if (type == 'id' && qualifiers[0] == baseName) {
|
|
133
134
|
subspell.orders.push([{ type, value }, direction]);
|
|
134
135
|
}
|
|
@@ -219,13 +220,13 @@ function formatSelectWithJoin(spell) {
|
|
|
219
220
|
|
|
220
221
|
let hoistable = skip > 0 || rowCount > 0;
|
|
221
222
|
if (hoistable) {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
}
|
|
227
|
-
});
|
|
223
|
+
function checkQualifier({ type, qualifiers = [] }) {
|
|
224
|
+
if (type === 'id' && qualifiers.length> 0 && !qualifiers.includes(baseName)) {
|
|
225
|
+
hoistable = false;
|
|
226
|
+
}
|
|
228
227
|
}
|
|
228
|
+
for (const condition of whereConditions) walkExpr(condition, checkQualifier);
|
|
229
|
+
for (const orderExpr of orders) walkExpr(orderExpr[0], checkQualifier);
|
|
229
230
|
}
|
|
230
231
|
|
|
231
232
|
if (hoistable) {
|
|
@@ -386,7 +387,7 @@ function formatInsert(spell) {
|
|
|
386
387
|
const value = sets[name];
|
|
387
388
|
// upsert should not update createdAt
|
|
388
389
|
columns.push(Model.unalias(name));
|
|
389
|
-
if (value
|
|
390
|
+
if (value instanceof Raw) {
|
|
390
391
|
values.push(SqlString.raw(value.value));
|
|
391
392
|
} else {
|
|
392
393
|
values.push(value);
|
|
@@ -449,7 +450,7 @@ function formatUpdate(spell) {
|
|
|
449
450
|
if (value && value.__expr) {
|
|
450
451
|
assigns.push(`${escapeId(Model.unalias(name))} = ${formatExpr(spell, value)}`);
|
|
451
452
|
collectLiteral(spell, value, values);
|
|
452
|
-
} else if (value
|
|
453
|
+
} else if (value instanceof Raw) {
|
|
453
454
|
assigns.push(`${escapeId(Model.unalias(name))} = ${value.value}`);
|
|
454
455
|
} else {
|
|
455
456
|
assigns.push(`${escapeId(Model.unalias(name))} = ?`);
|
|
@@ -457,7 +458,6 @@ function formatUpdate(spell) {
|
|
|
457
458
|
}
|
|
458
459
|
}
|
|
459
460
|
|
|
460
|
-
for (const condition of whereConditions) collectLiteral(spell, condition, values);
|
|
461
461
|
// see https://dev.mysql.com/doc/refman/8.0/en/optimizer-hints.html
|
|
462
462
|
const hintStr = this.formatOptimizerHints(spell);
|
|
463
463
|
// see https://dev.mysql.com/doc/refman/8.0/en/index-hints.html
|
|
@@ -472,7 +472,11 @@ function formatUpdate(spell) {
|
|
|
472
472
|
}
|
|
473
473
|
|
|
474
474
|
chunks.push(`SET ${assigns.join(', ')}`);
|
|
475
|
-
|
|
475
|
+
if (whereConditions.length > 0) {
|
|
476
|
+
for (const condition of whereConditions) collectLiteral(spell, condition, values);
|
|
477
|
+
chunks.push(`WHERE ${formatConditions(spell, whereConditions)}`);
|
|
478
|
+
}
|
|
479
|
+
|
|
476
480
|
return {
|
|
477
481
|
sql: chunks.join(' '),
|
|
478
482
|
values,
|
|
@@ -603,4 +607,5 @@ module.exports = {
|
|
|
603
607
|
formatSelectWithoutJoin,
|
|
604
608
|
formatUpdateOnDuplicate,
|
|
605
609
|
formatReturning,
|
|
610
|
+
formatOrders
|
|
606
611
|
};
|
|
@@ -93,7 +93,7 @@ class MysqlDriver extends AbstractDriver {
|
|
|
93
93
|
try {
|
|
94
94
|
result = await promise;
|
|
95
95
|
} catch (err) {
|
|
96
|
-
logger.logQueryError(
|
|
96
|
+
logger.logQueryError(err, sql, calculateDuration(start), opts);
|
|
97
97
|
throw err;
|
|
98
98
|
} finally {
|
|
99
99
|
if (!opts.connection) connection.release();
|
|
@@ -17,7 +17,8 @@ module.exports = {
|
|
|
17
17
|
tables = [].concat(tables);
|
|
18
18
|
const sql = heresql(`
|
|
19
19
|
SELECT table_name, column_name, column_type, data_type, is_nullable,
|
|
20
|
-
column_default, column_key, column_comment
|
|
20
|
+
column_default, column_key, column_comment,
|
|
21
|
+
datetime_precision
|
|
21
22
|
FROM information_schema.columns
|
|
22
23
|
WHERE table_schema = ? AND table_name in (?)
|
|
23
24
|
ORDER BY table_name, column_name
|
|
@@ -43,6 +44,7 @@ module.exports = {
|
|
|
43
44
|
allowNull: row.is_nullable === 'YES',
|
|
44
45
|
primaryKey: row.column_key == 'PRI',
|
|
45
46
|
unique: row.column_key == 'PRI' || row.column_key == 'UNI',
|
|
47
|
+
datetimePrecision: row.datetime_precision,
|
|
46
48
|
});
|
|
47
49
|
}
|
|
48
50
|
|
|
@@ -58,4 +58,21 @@ module.exports = {
|
|
|
58
58
|
formatReturning() {
|
|
59
59
|
return '';
|
|
60
60
|
},
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* UPDATE ... ORDER BY ... LIMIT ${rowCount}
|
|
64
|
+
* - https://dev.mysql.com/doc/refman/8.0/en/update.html
|
|
65
|
+
* @param {Spell} spell
|
|
66
|
+
*/
|
|
67
|
+
formatUpdate(spell) {
|
|
68
|
+
const result = spellbook.formatUpdate.call(this, spell);
|
|
69
|
+
const { rowCount, orders } = spell;
|
|
70
|
+
const chunks = [];
|
|
71
|
+
|
|
72
|
+
if (orders.length > 0) chunks.push(`ORDER BY ${this.formatOrders(spell, orders).join(', ')}`);
|
|
73
|
+
if (rowCount > 0) chunks.push(`LIMIT ${rowCount}`);
|
|
74
|
+
if (chunks.length > 0) result.sql += ` ${chunks.join(' ')}`;
|
|
75
|
+
|
|
76
|
+
return result;
|
|
77
|
+
},
|
|
61
78
|
};
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const DataTypes = require('../../data_types');
|
|
4
|
+
const util = require('util');
|
|
5
|
+
const Raw = require('../../raw');
|
|
6
|
+
|
|
4
7
|
|
|
5
8
|
class Postgres_DATE extends DataTypes.DATE {
|
|
6
9
|
constructor(precision, timezone = true) {
|
|
@@ -37,12 +40,35 @@ class Postgres_BINARY extends DataTypes {
|
|
|
37
40
|
}
|
|
38
41
|
}
|
|
39
42
|
|
|
43
|
+
class Postgres_INTEGER extends DataTypes.INTEGER {
|
|
44
|
+
constructor(length) {
|
|
45
|
+
super(length);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
uncast(value) {
|
|
49
|
+
const originValue = value;
|
|
50
|
+
if (value == null || value instanceof Raw) return value;
|
|
51
|
+
if (typeof value === 'string') value = parseInt(value, 10);
|
|
52
|
+
if (isNaN(value)) throw new Error(util.format('invalid integer: %s', originValue));
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
class Postgres_BIGINT extends Postgres_INTEGER {
|
|
58
|
+
constructor() {
|
|
59
|
+
super();
|
|
60
|
+
this.dataType = 'bigint';
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
40
64
|
class Postgres_DataTypes extends DataTypes {
|
|
41
65
|
static DATE = Postgres_DATE;
|
|
42
66
|
static JSONB = Postgres_JSONB;
|
|
43
67
|
static BINARY = Postgres_BINARY;
|
|
44
68
|
static VARBINARY = Postgres_BINARY;
|
|
45
69
|
static BLOB = Postgres_BINARY;
|
|
70
|
+
static INTEGER = Postgres_INTEGER;
|
|
71
|
+
static BIGINT = Postgres_BIGINT;
|
|
46
72
|
}
|
|
47
73
|
|
|
48
74
|
module.exports = Postgres_DataTypes;
|
|
@@ -137,7 +137,7 @@ class PostgresDriver extends AbstractDriver {
|
|
|
137
137
|
try {
|
|
138
138
|
result = await connection.query(...args);
|
|
139
139
|
} catch (err) {
|
|
140
|
-
logger.logQueryError(
|
|
140
|
+
logger.logQueryError(err, formatted, calculateDuration(start), spell);
|
|
141
141
|
throw err;
|
|
142
142
|
} finally {
|
|
143
143
|
if (!spell.connection) connection.release();
|
|
@@ -58,6 +58,8 @@ module.exports = {
|
|
|
58
58
|
if (dataType === 'character varying') dataType = 'varchar';
|
|
59
59
|
if (dataType === 'timestamp without time zone') dataType = 'timestamp';
|
|
60
60
|
const primaryKey = row.constraint_type === 'PRIMARY KEY';
|
|
61
|
+
const precision = row.datetime_precision;
|
|
62
|
+
|
|
61
63
|
columns.push({
|
|
62
64
|
columnName: row.column_name,
|
|
63
65
|
columnType: length > 0 ? `${dataType}(${length})` : dataType,
|
|
@@ -67,6 +69,7 @@ module.exports = {
|
|
|
67
69
|
// https://www.postgresql.org/docs/9.5/infoschema-table-constraints.html
|
|
68
70
|
primaryKey,
|
|
69
71
|
unique: row.constraint_type === 'UNIQUE',
|
|
72
|
+
datetimePrecision: precision === 6 ? null : precision,
|
|
70
73
|
});
|
|
71
74
|
}
|
|
72
75
|
|
|
@@ -3,16 +3,46 @@
|
|
|
3
3
|
const DataTypes = require('../../data_types');
|
|
4
4
|
|
|
5
5
|
class Sqlite_DATE extends DataTypes.DATE {
|
|
6
|
-
constructor(precision) {
|
|
7
|
-
super(precision);
|
|
6
|
+
constructor(precision, timezone) {
|
|
7
|
+
super(precision, timezone);
|
|
8
8
|
this.dataType = 'datetime';
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
uncast(value) {
|
|
12
|
+
try {
|
|
13
|
+
return super.uncast(value);
|
|
14
|
+
} catch (error) {
|
|
15
|
+
console.error(new Error(`unable to cast ${value} to DATE`));
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
class Sqlite_DATEONLY extends DataTypes.DATEONLY {
|
|
22
|
+
constructor() {
|
|
23
|
+
super();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
uncast(value) {
|
|
27
|
+
try {
|
|
28
|
+
return super.uncast(value);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.error(new Error(`unable to cast ${value} to DATEONLY`));
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
13
33
|
}
|
|
14
34
|
}
|
|
15
35
|
|
|
36
|
+
class Sqlite_INTEGER extends DataTypes.INTEGER {
|
|
37
|
+
constructor(length) {
|
|
38
|
+
super(length);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
uncast(value) {
|
|
42
|
+
return super.uncast(value, false);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
}
|
|
16
46
|
class Sqlite_BIGINT extends DataTypes.BIGINT {
|
|
17
47
|
constructor() {
|
|
18
48
|
super();
|
|
@@ -62,6 +92,14 @@ class Sqlite_DataTypes extends DataTypes {
|
|
|
62
92
|
static get VARBINARY() {
|
|
63
93
|
return Sqlite_VARBINARY;
|
|
64
94
|
}
|
|
95
|
+
|
|
96
|
+
static get DATEONLY() {
|
|
97
|
+
return Sqlite_DATEONLY;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
static get INTEGER() {
|
|
101
|
+
return Sqlite_INTEGER;
|
|
102
|
+
}
|
|
65
103
|
}
|
|
66
104
|
|
|
67
105
|
module.exports = Sqlite_DataTypes;
|
|
@@ -48,7 +48,7 @@ class SqliteDriver extends AbstractDriver {
|
|
|
48
48
|
try {
|
|
49
49
|
result = await connection.query(query, values, opts);
|
|
50
50
|
} catch (err) {
|
|
51
|
-
logger.logQueryError(
|
|
51
|
+
logger.logQueryError(err, sql, calculateDuration(start), opts);
|
|
52
52
|
throw err;
|
|
53
53
|
} finally {
|
|
54
54
|
if (!opts.connection) connection.release();
|
|
@@ -101,19 +101,24 @@ module.exports = {
|
|
|
101
101
|
});
|
|
102
102
|
const results = await Promise.all(queries);
|
|
103
103
|
const schemaInfo = {};
|
|
104
|
+
const rColumnType = /^(\w+)(?:\(([^)]+)\))?/i;
|
|
105
|
+
const rDateType = /(?:date|datetime|timestamp)/i;
|
|
106
|
+
|
|
104
107
|
for (let i = 0; i < tables.length; i++) {
|
|
105
108
|
const table = tables[i];
|
|
106
109
|
const { rows } = results[i];
|
|
107
110
|
const columns = rows.map(({ name, type, notnull, dflt_value, pk }) => {
|
|
108
111
|
const columnType = type.toLowerCase();
|
|
112
|
+
const [, dataType, precision ] = columnType.match(rColumnType);
|
|
109
113
|
const primaryKey = pk === 1;
|
|
110
114
|
const result = {
|
|
111
115
|
columnName: name,
|
|
112
116
|
columnType,
|
|
113
117
|
defaultValue: parseDefaultValue(dflt_value),
|
|
114
|
-
dataType:
|
|
118
|
+
dataType: dataType,
|
|
115
119
|
allowNull: primaryKey ? false : notnull == 0,
|
|
116
120
|
primaryKey,
|
|
121
|
+
datetimePrecision: rDateType.test(dataType) ? parseInt(precision, 10) : null,
|
|
117
122
|
};
|
|
118
123
|
return result;
|
|
119
124
|
});
|
package/src/expr_formatter.js
CHANGED
|
@@ -208,7 +208,7 @@ function coerceLiteral(spell, ast) {
|
|
|
208
208
|
if (arg.type === 'literal') {
|
|
209
209
|
// { params: { $like: '%foo%' } }
|
|
210
210
|
if (attribute.jsType === JSON && typeof arg.value === 'string') continue;
|
|
211
|
-
arg.value = attribute.uncast(arg.value);
|
|
211
|
+
arg.value = attribute.uncast(arg.value, false);
|
|
212
212
|
}
|
|
213
213
|
}
|
|
214
214
|
}
|
package/src/query_object.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const util = require('util');
|
|
4
4
|
const { isPlainObject } = require('./utils');
|
|
5
5
|
const { parseExpr } = require('./expr');
|
|
6
|
+
const Raw = require('./raw');
|
|
6
7
|
// deferred to break cyclic dependencies
|
|
7
8
|
let Spell;
|
|
8
9
|
|
|
@@ -29,7 +30,7 @@ const OPERATOR_MAP = {
|
|
|
29
30
|
*/
|
|
30
31
|
function parseValue(value) {
|
|
31
32
|
if (value instanceof Spell) return { type: 'subquery', value };
|
|
32
|
-
if (value
|
|
33
|
+
if (value instanceof Raw) return { type: 'raw', value: value.value };
|
|
33
34
|
return parseExpr('?', value);
|
|
34
35
|
}
|
|
35
36
|
|
package/src/raw.js
ADDED
package/src/realm.js
CHANGED
|
@@ -7,6 +7,8 @@ const Bone = require('./bone');
|
|
|
7
7
|
const { findDriver } = require('./drivers');
|
|
8
8
|
const { camelCase } = require('./utils/string');
|
|
9
9
|
const sequelize = require('./adapters/sequelize');
|
|
10
|
+
const Raw = require('./raw');
|
|
11
|
+
const { LEGACY_TIMESTAMP_MAP } = require('./constants');
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
14
|
*
|
|
@@ -38,12 +40,6 @@ async function findModels(dir) {
|
|
|
38
40
|
return models;
|
|
39
41
|
}
|
|
40
42
|
|
|
41
|
-
const LEGACY_TIMESTAMP_MAP = {
|
|
42
|
-
gmtCreate: 'createdAt',
|
|
43
|
-
gmtModified: 'updatedAt',
|
|
44
|
-
gmtDeleted: 'deletedAt',
|
|
45
|
-
};
|
|
46
|
-
|
|
47
43
|
/**
|
|
48
44
|
* construct model attributes entirely from column definitions
|
|
49
45
|
* @param {Bone} model
|
|
@@ -163,12 +159,12 @@ class Realm {
|
|
|
163
159
|
return this.Bone;
|
|
164
160
|
}
|
|
165
161
|
|
|
166
|
-
async sync() {
|
|
162
|
+
async sync(options) {
|
|
167
163
|
if (!this.connected) await this.connect();
|
|
168
164
|
const { models } = this;
|
|
169
165
|
|
|
170
166
|
for (const model of Object.values(models)) {
|
|
171
|
-
await model.sync();
|
|
167
|
+
await model.sync(options);
|
|
172
168
|
}
|
|
173
169
|
}
|
|
174
170
|
|
|
@@ -239,11 +235,7 @@ class Realm {
|
|
|
239
235
|
if (typeof sql !== 'string') {
|
|
240
236
|
throw new TypeError('sql must be a string');
|
|
241
237
|
}
|
|
242
|
-
return
|
|
243
|
-
__raw: true,
|
|
244
|
-
value: sql,
|
|
245
|
-
type: 'raw',
|
|
246
|
-
};
|
|
238
|
+
return new Raw(sql);
|
|
247
239
|
}
|
|
248
240
|
|
|
249
241
|
// instance.raw
|
package/src/spell.js
CHANGED
|
@@ -10,6 +10,8 @@ const { parseExprList, parseExpr, walkExpr } = require('./expr');
|
|
|
10
10
|
const { isPlainObject } = require('./utils');
|
|
11
11
|
const { IndexHint, INDEX_HINT_TYPE, Hint } = require('./hint');
|
|
12
12
|
const { parseObject } = require('./query_object');
|
|
13
|
+
const Raw = require('./raw');
|
|
14
|
+
const { AGGREGATOR_MAP } = require('./constants');
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
17
|
* Parse condition expressions
|
|
@@ -21,7 +23,7 @@ const { parseObject } = require('./query_object');
|
|
|
21
23
|
* @returns {Array}
|
|
22
24
|
*/
|
|
23
25
|
function parseConditions(conditions, ...values) {
|
|
24
|
-
if (conditions
|
|
26
|
+
if (conditions instanceof Raw) return [ conditions ];
|
|
25
27
|
if (isPlainObject(conditions)) {
|
|
26
28
|
return parseObject(conditions);
|
|
27
29
|
} else if (typeof conditions == 'string') {
|
|
@@ -41,7 +43,7 @@ function parseSelect(spell, ...names) {
|
|
|
41
43
|
|
|
42
44
|
const columns = [];
|
|
43
45
|
for (const name of names) {
|
|
44
|
-
if (name
|
|
46
|
+
if (name instanceof Raw) columns.push(name);
|
|
45
47
|
else columns.push(...parseExprList(name));
|
|
46
48
|
}
|
|
47
49
|
|
|
@@ -68,8 +70,7 @@ function parseSelect(spell, ...names) {
|
|
|
68
70
|
* @returns {Object}
|
|
69
71
|
*/
|
|
70
72
|
function formatValueSet(spell, obj, strict = true) {
|
|
71
|
-
const { Model
|
|
72
|
-
const { timestamps } = Model;
|
|
73
|
+
const { Model } = spell;
|
|
73
74
|
const sets = {};
|
|
74
75
|
for (const name in obj) {
|
|
75
76
|
const attribute = Model.attributes[name];
|
|
@@ -79,12 +80,8 @@ function formatValueSet(spell, obj, strict = true) {
|
|
|
79
80
|
throw new Error(`Undefined attribute "${name}"`);
|
|
80
81
|
}
|
|
81
82
|
|
|
82
|
-
if (silent && timestamps.updatedAt && name === timestamps.updatedAt && command === 'update') {
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
83
|
// raw sql don't need to uncast
|
|
87
|
-
if (value
|
|
84
|
+
if (value instanceof Raw) {
|
|
88
85
|
sets[name] = value;
|
|
89
86
|
} else {
|
|
90
87
|
sets[name] = attribute.uncast(value);
|
|
@@ -402,7 +399,6 @@ class Spell {
|
|
|
402
399
|
hints: [...this.hints],
|
|
403
400
|
// used by transaction
|
|
404
401
|
connection: this.connection,
|
|
405
|
-
silent: this.silent,
|
|
406
402
|
});
|
|
407
403
|
}
|
|
408
404
|
|
|
@@ -530,8 +526,8 @@ class Spell {
|
|
|
530
526
|
}
|
|
531
527
|
|
|
532
528
|
$increment(name, by = 1, opts = {}) {
|
|
533
|
-
|
|
534
|
-
|
|
529
|
+
const { Model } = this;
|
|
530
|
+
const silent = opts.silent;
|
|
535
531
|
const { timestamps } = Model;
|
|
536
532
|
this.command = 'update';
|
|
537
533
|
if (!Number.isFinite(by)) throw new Error(`unexpected increment value ${by}`);
|
|
@@ -652,7 +648,7 @@ class Spell {
|
|
|
652
648
|
*/
|
|
653
649
|
$order(name, direction) {
|
|
654
650
|
if (isPlainObject(name)) {
|
|
655
|
-
if (name
|
|
651
|
+
if (name instanceof Raw) {
|
|
656
652
|
this.orders.push([
|
|
657
653
|
name,
|
|
658
654
|
]);
|
|
@@ -717,7 +713,7 @@ class Spell {
|
|
|
717
713
|
for (const condition of parseConditions(conditions, ...values)) {
|
|
718
714
|
// Postgres can't have alias in HAVING caluse
|
|
719
715
|
// https://stackoverflow.com/questions/32730296/referring-to-a-select-aggregate-column-alias-in-the-having-clause-in-postgres
|
|
720
|
-
if (this.Model.driver.type === 'postgres' && !condition
|
|
716
|
+
if (this.Model.driver.type === 'postgres' && !(condition instanceof Raw)) {
|
|
721
717
|
const { value } = condition.args[0];
|
|
722
718
|
for (const column of this.columns) {
|
|
723
719
|
if (column.value === value && column.type === 'alias') {
|
|
@@ -908,14 +904,6 @@ class Spell {
|
|
|
908
904
|
}
|
|
909
905
|
}
|
|
910
906
|
|
|
911
|
-
const AGGREGATOR_MAP = {
|
|
912
|
-
count: 'count',
|
|
913
|
-
average: 'avg',
|
|
914
|
-
minimum: 'min',
|
|
915
|
-
maximum: 'max',
|
|
916
|
-
sum: 'sum'
|
|
917
|
-
};
|
|
918
|
-
|
|
919
907
|
for (const aggregator in AGGREGATOR_MAP) {
|
|
920
908
|
const func = AGGREGATOR_MAP[aggregator];
|
|
921
909
|
Object.defineProperty(Spell.prototype, `$${aggregator}`, {
|
package/types/index.d.ts
CHANGED
|
@@ -64,10 +64,10 @@ export class Spell<T extends typeof Bone, U = InstanceType<T> | Collection<Insta
|
|
|
64
64
|
constructor(Model: T, opts: SpellOptions);
|
|
65
65
|
|
|
66
66
|
select(...names: Array<string | RawSql> | Array<(name: string) => boolean>): Spell<T, U>;
|
|
67
|
-
insert(opts: SetOptions): Spell<T,
|
|
68
|
-
update(opts: SetOptions): Spell<T,
|
|
69
|
-
upsert(opts: SetOptions): Spell<T,
|
|
70
|
-
delete(): Spell<T,
|
|
67
|
+
insert(opts: SetOptions): Spell<T, QueryResult>;
|
|
68
|
+
update(opts: SetOptions): Spell<T, QueryResult>;
|
|
69
|
+
upsert(opts: SetOptions): Spell<T, QueryResult>;
|
|
70
|
+
delete(): Spell<T, QueryResult>;
|
|
71
71
|
|
|
72
72
|
from(table: string | Spell<T>): Spell<T, U>;
|
|
73
73
|
|
|
@@ -107,8 +107,8 @@ export class Spell<T extends typeof Bone, U = InstanceType<T> | Collection<Insta
|
|
|
107
107
|
|
|
108
108
|
batch(size?: number): AsyncIterable<T>;
|
|
109
109
|
|
|
110
|
-
increment(name: string, by?: number, options?: QueryOptions): Spell<T,
|
|
111
|
-
decrement(name: string, by?: number, options?: QueryOptions): Spell<T,
|
|
110
|
+
increment(name: string, by?: number, options?: QueryOptions): Spell<T, QueryResult>;
|
|
111
|
+
decrement(name: string, by?: number, options?: QueryOptions): Spell<T, QueryResult>;
|
|
112
112
|
|
|
113
113
|
toSqlString(): string;
|
|
114
114
|
toString(): string;
|