Code and test tweaks

This commit is contained in:
Timothy Warren 2016-03-11 16:32:38 -05:00
parent 137f8ec693
commit 58008b46c0
19 changed files with 295 additions and 244 deletions

5
.gitignore vendored
View File

@ -1,6 +1,5 @@
build/*
coverage
coverage/*
npm-debug.log
tests/FB_TEST_DB.FD
node_modules/*
node_modules/*
.sonar/*

25
API.md
View File

@ -20,20 +20,6 @@ Return an existing query builder instance
Returns **QueryBuilder** The Query Builder object
## init
Create a query builder object
**Parameters**
- `driverType` **String** The name of the database type, eg. mysql or pg
- `connObject` **Object** A connection object from the database library
you are connecting with
- `connLib` **[String]** The name of the db connection library you are
using, eg. mysql or mysql2. Optional if the same as driverType
Returns **QueryBuilder** The Query Builder object
# QueryBuilder
Main object that builds SQL queries.
@ -439,6 +425,17 @@ query.set({foo:'bar'}); // Set with an object
Returns **QueryBuilder** The Query Builder object, for chaining
## truncate
Empties the selected database table
**Parameters**
- `table` **string** the name of the table to truncate
- `callback` **[function]** Optional callback
Returns **void or Promise** Returns a promise if no callback is supplied
## update
Run the generated update query

View File

@ -3,6 +3,7 @@
## 4.0.0
* Changed connection setup to just use a config object - the appropriate adapter object is created by the library.
* Removed mysql adapter, as mysql2 is very similar and does proper prepared statements
* Removed firebird entirely
## 3.2.0
* Added public `query` method for making arbitrary sql calls

View File

@ -10,13 +10,11 @@ A node query builder for various SQL databases, based on [CodeIgniter](http://ww
### Features
* Callback and Promise API for making database calls.
### Supported adapters
### Supported databases
* mysql
* mysql2
* pg
* dblite
* node-firebird (Not supported as of version 3.1.0, as the adapter is very difficult to test)
* Mysql (via `mysql2`)
* PostgreSQL (via `pg`)
* Sqlite (via `dblite`)
### Installation
@ -26,20 +24,20 @@ A node query builder for various SQL databases, based on [CodeIgniter](http://ww
### Basic use
```javascript
var nodeQuery = require('ci-node-query');
var connection = ... // Database module connection
// Set the database connection details
const nodeQuery = require('ci-node-query')({
"driver": "mysql",
"connection": {
"host": "localhost",
"user": "test",
"password": "",
"database": "test"
}
});
// Three arguments: database type, database connection, database connection library
var query = nodeQuery.init('mysql', connection, 'mysql2');
// The third argument is optional if the database connection library has the same name as the adapter, eg..
nodeQuery.init('mysql', connection, 'mysql');
// Can be instead
nodeQuery.init('mysql', connection);
// You can also retrieve the instance later
query = nodeQuery.getQuery();
// Get the query builder
const query = nodeQuery.getQuery();
query.select('foo')
.from('bar')

View File

@ -37,11 +37,6 @@
class='regular block'>
#getQuery
</a>
<a
href='#NodeQuery.init'
class='regular block'>
#init
</a>
<a
href='#QueryBuilder'
class='block bold'>
@ -212,6 +207,11 @@
class='regular block'>
#set
</a>
<a
href='#QueryBuilder.truncate'
class='regular block'>
#truncate
</a>
<a
href='#QueryBuilder.update'
class='regular block'>
@ -320,59 +320,6 @@
</section>
</div>
</div>
<div class='collapsible' id='NodeQuery.init'>
<a href='#NodeQuery.init'>
<code>
#init<span class='gray'>(driverType, connObject, [connLib])</span>
</code>
<div class='force-inline'>
<p>Create a query builder object</p>
</div>
</a>
<div class='collapser border px2'>
<section class='py2 clearfix'>
<h2 id='NodeQuery.init' class='mt0'>
init<span class='gray'>(driverType, connObject, [connLib])</span>
</h2>
<p>Create a query builder object</p>
<h4>Parameters</h4>
<ul class='suppress-p-margin'>
<li><code><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String">String</a></code> <strong>driverType</strong>
:
<div class='force-inline'>
<p>The name of the database type, eg. mysql or pg</p>
</div>
</li>
<li><code><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object">Object</a></code> <strong>connObject</strong>
:
<div class='force-inline'>
<p>A connection object from the database library
you are connecting with</p>
</div>
</li>
<li><code>[<code><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String">String</a></code>]</code> <strong>connLib</strong>
:
<div class='force-inline'>
<p>The name of the db connection library you are
using, eg. mysql or mysql2. Optional if the same as driverType</p>
</div>
</li>
</ul>
<h4>Returns</h4>
<code><code><a href="#QueryBuilder">QueryBuilder</a></code></code>
:
<div class='force-inline'>
<p>The Query Builder object</p>
</div>
</section>
</div>
</div>
</section>
</div><div class='py1'><section class='py2 clearfix'>
<h2 id='QueryBuilder' class='mt0'>
@ -1813,6 +1760,50 @@ prefixed with &apos;OR NOT&apos;</p>
</section>
</div>
</div>
<div class='collapsible' id='QueryBuilder.truncate'>
<a href='#QueryBuilder.truncate'>
<code>
#truncate<span class='gray'>(table, [callback])</span>
</code>
<div class='force-inline'>
<p>Empties the selected database table</p>
</div>
</a>
<div class='collapser border px2'>
<section class='py2 clearfix'>
<h2 id='QueryBuilder.truncate' class='mt0'>
truncate<span class='gray'>(table, [callback])</span>
</h2>
<p>Empties the selected database table</p>
<h4>Parameters</h4>
<ul class='suppress-p-margin'>
<li><code><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String">string</a></code> <strong>table</strong>
:
<div class='force-inline'>
<p>the name of the table to truncate</p>
</div>
</li>
<li><code>[<code><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function">function</a></code>]</code> <strong>callback</strong>
:
<div class='force-inline'>
<p>Optional callback</p>
</div>
</li>
</ul>
<h4>Returns</h4>
<code><code>void</code> or <code><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">Promise</a></code></code>
:
<div class='force-inline'>
<p>Returns a promise if no callback is supplied</p>
</div>
</section>
</div>
</div>
<div class='collapsible' id='QueryBuilder.update'>
<a href='#QueryBuilder.update'>
<code>

View File

@ -62,11 +62,11 @@ gulp.task('mocha', ['lint-tests', 'sloc'], () => {
return gulp.src(TEST_FILES)
.pipe(mocha(MOCHA_OPTIONS))
.once('error', () => {
process.exit(1);
})
process.exit(1);
})
.once('end', () => {
process.exit();
});
process.exit();
});
});
gulp.task('test', ['test-sloc', 'lint-tests'], function(cb) {
@ -78,14 +78,8 @@ gulp.task('test', ['test-sloc', 'lint-tests'], function(cb) {
mocha(MOCHA_OPTIONS),
istanbul.writeReports({
dir: './coverage',
reporters: ['clover', 'lcov', 'lcovonly', 'html', 'text']
})
.once('error', () => {
process.exit(1);
})
.once('end', () => {
process.exit();
})
reporters: ['clover', 'lcov', 'lcovonly', 'html', 'text'],
}),
]);
});
});

View File

@ -34,6 +34,11 @@ class NodeQuery {
if (config != null) {
let drivername = dbDriverMap.get(config.driver);
if (! drivername) {
throw new Error(`Selected driver (${config.driver}) does not exist!`);
}
let driver = require(`./drivers/${drivername}`);
let $adapter = require(`./adapters/${drivername}`);
@ -42,43 +47,6 @@ class NodeQuery {
}
}
/**
* Create a query builder object
*
* @param {String} driverType - The name of the database type, eg. mysql or pg
* @param {Object} connObject - A connection object from the database library
* you are connecting with
* @param {String} [connLib] - The name of the db connection library you are
* using, eg. mysql or mysql2. Optional if the same as driverType
* @return {QueryBuilder} - The Query Builder object
*/
init(driverType, connObject, connLib) {
connLib = connLib || driverType;
let paths = {
driver: `${__dirname}/drivers/${helpers.upperCaseFirst(driverType)}`,
adapter: `${__dirname}/adapters/${connLib}`,
};
Object.keys(paths).forEach(type => {
try {
fs.statSync(`${paths[type]}.js`);
} catch (e) {
throw new Error(
`Selected ${type} (${helpers.upperCaseFirst(driverType)}) does not exist!`
);
}
});
let driver = require(paths.driver);
let $adapter = require(paths.adapter);
let adapter = new $adapter(connObject);
this.instance = new QueryBuilder(driver, adapter);
return this.instance;
}
/**
* Return an existing query builder instance
*

View File

@ -339,6 +339,21 @@ class QueryBuilder extends QueryBuilderBase {
return this.state;
}
/**
* Empties the selected database table
*
* @param {string} table - the name of the table to truncate
* @param {function} [callback] - Optional callback
* @return {void|Promise} - Returns a promise if no callback is supplied
*/
truncate(/*table:string, [callback]:function*/) {
getArgs('table:string, [callback]:function', arguments);
let args = [].slice.apply(arguments);
let sql = this.driver.truncate(args.shift());
args.unshift(sql);
return this.query.apply(this, args);
}
/**
* Closes the database connection for the current adapter
*

View File

@ -29,7 +29,15 @@ class Mysql extends Adapter {
let args = getArgs('sql:string, [params]:array, [callback]:function', arguments);
if (! args.callback) {
return promisify(this.instance.execute)(args.sql, args.params);
return new Promise((resolve, reject) => {
this.instance.execute(args.sql, args.params, (err, result) => {
if (err) {
return reject(err);
}
return resolve(result);
});
});
}
return this.instance.execute(args.sql, args.params, args.callback);

View File

@ -10,6 +10,9 @@ 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', () => {});
}
/**

View File

@ -78,4 +78,29 @@ CREATE TABLE IF NOT EXISTS "create_join" ("id" INTEGER PRIMARY KEY, "key" TEXT,
expect(promise).to.be.fulfilled;
});
test('Promise - Test Insert Batch', () => {
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'),
},
];
let promise = qb.query(qb.driver.truncate('create_test')).then(
() => qb.insertBatch('create_test', data)
);
expect(promise).to.be.fulfilled;
});
suiteTeardown(() => {
qb.end();
});
});

View File

@ -10,8 +10,6 @@ const expect = testBase.expect;
const promiseTestRunner = testBase.promiseTestRunner;
const testRunner = testBase.testRunner;
let getArgs = reload('getargs');
// Load the test config file
let adapterName = 'mysql2';
let config = reload(configFile)[adapterName];
@ -19,69 +17,89 @@ let config = reload(configFile)[adapterName];
// Set up the query builder object
let nodeQuery = reload('../../lib/NodeQuery')(config);
let qb = nodeQuery.getQuery();
qb.query(qb.driver.truncate('create_test')).then(() => {
suite('Mysql2 adapter tests -', () => {
test('nodeQuery.getQuery = nodeQuery.init', () => {
expect(nodeQuery.getQuery())
.to.be.deep.equal(qb);
});
suite('Mysql2 adapter tests -', () => {
/*---------------------------------------------------------------------------
Callback Tests
---------------------------------------------------------------------------*/
testRunner(qb, (err, done) => {
expect(err).is.not.ok;
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();
});
});
suiteSetup(() => qb.truncate('create_test'));
/*---------------------------------------------------------------------------
Promise Tests
---------------------------------------------------------------------------*/
promiseTestRunner(qb);
test('Promise - Select with function and argument in WHERE clause', () => {
let promise = qb.select('id')
.from('create_test')
.where('id', 'CEILING(SQRT(88))')
.get();
test('nodeQuery.getQuery = nodeQuery.init', () => {
expect(nodeQuery.getQuery())
.to.be.deep.equal(qb);
});
expect(promise).to.be.fulfilled;
});
suiteTeardown(() => {
qb.end();
});
test('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, rows) => {
//--------------------------------------------------------------------------
// Callback Tests
//--------------------------------------------------------------------------
testRunner(qb, (err, done) => {
expect(err).is.not.ok;
return done(err);
});
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 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);
test('Promise - Select with function and argument in WHERE clause', () => {
let promise = qb.select('id')
.from('create_test')
.where('id', 'CEILING(SQRT(88))')
.get();
return expect(promise).to.be.fulfilled;
});
test('Test Insert Batch', () => {
let data = [
{
id: 5442,
key: 4,
val: new Buffer('7'),
}, {
id: 892,
key: 35,
val: new Buffer('10 o\'clock'),
}, {
id: 482,
key: 404,
val: 97,
},
];
return expect(qb.insertBatch('create_test', data)).to.be.fulfilled;
});
suiteTeardown(() => {
qb.end();
});
});

View File

@ -12,11 +12,13 @@ const testRunner = testBase.testRunner;
// Load the test config file
let adapterName = 'pg';
let config = reload(configFile)[adapterName];
let allConfig = reload(configFile);
let config = allConfig[adapterName];
// Set up the query builder object
let nodeQuery = reload('../../lib/NodeQuery')(config);
let qb = nodeQuery.getQuery();
let qb2 = null;
suite('Pg adapter tests -', () => {
test('nodeQuery.getQuery = nodeQuery.init', () => {
@ -24,26 +26,57 @@ suite('Pg adapter tests -', () => {
.to.be.deep.equal(qb);
});
/*---------------------------------------------------------------------------
Callback Tests
---------------------------------------------------------------------------*/
test('Connecting with an object also works', () => {
let config = allConfig[`${adapterName}-object`];
let nodeQuery = reload('../../lib/NodeQuery')(config);
qb2 = nodeQuery.getQuery();
return expect(qb2).to.be.ok;
});
//--------------------------------------------------------------------------
// Callback Tests
//--------------------------------------------------------------------------
testRunner(qb, (err, done) => {
expect(err).is.not.ok;
done();
return done(err);
});
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();
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'),
},
];
/*---------------------------------------------------------------------------
Promise Tests
---------------------------------------------------------------------------*/
qb.insertBatch('create_test', data, (err, res) => {
expect(err).is.not.ok;
return done(err);
});
});
//--------------------------------------------------------------------------
// Promise Tests
//--------------------------------------------------------------------------
promiseTestRunner(qb);
test('Promise - Select with function and argument in WHERE clause', () => {
let promise = qb.select('id')
@ -51,7 +84,7 @@ suite('Pg adapter tests -', () => {
.where('id', 'CEILING(SQRT(88))')
.get();
expect(promise).to.be.fulfilled;
return expect(promise).to.be.fulfilled;
});
test('Promise - Test Insert Batch', () => {
let data = [
@ -73,4 +106,8 @@ suite('Pg adapter tests -', () => {
let promise = qb.insertBatch('create_test', data);
return expect(promise).to.be.fulfilled;
});
suiteTeardown(() => {
qb.end();
qb2.end();
});
});

View File

@ -46,12 +46,7 @@ module.exports = function testRunner(qb, callback) {
});
});
suite('DB update tests -', () => {
setup(done => {
let sql = qb.driver.truncate('create_test');
qb.adapter.execute(sql, (err, res) => {
done();
});
});
suiteSetup(() => qb.truncate('create_test'));
test('Callback - Test Insert', done => {
qb.set('id', 98)
.set('key', '84')
@ -184,32 +179,32 @@ module.exports = function testRunner(qb, callback) {
.from('create_test')
.getCompiledSelect(true);
expect(helpers.isString(sql)).to.be.true;
return expect(helpers.isString(sql)).to.be.true;
});
test('select from', () => {
let sql = qb.select('id')
.getCompiledSelect('create_test', true);
expect(helpers.isString(sql)).to.be.true;
return expect(helpers.isString(sql)).to.be.true;
});
test('insert', () => {
let sql = qb.set('id', 3)
.getCompiledInsert('create_test');
expect(helpers.isString(sql)).to.be.true;
return expect(helpers.isString(sql)).to.be.true;
});
test('update', () => {
let sql = qb.set('id', 3)
.where('id', 5)
.getCompiledUpdate('create_test');
expect(helpers.isString(sql)).to.be.true;
return expect(helpers.isString(sql)).to.be.true;
});
test('delete', () => {
let sql = qb.where('id', 5)
.getCompiledDelete('create_test');
expect(helpers.isString(sql)).to.be.true;
return expect(helpers.isString(sql)).to.be.true;
});
});
suite('Misc tests -', () => {

View File

@ -40,7 +40,7 @@ module.exports = function promiseTestRunner(qb) {
});
let promise = results.pop();
expect(promise).to.be.fulfilled;
return expect(promise).to.be.fulfilled;
});
});
});
@ -48,7 +48,7 @@ module.exports = function promiseTestRunner(qb) {
suite('DB update tests -', () => {
suiteSetup(done => {
let sql = qb.driver.truncate('create_test');
qb.adapter.execute(sql).then(res => {
qb.query(sql).then(res => {
return done();
}).catch(err => {
return done(err);
@ -60,7 +60,7 @@ module.exports = function promiseTestRunner(qb) {
.set('val', new Buffer('120'))
.insert('create_test');
expect(promise).to.be.fulfilled;
return expect(promise).to.be.fulfilled;
});
test('Promise - Test Insert Object', () => {
let promise = qb.insert('create_test', {
@ -69,7 +69,7 @@ module.exports = function promiseTestRunner(qb) {
val: new Buffer('2'),
});
expect(promise).to.be.fulfilled;
return expect(promise).to.be.fulfilled;
});
test('Promise - Test Update', () => {
let promise = qb.where('id', 7)
@ -79,7 +79,7 @@ module.exports = function promiseTestRunner(qb) {
val: new Buffer('non-word'),
});
expect(promise).to.be.fulfilled;
return expect(promise).to.be.fulfilled;
});
test('Promise - Test set Array Update', () => {
let object = {
@ -92,7 +92,7 @@ module.exports = function promiseTestRunner(qb) {
.where('id', 22)
.update('create_test');
expect(promise).to.be.fulfilled;
return expect(promise).to.be.fulfilled;
});
test('Promise - Test where set update', () => {
let promise = qb.where('id', 36)
@ -101,17 +101,17 @@ module.exports = function promiseTestRunner(qb) {
.set('val', new Buffer('non-word'))
.update('create_test');
expect(promise).to.be.fulfilled;
return expect(promise).to.be.fulfilled;
});
test('Promise - Test delete', () => {
let promise = qb.delete('create_test', {id: 5});
expect(promise).to.be.fulfilled;
return expect(promise).to.be.fulfilled;
});
test('Promise - Delete with where', () => {
let promise = qb.where('id', 5)
.delete('create_test');
expect(promise).to.be.fulfilled;
return expect(promise).to.be.fulfilled;
});
test('Promise - Delete multiple where values', () => {
let promise = qb.delete('create_test', {
@ -119,7 +119,7 @@ module.exports = function promiseTestRunner(qb) {
key: 'gogle',
});
expect(promise).to.be.fulfilled;
return expect(promise).to.be.fulfilled;
});
});
suite('Grouping tests -', () => {
@ -133,7 +133,7 @@ module.exports = function promiseTestRunner(qb) {
.limit(2, 1)
.get();
expect(promise).to.be.fulfilled;
return expect(promise).to.be.fulfilled;
});
test('Promise - Using where first grouping', () => {
let promise = qb.select('id, key as k, val')
@ -146,7 +146,7 @@ module.exports = function promiseTestRunner(qb) {
.limit(2, 1)
.get();
expect(promise).to.be.fulfilled;
return expect(promise).to.be.fulfilled;
});
test('Promise - Using or grouping method', () => {
let promise = qb.select('id, key as k, val')
@ -161,7 +161,7 @@ module.exports = function promiseTestRunner(qb) {
.limit(2, 1)
.get();
expect(promise).to.be.fulfilled;
return expect(promise).to.be.fulfilled;
});
test('Promise - Using or not grouping method', () => {
let promise = qb.select('id, key as k, val')
@ -176,7 +176,7 @@ module.exports = function promiseTestRunner(qb) {
.limit(2, 1)
.get();
expect(promise).to.be.fulfilled;
return expect(promise).to.be.fulfilled;
});
});
};

View File

@ -30,7 +30,9 @@ suite('Base tests -', () => {
test('Invalid driver type', () => {
expect(() => {
nodeQuery.init('foo', {}, 'bar');
reload('../lib/NodeQuery')({
driver: 'Foo',
});
}).to.throw(Error, 'Selected driver (Foo) does not exist!');
});

View File

@ -15,9 +15,6 @@
"pg-object": {
"driver": "pg",
"connection": {
"host": "localhost",
"user": "postgres",
"password": "",
"database": "test"
}
},

View File

@ -15,9 +15,6 @@
"pg-object": {
"driver": "pg",
"connection": {
"host": "localhost",
"user": "test",
"password": "test",
"database": "test"
}
},

View File

@ -148,5 +148,11 @@ suite('Helper Module Tests -', () => {
expect(helpers.regexInArray([], /.*/)).to.be.false;
});
});
suite('upperCaseFirst -', () => {
test('Capitalizes only the first letter of the string', () => {
expect(helpers.upperCaseFirst('foobar')).to.equal('Foobar');
expect(helpers.upperCaseFirst('FOOBAR')).to.equal('FOOBAR');
});
});
});
});