leoric 1.14.3 → 2.0.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 +48 -0
- package/package.json +1 -1
- package/src/bone.js +34 -15
- package/src/collection.js +2 -1
- package/src/data_types.js +30 -15
- package/src/drivers/abstract/spellbook.js +28 -19
- package/src/drivers/mysql/index.js +6 -3
- package/src/drivers/mysql/schema.js +3 -1
- package/src/drivers/postgres/attribute.js +0 -1
- package/src/drivers/postgres/index.js +5 -3
- package/src/drivers/postgres/schema.js +3 -0
- package/src/drivers/sqlite/data_types.js +2 -6
- package/src/drivers/sqlite/index.js +5 -3
- package/src/drivers/sqlite/schema.js +6 -1
- package/src/query_object.js +2 -1
- package/src/raw.js +9 -0
- package/src/realm.js +4 -7
- package/src/spell.js +9 -14
- package/src/utils/index.js +8 -0
- package/types/index.d.ts +9 -5
package/History.md
CHANGED
|
@@ -1,3 +1,51 @@
|
|
|
1
|
+
2.0.0 / 2021-12-28
|
|
2
|
+
==================
|
|
3
|
+
|
|
4
|
+
## What's Changed
|
|
5
|
+
* breaking: model.sync add force/alter option by @SmartOrange in https://github.com/cyjake/leoric/pull/224
|
|
6
|
+
* breaking: logQueryError(err, sql, duration, options) by @cyjake in https://github.com/cyjake/leoric/pull/237
|
|
7
|
+
* test: add utf8mb4 test cases by @fengmk2 in https://github.com/cyjake/leoric/pull/239
|
|
8
|
+
* Merge 1.x changes by @cyjake in https://github.com/cyjake/leoric/pull/249
|
|
9
|
+
|
|
10
|
+
## New Contributors
|
|
11
|
+
* @SmartOrange made their first contribution in https://github.com/cyjake/leoric/pull/222
|
|
12
|
+
|
|
13
|
+
**Full Changelog**: https://github.com/cyjake/leoric/compare/v1.15.1...v2.0.0
|
|
14
|
+
|
|
15
|
+
1.15.1 / 2021-12-28
|
|
16
|
+
===================
|
|
17
|
+
|
|
18
|
+
## What's Changed
|
|
19
|
+
* fix: fix #242 date string format by @JimmyDaddy in https://github.com/cyjake/leoric/pull/243
|
|
20
|
+
* fix: update with empty conditions by @JimmyDaddy in https://github.com/cyjake/leoric/pull/241
|
|
21
|
+
* fix: silent option's priority should be lower than valueSet by @JimmyDaddy in https://github.com/cyjake/leoric/pull/244
|
|
22
|
+
* fix: information_schema.columns.datetime_precision by @cyjake in https://github.com/cyjake/leoric/pull/246
|
|
23
|
+
* fix: should not hoist subquery if query is ordered by external columns by @cyjake in https://github.com/cyjake/leoric/pull/247
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
**Full Changelog**: https://github.com/cyjake/leoric/compare/v1.15.0...v1.15.1
|
|
27
|
+
|
|
28
|
+
1.15.0 / 2021-11-22
|
|
29
|
+
===================
|
|
30
|
+
|
|
31
|
+
## What's Changed
|
|
32
|
+
* feat: make duration in precise milliseconds by @fengmk2 in https://github.com/cyjake/leoric/pull/236
|
|
33
|
+
* fix: spell.increment() & spell.decrement() @cyjake https://github.com/cyjake/leoric/pull/234
|
|
34
|
+
* fix: bulkCreate should adapte empty data @JimmyDaddy https://github.com/cyjake/leoric/pull/232
|
|
35
|
+
|
|
36
|
+
**Full Changelog**: https://github.com/cyjake/leoric/compare/v1.14.4...v1.14.5
|
|
37
|
+
|
|
38
|
+
1.14.4 / 2021-11-15
|
|
39
|
+
===================
|
|
40
|
+
|
|
41
|
+
## What's Changed
|
|
42
|
+
|
|
43
|
+
* test: PostgreSQL v14 test case compatibility by @cyjake https://github.com/cyjake/leoric/pull/230
|
|
44
|
+
* fix: turn off subquery optimization if query criteria contains other column by @cyjake https://github.com/cyjake/leoric/pull/229
|
|
45
|
+
* fix: bone.changed() return `false | string[]` type by @fengmk2 https://github.com/cyjake/leoric/pull/231
|
|
46
|
+
|
|
47
|
+
**Full Changelog**: https://github.com/cyjake/leoric/compare/v1.14.3...v1.14.4
|
|
48
|
+
|
|
1
49
|
1.14.3 / 2021-11-12
|
|
2
50
|
===================
|
|
3
51
|
|
package/package.json
CHANGED
package/src/bone.js
CHANGED
|
@@ -11,6 +11,7 @@ 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');
|
|
@@ -27,12 +28,8 @@ function looseReadonly(props) {
|
|
|
27
28
|
}, {});
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
function compare(attributes,
|
|
31
|
+
function compare(attributes, columnMap) {
|
|
31
32
|
const diff = {};
|
|
32
|
-
const columnMap = columns.reduce((result, entry) => {
|
|
33
|
-
result[entry.columnName] = entry;
|
|
34
|
-
return result;
|
|
35
|
-
}, {});
|
|
36
33
|
|
|
37
34
|
for (const name in attributes) {
|
|
38
35
|
const attribute = attributes[name];
|
|
@@ -71,7 +68,7 @@ function copyValues(values) {
|
|
|
71
68
|
for (const key in values) {
|
|
72
69
|
if (Object.hasOwnProperty.call(values, key)) {
|
|
73
70
|
const v = values[key];
|
|
74
|
-
if (v && (v
|
|
71
|
+
if (v && ((v instanceof Raw) || v.__expr || (v instanceof Spell))) continue;
|
|
75
72
|
copyValue[key] = v;
|
|
76
73
|
}
|
|
77
74
|
}
|
|
@@ -162,14 +159,15 @@ class Bone {
|
|
|
162
159
|
attribute(...args) {
|
|
163
160
|
const [ name, value ] = args;
|
|
164
161
|
const { attributes } = this.constructor;
|
|
162
|
+
const attribute = attributes[name];
|
|
165
163
|
|
|
166
|
-
if (!
|
|
164
|
+
if (!attribute) {
|
|
167
165
|
throw new Error(`${this.constructor.name} has no attribute "${name}"`);
|
|
168
166
|
}
|
|
169
167
|
|
|
170
168
|
if (args.length > 1) {
|
|
171
169
|
// execute validators
|
|
172
|
-
this.#raw[name] = value;
|
|
170
|
+
this.#raw[name] = value instanceof Raw ? value : attribute.cast(value);
|
|
173
171
|
this.#rawUnset.delete(name);
|
|
174
172
|
return this;
|
|
175
173
|
}
|
|
@@ -682,7 +680,7 @@ class Bone {
|
|
|
682
680
|
if (shardingKey) where[shardingKey] = this[shardingKey];
|
|
683
681
|
|
|
684
682
|
const { updatedAt, deletedAt } = Model.timestamps;
|
|
685
|
-
if (attributes[updatedAt] && !changes[updatedAt] && !changes[deletedAt]) {
|
|
683
|
+
if (attributes[updatedAt] && !changes[updatedAt] && !changes[deletedAt] && !options.silent) {
|
|
686
684
|
changes[updatedAt] = new Date();
|
|
687
685
|
}
|
|
688
686
|
if (options.validate !== false ) {
|
|
@@ -933,10 +931,20 @@ class Bone {
|
|
|
933
931
|
};
|
|
934
932
|
}
|
|
935
933
|
|
|
934
|
+
const columnMap = columns.reduce((result, entry) => {
|
|
935
|
+
result[entry.columnName] = entry;
|
|
936
|
+
return result;
|
|
937
|
+
}, {});
|
|
938
|
+
|
|
936
939
|
for (const name of Object.keys(attributes)) {
|
|
937
940
|
const attribute = new Attribute(name, attributes[name], options.define);
|
|
938
941
|
attributeMap[attribute.columnName] = attribute;
|
|
939
942
|
attributes[name] = attribute;
|
|
943
|
+
const columnInfo = columnMap[attribute.columnName];
|
|
944
|
+
// if datetime or timestamp precision not defined, default to column info
|
|
945
|
+
if (columnInfo && attribute.type instanceof DataTypes.DATE && attribute.type.precision == null) {
|
|
946
|
+
attribute.type.precision = columnInfo.datetimePrecision;
|
|
947
|
+
}
|
|
940
948
|
}
|
|
941
949
|
|
|
942
950
|
const primaryKey = Object.keys(attributes).find(key => attributes[key].primaryKey);
|
|
@@ -945,8 +953,7 @@ class Bone {
|
|
|
945
953
|
const name = attributes.hasOwnProperty(key) ? key : snakeCase(key);
|
|
946
954
|
const attribute = attributes[name];
|
|
947
955
|
|
|
948
|
-
if (
|
|
949
|
-
if (columns.some(column => column.columnName === attribute.columnName)) {
|
|
956
|
+
if (attribute && columnMap[attribute.columnName]) {
|
|
950
957
|
timestamps[key] = name;
|
|
951
958
|
}
|
|
952
959
|
}
|
|
@@ -960,7 +967,7 @@ class Bone {
|
|
|
960
967
|
attributeMap,
|
|
961
968
|
associations,
|
|
962
969
|
tableAlias,
|
|
963
|
-
synchronized: Object.keys(compare(attributes,
|
|
970
|
+
synchronized: Object.keys(compare(attributes, columnMap)).length === 0,
|
|
964
971
|
}));
|
|
965
972
|
|
|
966
973
|
for (const hookName of hookNames) {
|
|
@@ -1322,6 +1329,7 @@ class Bone {
|
|
|
1322
1329
|
}
|
|
1323
1330
|
|
|
1324
1331
|
static async bulkCreate(records, options = {}) {
|
|
1332
|
+
if (!records || !records.length) return records;
|
|
1325
1333
|
const { driver, attributes, primaryKey, primaryColumn } = this;
|
|
1326
1334
|
|
|
1327
1335
|
const { createdAt, updatedAt } = this.timestamps;
|
|
@@ -1415,7 +1423,7 @@ class Bone {
|
|
|
1415
1423
|
// values should be immutable
|
|
1416
1424
|
const data = Object.assign({}, values);
|
|
1417
1425
|
const { updatedAt, deletedAt } = this.timestamps;
|
|
1418
|
-
if (attributes[updatedAt] && !data[updatedAt] && !data[deletedAt]) {
|
|
1426
|
+
if (attributes[updatedAt] && !data[updatedAt] && !data[deletedAt] && !options.silent) {
|
|
1419
1427
|
data[updatedAt] = new Date();
|
|
1420
1428
|
}
|
|
1421
1429
|
|
|
@@ -1536,7 +1544,7 @@ class Bone {
|
|
|
1536
1544
|
Object.defineProperties(this, looseReadonly({ ...hookMethods, attributes, table }));
|
|
1537
1545
|
}
|
|
1538
1546
|
|
|
1539
|
-
static async sync() {
|
|
1547
|
+
static async sync({ force = false, alter = false } = {}) {
|
|
1540
1548
|
const { driver, physicTable: table } = this;
|
|
1541
1549
|
const { database } = this.options;
|
|
1542
1550
|
|
|
@@ -1553,11 +1561,22 @@ class Bone {
|
|
|
1553
1561
|
}
|
|
1554
1562
|
|
|
1555
1563
|
const { attributes, columns } = this;
|
|
1564
|
+
const columnMap = columns.reduce((result, entry) => {
|
|
1565
|
+
result[entry.columnName] = entry;
|
|
1566
|
+
return result;
|
|
1567
|
+
}, {});
|
|
1556
1568
|
|
|
1557
1569
|
if (columns.length === 0) {
|
|
1558
1570
|
await driver.createTable(table, attributes);
|
|
1559
1571
|
} else {
|
|
1560
|
-
|
|
1572
|
+
if (force) {
|
|
1573
|
+
await driver.dropTable(table);
|
|
1574
|
+
await driver.createTable(table, attributes);
|
|
1575
|
+
} else if (alter){
|
|
1576
|
+
await driver.alterTable(table, compare(attributes, columnMap));
|
|
1577
|
+
} else {
|
|
1578
|
+
console.warn('[synchronize_fail] %s couldn\'t be synchronized, please use force or alter to specify execution', this.name);
|
|
1579
|
+
}
|
|
1561
1580
|
}
|
|
1562
1581
|
|
|
1563
1582
|
const schemaInfo = await driver.querySchemaInfo(database, table);
|
package/src/collection.js
CHANGED
|
@@ -11,7 +11,8 @@ class Collection extends Array {
|
|
|
11
11
|
* @param {Array} fields
|
|
12
12
|
* @returns {Collection|Array}
|
|
13
13
|
*/
|
|
14
|
-
static init({ spell, rows, fields }) {
|
|
14
|
+
static init({ spell, rows, fields, insertId, affectedRows}) {
|
|
15
|
+
if (spell.command !== 'select') return { insertId, affectedRows };
|
|
15
16
|
return dispatch(spell, rows, fields);
|
|
16
17
|
}
|
|
17
18
|
|
package/src/data_types.js
CHANGED
|
@@ -214,7 +214,7 @@ class BIGINT extends INTEGER {
|
|
|
214
214
|
}
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
-
const rDateFormat = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:[,.]\d{3,6})
|
|
217
|
+
const rDateFormat = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:[,.]\d{3,6})$/;
|
|
218
218
|
|
|
219
219
|
class DATE extends DataType {
|
|
220
220
|
constructor(precision, timezone = true) {
|
|
@@ -229,14 +229,23 @@ class DATE extends DataType {
|
|
|
229
229
|
toSqlString() {
|
|
230
230
|
const { precision } = this;
|
|
231
231
|
const dataType = this.dataType.toUpperCase();
|
|
232
|
-
if (precision
|
|
232
|
+
if (precision != null && precision >= 0) return `${dataType}(${precision})`;
|
|
233
233
|
return dataType;
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
+
_round(value) {
|
|
237
|
+
const { precision } = this;
|
|
238
|
+
if (precision != null && precision < 3 && value instanceof Date) {
|
|
239
|
+
const divider = 10 ** (3 - precision);
|
|
240
|
+
return new Date(Math.round(value.getTime() / divider) * divider);
|
|
241
|
+
}
|
|
242
|
+
return value;
|
|
243
|
+
}
|
|
244
|
+
|
|
236
245
|
cast(value) {
|
|
237
246
|
if (value == null) return value;
|
|
238
|
-
if (value instanceof Date)
|
|
239
|
-
return
|
|
247
|
+
if (!(value instanceof Date)) value = new Date(value);
|
|
248
|
+
return this._round(value);
|
|
240
249
|
}
|
|
241
250
|
|
|
242
251
|
uncast(value) {
|
|
@@ -258,13 +267,7 @@ class DATE extends DataType {
|
|
|
258
267
|
if (!(value instanceof Date)) value = new Date(value);
|
|
259
268
|
if (isNaN(value)) throw new Error(util.format('invalid date: %s', originValue));
|
|
260
269
|
|
|
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;
|
|
270
|
+
return this._round(value);
|
|
268
271
|
}
|
|
269
272
|
}
|
|
270
273
|
|
|
@@ -278,10 +281,17 @@ class DATEONLY extends DataType {
|
|
|
278
281
|
return this.dataType.toUpperCase();
|
|
279
282
|
}
|
|
280
283
|
|
|
284
|
+
_round(value) {
|
|
285
|
+
if (value instanceof Date) {
|
|
286
|
+
return new Date(value.getFullYear(), value.getMonth(), value.getDate());
|
|
287
|
+
}
|
|
288
|
+
return value;
|
|
289
|
+
}
|
|
290
|
+
|
|
281
291
|
cast(value) {
|
|
282
292
|
if (value == null) return value;
|
|
283
|
-
if (value instanceof Date)
|
|
284
|
-
return
|
|
293
|
+
if (!(value instanceof Date)) value = new Date(value);
|
|
294
|
+
return this._round(value);
|
|
285
295
|
}
|
|
286
296
|
|
|
287
297
|
uncast(value) {
|
|
@@ -301,7 +311,7 @@ class DATEONLY extends DataType {
|
|
|
301
311
|
if (!(value instanceof Date)) value = new Date(value);
|
|
302
312
|
if (isNaN(value)) throw new Error(util.format('invalid date: %s', originValue));;
|
|
303
313
|
|
|
304
|
-
return
|
|
314
|
+
return this._round(value);
|
|
305
315
|
}
|
|
306
316
|
}
|
|
307
317
|
|
|
@@ -374,7 +384,12 @@ class JSON extends DataType {
|
|
|
374
384
|
if (!value) return value;
|
|
375
385
|
// type === JSONB
|
|
376
386
|
if (typeof value === 'object') return value;
|
|
377
|
-
|
|
387
|
+
try {
|
|
388
|
+
return global.JSON.parse(value);
|
|
389
|
+
} catch (err) {
|
|
390
|
+
console.error(new Error(`unable to cast ${value} to JSON`));
|
|
391
|
+
return value;
|
|
392
|
+
}
|
|
378
393
|
}
|
|
379
394
|
|
|
380
395
|
uncast(value) {
|
|
@@ -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
|
|
@@ -117,21 +118,12 @@ function createSubspell(spell) {
|
|
|
117
118
|
subspell.groups = [];
|
|
118
119
|
|
|
119
120
|
subspell.whereConditions = [];
|
|
120
|
-
|
|
121
|
-
const condition = whereConditions
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if (type == 'id' && qualifiers[0] != baseName) {
|
|
125
|
-
internal = false;
|
|
126
|
-
}
|
|
121
|
+
while (whereConditions.length > 0) {
|
|
122
|
+
const condition = whereConditions.shift();
|
|
123
|
+
const token = copyExpr(condition, ({ type, value }) => {
|
|
124
|
+
if (type === 'id') return { type, value };
|
|
127
125
|
});
|
|
128
|
-
|
|
129
|
-
const token = copyExpr(condition, ({ type, value }) => {
|
|
130
|
-
if (type === 'id') return { type, value };
|
|
131
|
-
});
|
|
132
|
-
subspell.whereConditions.unshift(token);
|
|
133
|
-
whereConditions.splice(i, 1);
|
|
134
|
-
}
|
|
126
|
+
subspell.whereConditions.push(token);
|
|
135
127
|
}
|
|
136
128
|
|
|
137
129
|
subspell.orders = [];
|
|
@@ -226,7 +218,18 @@ function formatSelectWithJoin(spell) {
|
|
|
226
218
|
}
|
|
227
219
|
chunks.push(selects.join(', '));
|
|
228
220
|
|
|
229
|
-
|
|
221
|
+
let hoistable = skip > 0 || rowCount > 0;
|
|
222
|
+
if (hoistable) {
|
|
223
|
+
function checkQualifier({ type, qualifiers }) {
|
|
224
|
+
if (type === 'id' && qualifiers.length> 0 && !qualifiers.includes(baseName)) {
|
|
225
|
+
hoistable = false;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
for (const condition of whereConditions) walkExpr(condition, checkQualifier);
|
|
229
|
+
for (const orderExpr of orders) walkExpr(orderExpr[0], checkQualifier);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (hoistable) {
|
|
230
233
|
const subspell = createSubspell(spell);
|
|
231
234
|
const subquery = this.formatSelectWithoutJoin(subspell);
|
|
232
235
|
values.push(...subquery.values);
|
|
@@ -262,6 +265,10 @@ function formatSelectWithJoin(spell) {
|
|
|
262
265
|
}
|
|
263
266
|
|
|
264
267
|
if (orders.length > 0) chunks.push(`ORDER BY ${formatOrders(spell, orders).join(', ')}`);
|
|
268
|
+
if (!hoistable) {
|
|
269
|
+
if (rowCount > 0) chunks.push(`LIMIT ${rowCount}`);
|
|
270
|
+
if (skip > 0) chunks.push(`OFFSET ${skip}`);
|
|
271
|
+
}
|
|
265
272
|
return { sql: chunks.join(' '), values };
|
|
266
273
|
}
|
|
267
274
|
|
|
@@ -380,7 +387,7 @@ function formatInsert(spell) {
|
|
|
380
387
|
const value = sets[name];
|
|
381
388
|
// upsert should not update createdAt
|
|
382
389
|
columns.push(Model.unalias(name));
|
|
383
|
-
if (value
|
|
390
|
+
if (value instanceof Raw) {
|
|
384
391
|
values.push(SqlString.raw(value.value));
|
|
385
392
|
} else {
|
|
386
393
|
values.push(value);
|
|
@@ -443,7 +450,7 @@ function formatUpdate(spell) {
|
|
|
443
450
|
if (value && value.__expr) {
|
|
444
451
|
assigns.push(`${escapeId(Model.unalias(name))} = ${formatExpr(spell, value)}`);
|
|
445
452
|
collectLiteral(spell, value, values);
|
|
446
|
-
} else if (value
|
|
453
|
+
} else if (value instanceof Raw) {
|
|
447
454
|
assigns.push(`${escapeId(Model.unalias(name))} = ${value.value}`);
|
|
448
455
|
} else {
|
|
449
456
|
assigns.push(`${escapeId(Model.unalias(name))} = ?`);
|
|
@@ -451,7 +458,6 @@ function formatUpdate(spell) {
|
|
|
451
458
|
}
|
|
452
459
|
}
|
|
453
460
|
|
|
454
|
-
for (const condition of whereConditions) collectLiteral(spell, condition, values);
|
|
455
461
|
// see https://dev.mysql.com/doc/refman/8.0/en/optimizer-hints.html
|
|
456
462
|
const hintStr = this.formatOptimizerHints(spell);
|
|
457
463
|
// see https://dev.mysql.com/doc/refman/8.0/en/index-hints.html
|
|
@@ -466,7 +472,10 @@ function formatUpdate(spell) {
|
|
|
466
472
|
}
|
|
467
473
|
|
|
468
474
|
chunks.push(`SET ${assigns.join(', ')}`);
|
|
469
|
-
|
|
475
|
+
if (whereConditions.length > 0) {
|
|
476
|
+
for (const condition of whereConditions) collectLiteral(spell, condition, values);
|
|
477
|
+
chunks.push(`WHERE ${formatConditions(spell, whereConditions)}`);
|
|
478
|
+
}
|
|
470
479
|
return {
|
|
471
480
|
sql: chunks.join(' '),
|
|
472
481
|
values,
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { performance } = require('perf_hooks');
|
|
4
|
+
|
|
3
5
|
const AbstractDriver = require('../abstract');
|
|
4
6
|
const Attribute = require('./attribute');
|
|
5
7
|
const DataTypes = require('./data_types');
|
|
6
8
|
const spellbook = require('./spellbook');
|
|
7
9
|
const schema = require('./schema');
|
|
10
|
+
const { calculateDuration } = require('../../utils');
|
|
8
11
|
|
|
9
12
|
class MysqlDriver extends AbstractDriver {
|
|
10
13
|
/**
|
|
@@ -84,19 +87,19 @@ class MysqlDriver extends AbstractDriver {
|
|
|
84
87
|
});
|
|
85
88
|
});
|
|
86
89
|
const sql = logger.format(query, values, opts);
|
|
87
|
-
const start =
|
|
90
|
+
const start = performance.now();
|
|
88
91
|
let result;
|
|
89
92
|
|
|
90
93
|
try {
|
|
91
94
|
result = await promise;
|
|
92
95
|
} catch (err) {
|
|
93
|
-
logger.logQueryError(
|
|
96
|
+
logger.logQueryError(err, sql, calculateDuration(start), opts);
|
|
94
97
|
throw err;
|
|
95
98
|
} finally {
|
|
96
99
|
if (!opts.connection) connection.release();
|
|
97
100
|
}
|
|
98
101
|
|
|
99
|
-
logger.tryLogQuery(sql,
|
|
102
|
+
logger.tryLogQuery(sql, calculateDuration(start), opts);
|
|
100
103
|
const [ results, fields ] = result;
|
|
101
104
|
if (fields) return { rows: results, fields };
|
|
102
105
|
return results;
|
|
@@ -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
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { Pool } = require('pg');
|
|
4
|
+
const { performance } = require('perf_hooks');
|
|
4
5
|
|
|
5
6
|
const AbstractDriver = require('../abstract');
|
|
6
7
|
const Attribute = require('./attribute');
|
|
@@ -8,6 +9,7 @@ const DataTypes = require('./data_types');
|
|
|
8
9
|
const { escape, escapeId } = require('./sqlstring');
|
|
9
10
|
const spellbook = require('./spellbook');
|
|
10
11
|
const schema = require('./schema');
|
|
12
|
+
const { calculateDuration } = require('../../utils');
|
|
11
13
|
|
|
12
14
|
/**
|
|
13
15
|
* The actual column type can be found by mapping the `oid` (which is called `dataTypeID`) in the `RowDescription`.
|
|
@@ -129,19 +131,19 @@ class PostgresDriver extends AbstractDriver {
|
|
|
129
131
|
|
|
130
132
|
async function tryQuery(...args) {
|
|
131
133
|
const formatted = logger.format(sql, values, spell);
|
|
132
|
-
const start =
|
|
134
|
+
const start = performance.now();
|
|
133
135
|
let result;
|
|
134
136
|
|
|
135
137
|
try {
|
|
136
138
|
result = await connection.query(...args);
|
|
137
139
|
} catch (err) {
|
|
138
|
-
logger.logQueryError(
|
|
140
|
+
logger.logQueryError(err, formatted, calculateDuration(start), spell);
|
|
139
141
|
throw err;
|
|
140
142
|
} finally {
|
|
141
143
|
if (!spell.connection) connection.release();
|
|
142
144
|
}
|
|
143
145
|
|
|
144
|
-
logger.tryLogQuery(formatted,
|
|
146
|
+
logger.tryLogQuery(formatted, calculateDuration(start), spell);
|
|
145
147
|
return result;
|
|
146
148
|
}
|
|
147
149
|
|
|
@@ -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,14 +3,10 @@
|
|
|
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
|
-
|
|
11
|
-
toSqlString() {
|
|
12
|
-
return this.dataType.toUpperCase();
|
|
13
|
-
}
|
|
14
10
|
}
|
|
15
11
|
|
|
16
12
|
class Sqlite_BIGINT extends DataTypes.BIGINT {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const strftime = require('strftime');
|
|
4
|
+
const { performance } = require('perf_hooks');
|
|
4
5
|
|
|
5
6
|
const AbstractDriver = require('../abstract');
|
|
6
7
|
const Attribute = require('./attribute');
|
|
@@ -9,6 +10,7 @@ const { escapeId, escape } = require('./sqlstring');
|
|
|
9
10
|
const schema = require('./schema');
|
|
10
11
|
const spellbook = require('./spellbook');
|
|
11
12
|
const Pool = require('./pool');
|
|
13
|
+
const { calculateDuration } = require('../../utils');
|
|
12
14
|
|
|
13
15
|
class SqliteDriver extends AbstractDriver {
|
|
14
16
|
constructor(opts = {}) {
|
|
@@ -40,19 +42,19 @@ class SqliteDriver extends AbstractDriver {
|
|
|
40
42
|
|
|
41
43
|
const { logger } = this;
|
|
42
44
|
const sql = logger.format(query, values, opts);
|
|
43
|
-
const start =
|
|
45
|
+
const start = performance.now();
|
|
44
46
|
let result;
|
|
45
47
|
|
|
46
48
|
try {
|
|
47
49
|
result = await connection.query(query, values, opts);
|
|
48
50
|
} catch (err) {
|
|
49
|
-
logger.logQueryError(
|
|
51
|
+
logger.logQueryError(err, sql, calculateDuration(start), opts);
|
|
50
52
|
throw err;
|
|
51
53
|
} finally {
|
|
52
54
|
if (!opts.connection) connection.release();
|
|
53
55
|
}
|
|
54
56
|
|
|
55
|
-
logger.tryLogQuery(sql,
|
|
57
|
+
logger.tryLogQuery(sql, calculateDuration(start), opts);
|
|
56
58
|
return result;
|
|
57
59
|
}
|
|
58
60
|
|
|
@@ -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/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,7 @@ 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');
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
*
|
|
@@ -163,12 +164,12 @@ class Realm {
|
|
|
163
164
|
return this.Bone;
|
|
164
165
|
}
|
|
165
166
|
|
|
166
|
-
async sync() {
|
|
167
|
+
async sync(options) {
|
|
167
168
|
if (!this.connected) await this.connect();
|
|
168
169
|
const { models } = this;
|
|
169
170
|
|
|
170
171
|
for (const model of Object.values(models)) {
|
|
171
|
-
await model.sync();
|
|
172
|
+
await model.sync(options);
|
|
172
173
|
}
|
|
173
174
|
}
|
|
174
175
|
|
|
@@ -239,11 +240,7 @@ class Realm {
|
|
|
239
240
|
if (typeof sql !== 'string') {
|
|
240
241
|
throw new TypeError('sql must be a string');
|
|
241
242
|
}
|
|
242
|
-
return
|
|
243
|
-
__raw: true,
|
|
244
|
-
value: sql,
|
|
245
|
-
type: 'raw',
|
|
246
|
-
};
|
|
243
|
+
return new Raw(sql);
|
|
247
244
|
}
|
|
248
245
|
|
|
249
246
|
// instance.raw
|
package/src/spell.js
CHANGED
|
@@ -10,6 +10,7 @@ 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');
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Parse condition expressions
|
|
@@ -21,7 +22,7 @@ const { parseObject } = require('./query_object');
|
|
|
21
22
|
* @returns {Array}
|
|
22
23
|
*/
|
|
23
24
|
function parseConditions(conditions, ...values) {
|
|
24
|
-
if (conditions
|
|
25
|
+
if (conditions instanceof Raw) return [ conditions ];
|
|
25
26
|
if (isPlainObject(conditions)) {
|
|
26
27
|
return parseObject(conditions);
|
|
27
28
|
} else if (typeof conditions == 'string') {
|
|
@@ -41,7 +42,7 @@ function parseSelect(spell, ...names) {
|
|
|
41
42
|
|
|
42
43
|
const columns = [];
|
|
43
44
|
for (const name of names) {
|
|
44
|
-
if (name
|
|
45
|
+
if (name instanceof Raw) columns.push(name);
|
|
45
46
|
else columns.push(...parseExprList(name));
|
|
46
47
|
}
|
|
47
48
|
|
|
@@ -68,8 +69,7 @@ function parseSelect(spell, ...names) {
|
|
|
68
69
|
* @returns {Object}
|
|
69
70
|
*/
|
|
70
71
|
function formatValueSet(spell, obj, strict = true) {
|
|
71
|
-
const { Model
|
|
72
|
-
const { timestamps } = Model;
|
|
72
|
+
const { Model } = spell;
|
|
73
73
|
const sets = {};
|
|
74
74
|
for (const name in obj) {
|
|
75
75
|
const attribute = Model.attributes[name];
|
|
@@ -79,12 +79,8 @@ function formatValueSet(spell, obj, strict = true) {
|
|
|
79
79
|
throw new Error(`Undefined attribute "${name}"`);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
if (silent && timestamps.updatedAt && name === timestamps.updatedAt && command === 'update') {
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
82
|
// raw sql don't need to uncast
|
|
87
|
-
if (value
|
|
83
|
+
if (value instanceof Raw) {
|
|
88
84
|
sets[name] = value;
|
|
89
85
|
} else {
|
|
90
86
|
sets[name] = attribute.uncast(value);
|
|
@@ -402,7 +398,6 @@ class Spell {
|
|
|
402
398
|
hints: [...this.hints],
|
|
403
399
|
// used by transaction
|
|
404
400
|
connection: this.connection,
|
|
405
|
-
silent: this.silent,
|
|
406
401
|
});
|
|
407
402
|
}
|
|
408
403
|
|
|
@@ -530,8 +525,8 @@ class Spell {
|
|
|
530
525
|
}
|
|
531
526
|
|
|
532
527
|
$increment(name, by = 1, opts = {}) {
|
|
533
|
-
|
|
534
|
-
|
|
528
|
+
const { Model } = this;
|
|
529
|
+
const silent = opts.silent;
|
|
535
530
|
const { timestamps } = Model;
|
|
536
531
|
this.command = 'update';
|
|
537
532
|
if (!Number.isFinite(by)) throw new Error(`unexpected increment value ${by}`);
|
|
@@ -652,7 +647,7 @@ class Spell {
|
|
|
652
647
|
*/
|
|
653
648
|
$order(name, direction) {
|
|
654
649
|
if (isPlainObject(name)) {
|
|
655
|
-
if (name
|
|
650
|
+
if (name instanceof Raw) {
|
|
656
651
|
this.orders.push([
|
|
657
652
|
name,
|
|
658
653
|
]);
|
|
@@ -717,7 +712,7 @@ class Spell {
|
|
|
717
712
|
for (const condition of parseConditions(conditions, ...values)) {
|
|
718
713
|
// Postgres can't have alias in HAVING caluse
|
|
719
714
|
// 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
|
|
715
|
+
if (this.Model.driver.type === 'postgres' && !(condition instanceof Raw)) {
|
|
721
716
|
const { value } = condition.args[0];
|
|
722
717
|
for (const column of this.columns) {
|
|
723
718
|
if (column.value === value && column.type === 'alias') {
|
package/src/utils/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { performance } = require('perf_hooks');
|
|
4
|
+
|
|
3
5
|
function isPlainObject(value) {
|
|
4
6
|
return Object.prototype.toString.call(value) === '[object Object]';
|
|
5
7
|
}
|
|
@@ -28,6 +30,11 @@ function getPropertyNames(obj) {
|
|
|
28
30
|
return Array.from(propertyNamesSet);
|
|
29
31
|
}
|
|
30
32
|
|
|
33
|
+
// microseconds to millisecond, 10.456
|
|
34
|
+
function calculateDuration(starttime) {
|
|
35
|
+
return Math.floor((performance.now() - starttime) * 1000) / 1000;
|
|
36
|
+
}
|
|
37
|
+
|
|
31
38
|
const logger = {};
|
|
32
39
|
|
|
33
40
|
[ 'log', 'warn', 'debug', 'info', 'error' ].forEach(key => {
|
|
@@ -40,5 +47,6 @@ module.exports = {
|
|
|
40
47
|
isPlainObject,
|
|
41
48
|
compose,
|
|
42
49
|
getPropertyNames,
|
|
50
|
+
calculateDuration,
|
|
43
51
|
logger,
|
|
44
52
|
};
|
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,6 +107,9 @@ 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, QueryResult>;
|
|
111
|
+
decrement(name: string, by?: number, options?: QueryOptions): Spell<T, QueryResult>;
|
|
112
|
+
|
|
110
113
|
toSqlString(): string;
|
|
111
114
|
toString(): string;
|
|
112
115
|
}
|
|
@@ -158,6 +161,7 @@ interface QueryOptions {
|
|
|
158
161
|
individualHooks?: boolean;
|
|
159
162
|
hooks?: boolean;
|
|
160
163
|
paranoid?: boolean;
|
|
164
|
+
silent?: boolean;
|
|
161
165
|
}
|
|
162
166
|
|
|
163
167
|
interface QueryResult {
|
|
@@ -494,7 +498,7 @@ export class Bone {
|
|
|
494
498
|
* Get changed attributes or check if given attribute is changed or not
|
|
495
499
|
*/
|
|
496
500
|
changed(name: string): boolean;
|
|
497
|
-
changed(): Array<string
|
|
501
|
+
changed(): Array<string> | false;
|
|
498
502
|
|
|
499
503
|
/**
|
|
500
504
|
* Get attribute changes
|