Really ugly progress commit

Start work of using promises to wrap connections and removing old callback api.
This commit is contained in:
Timothy Warren 2016-11-10 22:10:45 -05:00
parent 3ffe111df1
commit 7fbbff41c8
16 changed files with 101 additions and 483 deletions

View File

@ -1,18 +1,17 @@
'use strict';
/**
* Class that wraps database connection libraries
*
* @private
* @param {Object} instance - The connection object
* @param {Promise} instance - The connection object
*/
class Adapter {
/**
* Invoke an adapter
*
* @constructor
* @param {Object} instance - The connection object
* @param {Promise} instance - Promise holding connection object
*/
constructor (instance) {
this.instance = instance;
@ -23,10 +22,9 @@ class Adapter {
*
* @param {String} sql - The sql with placeholders
* @param {Array} params - The values to insert into the query
* @param {Function} [callback] - Callback to run when a response is recieved
* @return {void|Promise} - returns a promise if no callback is passed
* @return {Promise} - returns a promise if no callback is passed
*/
execute (/* sql, params, callback */) {
execute (sql, params) {
throw new Error('Correct adapter not defined for query execution');
}
@ -45,7 +43,7 @@ class Adapter {
* @return {void}
*/
close () {
this.instance.end();
this.instance.then(conn => conn.end());
}
}

View File

@ -22,10 +22,10 @@ class QueryBuilder extends QueryBuilderBase {
* @param {string} sql - The sql to execute
* @param {array} [params] - The query parameters
* @param {function} [callback] - Optional callback
* @return {void|Promise} - Returns a promise if no callback is supplied
* @return {Promise} - Returns a promise if no callback is supplied
*/
query (/* sql:string, [params]:array, [callback]:function */) {
return this.adapter.execute.apply(this.adapter, arguments);
query (string, params) {
return this.adapter.execute(string, params);
}
/**

View File

@ -6,14 +6,16 @@ const fb = require('node-firebird');
class Firebird extends Adapter {
constructor(config) {
constructor (config) {
super({});
fb.attach(config, (err, instance) => {
this.instance = instance;
this.instance = new Promise((resolve, reject) => {
fb.attach(config, (err, instance) => {
if (err) {
return reject(err);
}
if (err) {
throw new Error(err);
}
return resolve(instance)
});
});
}
@ -22,36 +24,28 @@ class Firebird extends Adapter {
*
* @param {String} sql - The sql with placeholders
* @param {Array} params - The values to insert into the query
* @param {Function} [callback] - Callback to run when a response is recieved
* @return {void|Promise} - Returns a promise if no callback is provided
* @return {Promise} - Returns a promise if no callback is provided
*/
execute(/*sql, params, callback*/) {
let args = getArgs('sql:string, [params], [callback]:function', arguments);
execute (sql, params) {
return this.instance.then(conn => {
return new Promise((resolve, reject) => {
conn.query(args.sql. args.params, (err, result) => {
if (err) {
return reject(err);
}
if (! args.callback) {
//return instance.queryAsync(args.sql, args.params);
if (! args.callback) {
return new Promise((resolve, reject) => {
this.instance.query(args.sql, args.params, (err, result) => {
if (err) {
return reject(err);
}
return resolve(result);
});
});
}
}
return this.instance.query(args.sql, args.params, args.callback);
return resolve(result);
})
});
});
}
/**
* Close the current database connection
* @return {void}
*/
close() {
this.instance.detach();
close () {
this.instance.then(conn => conn.detach());
}
}

View File

@ -4,17 +4,12 @@ const Adapter = require('../Adapter');
const Result = require('../Result');
const helpers = require('../helpers');
const getArgs = require('getargs');
const mysql2 = require('mysql2');
const mysql2 = require('mysql2/promise');
class Mysql extends Adapter {
constructor (config) {
let instance = mysql2.createConnection(config);
instance.connect(err => {
if (err) {
throw new Error(err);
}
});
const instance = mysql2.createConnection(config);
super(instance);
}
@ -45,26 +40,13 @@ class Mysql extends Adapter {
* 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
* @param {Function} [callback] - Callback to run when a response is recieved
* @return {void|Promise} - Returns a promise if no callback is provided
* @param {Array|undefined} params - The values to insert into the query
* @return {Promise}
*/
execute (/* sql, params, callback */) {
let args = getArgs('sql:string, [params]:array, [callback]:function', arguments);
if (!args.callback) {
return new Promise((resolve, reject) => {
this.instance.execute(args.sql, args.params, (err, result) =>
(err)
? reject(err)
: resolve(this.transformResult(result))
);
});
}
return this.instance.execute(args.sql, args.params, (err, rows) =>
args.callback(err, this.transformResult(rows))
);
execute (sql, params) {
return this.instance.then(conn => {
return conn.execute(sql, params);
}).then(result => this.transformResult(result));
}
}

View File

@ -2,17 +2,26 @@
const Adapter = require('../Adapter');
const Result = require('../Result');
const getArgs = require('getargs');
const helpers = require('../helpers');
const dbliteAdapter = require('dblite');
class Sqlite extends Adapter {
constructor (config) {
let file = (helpers.isString(config)) ? config : config.file;
super(dbliteAdapter(file));
// Stop the stupid 'bye bye' message being output
this.instance.on('close', () => {});
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);
});
// Make sure to actually pass on the connection!
return conn;
}).catch(e => console.error(e));
super(instance);
}
/**
@ -30,24 +39,17 @@ class Sqlite extends Adapter {
*
* @param {String} sql - The sql with placeholders
* @param {Array} params - The values to insert into the query
* @param {Function} [callback] - Callback to run when a response is recieved
* @return {void|Promise} - Returns a promise if no callback is provided
* @return {Promise} - Returns a promise if no callback is provided
*/
execute (/* sql, params, callback */) {
let args = getArgs('sql:string, [params]:array, [callback]:function', arguments);
execute (sql, params) {
if (!args.callback) {
return new Promise((resolve, reject) => {
this.instance.query(args.sql, args.params, (err, result) =>
(err)
? reject(err)
: resolve(this.transformResult(result))
);
return this.instance.then(conn => {
return conn.query(sql, params, (err, result) => {
if (err) {
throw new Error(err);
}
return Promise.resolve(this.instance).then(() => result);
});
}
return this.instance.query(args.sql, args.params, (err, res) => {
args.callback(err, this.transformResult(res));
});
}
@ -57,7 +59,7 @@ class Sqlite extends Adapter {
* @return {void}
*/
close () {
this.instance.close();
this.instance.then(conn => conn.close());
}
}

View File

@ -24,11 +24,9 @@ module.exports = (() => {
// Get the data values to insert, so they can
// be parameterized
let sql = '';
let vals = [];
let cols = [];
let fields = [];
let first = data.shift();
let vals = [];
data.forEach(obj => {
let row = [];
Object.keys(obj).forEach(key => {
@ -41,7 +39,8 @@ module.exports = (() => {
// Get the field names from the keys of the first
// object to be inserted
fields = Object.keys(first);
let fields = Object.keys(first);
let cols = [];
fields.forEach(key => {
cols.push(`'${driver._quote(first[key])}' AS ${driver.quoteIdentifiers(key)}`);
});

View File

@ -1,10 +1,10 @@
{
"name": "ci-node-query",
"version": "4.0.1",
"version": "5.0.0",
"description": "A query builder for node based on the one in CodeIgniter",
"author": "Timothy J Warren <tim@timshomepage.net>",
"engines": {
"node": ">=4.0.0"
"node": ">=6.0.0"
},
"files": [
"lib/"
@ -39,18 +39,18 @@
"getargs": "~0.0.8",
"glob": "^7.0.3",
"mysql2": "^1.0.0-rc.1",
"node-firebird": "^0.7.5",
"pg": "^6.0.0",
"require-reload": "~0.2.2",
"xregexp": "^3.0.0"
},
"devDependencies": {
"chai": "^3.5.0",
"chai-as-promised": "^5.2.0",
"documentation": "",
"chai-as-promised": "^6.0.0",
"documentation": "^3.0.4",
"eslint": "^3.5.0",
"glob": "~6.0.4",
"globstar": "^1.0.0",
"happiness": "^5.5.0",
"happiness": "^7.1.2",
"istanbul": "~0.4.2",
"mocha": "^3.0.0",
"npm-run-all": "^3.0.0",

View File

@ -1,5 +0,0 @@
sonar.projectKey=node-query
sonar.projectName=NodeJS Query Builder
sonar.projectVersion=3.1.0
sonar.sources=lib
sonar.javascript.lcov.reportPath=coverage/lcov.info

Binary file not shown.

View File

@ -7,8 +7,7 @@ reload.emptyCache();
const fs = require('fs');
const testBase = reload('../base');
const expect = testBase.expect;
const promiseTestRunner = testBase.promiseTestRunner;
const testRunner = testBase.testRunner;
const testRunner = testBase.promiseTestRunner;
// let tests = reload('../base/tests');
// Load the test config file
@ -26,7 +25,9 @@ suite('Dblite adapter tests -', () => {
return done(err);
}
qb.query(data, () => done());
qb.query(data)
.then(() => done)
.catch(e => done(e));
});
});
@ -34,50 +35,19 @@ suite('Dblite adapter tests -', () => {
// Callback Tests
// ---------------------------------------------------------------------------
testRunner(qb, (err, result, done) => {
/* 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();
});
test('Callback - Select with function and argument in WHERE clause', done => {
qb.select('id')
.from('create_test')
.where('id', 'ABS(-88)')
.get((err, rows) => {
expect(err).is.not.ok;
return done();
});
});
test('Callback - Test Insert Batch', done => {
let data = [
{
id: 544,
key: 3,
val: new Buffer('7')
}, {
id: 89,
key: 34,
val: new Buffer('10 o\'clock')
}, {
id: 48,
key: 403,
val: new Buffer('97')
}
];
qb.insertBatch('create_test', data, err => {
expect(err).is.not.ok;
return done();
});
});
}); */
// ---------------------------------------------------------------------------
// Promise Tests
// ---------------------------------------------------------------------------
promiseTestRunner(qb);
testRunner(qb);
test('Promise - Select with function and argument in WHERE clause', () => {
let promise = qb.select('id')
.from('create_test')
@ -91,21 +61,19 @@ suite('Dblite adapter tests -', () => {
{
id: 544,
key: 3,
val: new Buffer('7')
val: Buffer.from('7')
}, {
id: 89,
key: 34,
val: new Buffer('10 o\'clock')
val: Buffer.from('10 o\'clock')
}, {
id: 48,
key: 403,
val: new Buffer('97')
val: Buffer.from('97')
}
];
let promise = qb.query(qb.driver.truncate('create_test')).then(
() => qb.insertBatch('create_test', data)
);
let promise = qb.insertBatch('create_test', data);
expect(promise).to.be.fulfilled;
});
suiteTeardown(() => {

View File

@ -6,8 +6,7 @@ const reload = require('require-reload')(require);
reload.emptyCache();
const testBase = reload('../base');
const expect = testBase.expect;
const promiseTestRunner = testBase.promiseTestRunner;
const testRunner = testBase.testRunner;
const testRunner = testBase.promiseTestRunner;
// Load the test config file
let adapterName = 'mysql2';
@ -26,55 +25,19 @@ suite('Mysql2 adapter tests -', () => {
// --------------------------------------------------------------------------
// Callback Tests
// --------------------------------------------------------------------------
testRunner(qb, (err, result, done) => {
/* 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();
});
test('Callback - Select with function and argument in WHERE clause', done => {
qb.select('id')
.from('create_test')
.where('id', 'CEILING(SQRT(88))')
.get((err, rows) => {
expect(err).is.not.ok;
return done();
});
});
test('Callback - Test Truncate', done => {
qb.truncate('create_test', (err, res) => {
return done(err);
});
});
test('Callback - Test Insert Batch', done => {
let data = [
{
id: 5441,
key: 3,
val: new Buffer('7')
}, {
id: 891,
key: 34,
val: new Buffer('10 o\'clock')
}, {
id: 481,
key: 403,
val: new Buffer('97')
}
];
qb.insertBatch('create_test', data, (err, res) => {
expect(err).is.not.ok;
return done(err);
});
});
}); */
// ---------------------------------------------------------------------------
// Promise Tests
// ---------------------------------------------------------------------------
promiseTestRunner(qb);
testRunner(qb);
test('Promise - Select with function and argument in WHERE clause', () => {
let promise = qb.select('id')
.from('create_test')
@ -92,11 +55,11 @@ suite('Mysql2 adapter tests -', () => {
{
id: 5442,
key: 4,
val: new Buffer('7')
val: Buffer.from('7')
}, {
id: 892,
key: 35,
val: new Buffer('10 o\'clock')
val: Buffer.from('10 o\'clock')
}, {
id: 482,
key: 404,

View File

@ -5,10 +5,8 @@
const path = require('path');
const reload = require('require-reload')(require);
const testBase = reload('../base');
const promisify = require('../../lib/promisify');
const expect = reload('chai').expect;
const testRunner = testBase.testRunner;
const promiseTestRunner = testBase.promiseTestRunner;
const testRunner = testBase.promiseTestRunner;
// Skip on CI
if (process.env.CI || process.env.TRAVIS) {
@ -35,20 +33,8 @@
}).to.throw(Error, 'Not Implemented');
});
//---------------------------------------------------------------------------
// Callback Tests
//---------------------------------------------------------------------------
testRunner(qb, (err, done) => {
expect(err).is.not.ok;
done();
});
testRunner(qb);
//---------------------------------------------------------------------------
// Promise Tests
//---------------------------------------------------------------------------
qb.adapter.execute(qb.driver.truncate('create_test')).then(() => {
promiseTestRunner(qb);
});
suiteTeardown(() => {
qb.end();
});

View File

@ -6,8 +6,7 @@ const reload = require('require-reload')(require);
reload.emptyCache();
const testBase = reload('../base');
const expect = testBase.expect;
const promiseTestRunner = testBase.promiseTestRunner;
const testRunner = testBase.testRunner;
const testRunner = testBase.promiseTestRunner;
// Load the test config file
let adapterName = 'pg';
@ -47,56 +46,18 @@ suite('Pg adapter tests -', () => {
// --------------------------------------------------------------------------
// Callback Tests
// --------------------------------------------------------------------------
testRunner(qb, (err, result, done) => {
/* 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();
});
test('Callback - Select with function and argument in WHERE clause', done => {
qb.select('id')
.from('create_test')
.where('id', 'CEILING(SQRT(88))')
.get((err, rows) => {
expect(rows).is.ok;
expect(err).is.not.ok;
return done(err);
});
});
test('Callback - Test Truncate', done => {
qb.truncate('create_test', (err, res) => {
expect(err).is.not.ok;
return done(err);
});
});
test('Callback - Test Insert Batch', done => {
let data = [
{
id: 5441,
key: 3,
val: new Buffer('7')
}, {
id: 891,
key: 34,
val: new Buffer('10 o\'clock')
}, {
id: 481,
key: 403,
val: new Buffer('97')
}
];
qb.insertBatch('create_test', data, (err, res) => {
expect(err).is.not.ok;
return done(err);
});
});
}); */
// --------------------------------------------------------------------------
// Promise Tests
// --------------------------------------------------------------------------
promiseTestRunner(qb);
testRunner(qb);
test('Promise - Select with function and argument in WHERE clause', () => {
let promise = qb.select('id')
.from('create_test')
@ -114,15 +75,15 @@ suite('Pg adapter tests -', () => {
{
id: 544,
key: 3,
val: new Buffer('7')
val: Buffer.from('7')
}, {
id: 89,
key: 34,
val: new Buffer('10 o\'clock')
val: Buffer.from('10 o\'clock')
}, {
id: 48,
key: 403,
val: new Buffer('97')
val: Buffer.from('97')
}
];

View File

@ -11,6 +11,5 @@ module.exports = {
config: require(configFile),
expect: chai.expect,
tests: require('./base/tests'),
testRunner: require('./base/adapterCallbackTestRunner'),
promiseTestRunner: require('./base/adapterPromiseTestRunner')
};

View File

@ -1,229 +0,0 @@
/* eslint-env node, mocha */
'use strict';
// Load the test base
const chai = require('chai');
const chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised);
const expect = chai.expect;
const reload = require('require-reload')(require);
let tests = reload('../base/tests');
const helpers = reload('../../lib/helpers');
const State = reload('../../lib/State');
module.exports = function testRunner (qb, callback) {
Object.keys(tests).forEach(suiteName => {
suite(suiteName, () => {
let currentSuite = tests[suiteName];
Object.keys(currentSuite).forEach(testDesc => {
test(`Callback - ${testDesc}`, done => {
const methodObj = currentSuite[testDesc];
const methodNames = Object.keys(methodObj);
const lastMethodIndex = methodNames[methodNames.length - 1];
methodObj[lastMethodIndex].push((err, rows) => callback(err, rows, done));
methodNames.forEach(name => {
const args = methodObj[name];
const method = qb[name];
if (args[0] === 'multiple') {
args.shift();
args.forEach(argSet => {
method.apply(qb, argSet);
});
} else {
method.apply(qb, args);
}
});
});
});
});
});
suite('DB update tests -', () => {
suiteSetup(() => qb.truncate('create_test'));
test('Callback - Test Insert', done => {
qb.set('id', 98)
.set('key', '84')
.set('val', new Buffer('120'))
.insert('create_test', (err, rows) => {
return callback(err, rows, done);
});
});
test('Callback - Test Insert Object', done => {
qb.insert('create_test', {
id: 587,
key: 1,
val: new Buffer('2')
}, (err, rows) => {
return callback(err, rows, done);
});
});
test('Callback - Test Update', done => {
qb.where('id', 7)
.update('create_test', {
id: 7,
key: 'gogle',
val: new Buffer('non-word')
}, (err, rows) => {
return callback(err, rows, done);
});
});
test('Callback - Test set Array Update', done => {
let object = {
id: 22,
key: 'gogle',
val: new Buffer('non-word')
};
qb.set(object)
.where('id', 22)
.update('create_test', (err, rows) => {
return callback(err, rows, done);
});
});
test('Callback - Test where set update', done => {
qb.where('id', 36)
.set('id', 36)
.set('key', 'gogle')
.set('val', new Buffer('non-word'))
.update('create_test', (err, rows) => {
return callback(err, rows, done);
});
});
test('Callback - Test delete', done => {
qb.delete('create_test', {id: 5}, (err, rows) => {
return callback(err, rows, done);
});
});
test('Callback - Delete with where', done => {
qb.where('id', 5)
.delete('create_test', (err, rows) => {
return callback(err, rows, done);
});
});
test('Callback - Delete multiple where values', done => {
qb.delete('create_test', {
id: 5,
key: 'gogle'
}, (err, rows) => {
return callback(err, rows, done);
});
});
});
suite('Grouping tests -', () => {
test('Callback - Using grouping method', done => {
qb.select('id, key as k, val')
.from('create_test')
.groupStart()
.where('id >', 1)
.where('id <', 900)
.groupEnd()
.limit(2, 1)
.get((err, rows) => {
return callback(err, rows, done);
});
});
test('Callback - Using where first grouping', done => {
qb.select('id, key as k, val')
.from('create_test')
.where('id !=', 5)
.groupStart()
.where('id >', 1)
.where('id <', 900)
.groupEnd()
.limit(2, 1)
.get((err, rows) => {
return callback(err, rows, done);
});
});
test('Callback - Using or grouping method', done => {
qb.select('id, key as k, val')
.from('create_test')
.groupStart()
.where('id >', 1)
.where('id <', 900)
.groupEnd()
.orGroupStart()
.where('id', 0)
.groupEnd()
.limit(2, 1)
.get((err, rows) => {
return callback(err, rows, done);
});
});
test('Callback - Using or not grouping method', done => {
qb.select('id, key as k, val')
.from('create_test')
.groupStart()
.where('id >', 1)
.where('id <', 900)
.groupEnd()
.orNotGroupStart()
.where('id', 0)
.groupEnd()
.limit(2, 1)
.get((err, rows) => {
return callback(err, rows, done);
});
});
});
suite('Get compiled tests -', () => {
test('select', () => {
let sql = qb.select('id')
.from('create_test')
.getCompiledSelect(true);
return expect(helpers.isString(sql)).to.be.true;
});
test('select from', () => {
let sql = qb.select('id')
.getCompiledSelect('create_test', true);
return expect(helpers.isString(sql)).to.be.true;
});
test('insert', () => {
let sql = qb.set('id', 3)
.getCompiledInsert('create_test');
return expect(helpers.isString(sql)).to.be.true;
});
test('update', () => {
let sql = qb.set('id', 3)
.where('id', 5)
.getCompiledUpdate('create_test');
return expect(helpers.isString(sql)).to.be.true;
});
test('delete', () => {
let sql = qb.where('id', 5)
.getCompiledDelete('create_test');
return expect(helpers.isString(sql)).to.be.true;
});
});
suite('Misc tests -', () => {
test('Get State', () => {
qb.select('foo')
.from('bar')
.where('baz', 'foobar');
let state = new State();
expect(JSON.stringify(state)).to.not.be.deep.equal(qb.getState());
});
test('Reset State', () => {
qb.select('foo')
.from('bar')
.where('baz', 'foobar');
qb.resetQuery();
let state = new State();
expect(qb.getState()).to.be.deep.equal(state);
});
});
};

View File

@ -49,7 +49,7 @@ module.exports = function promiseTestRunner (qb) {
test('Promise - Test Insert', () => {
let promise = qb.set('id', 98)
.set('key', '84')
.set('val', new Buffer('120'))
.set('val', Buffer.from('120'))
.insert('create_test');
return expect(promise).to.be.fulfilled;
@ -58,7 +58,7 @@ module.exports = function promiseTestRunner (qb) {
let promise = qb.insert('create_test', {
id: 587,
key: 1,
val: new Buffer('2')
val: Buffer.from('2')
});
return expect(promise).to.be.fulfilled;
@ -68,7 +68,7 @@ module.exports = function promiseTestRunner (qb) {
.update('create_test', {
id: 7,
key: 'gogle',
val: new Buffer('non-word')
val: Buffer.from('non-word')
});
return expect(promise).to.be.fulfilled;
@ -77,7 +77,7 @@ module.exports = function promiseTestRunner (qb) {
let object = {
id: 22,
key: 'gogle',
val: new Buffer('non-word')
val: Buffer.from('non-word')
};
let promise = qb.set(object)
@ -90,7 +90,7 @@ module.exports = function promiseTestRunner (qb) {
let promise = qb.where('id', 36)
.set('id', 36)
.set('key', 'gogle')
.set('val', new Buffer('non-word'))
.set('val', Buffer.from('non-word'))
.update('create_test');
return expect(promise).to.be.fulfilled;