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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leoric",
3
- "version": "2.9.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 this.driver.query('BEGIN', [], { connection, Model: this, command: 'BEGIN' });
1601
+ await begin();
1598
1602
  try {
1599
- result = await callback({ connection });
1600
- await this.driver.query('COMMIT', [], { connection, Model: this, command: 'COMMIT' });
1603
+ result = await callback({ connection, commit, rollback });
1604
+ await commit();
1601
1605
  } catch (err) {
1602
- await this.driver.query('ROLLBACK', [], { connection, Model: this, command: 'ROLLBACK' });
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 this.driver.query('BEGIN', [], { connection, Model: this, command: 'BEGIN' });
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 this.driver.query('COMMIT', [], { connection, Model: this, command: 'COMMIT' });
1622
+ await commit();
1619
1623
  } catch (err) {
1620
- await this.driver.query('ROLLBACK', [], { connection, Model: this, command: 'ROLLBACK' });
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 { copyExpr, findExpr, walkExpr } = require('../../expr');
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
- chunks.push(`AS t${spell.subqueryIndex++}`);
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
- let hoistable = skip > 0 || rowCount > 0;
538
- if (hoistable) {
539
- function checkQualifier({ type, qualifiers = [] }) {
540
- if (type === 'id' && qualifiers.length> 0 && !qualifiers.includes(baseName)) {
541
- hoistable = false;
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(`FROM ${escapeId(Model.table)} AS ${escapeId(baseName)}`);
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 (!hoistable) {
585
- if (rowCount > 0) chunks.push(`LIMIT ${rowCount}`);
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
  /**