diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..d1dfc15 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,33 @@ +mariadb: + image: mariadb:latest + environment: + - MYSQL_USER=test + - MYSQL_PASSWORD=test + - MYSQL_DATABASE=test + - MYSQL_RANDOM_ROOT_PASSWORD=yes + ports: + - 3306:3306 + +postgresql: + image: postgres:latest + environment: + - POSTGRES_USER=test + - POSTGRES_PASSWORD=test + - POSTGRES_DB=test + ports: + - 5432:5432 + +sqlserver: + image: microsoft/mssql-server-linux + environment: + - ACCEPT_EULA=Y + - SA_PASSWORD=t3571ng0n1y + ports: + - 1433:1433 + +firebird: + image: itherz/firebird3:latest + ports: + - 5040:5040 + volumes: + - ./test:/databases diff --git a/lib/helpers.js b/lib/Helpers.js similarity index 73% rename from lib/helpers.js rename to lib/Helpers.js index 8735b80..f5ff917 100755 --- a/lib/helpers.js +++ b/lib/Helpers.js @@ -1,18 +1,40 @@ 'use strict'; +const fs = require('fs'); + /** * Various internal helper functions * * @private */ -const helpers = { +class Helpers { + /** + * Get the contents of a file + * + * @param {string} file - The path to the file + * @return {Promise} - Promise resolving to the contents of the file + */ + static readFile (file) { + return new Promise((resolve, reject) => { + fs.readFile(file, (err, data) => { + if (err) { + return reject(err); + } + return resolve(Buffer.from(data).toString()); + }); + }); + } + /** * Wrap String.prototype.trim in a way that is easily mappable * * @param {String} str - The string to trim * @return {String} - The trimmed string */ - stringTrim: str => str.trim(), + static stringTrim (str) { + return str.trim(); + } + /** * Get the type of the variable passed * @@ -21,7 +43,7 @@ const helpers = { * @param {mixed} o - Object to type check * @return {String} - Type of the object */ - type: o => { + static type (o) { let type = Object.prototype.toString.call(o).slice(8, -1).toLowerCase(); // handle NaN and Infinity @@ -36,17 +58,19 @@ const helpers = { } return type; - }, + } + /** * Determine whether an object is scalar * * @param {mixed} obj - Object to test * @return {bool} - Is object scalar */ - isScalar: obj => { + static isScalar (obj) { let scalar = ['string', 'number', 'boolean']; - return scalar.indexOf(helpers.type(obj)) !== -1; - }, + return scalar.indexOf(Helpers.type(obj)) !== -1; + } + /** * Get a list of values with a common key from an array of objects * @@ -54,7 +78,7 @@ const helpers = { * @param {String} key - The key of the object to get * @return {Array} - The new array of plucked values */ - arrayPluck: (arr, key) => { + static arrayPluck (arr, key) { let output = []; // Empty case @@ -63,13 +87,14 @@ const helpers = { } arr.forEach(obj => { - if (!helpers.isUndefined(obj[key])) { + if (!Helpers.isUndefined(obj[key])) { output.push(obj[key]); } }); return output; - }, + } + /** * Determine if a value matching the passed regular expression is * in the passed array @@ -78,9 +103,9 @@ const helpers = { * @param {RegExp} pattern - The pattern to match * @return {Boolean} - If an array item matches the pattern */ - regexInArray: (arr, pattern) => { + static regexInArray (arr, pattern) { // Empty case(s) - if (!helpers.isArray(arr) || arr.length === 0) { + if (!Helpers.isArray(arr) || arr.length === 0) { return false; } @@ -93,19 +118,20 @@ const helpers = { } return false; - }, + } + /** * Make the first letter of the string uppercase * * @param {String} str - The string to modify * @return {String} - The modified string */ - upperCaseFirst: str => { + static upperCaseFirst (str) { str += ''; let first = str.charAt(0).toUpperCase(); return first + str.substr(1); } -}; +} // Define an 'is' method for each type let types = [ @@ -119,7 +145,8 @@ let types = [ 'Function', 'RegExp', 'NaN', - 'Infinite' + 'Infinite', + 'Promise' ]; types.forEach(t => { /** @@ -127,19 +154,19 @@ types.forEach(t => { * function name, eg isNumber * * Types available are Null, Undefined, Object, Array, String, Number, - * Boolean, Function, RegExp, NaN and Infinite + * Boolean, Function, RegExp, NaN, Infinite, Promise * * @private * @param {mixed} o - The object to check its type * @return {Boolean} - If the type matches */ - helpers[`is${t}`] = function (o) { + Helpers[`is${t}`] = function (o) { if (t.toLowerCase() === 'infinite') { t = 'infinity'; } - return helpers.type(o) === t.toLowerCase(); + return Helpers.type(o) === t.toLowerCase(); }; }); -module.exports = helpers; +module.exports = Helpers; diff --git a/lib/QueryBuilder.js b/lib/QueryBuilder.js index 9460c1d..03f69e5 100755 --- a/lib/QueryBuilder.js +++ b/lib/QueryBuilder.js @@ -1,6 +1,6 @@ 'use strict'; -const helpers = require('./helpers'); +const Helpers = require('./Helpers'); const QueryBuilderBase = require('./QueryBuilderBase'); /** @@ -15,6 +15,26 @@ class QueryBuilder extends QueryBuilderBase { // ! Miscellaneous Methods // ---------------------------------------------------------------------------- + /** + * Run a set of queries from a file + * + * @param {string} file - The path to the sql file + * @param {string} [separator=';'] - The character separating each query + * @return {Promise} - The result of all the queries + */ + queryFile (file, separator = ';') { + return Helpers.readFile(file).then(sqlFile => { + const queries = sqlFile.split(separator); + const results = []; + + queries.forEach(sql => { + results.push(this.query(sql)); + }); + + return Promise.all(results); + }); + } + /** * Run an arbitrary sql query. Run as a prepared statement. * @@ -80,12 +100,12 @@ class QueryBuilder extends QueryBuilderBase { // Split/trim fields by comma fields = (Array.isArray(fields)) ? fields - : fields.split(',').map(helpers.stringTrim); + : fields.split(',').map(Helpers.stringTrim); // Split on 'As' fields.forEach((field, index) => { if (/as/i.test(field)) { - fields[index] = field.split(/ as /i).map(helpers.stringTrim); + fields[index] = field.split(/ as /i).map(Helpers.stringTrim); } }); @@ -113,7 +133,7 @@ class QueryBuilder extends QueryBuilderBase { */ from (tableName) { // Split identifiers on spaces - let identArray = tableName.trim().split(' ').map(helpers.stringTrim); + let identArray = tableName.trim().split(' ').map(Helpers.stringTrim); // Quote/prefix identifiers identArray[0] = this.driver.quoteTable(identArray[0]); @@ -354,7 +374,7 @@ class QueryBuilder extends QueryBuilderBase { type = type || 'inner'; // Prefix/quote table name - table = table.split(' ').map(helpers.stringTrim); + table = table.split(' ').map(Helpers.stringTrim); table[0] = this.driver.quoteTable(table[0]); table = table.map(this.driver.quoteIdentifiers); table = table.join(' '); @@ -376,7 +396,7 @@ class QueryBuilder extends QueryBuilderBase { * @return {QueryBuilder} - The Query Builder object, for chaining */ groupBy (field) { - if (!helpers.isScalar(field)) { + if (!Helpers.isScalar(field)) { let newGroupArray = field.map(this.driver.quoteIdentifiers); this.state.groupArray = this.state.groupArray.concat(newGroupArray); } else { diff --git a/lib/adapters/Mysql/mysql2.js b/lib/adapters/Mysql/mysql2.js index 438c3f3..c13483c 100644 --- a/lib/adapters/Mysql/mysql2.js +++ b/lib/adapters/Mysql/mysql2.js @@ -2,7 +2,7 @@ const Adapter = require('../../Adapter'); const Result = require('../../Result'); -const helpers = require('../../helpers'); +const Helpers = require('../../Helpers'); const mysql2 = require('mysql2/promise'); class Mysql extends Adapter { @@ -22,7 +22,7 @@ class Mysql extends Adapter { // For insert and update queries, the result object // works differently. Just apply the properties of // this special result to the standard result object. - if (helpers.type(result) === 'object') { + if (Helpers.type(result) === 'object') { let r = new Result(); Object.keys(result).forEach(key => { diff --git a/lib/adapters/Pg/Pg.js b/lib/adapters/Pg/Pg.js index b1f37a8..4d50492 100644 --- a/lib/adapters/Pg/Pg.js +++ b/lib/adapters/Pg/Pg.js @@ -2,7 +2,7 @@ const Adapter = require('../../Adapter'); const Result = require('../../Result'); -const helpers = require('../../helpers'); +const Helpers = require('../../Helpers'); const pg = require('pg'); const url = require('url'); @@ -10,7 +10,7 @@ class Pg extends Adapter { constructor (config) { let instance = null; let connectionString = ''; - if (helpers.isObject(config)) { + if (Helpers.isObject(config)) { let host = config.host || 'localhost'; let user = config.user || 'postgres'; let password = `:${config.password}` || ''; @@ -25,7 +25,7 @@ class Pg extends Adapter { }; connectionString = url.format(conn); - } else if (helpers.isString(config)) { + } else if (Helpers.isString(config)) { connectionString = config; } diff --git a/lib/adapters/Sqlite/dblite.js b/lib/adapters/Sqlite/dblite.js index f62bd2c..21af28b 100644 --- a/lib/adapters/Sqlite/dblite.js +++ b/lib/adapters/Sqlite/dblite.js @@ -2,12 +2,12 @@ const Adapter = require('../../Adapter'); const Result = require('../../Result'); -const helpers = require('../../helpers'); +const Helpers = require('../../Helpers'); const dbliteAdapter = require('dblite'); class SqliteDblite extends Adapter { constructor (config) { - let file = (helpers.isString(config)) ? config : config.file; + let file = (Helpers.isString(config)) ? config : config.file; const instance = new Promise((resolve, reject) => { let conn = dbliteAdapter(file); diff --git a/lib/adapters/Sqlite/sqlite3.js b/lib/adapters/Sqlite/sqlite3.js index b80bc00..4c0529a 100644 --- a/lib/adapters/Sqlite/sqlite3.js +++ b/lib/adapters/Sqlite/sqlite3.js @@ -2,12 +2,12 @@ const Adapter = require('../../Adapter'); const Result = require('../../Result'); -const helpers = require('../../helpers'); +const Helpers = require('../../Helpers'); const sqlite3 = require('sqlite3').verbose(); class SqliteSqlite3 extends Adapter { constructor (config) { - let file = (helpers.isString(config)) ? config : config.file; + let file = (Helpers.isString(config)) ? config : config.file; const instance = new Promise((resolve, reject) => { let conn = new sqlite3.Database(file, sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE, err => { diff --git a/test/adapters/00node-firebird_test.js b/test/adapters/00node-firebirdtest.js similarity index 100% rename from test/adapters/00node-firebird_test.js rename to test/adapters/00node-firebirdtest.js diff --git a/test/adapters/mysql2_test.js b/test/adapters/mysql2_test.js index ea87a72..0d85888 100644 --- a/test/adapters/mysql2_test.js +++ b/test/adapters/mysql2_test.js @@ -17,6 +17,12 @@ let nodeQuery = reload('../../lib/NodeQuery')(config); let qb = nodeQuery.getQuery(); suite('Mysql2 adapter tests -', () => { + suiteSetup(done => { + qb.queryFile(`${__dirname}/../sql/mysql.sql`) + .then(() => done()) + .catch(e => done(e)); + }); + test('nodeQuery.getQuery = nodeQuery.init', () => { expect(nodeQuery.getQuery()) .to.be.deep.equal(qb); diff --git a/test/adapters/pg_test.js b/test/adapters/pg_test.js index 3a6d3e6..77a4470 100644 --- a/test/adapters/pg_test.js +++ b/test/adapters/pg_test.js @@ -19,6 +19,12 @@ let qb = nodeQuery.getQuery(); let qb2 = null; suite('Pg adapter tests -', () => { + suiteSetup(done => { + qb.queryFile(`${__dirname}/../sql/pgsql.sql`) + .then(() => done()) + .catch(e => done(e)); + }); + test('nodeQuery.getQuery = nodeQuery.init', () => { expect(nodeQuery.getQuery()) .to.be.deep.equal(qb); diff --git a/test/helpers_test.js b/test/helpers_test.js index f44d4e2..9e81967 100644 --- a/test/helpers_test.js +++ b/test/helpers_test.js @@ -5,22 +5,22 @@ const chai = require('chai'); const assert = chai.assert; const expect = chai.expect; -let helpers = require('../lib/helpers'); +let Helpers = require('../lib/Helpers'); suite('Helper Module Tests -', () => { suite('Type-checking methods -', () => { suite('Object wrappers are listed as their native type', () => { test('Boolean Wrapper returns \'boolean\' not \'object\'', () => { let item = Boolean(true); - expect(helpers.type(item)).to.deep.equal('boolean'); + expect(Helpers.type(item)).to.deep.equal('boolean'); }); test('Number Wrapper returns \'number\' not \'object\'', () => { let item = Number(4867); - expect(helpers.type(item)).to.deep.equal('number'); + expect(Helpers.type(item)).to.deep.equal('number'); }); test('String Wrapper returns \'string\' not \'object\'', () => { let item = String('Foo'); - expect(helpers.type(item)).to.deep.equal('string'); + expect(Helpers.type(item)).to.deep.equal('string'); }); }); suite('is..Method methods exist -', () => { @@ -40,7 +40,7 @@ suite('Helper Module Tests -', () => { types.forEach(type => { test(`is${type} method exists`, () => { - assert.ok(helpers[`is${type}`]); + assert.ok(Helpers[`is${type}`]); }); }); }); @@ -52,7 +52,7 @@ suite('Helper Module Tests -', () => { }; Object.keys(trueCases).forEach(desc => { test(desc, () => { - expect(helpers.isScalar(trueCases[desc])).to.be.true; + expect(Helpers.isScalar(trueCases[desc])).to.be.true; }); }); @@ -62,24 +62,24 @@ suite('Helper Module Tests -', () => { }; Object.keys(falseCases).forEach(desc => { test(desc, () => { - expect(helpers.isScalar(falseCases[desc])).to.be.false; + expect(Helpers.isScalar(falseCases[desc])).to.be.false; }); }); }); suite('isInfinity -', () => { test('The type of 1/0 is infinity', () => { - expect(helpers.type(1 / 0)).to.equal('infinity'); + expect(Helpers.type(1 / 0)).to.equal('infinity'); }); test('isInfinity is the same as isInfinite', () => { - expect(helpers.isInfinite(1 / 0)).to.be.true; + expect(Helpers.isInfinite(1 / 0)).to.be.true; }); }); suite('isNaN -', () => { test('The type of 0 / 0 is NaN', () => { - expect(helpers.type(0 / 0)).to.equal('nan'); + expect(Helpers.type(0 / 0)).to.equal('nan'); }); test('isNaN method agrees with type', () => { - expect(helpers.isNaN(0 / 0)).to.be.true; + expect(Helpers.isNaN(0 / 0)).to.be.true; }); }); }); @@ -89,7 +89,7 @@ suite('Helper Module Tests -', () => { let orig = [' x y ', 'z ', ' q']; let ret = ['x y', 'z', 'q']; - expect(orig.map(helpers.stringTrim)).to.be.deep.equal(ret); + expect(orig.map(Helpers.stringTrim)).to.be.deep.equal(ret); }); }); suite('arrayPluck -', () => { @@ -106,13 +106,13 @@ suite('Helper Module Tests -', () => { ]; test('Finding members in all objects', () => { - expect(helpers.arrayPluck(orig, 'foo')).to.be.deep.equal([1, 2, 3]); + expect(Helpers.arrayPluck(orig, 'foo')).to.be.deep.equal([1, 2, 3]); }); test('Some members are missing in some objects', () => { - expect(helpers.arrayPluck(orig, 'bar')).to.be.deep.equal([10, 15]); + expect(Helpers.arrayPluck(orig, 'bar')).to.be.deep.equal([10, 15]); }); test('Empty case', () => { - expect(helpers.arrayPluck([], 'apple')).to.be.deep.equal([]); + expect(Helpers.arrayPluck([], 'apple')).to.be.deep.equal([]); }); }); suite('regexInArray -', () => { @@ -133,25 +133,25 @@ suite('Helper Module Tests -', () => { Object.keys(boolCase).forEach(desc => { test(desc, () => { if (i) { - expect(helpers.regexInArray(orig, boolCase[desc])).to.be.true; + expect(Helpers.regexInArray(orig, boolCase[desc])).to.be.true; } else { - expect(helpers.regexInArray(orig, boolCase[desc])).to.be.false; + expect(Helpers.regexInArray(orig, boolCase[desc])).to.be.false; } }); }); }); test('First argument is not an array', () => { - expect(helpers.regexInArray(5, /5/)).to.be.false; + expect(Helpers.regexInArray(5, /5/)).to.be.false; }); test('Array is empty', () => { - expect(helpers.regexInArray([], /.*/)).to.be.false; + 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'); + expect(Helpers.upperCaseFirst('foobar')).to.equal('Foobar'); + expect(Helpers.upperCaseFirst('FOOBAR')).to.equal('FOOBAR'); }); }); }); diff --git a/test/sql/mysql.sql b/test/sql/mysql.sql index 5b06c28..bad4cea 100644 --- a/test/sql/mysql.sql +++ b/test/sql/mysql.sql @@ -3,25 +3,21 @@ -- -- Table structure for table `create_join` -- - DROP TABLE IF EXISTS `create_join` CASCADE; -CREATE TABLE `create_join` ( - `id` int(10) NOT NULL, - `key` text, - `val` text +CREATE TABLE IF NOT EXISTS `create_join` ( + `id` int(10) PRIMARY KEY NOT NULL, + `key` VARCHAR(128), + `val` MEDIUMTEXT ); -ALTER TABLE `create_join` - ADD PRIMARY KEY (`id`); -- -- Table structure for table `create_test` -- - DROP TABLE IF EXISTS `create_test` CASCADE; CREATE TABLE `create_test` ( `id` int(10) NOT NULL, - `key` text, - `val` text + `key` TEXT, + `val` LONGTEXT ); ALTER TABLE `create_test` - ADD PRIMARY KEY (`id`); \ No newline at end of file + ADD PRIMARY KEY (`id`) diff --git a/test/sql/pgsql.sql b/test/sql/pgsql.sql index 09ad873..cad420c 100644 --- a/test/sql/pgsql.sql +++ b/test/sql/pgsql.sql @@ -13,4 +13,4 @@ CREATE TABLE "create_test" ( id integer NOT NULL, key text, val text -); \ No newline at end of file +)