leoric 2.9.2 → 2.10.0-beta.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/package.json +21 -1
- package/src/bone.js +13 -9
- package/src/drivers/abstract/index.js +16 -0
- package/src/drivers/abstract/spellbook.js +13 -79
- package/src/types/abstract_bone.d.ts +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "leoric",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.10.0-beta.0",
|
|
4
4
|
"description": "JavaScript Object-relational mapping alchemy",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -58,6 +58,26 @@
|
|
|
58
58
|
"sqlstring": "^2.3.0",
|
|
59
59
|
"validator": "^13.5.2"
|
|
60
60
|
},
|
|
61
|
+
"peerDependencies": {
|
|
62
|
+
"mysql": "^2.17.1",
|
|
63
|
+
"mysql2": "^2.3.0",
|
|
64
|
+
"pg": "^8.5.1",
|
|
65
|
+
"sqlite3": "^5.0.2"
|
|
66
|
+
},
|
|
67
|
+
"peerDependenciesMeta": {
|
|
68
|
+
"mysql": {
|
|
69
|
+
"optional": true
|
|
70
|
+
},
|
|
71
|
+
"mysql2": {
|
|
72
|
+
"optional": true
|
|
73
|
+
},
|
|
74
|
+
"pg": {
|
|
75
|
+
"optional": true
|
|
76
|
+
},
|
|
77
|
+
"sqlite3": {
|
|
78
|
+
"optional": true
|
|
79
|
+
}
|
|
80
|
+
},
|
|
61
81
|
"devDependencies": {
|
|
62
82
|
"@babel/core": "^7.14.6",
|
|
63
83
|
"@babel/eslint-parser": "^7.14.7",
|
package/src/bone.js
CHANGED
|
@@ -1591,33 +1591,37 @@ class Bone {
|
|
|
1591
1591
|
|
|
1592
1592
|
static async transaction(callback) {
|
|
1593
1593
|
const connection = await this.driver.getConnection();
|
|
1594
|
+
const begin = async () => await this.driver.begin({ Model: this, connection });
|
|
1595
|
+
const commit = async () => await this.driver.commit({ Model: this, connection });
|
|
1596
|
+
const rollback = async () => await this.driver.rollback({ Model: this, connection });
|
|
1597
|
+
|
|
1594
1598
|
let result;
|
|
1595
1599
|
if (callback.constructor.name === 'AsyncFunction') {
|
|
1596
1600
|
// if callback is an AsyncFunction
|
|
1597
|
-
await
|
|
1601
|
+
await begin();
|
|
1598
1602
|
try {
|
|
1599
|
-
result = await callback({ connection });
|
|
1600
|
-
await
|
|
1603
|
+
result = await callback({ connection, commit, rollback });
|
|
1604
|
+
await commit();
|
|
1601
1605
|
} catch (err) {
|
|
1602
|
-
await
|
|
1606
|
+
await rollback();
|
|
1603
1607
|
throw err;
|
|
1604
1608
|
} finally {
|
|
1605
1609
|
connection.release();
|
|
1606
1610
|
}
|
|
1607
1611
|
} else if (callback.constructor.name === 'GeneratorFunction') {
|
|
1608
|
-
const gen = callback({ connection });
|
|
1612
|
+
const gen = callback({ connection, commit, rollback });
|
|
1609
1613
|
|
|
1610
1614
|
try {
|
|
1611
|
-
await
|
|
1615
|
+
await begin();
|
|
1612
1616
|
while (true) {
|
|
1613
1617
|
const { value: spell, done } = gen.next(result);
|
|
1614
1618
|
if (spell instanceof Spell) spell.connection = connection;
|
|
1615
1619
|
result = spell && typeof spell.then === 'function' ? await spell : spell;
|
|
1616
1620
|
if (done) break;
|
|
1617
1621
|
}
|
|
1618
|
-
await
|
|
1622
|
+
await commit();
|
|
1619
1623
|
} catch (err) {
|
|
1620
|
-
await
|
|
1624
|
+
await rollback();
|
|
1621
1625
|
throw err;
|
|
1622
1626
|
} finally {
|
|
1623
1627
|
connection.release();
|
|
@@ -1716,7 +1720,7 @@ class Bone {
|
|
|
1716
1720
|
}
|
|
1717
1721
|
|
|
1718
1722
|
const Spell_methods = [
|
|
1719
|
-
'select', 'join', 'where', 'group', 'order', 'get', 'count', 'average', 'minimum', 'maximum', 'sum',
|
|
1723
|
+
'select', 'join', 'where', 'group', 'order', 'get', 'count', 'average', 'minimum', 'maximum', 'sum', 'from',
|
|
1720
1724
|
];
|
|
1721
1725
|
for (const method of Spell_methods) {
|
|
1722
1726
|
Object.defineProperty(Bone, method, {
|
|
@@ -213,6 +213,22 @@ class AbstractDriver {
|
|
|
213
213
|
await this.query(sql);
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
+
async rollback(opts) {
|
|
217
|
+
const connection = opts.connection || await this.getConnection();
|
|
218
|
+
return await this.query('ROLLBACK', [], { command: 'ROLLBACK', ...opts, connection });
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async commit(opts) {
|
|
222
|
+
const connection = opts.connection || await this.getConnection();
|
|
223
|
+
|
|
224
|
+
return await this.query('COMMIT', [], { command: 'COMMIT', ...opts, connection });
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async begin(opts) {
|
|
228
|
+
const connection = opts.connection || await this.getConnection();
|
|
229
|
+
return await this.query('BEGIN', [], { command: 'BEGIN', ...opts, connection });
|
|
230
|
+
}
|
|
231
|
+
|
|
216
232
|
};
|
|
217
233
|
|
|
218
234
|
module.exports = AbstractDriver;
|
|
@@ -2,65 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
const SqlString = require('sqlstring');
|
|
4
4
|
|
|
5
|
-
const {
|
|
5
|
+
const { findExpr, walkExpr } = require('../../expr');
|
|
6
6
|
const { formatExpr, formatConditions, collectLiteral, isAggregatorExpr } = require('../../expr_formatter');
|
|
7
7
|
const Raw = require('../../raw').default;
|
|
8
8
|
|
|
9
|
-
/**
|
|
10
|
-
* Create a subquery to make sure OFFSET and LIMIT on left table takes effect.
|
|
11
|
-
* @param {Spell} spell
|
|
12
|
-
*/
|
|
13
|
-
function createSubspell(spell) {
|
|
14
|
-
const { Model, columns, joins, whereConditions, orders } = spell;
|
|
15
|
-
const baseName = Model.tableAlias;
|
|
16
|
-
const subspell = spell.dup;
|
|
17
|
-
|
|
18
|
-
subspell.columns = [];
|
|
19
|
-
for (const token of columns) {
|
|
20
|
-
walkExpr(token, ({ type, qualifiers, value }) => {
|
|
21
|
-
if (type == 'id' && qualifiers[0] == baseName) {
|
|
22
|
-
subspell.columns.push({ type, value });
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// If columns were whitelisted, make sure JOIN columns are included.
|
|
28
|
-
if (subspell.columns.length > 0) {
|
|
29
|
-
for (const qualifier in joins) {
|
|
30
|
-
const association = joins[qualifier];
|
|
31
|
-
walkExpr(association.on, ({ type, qualifiers, value }) => {
|
|
32
|
-
if (type == 'id' && qualifiers[0] == baseName) {
|
|
33
|
-
subspell.columns.push({ type, value });
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// TODO: how to handle subqueries with GROUP?
|
|
40
|
-
subspell.groups = [];
|
|
41
|
-
|
|
42
|
-
subspell.whereConditions = [];
|
|
43
|
-
while (whereConditions.length > 0) {
|
|
44
|
-
const condition = whereConditions.shift();
|
|
45
|
-
const token = copyExpr(condition, ({ type, value }) => {
|
|
46
|
-
if (type === 'id') return { type, value };
|
|
47
|
-
});
|
|
48
|
-
subspell.whereConditions.push(token);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
subspell.orders = [];
|
|
52
|
-
for (const order of orders) {
|
|
53
|
-
const [token, direction] = order;
|
|
54
|
-
const { type, qualifiers = [], value } = token;
|
|
55
|
-
if (type == 'id' && qualifiers[0] == baseName) {
|
|
56
|
-
subspell.orders.push([{ type, value }, direction]);
|
|
57
|
-
if (subspell.columns.length > 0) subspell.columns.push({ type, value });
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return subspell;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
9
|
/**
|
|
65
10
|
* Make sure columns are qualified
|
|
66
11
|
*/
|
|
@@ -247,7 +192,8 @@ class SpellBook {
|
|
|
247
192
|
* @param {Spell} spell
|
|
248
193
|
*/
|
|
249
194
|
formatSelectWithoutJoin(spell) {
|
|
250
|
-
const { columns, whereConditions, groups, havingConditions, orders, rowCount, skip } = spell;
|
|
195
|
+
const { columns, whereConditions, groups, havingConditions, orders, rowCount, skip, Model } = spell;
|
|
196
|
+
const { escapeId } = Model.driver;
|
|
251
197
|
const chunks = ['SELECT'];
|
|
252
198
|
const values = [];
|
|
253
199
|
|
|
@@ -273,7 +219,8 @@ class SpellBook {
|
|
|
273
219
|
const table = formatExpr(spell, spell.table);
|
|
274
220
|
chunks.push(`FROM ${table}`);
|
|
275
221
|
if (spell.table.value instanceof spell.constructor) {
|
|
276
|
-
|
|
222
|
+
const subTableAlias = spell.table.value.Model && spell.table.value.Model.tableAlias;
|
|
223
|
+
chunks.push(`AS ${subTableAlias? escapeId(subTableAlias) : `t${spell.subqueryIndex++}`}`);
|
|
277
224
|
}
|
|
278
225
|
|
|
279
226
|
// see https://dev.mysql.com/doc/refman/8.0/en/index-hints.html
|
|
@@ -534,24 +481,13 @@ class SpellBook {
|
|
|
534
481
|
}
|
|
535
482
|
chunks.push(selects.join(', '));
|
|
536
483
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
for (const condition of whereConditions) walkExpr(condition, checkQualifier);
|
|
545
|
-
for (const orderExpr of orders) walkExpr(orderExpr[0], checkQualifier);
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
if (hoistable) {
|
|
549
|
-
const subspell = createSubspell(spell);
|
|
550
|
-
const subquery = this.formatSelectWithoutJoin(subspell);
|
|
551
|
-
values.push(...subquery.values);
|
|
552
|
-
chunks.push(`FROM (${subquery.sql}) AS ${escapeId(baseName)}`);
|
|
484
|
+
const table = formatExpr(spell, spell.table);
|
|
485
|
+
chunks.push(`FROM ${table}`);
|
|
486
|
+
if (spell.table.value instanceof spell.constructor) {
|
|
487
|
+
const subTableAlias = spell.table.value.Model && spell.table.value.Model.tableAlias;
|
|
488
|
+
chunks.push(`AS ${subTableAlias? escapeId(subTableAlias) : `t${spell.subqueryIndex++}`}`);
|
|
553
489
|
} else {
|
|
554
|
-
chunks.push(`
|
|
490
|
+
chunks.push(`AS ${escapeId(baseName)}`);
|
|
555
491
|
}
|
|
556
492
|
|
|
557
493
|
for (const qualifier in joins) {
|
|
@@ -581,10 +517,8 @@ class SpellBook {
|
|
|
581
517
|
}
|
|
582
518
|
|
|
583
519
|
if (orders.length > 0) chunks.push(`ORDER BY ${this.formatOrders(spell, orders).join(', ')}`);
|
|
584
|
-
if (
|
|
585
|
-
|
|
586
|
-
if (skip > 0) chunks.push(`OFFSET ${skip}`);
|
|
587
|
-
}
|
|
520
|
+
if (rowCount > 0) chunks.push(`LIMIT ${rowCount}`);
|
|
521
|
+
if (skip > 0) chunks.push(`OFFSET ${skip}`);
|
|
588
522
|
return { sql: chunks.join(' '), values };
|
|
589
523
|
}
|
|
590
524
|
|
|
@@ -264,6 +264,8 @@ export class AbstractBone {
|
|
|
264
264
|
|
|
265
265
|
static initialize(): void;
|
|
266
266
|
|
|
267
|
+
static from<T extends typeof AbstractBone>(table: string | Spell<T>): Spell<T>;
|
|
268
|
+
|
|
267
269
|
constructor(values: { [key: string]: Literal }, opts?: { isNewRecord?: boolean });
|
|
268
270
|
|
|
269
271
|
/**
|