diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..a20743b --- /dev/null +++ b/.editorconfig @@ -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 + diff --git a/lib/NodeQuery.js b/lib/NodeQuery.js index e4827e2..e64d696 100755 --- a/lib/NodeQuery.js +++ b/lib/NodeQuery.js @@ -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?'); } } diff --git a/lib/QueryBuilder.js b/lib/QueryBuilder.js index b64bada..268fd10 100755 --- a/lib/QueryBuilder.js +++ b/lib/QueryBuilder.js @@ -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); } diff --git a/lib/adapters/Firebird/index.js b/lib/adapters/Firebird/index.js new file mode 100644 index 0000000..1d93c17 --- /dev/null +++ b/lib/adapters/Firebird/index.js @@ -0,0 +1,8 @@ +'use strict'; + +const NodeFirebird = require('./node-firebird'); + +module.exports = config => { + return new NodeFirebird(config.connection); +}; + diff --git a/lib/adapters/Firebird.js b/lib/adapters/Firebird/node-firebird.js similarity index 65% rename from lib/adapters/Firebird.js rename to lib/adapters/Firebird/node-firebird.js index 43d6c0a..e62e7d5 100644 --- a/lib/adapters/Firebird.js +++ b/lib/adapters/Firebird/node-firebird.js @@ -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; \ No newline at end of file +module.exports = Firebird; diff --git a/lib/adapters/MariaDB.js b/lib/adapters/MariaDB.js deleted file mode 100644 index a2aab43..0000000 --- a/lib/adapters/MariaDB.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict'; - -module.exports = require('./Mysql'); diff --git a/lib/adapters/MariaDB/index.js b/lib/adapters/MariaDB/index.js new file mode 100644 index 0000000..f351d4d --- /dev/null +++ b/lib/adapters/MariaDB/index.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = require('../Mysql'); diff --git a/lib/adapters/Mysql/index.js b/lib/adapters/Mysql/index.js new file mode 100644 index 0000000..985c3f4 --- /dev/null +++ b/lib/adapters/Mysql/index.js @@ -0,0 +1,7 @@ +'use strict'; + +const Mysql2 = require('./mysql2'); + +module.exports = config => { + return new Mysql2(config.connection); +}; diff --git a/lib/adapters/Mysql.js b/lib/adapters/Mysql/mysql2.js similarity index 76% rename from lib/adapters/Mysql.js rename to lib/adapters/Mysql/mysql2.js index 7498600..438c3f3 100644 --- a/lib/adapters/Mysql.js +++ b/lib/adapters/Mysql/mysql2.js @@ -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)); } } diff --git a/lib/adapters/Pg.js b/lib/adapters/Pg/Pg.js similarity index 91% rename from lib/adapters/Pg.js rename to lib/adapters/Pg/Pg.js index 38251d0..b1f37a8 100644 --- a/lib/adapters/Pg.js +++ b/lib/adapters/Pg/Pg.js @@ -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)) diff --git a/lib/adapters/Pg/index.js b/lib/adapters/Pg/index.js new file mode 100644 index 0000000..f0a653d --- /dev/null +++ b/lib/adapters/Pg/index.js @@ -0,0 +1,7 @@ +'use strict'; + +const Pg = require('./pg'); + +module.exports = config => { + return new Pg(config.connection); +}; diff --git a/lib/adapters/Sqlite/dblite.js b/lib/adapters/Sqlite/dblite.js new file mode 100644 index 0000000..f62bd2c --- /dev/null +++ b/lib/adapters/Sqlite/dblite.js @@ -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; diff --git a/lib/adapters/Sqlite/index.js b/lib/adapters/Sqlite/index.js new file mode 100644 index 0000000..b4c891c --- /dev/null +++ b/lib/adapters/Sqlite/index.js @@ -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); +}; + diff --git a/lib/adapters/Sqlite.js b/lib/adapters/Sqlite/sqlite3.js similarity index 53% rename from lib/adapters/Sqlite.js rename to lib/adapters/Sqlite/sqlite3.js index 34e3c7f..b80bc00 100644 --- a/lib/adapters/Sqlite.js +++ b/lib/adapters/Sqlite/sqlite3.js @@ -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; diff --git a/lib/drivers/Firebird.js b/lib/drivers/Firebird.js index 7f6d93c..2c91827 100644 --- a/lib/drivers/Firebird.js +++ b/lib/drivers/Firebird.js @@ -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; -})(); \ No newline at end of file +})(); diff --git a/lib/drivers/Sqlite.js b/lib/drivers/Sqlite.js index bb97e4d..0ae0c1a 100644 --- a/lib/drivers/Sqlite.js +++ b/lib/drivers/Sqlite.js @@ -54,7 +54,7 @@ module.exports = (() => { return { sql: sql, - values: null + values: undefined }; }; diff --git a/package.json b/package.json index 2fc6ca1..1ca0b83 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/test/adapters/00node-firebird_test.js b/test/adapters/00node-firebird_test.js new file mode 100644 index 0000000..6a87c52 --- /dev/null +++ b/test/adapters/00node-firebird_test.js @@ -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(); + }); + }); +} diff --git a/test/adapters/dblite_test.js b/test/adapters/dblite_test.js index ae0f994..690fc3e 100644 --- a/test/adapters/dblite_test.js +++ b/test/adapters/dblite_test.js @@ -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); - } + 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(data) - .then(() => done) - .catch(e => done(e)); - }); + 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') diff --git a/test/adapters/mysql2_est.js b/test/adapters/mysql2_test.js similarity index 67% rename from test/adapters/mysql2_est.js rename to test/adapters/mysql2_test.js index 9d11808..ea87a72 100644 --- a/test/adapters/mysql2_est.js +++ b/test/adapters/mysql2_test.js @@ -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(); - }); + }); */ }); diff --git a/test/adapters/node-firebird_test.js b/test/adapters/node-firebird_test.js deleted file mode 100644 index 9a02faf..0000000 --- a/test/adapters/node-firebird_test.js +++ /dev/null @@ -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(); - }); -}); diff --git a/test/adapters/pg_test.js b/test/adapters/pg_test.js index fc85c53..3a6d3e6 100644 --- a/test/adapters/pg_test.js +++ b/test/adapters/pg_test.js @@ -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') diff --git a/test/adapters/sqlite3_test.js b/test/adapters/sqlite3_test.js new file mode 100644 index 0000000..e86f1fc --- /dev/null +++ b/test/adapters/sqlite3_test.js @@ -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(); + }); +}); diff --git a/test/base/adapterPromiseTestRunner.js b/test/base/adapterPromiseTestRunner.js index 3687c5a..58c391b 100644 --- a/test/base/adapterPromiseTestRunner.js +++ b/test/base/adapterPromiseTestRunner.js @@ -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)); }); }); }); diff --git a/test/query-parser_test.js b/test/query-parser_test.js index 79a9f24..7e10dcb 100644 --- a/test/query-parser_test.js +++ b/test/query-parser_test.js @@ -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) {