Browse Source

No more callbacks in the public interface/New implementation to allow multiple adapters for a database type

keep-around/26febfd7abbe7eae1b1dc19a4145450b21b6cbce
Timothy Warren 3 years ago
parent
commit
d4a8231947
25 changed files with 321 additions and 175 deletions
  1. +29
    -0
      .editorconfig
  2. +1
    -3
      lib/NodeQuery.js
  3. +1
    -3
      lib/QueryBuilder.js
  4. +8
    -0
      lib/adapters/Firebird/index.js
  5. +16
    -6
      lib/adapters/Firebird/node-firebird.js
  6. +0
    -3
      lib/adapters/MariaDB.js
  7. +3
    -0
      lib/adapters/MariaDB/index.js
  8. +7
    -0
      lib/adapters/Mysql/index.js
  9. +7
    -8
      lib/adapters/Mysql/mysql2.js
  10. +4
    -6
      lib/adapters/Pg/Pg.js
  11. +7
    -0
      lib/adapters/Pg/index.js
  12. +67
    -0
      lib/adapters/Sqlite/dblite.js
  13. +10
    -0
      lib/adapters/Sqlite/index.js
  14. +18
    -21
      lib/adapters/Sqlite/sqlite3.js
  15. +2
    -3
      lib/drivers/Firebird.js
  16. +1
    -1
      lib/drivers/Sqlite.js
  17. +1
    -0
      package.json
  18. +38
    -0
      test/adapters/00node-firebird_test.js
  19. +8
    -27
      test/adapters/dblite_test.js
  20. +2
    -17
      test/adapters/mysql2_test.js
  21. +0
    -40
      test/adapters/node-firebird_test.js
  22. +0
    -14
      test/adapters/pg_test.js
  23. +63
    -0
      test/adapters/sqlite3_test.js
  24. +7
    -2
      test/base/adapterPromiseTestRunner.js
  25. +21
    -21
      test/query-parser_test.js

+ 29
- 0
.editorconfig View File

@@ -0,0 +1,29 @@
# EditorConfig is awesome: http://EditorConfig.org

# top-most EditorConfig file
root = true

# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true

# Matches multiple files with brace expansion notation
# Set default charset
[*]
charset = utf-8

# Tab indentation (no size specified)
[*]
indent_style = tab
indent_size = 4

# Indentation override for all JS under lib directory
[*.js]
indent_size = 2

# Matches the exact files either package.json or .travis.yml
[*.yml]
indent_style = space
indent_size = 2


+ 1
- 3
lib/NodeQuery.js View File

@@ -55,10 +55,8 @@ class NodeQuery {
const driver = require(`./drivers/${drivername}`);
const Adapter = require(`./adapters/${drivername}`);

let adapter = new Adapter(config.connection);
let adapter = Adapter(config);
this.instance = new QueryBuilder(driver, adapter);
} else {
throw new Error('What am I supposed to do without any config options, guess?');
}
}


+ 1
- 3
lib/QueryBuilder.js View File

@@ -23,8 +23,7 @@ class QueryBuilder extends QueryBuilderBase {
* @return {Promise} - Promise with result of query
*/
query (sql, params) {
return this.adapter.execute(sql, params)
.catch(e => console.error(e));
return this.adapter.execute(sql, params);
}

/**
@@ -546,7 +545,6 @@ class QueryBuilder extends QueryBuilderBase {
* @return {Promise} - If no callback is passed, a promise is returned
*/
update (table, data) {

if (data) {
this.set(data);
}

+ 8
- 0
lib/adapters/Firebird/index.js View File

@@ -0,0 +1,8 @@
'use strict';

const NodeFirebird = require('./node-firebird');

module.exports = config => {
return new NodeFirebird(config.connection);
};


lib/adapters/Firebird.js → lib/adapters/Firebird/node-firebird.js View File

@@ -1,10 +1,10 @@
'use strict';

const Adapter = require('../Adapter');
const Adapter = require('../../Adapter');
const Result = require('../../Result');
const fb = require('node-firebird');

class Firebird extends Adapter {

constructor (config) {
super({});
this.instance = new Promise((resolve, reject) => {
@@ -13,7 +13,7 @@ class Firebird extends Adapter {
return reject(err);
}

return resolve(instance)
return resolve(instance);
});
});
}
@@ -33,12 +33,22 @@ class Firebird extends Adapter {
return reject(err);
}

return resolve(result);
})
return resolve(this.transformResult(result));
});
});
});
}

/**
* Transform the adapter's result into a standard format
*
* @param {*} originalResult - the original result object from the driver
* @return {Result} - the new result object
*/
transformResult (originalResult) {
return new Result(originalResult);
}

/**
* Close the current database connection
* @return {void}
@@ -48,4 +58,4 @@ class Firebird extends Adapter {
}
}

module.exports = Firebird;
module.exports = Firebird;

+ 0
- 3
lib/adapters/MariaDB.js View File

@@ -1,3 +0,0 @@
'use strict';

module.exports = require('./Mysql');

+ 3
- 0
lib/adapters/MariaDB/index.js View File

@@ -0,0 +1,3 @@
'use strict';

module.exports = require('../Mysql');

+ 7
- 0
lib/adapters/Mysql/index.js View File

@@ -0,0 +1,7 @@
'use strict';

const Mysql2 = require('./mysql2');

module.exports = config => {
return new Mysql2(config.connection);
};

lib/adapters/Mysql.js → lib/adapters/Mysql/mysql2.js View File

@@ -1,9 +1,8 @@
'use strict';

const Adapter = require('../Adapter');
const Result = require('../Result');
const helpers = require('../helpers');
const getArgs = require('getargs');
const Adapter = require('../../Adapter');
const Result = require('../../Result');
const helpers = require('../../helpers');
const mysql2 = require('mysql2/promise');

class Mysql extends Adapter {
@@ -41,12 +40,12 @@ class Mysql extends Adapter {
*
* @param {String} sql - The sql with placeholders
* @param {Array|undefined} params - The values to insert into the query
* @return {Promise}
* @return {Promise} Result of query
*/
execute (sql, params) {
return this.instance.then(conn => {
return conn.execute(sql, params);
}).then(result => this.transformResult(result));
return this.instance
.then(conn => conn.execute(sql, params))
.then(result => this.transformResult(result));
}
}


lib/adapters/Pg.js → lib/adapters/Pg/Pg.js View File

@@ -1,13 +1,12 @@
'use strict';

const Adapter = require('../Adapter');
const Result = require('../Result');
const helpers = require('../helpers');
const Adapter = require('../../Adapter');
const Result = require('../../Result');
const helpers = require('../../helpers');
const pg = require('pg');
const url = require('url');

class Pg extends Adapter {

constructor (config) {
let instance = null;
let connectionString = '';
@@ -71,7 +70,6 @@ class Pg extends Adapter {
* @return {void|Promise} - Returns a promise if no callback is provided
*/
execute (sql, params) {

// Replace question marks with numbered placeholders, because this adapter is different...
let count = 0;
sql = sql.replace(/\?/g, () => {
@@ -81,7 +79,7 @@ class Pg extends Adapter {

return this.instance.then(conn => {
return new Promise((resolve, reject) => {
conn.query(sql, params, (err, result) =>
conn.query(sql, params, (err, result) =>
(err)
? reject(err)
: resolve(this.transformResult(result))

+ 7
- 0
lib/adapters/Pg/index.js View File

@@ -0,0 +1,7 @@
'use strict';

const Pg = require('./pg');

module.exports = config => {
return new Pg(config.connection);
};

+ 67
- 0
lib/adapters/Sqlite/dblite.js View File

@@ -0,0 +1,67 @@
'use strict';

const Adapter = require('../../Adapter');
const Result = require('../../Result');
const helpers = require('../../helpers');
const dbliteAdapter = require('dblite');

class SqliteDblite extends Adapter {
constructor (config) {
let file = (helpers.isString(config)) ? config : config.file;

const instance = new Promise((resolve, reject) => {
let conn = dbliteAdapter(file);

// Stop the stupid 'bye bye' message being output
conn.on('close', () => {});

conn.on('error', err => {
reject(err);
});

// Make sure to actually pass on the connection!
return resolve(conn);
});

super(instance);
}

/**
* Run the sql query as a prepared statement
*
* @param {String} sql - The sql with placeholders
* @param {Array} params - The values to insert into the query
* @return {Promise} - Returns a promise if no callback is provided
*/
execute (sql, params) {
return this.instance.then(conn => new Promise((resolve, reject) => {
return conn.query(sql, params, (err, rows) => {
if (err) {
return reject(err);
}
return resolve(this.transformResult(rows));
});
}));
}

/**
* Transform the adapter's result into a standard format
*
* @param {*} originalResult - the original result object from the driver
* @return {Result} - the new result object
*/
transformResult (originalResult) {
return new Result(originalResult);
}

/**
* Close the current database connection
*
* @return {void}
*/
close () {
this.instance.then(conn => conn.close());
}
}

module.exports = SqliteDblite;

+ 10
- 0
lib/adapters/Sqlite/index.js View File

@@ -0,0 +1,10 @@
'use strict';

module.exports = config => {
const Implementation = (config.adapter && config.adapter === 'dblite')
? require('./dblite')
: require('./sqlite3');

return new Implementation(config.connection);
};


lib/adapters/Sqlite.js → lib/adapters/Sqlite/sqlite3.js View File

@@ -1,25 +1,23 @@
'use strict';

const Adapter = require('../Adapter');
const Result = require('../Result');
const helpers = require('../helpers');
const dbliteAdapter = require('dblite');
const Adapter = require('../../Adapter');
const Result = require('../../Result');
const helpers = require('../../helpers');
const sqlite3 = require('sqlite3').verbose();

class Sqlite extends Adapter {
class SqliteSqlite3 extends Adapter {
constructor (config) {
let file = (helpers.isString(config)) ? config : config.file;

const instance = Promise.resolve(dbliteAdapter(file)).then(conn => {
// Stop the stupid 'bye bye' message being output
conn.on('close', () => {});

conn.on('error', (err) => {
throw new Error(err);
const instance = new Promise((resolve, reject) => {
let conn = new sqlite3.Database(file, sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE, err => {
if (err) {
reject(err);
}
});

// Make sure to actually pass on the connection!
return conn;
}).catch(e => console.error(e));
conn.on('open', resolve(conn));
});

super(instance);
}
@@ -42,15 +40,14 @@ class Sqlite extends Adapter {
* @return {Promise} - Returns a promise if no callback is provided
*/
execute (sql, params) {

return this.instance.then(conn => {
return conn.query(sql, params, (err, result) => {
return this.instance.then(conn => new Promise((resolve, reject) => {
conn.all(sql, params, (err, rows) => {
if (err) {
throw new Error(err);
return reject(err);
}
return Promise.resolve(this.instance).then(() => result);
return resolve(this.transformResult(rows));
});
});
}));
}

/**
@@ -63,4 +60,4 @@ class Sqlite extends Adapter {
}
}

module.exports = Sqlite;
module.exports = SqliteSqlite3;

+ 2
- 3
lib/drivers/Firebird.js View File

@@ -24,8 +24,7 @@ module.exports = (() => {
driver.limit = (origSql, limit, offset) => {
let sql = `FIRST ${limit}`;

if (helpers.isNumber(offset))
{
if (helpers.isNumber(offset)) {
sql += ` SKIP ${offset}`;
}

@@ -43,4 +42,4 @@ module.exports = (() => {
};

return driver;
})();
})();

+ 1
- 1
lib/drivers/Sqlite.js View File

@@ -54,7 +54,7 @@ module.exports = (() => {

return {
sql: sql,
values: null
values: undefined
};
};


+ 1
- 0
package.json View File

@@ -42,6 +42,7 @@
"node-firebird": "^0.7.5",
"pg": "^6.0.0",
"require-reload": "~0.2.2",
"sqlite3": "^3.1.8",
"xregexp": "^3.0.0"
},
"devDependencies": {

+ 38
- 0
test/adapters/00node-firebird_test.js View File

@@ -0,0 +1,38 @@
/* eslint-env node, mocha */
'use strict';

// Load the test base
const path = require('path');
const reload = require('require-reload')(require);
const testBase = reload('../base');
const expect = testBase.expect;
const testRunner = testBase.promiseTestRunner;

// Skip on CI
if (!(process.env.CI || process.env.TRAVIS)) {
// Load the test config file
let adapterName = 'node-firebird';
const config = reload('../config.json')[adapterName];
config.connection.database = path.join(__dirname, config.connection.database);
let nodeQuery = reload('../../lib/NodeQuery')(config);

let qb = nodeQuery.getQuery();

suite('Firebird adapter tests -', () => {
test('nodeQuery.getQuery = nodeQuery.init', () => {
expect(nodeQuery.getQuery())
.to.be.deep.equal(qb);
});
test('insertBatch throws error', () => {
expect(() => {
qb.driver.insertBatch('create_test', []);
}).to.throw(Error, 'Not Implemented');
});

testRunner(qb);

suiteTeardown(() => {
qb.end();
});
});
}

+ 8
- 27
test/adapters/dblite_test.js View File

@@ -4,11 +4,9 @@
// Load the test base
const reload = require('require-reload')(require);
reload.emptyCache();
const fs = require('fs');
const testBase = reload('../base');
const expect = testBase.expect;
const testRunner = testBase.promiseTestRunner;
// let tests = reload('../base/tests');

// Load the test config file
const config = testBase.config;
@@ -20,33 +18,16 @@ let qb = nodeQuery.getQuery();
suite('Dblite adapter tests -', () => {
suiteSetup(done => {
// Set up the sqlite database
fs.readFile(`${__dirname}/../sql/sqlite.sql`, 'utf8', (err, data) => {
if (err) {
return done(err);
}

qb.query(data)
.then(() => done)
.catch(e => done(e));
});
const createTest = 'CREATE TABLE IF NOT EXISTS "create_test" ("id" INTEGER PRIMARY KEY, "key" TEXT, "val" TEXT);';
const createJoin = 'CREATE TABLE IF NOT EXISTS "create_join" ("id" INTEGER PRIMARY KEY, "key" TEXT, "val" TEXT);';

qb.query(createTest)
.then(() => qb.query(createJoin))
.then(() => {
return done();
});
});

// ---------------------------------------------------------------------------
// Callback Tests
// ---------------------------------------------------------------------------

/* testRunner(qb, (err, result, done) => {
expect(err).is.not.ok;
expect(result.rows).is.an('array');
expect(result.columns).is.an('array');
expect(result.rowCount()).to.not.be.undefined;
expect(result.columnCount()).to.not.be.undefined;
done();
}); */

// ---------------------------------------------------------------------------
// Promise Tests
// ---------------------------------------------------------------------------
testRunner(qb);
test('Promise - Select with function and argument in WHERE clause', () => {
let promise = qb.select('id')

test/adapters/mysql2_est.js → test/adapters/mysql2_test.js View File

@@ -22,21 +22,6 @@ suite('Mysql2 adapter tests -', () => {
.to.be.deep.equal(qb);
});

// --------------------------------------------------------------------------
// Callback Tests
// --------------------------------------------------------------------------
/* testRunner(qb, (err, result, done) => {
expect(err).is.not.ok;
expect(result.rows).is.an('array');
expect(result.columns).is.an('array');
expect(result.rowCount()).to.not.be.undefined;
expect(result.columnCount()).to.not.be.undefined;
done();
}); */

// ---------------------------------------------------------------------------
// Promise Tests
// ---------------------------------------------------------------------------
testRunner(qb);
test('Promise - Select with function and argument in WHERE clause', () => {
let promise = qb.select('id')
@@ -70,7 +55,7 @@ suite('Mysql2 adapter tests -', () => {
return expect(qb.insertBatch('create_test', data)).to.be.fulfilled;
});

suiteTeardown(() => {
/* suiteTeardown(() => {
qb.end();
});
}); */
});

+ 0
- 40
test/adapters/node-firebird_test.js View File

@@ -1,40 +0,0 @@
'use strict';

// Load the test base
const path = require('path');
const reload = require('require-reload')(require);
const testBase = reload('../base');
const expect = testBase.expect;
const testRunner = testBase.promiseTestRunner;

// Skip on CI
if (process.env.CI || process.env.TRAVIS) {
return;
}

// Load the test config file
let adapterName = 'node-firebird';
let Firebird = reload(adapterName);
const config = reload('../config.json')[adapterName];
config.connection.database = path.join(__dirname, config.connection.database);
let nodeQuery = reload('../../lib/NodeQuery')(config);

let qb = nodeQuery.getQuery();

suite('Firebird adapter tests -', () => {
test('nodeQuery.getQuery = nodeQuery.init', () => {
expect(nodeQuery.getQuery())
.to.be.deep.equal(qb);
});
test('insertBatch throws error', () => {
expect(() => {
qb.driver.insertBatch('create_test', []);
}).to.throw(Error, 'Not Implemented');
});

testRunner(qb);

suiteTeardown(() => {
qb.end();
});
});

+ 0
- 14
test/adapters/pg_test.js View File

@@ -43,20 +43,6 @@ suite('Pg adapter tests -', () => {
}
});

// --------------------------------------------------------------------------
// Callback Tests
// --------------------------------------------------------------------------
/* testRunner(qb, (err, result, done) => {
expect(err).is.not.ok;
expect(result.rows).is.an('array');
expect(result.rowCount()).to.not.be.undefined;
expect(result.columnCount()).to.not.be.undefined;
done();
}); */
// --------------------------------------------------------------------------
// Promise Tests
// --------------------------------------------------------------------------
testRunner(qb);
test('Promise - Select with function and argument in WHERE clause', () => {
let promise = qb.select('id')

+ 63
- 0
test/adapters/sqlite3_test.js View File

@@ -0,0 +1,63 @@
/* eslint-env node, mocha */
'use strict';

// Load the test base
const reload = require('require-reload')(require);
reload.emptyCache();
const testBase = reload('../base');
const expect = testBase.expect;
const testRunner = testBase.promiseTestRunner;

// Load the test config file
const config = testBase.config;

// Set up the query builder object
let nodeQuery = require('../../lib/NodeQuery')(config.sqlite3);
let qb = nodeQuery.getQuery();

suite('Sqlite3 adapter tests -', () => {
suiteSetup(done => {
// Set up the sqlite database
const createTest = 'CREATE TABLE IF NOT EXISTS "create_test" ("id" INTEGER PRIMARY KEY, "key" TEXT, "val" TEXT);';
const createJoin = 'CREATE TABLE IF NOT EXISTS "create_join" ("id" INTEGER PRIMARY KEY, "key" TEXT, "val" TEXT);';

qb.query(createTest)
.then(() => qb.query(createJoin))
.then(() => {
return done();
});
});

testRunner(qb);
test('Promise - Select with function and argument in WHERE clause', () => {
let promise = qb.select('id')
.from('create_test')
.where('id', 'ABS(-88)')
.get();

expect(promise).to.be.fulfilled;
});
test('Promise - Test Insert Batch', () => {
let data = [
{
id: 544,
key: 3,
val: Buffer.from('7')
}, {
id: 89,
key: 34,
val: Buffer.from('10 o\'clock')
}, {
id: 48,
key: 403,
val: Buffer.from('97')
}
];

let promise = qb.insertBatch('create_test', data);
expect(promise).to.be.fulfilled;
});
suiteTeardown(() => {
qb.end();
});
});

+ 7
- 2
test/base/adapterPromiseTestRunner.js View File

@@ -15,7 +15,7 @@ module.exports = function promiseTestRunner (qb) {
suite(suiteName, () => {
let currentSuite = tests[suiteName];
Object.keys(currentSuite).forEach(testDesc => {
test(testDesc, () => {
test(testDesc, done => {
const methodObj = currentSuite[testDesc];
const methodNames = Object.keys(methodObj);
let results = [];
@@ -35,7 +35,12 @@ module.exports = function promiseTestRunner (qb) {
});

let promise = results.pop();
return expect(promise).to.be.fulfilled;
promise.then(result => {
expect(result.rows).is.an('array');
expect(result.rowCount()).to.not.be.undefined;
expect(result.columnCount()).to.not.be.undefined;
return done();
}).catch(e => done(e));
});
});
});

+ 21
- 21
test/query-parser_test.js View File

@@ -14,30 +14,30 @@ const State = require('../lib/State');
// Simulate query builder state
let state = new State();

let mixedSet = function mixedSet(letName, valType, key, val) {
let obj = {};
let mixedSet = function mixedSet (letName, valType, key, val) {
let obj = {};

if (helpers.isScalar(key) && !helpers.isUndefined(val)) {
// Convert key/val pair to a simple object
obj[key] = val;
} else if (helpers.isScalar(key) && helpers.isUndefined(val)) {
// If just a string for the key, and no value, create a simple object with duplicate key/val
obj[key] = key;
} else {
obj = key;
}
if (helpers.isScalar(key) && !helpers.isUndefined(val)) {
// Convert key/val pair to a simple object
obj[key] = val;
} else if (helpers.isScalar(key) && helpers.isUndefined(val)) {
// If just a string for the key, and no value, create a simple object with duplicate key/val
obj[key] = key;
} else {
obj = key;
}

Object.keys(obj).forEach(k => {
// If a single value for the return
if (['key', 'value'].indexOf(valType) !== -1) {
let pushVal = (valType === 'key') ? k : obj[k];
state[letName].push(pushVal);
} else {
state[letName][k] = obj[k];
}
});
Object.keys(obj).forEach(k => {
// If a single value for the return
if (['key', 'value'].indexOf(valType) !== -1) {
let pushVal = (valType === 'key') ? k : obj[k];
state[letName].push(pushVal);
} else {
state[letName][k] = obj[k];
}
});

return state[letName];
return state[letName];
};

let whereMock = function (key, val) {

Loading…
Cancel
Save