From 3460abdd967690cf44476d62646947774ba90f3f Mon Sep 17 00:00:00 2001 From: Timothy J Warren Date: Mon, 21 Nov 2016 19:48:00 -0500 Subject: [PATCH 01/16] Documentation updates --- lib/Adapter.js | 2 +- lib/QueryBuilder.js | 10 +++++----- lib/QueryParser.js | 2 +- lib/adapters/MSSQLServer/index.js | 5 +++++ lib/drivers/MSSQLDriver.js | 17 +++++++++++++++++ lib/helpers.js | 2 +- 6 files changed, 30 insertions(+), 8 deletions(-) create mode 100644 lib/drivers/MSSQLDriver.js diff --git a/lib/Adapter.js b/lib/Adapter.js index e0c36ed..772e341 100755 --- a/lib/Adapter.js +++ b/lib/Adapter.js @@ -22,7 +22,7 @@ class Adapter { * * @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 passed + * @return {Promise} - returns a promise resolving to the result of the database query */ execute (sql, params) { throw new Error('Correct adapter not defined for query execution'); diff --git a/lib/QueryBuilder.js b/lib/QueryBuilder.js index 268fd10..9460c1d 100755 --- a/lib/QueryBuilder.js +++ b/lib/QueryBuilder.js @@ -490,7 +490,7 @@ class QueryBuilder extends QueryBuilderBase { * @example query.get('table_name').then(promiseCallback); // Get all the rows in the table * @example query.get('table_name', 5); // Get 5 rows from the table * @example query.get(); // Get the results of a query generated with other methods - * @return {void|Promise} - If no callback is passed, a promise is returned + * @return {Promise} - Promise containing the result of the query */ get (table, limit, offset) { if (table) { @@ -510,7 +510,7 @@ class QueryBuilder extends QueryBuilderBase { * * @param {String} table - The table to insert into * @param {Object} [data] - Data to insert, if not already added with the 'set' method - * @return {Promise} - If no callback is passed, a promise is returned + * @return {Promise} - Promise containing the result of the query */ insert (table, data) { if (data) { @@ -528,7 +528,7 @@ class QueryBuilder extends QueryBuilderBase { * @param {Array} data - The array of objects containing data rows to insert * @example query.insertBatch('foo',[{id:1,val:'bar'},{id:2,val:'baz'}]) *.then(promiseCallback); - * @return {Promise} - If no callback is passed, a promise is returned + * @return {Promise} - Promise containing the result of the query */ insertBatch (table, data) { let batch = this.driver.insertBatch(table, data); @@ -542,7 +542,7 @@ class QueryBuilder extends QueryBuilderBase { * * @param {String} table - The table to insert into * @param {Object} [data] - Data to insert, if not already added with the 'set' method - * @return {Promise} - If no callback is passed, a promise is returned + * @return {Promise} - Promise containing the result of the query */ update (table, data) { if (data) { @@ -558,7 +558,7 @@ class QueryBuilder extends QueryBuilderBase { * * @param {String} table - The table to insert into * @param {Object} [where] - Where clause for delete statement - * @return {Promise} - If no callback is passed, a promise is returned + * @return {Promise} - Promise containing the result of the query */ delete (table, where) { if (where) { diff --git a/lib/QueryParser.js b/lib/QueryParser.js index e162385..27a140a 100644 --- a/lib/QueryParser.js +++ b/lib/QueryParser.js @@ -115,7 +115,7 @@ class QueryParser { /** * Return the output of the parsing of the join condition * - * @param {String} condition - The join condition to evalate + * @param {String} condition - The join condition to evaluate * @return {String} - The parsed/escaped join condition */ compileJoin (condition) { diff --git a/lib/adapters/MSSQLServer/index.js b/lib/adapters/MSSQLServer/index.js index e69de29..5c41b25 100644 --- a/lib/adapters/MSSQLServer/index.js +++ b/lib/adapters/MSSQLServer/index.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = config => { + +}; diff --git a/lib/drivers/MSSQLDriver.js b/lib/drivers/MSSQLDriver.js new file mode 100644 index 0000000..0deab0f --- /dev/null +++ b/lib/drivers/MSSQLDriver.js @@ -0,0 +1,17 @@ +'use strict'; + +/** + * Driver for Microsoft SQL Server databases + * + * @module drivers/MSSQLDriver + */ +module.exports = (() => { + delete require.cache[require.resolve('../Driver')]; + const driver = require('../Driver'); + const helpers = require('../helpers'); + + driver.identifierStartChar = '['; + driver.identifierEndChar = ']'; + + return driver; +})(); diff --git a/lib/helpers.js b/lib/helpers.js index afb08d4..8735b80 100755 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -5,7 +5,7 @@ * * @private */ -let helpers = { +const helpers = { /** * Wrap String.prototype.trim in a way that is easily mappable * From 7f22eee84db05944a29aff75b74b87e3641f1dbc Mon Sep 17 00:00:00 2001 From: Timothy Warren Date: Tue, 22 Nov 2016 16:03:46 -0500 Subject: [PATCH 02/16] Make helpers a class of static functions, add helper methods to run a full file of sql queries --- docker-compose.yml | 33 +++++++++ lib/{helpers.js => Helpers.js} | 67 +++++++++++++------ lib/QueryBuilder.js | 32 +++++++-- lib/adapters/Mysql/mysql2.js | 4 +- lib/adapters/Pg/Pg.js | 6 +- lib/adapters/Sqlite/dblite.js | 4 +- lib/adapters/Sqlite/sqlite3.js | 4 +- ...irebird_test.js => 00node-firebirdtest.js} | 0 test/adapters/mysql2_test.js | 6 ++ test/adapters/pg_test.js | 6 ++ test/helpers_test.js | 42 ++++++------ test/sql/mysql.sql | 18 ++--- test/sql/pgsql.sql | 2 +- 13 files changed, 156 insertions(+), 68 deletions(-) create mode 100644 docker-compose.yml rename lib/{helpers.js => Helpers.js} (73%) rename test/adapters/{00node-firebird_test.js => 00node-firebirdtest.js} (100%) 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 +) From b54e69570f3f6d857f228d666a1def2bed7a165e Mon Sep 17 00:00:00 2001 From: Timothy Warren Date: Tue, 22 Nov 2016 18:26:43 -0500 Subject: [PATCH 03/16] Make 'navtive' a connection option to use a driver with native bindings --- lib/NodeQuery.js | 6 ++++++ lib/adapters/Pg/PgNative.js | 28 ++++++++++++++++++++++++++++ lib/adapters/Pg/index.js | 5 ++++- lib/adapters/Sqlite/index.js | 6 +++--- package.json | 7 ++++++- test/adapters/dblite_test.js | 12 +++--------- test/adapters/sqlite3_test.js | 12 +++--------- 7 files changed, 53 insertions(+), 23 deletions(-) create mode 100644 lib/adapters/Pg/PgNative.js diff --git a/lib/NodeQuery.js b/lib/NodeQuery.js index e64d696..8af3938 100755 --- a/lib/NodeQuery.js +++ b/lib/NodeQuery.js @@ -28,6 +28,12 @@ class NodeQuery { * Constructor * * @param {object} config - connection parameters + * @param {string} config.driver - the database driver to use, such as mysql, sqlite, mssql, or pgsql + * @param {object|string} config.connection - the connection options for the database + * @param {string} config.connection.host - the ip or hostname of the database server + * @param {string} config.connection.user - the user to log in as + * @param {string} config.connection.password - the password to log in with + * @param {string} config.connection.database - the name of the database to connect to * @example let nodeQuery = require('ci-node-query')({ * driver: 'mysql', * connection: { diff --git a/lib/adapters/Pg/PgNative.js b/lib/adapters/Pg/PgNative.js new file mode 100644 index 0000000..c341d26 --- /dev/null +++ b/lib/adapters/Pg/PgNative.js @@ -0,0 +1,28 @@ +'use strict'; + +const Pg = require('./Pg'); +const Result = require('../../Result'); +const pg = require('pg').native; +const url = require('url'); + +class PgNative extends Pg { + constructor (config) { + super(config); + let instance = null; + let connectionString = Pg._formatConnectionString(config); + + if (connectionString !== '') { + let conn = new pg.Client(connectionString); + conn.connect(err => { + if (err) { + throw new Error(err); + } + }); + + instance = Promise.resolve(conn); + } + super(instance); + } +} + +module.exports = PgNative; diff --git a/lib/adapters/Pg/index.js b/lib/adapters/Pg/index.js index c20cdb3..5a940e7 100644 --- a/lib/adapters/Pg/index.js +++ b/lib/adapters/Pg/index.js @@ -1,7 +1,10 @@ 'use strict'; const Pg = require('./Pg'); +const PgNative = require('./PgNative') module.exports = config => { - return new Pg(config.connection); + return (config.native) + ? new PgNative(config.connection) + : new Pg(config.connection); }; diff --git a/lib/adapters/Sqlite/index.js b/lib/adapters/Sqlite/index.js index b4c891c..b59bcf2 100644 --- a/lib/adapters/Sqlite/index.js +++ b/lib/adapters/Sqlite/index.js @@ -1,9 +1,9 @@ 'use strict'; module.exports = config => { - const Implementation = (config.adapter && config.adapter === 'dblite') - ? require('./dblite') - : require('./sqlite3'); + const Implementation = (config.native) + ? require('./sqlite3') + : require('./dblite'); return new Implementation(config.connection); }; diff --git a/package.json b/package.json index 531487a..4336160 100644 --- a/package.json +++ b/package.json @@ -23,12 +23,14 @@ "codeigniter", "mariadb", "mysql", + "mssql", "query builder", "postgres", "postgresql", "sql", "sqlite", - "sqlite3" + "sqlite3", + "sqlserver" ], "bugs": { "url": "https://git.timshomepage.net/timw4mail/node-query/issues" @@ -43,6 +45,7 @@ "pg": "^6.0.0", "require-reload": "~0.2.2", "sqlite3": "^3.1.8", + "tedious": "^1.14.0", "xregexp": "^3.0.0" }, "devDependencies": { @@ -53,6 +56,7 @@ "globstar": "^1.0.0", "happiness": "^7.1.2", "istanbul": "~0.4.2", + "jsdoc": "^3.4.3", "mocha": "^3.0.0", "npm-run-all": "^3.0.0", "nsp": "^2.2.1" @@ -65,6 +69,7 @@ "default": "npm-run-all --parallel audit lint:src lint:tests && npm run test", "predocs": "globstar -- documentation build -f md -o API.md \"lib/*.js\"", "docs": "globstar -- documentation build -f html -o docs \"lib/*.js\"", + "postdocs": "jsdoc lib -r -d documentation", "happy": "happiness \"lib/**/*.js\" \"test/**/*.js\"", "happy:src": "happiness \"lib/**/*.js\"", "happy:tests": "happiness \"test/**/*.js\"", diff --git a/test/adapters/dblite_test.js b/test/adapters/dblite_test.js index 690fc3e..6a53f4e 100644 --- a/test/adapters/dblite_test.js +++ b/test/adapters/dblite_test.js @@ -17,15 +17,9 @@ let qb = nodeQuery.getQuery(); suite('Dblite 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(); - }); + qb.queryFile(`${__dirname}/../sql/sqlite.sql`) + .then(() => done()) + .catch(e => done(e)); }); testRunner(qb); diff --git a/test/adapters/sqlite3_test.js b/test/adapters/sqlite3_test.js index e86f1fc..a7cb8dd 100644 --- a/test/adapters/sqlite3_test.js +++ b/test/adapters/sqlite3_test.js @@ -17,15 +17,9 @@ 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(); - }); + qb.queryFile(`${__dirname}/../sql/sqlite.sql`) + .then(() => done()) + .catch(e => done(e)); }); testRunner(qb); From cdc5dcfa1799b160cdde94d310e54c78e7a05df9 Mon Sep 17 00:00:00 2001 From: Timothy J Warren Date: Tue, 28 Feb 2017 15:47:29 -0500 Subject: [PATCH 04/16] From mocha to jest --- .istanbul.yml | 9 --- package.json | 22 +++++-- test/FB_TEST_DB.FDB | Bin 1040384 -> 1040384 bytes test/adapters/dblite_test.js | 10 +-- test/adapters/mysql2_test.js | 12 ++-- ...firebird_test.js => node-firebird_test.js} | 8 +-- test/adapters/pg_test.js | 18 +++--- test/adapters/sqlite3_test.js | 10 +-- test/base/adapterPromiseTestRunner.js | 38 +++++------ test/base_test.js | 14 ++-- test/docker_install.sh | 4 +- test/helpers_test.js | 60 +++++++++--------- test/mocha.opts | 3 +- test/query-parser_test.js | 30 ++++----- 14 files changed, 120 insertions(+), 118 deletions(-) delete mode 100644 .istanbul.yml rename test/adapters/{00node-firebird_test.js => node-firebird_test.js} (83%) diff --git a/.istanbul.yml b/.istanbul.yml deleted file mode 100644 index 51f02d4..0000000 --- a/.istanbul.yml +++ /dev/null @@ -1,9 +0,0 @@ -reporting: - print: summary - reports: - - lcov - - lcovonly - - clover - - html - - text - dir: ./coverage \ No newline at end of file diff --git a/package.json b/package.json index 531487a..ccc1e3b 100644 --- a/package.json +++ b/package.json @@ -52,25 +52,37 @@ "eslint": "^3.5.0", "globstar": "^1.0.0", "happiness": "^7.1.2", - "istanbul": "~0.4.2", - "mocha": "^3.0.0", + "jest": "^19.0.2", "npm-run-all": "^3.0.0", "nsp": "^2.2.1" }, "license": "MIT", + "jest": { + "coverageDirectory": "coverage", + "coverageReporters": [ + "html", + "json", + "lcov", + "text-summary" + ], + "testEnvironment": "node", + "testMatch": [ + "**/test/**/*_test.js" + ] + }, "scripts": { "audit": "nsp check", "build": "npm-run-all --parallel lint:src lint:tests docs coverage", - "coverage": "istanbul cover ./node_modules/mocha/bin/_mocha", + "coverage": "jest --coverage", "default": "npm-run-all --parallel audit lint:src lint:tests && npm run test", "predocs": "globstar -- documentation build -f md -o API.md \"lib/*.js\"", "docs": "globstar -- documentation build -f html -o docs \"lib/*.js\"", "happy": "happiness \"lib/**/*.js\" \"test/**/*.js\"", "happy:src": "happiness \"lib/**/*.js\"", "happy:tests": "happiness \"test/**/*.js\"", - "lint": "npm-run-all lint:tests lint:src && happy", + "lint": "npm-run-all lint:tests lint:src happy", "lint:src": "eslint ./lib", "lint:tests": "eslint ./test", - "test": "mocha -R spec" + "test": "jest" } } diff --git a/test/FB_TEST_DB.FDB b/test/FB_TEST_DB.FDB index f68f70ad3367d4cf1fb0321867a34c3ec778b84a..0a7051b295bbb59880c94a78282a0faf5b5f82be 100755 GIT binary patch delta 1822 zcmZXTT}WI<6oAj%{c(54o4u_+Wlhz)rmov;5_B696AD_S38A0}l@>K3CD1}43O*zx zBx?*_leoGu0jH%D3l^G^7n77;A5CBSqw8AeORY2yHIcrgmiD30EauG2z3%R0F87}K z&N*}D%sF#Mm)b^`+ByjwYyNQ%3d5kI9)}(e06cMC0F8XM4)E^LT+`ce>0p3{&!7N; z-zNhPqBrk{ZyDa;8(!)1m_@&!4P@JnnxVDum}!~BGBwNeSf1Bo7%T;)?tD<_*rQR}*v6fHDU(iVlpi^B zt_Nqn=$H0kM+zH$0{$q^a?^*&UaIy$OR^cx4xR3G?Eo7C?D80WHBRyX#;%&k`4B-( z;Z4H&0PDO;Yb*VEp_cw0;Y`@0tX`G+6}z{HdJj4fUoUZ)$KjFgqFoLR2>CA$zIQYN zEe`>G(I*f6uAQ&IjF1Z%@OcuxR%7@-z+2IJ_&f0Vc@%G+y_MA)j}y34oDHsZnaAPJ z1vQeB4f{DmE7-*qJ(4`Hx)e6!dtoICLsX)l{hrmUThEnj4dAle8sL-~w|MIm=El3x z^04Uad4Bej>ax;wyf37Kb>T7iABNJo0{|zK^jsF{_2M=B>9l*U(niVB>>Z<(F_F*@ zs$XeBe$k7%!%1=Z@Zx=s{``XYcmwdvF3Y-v^Pd*tu~ddu$BQnjCH7X*z_WX-d$?Q-XezR6i{|Kb#h}yOREq zbTlJuc&-libez`M8yOlmNX<-lMUVi$Pgoo~x*hr}JYg(Q+gK?7cSmcjxBUxxPucBu z!y;|>y6Xb7!vU{e#l4+D?3dd2^o5#7uo4?WCx5rUcYi0Ri@V(k@ZIWIdsdI9w$Y2P zI>dW|(&d8+A5b`<@P36~S2(V4ljPLyhB3vVQQ_AV-luSb!cm1yh3f_D0G}7?#B$3z z?{US7&O6?>B4|$1mZU^h*haF#W=qCNpeKEO1`ZZ^n;f?4C(=jE-lpWLOn2KZi9aDA8fMW(7 zGvt`z%O;aXC*^&ekJJkT?>8k4xyw@2qSdHPXQKuk#d;7*AdeuwRcFyv=!-g=(sc%n zVBHJFkl)h`+R!YzpvCDV;;3fRey|p-%MptAP?ynB$h0Z_|(12=FLNzGwwidC^w|pS zk$HZK*~*de*Hir8~SyUo^aGEvo*c98&*QQxW-Xp5it_soH<2*gj z%(g_z+jGlZ5-8=q8P+aWhTJWb_9~glu-3;ViMsv&h?93sws=abLPxOEEZZrs3j_Z1 zEK`>zz8W)q> zG?`L(JiHK8T1oz1_p#phr;}w)HnNn}6nRH$PR(ks1SH=|ayhs(5mY9t-+*^jZ%s)C zl`v^43$EHT)_b_9sv*#jaP6jh)46z@%skNd1 z@Wq%}#ke^WX``bUH-iydrvyL{;1)CJDzjITT}=LTr24@=%CE0!PoQ_(3Yy!@D1WEm zg3mX>Ujpo2_bQIByA`<_i1GSsTDAYrToV;B{+R&9O|YxzRty)tipwsTzFE|uy4=lT Hv`POD0Xb)z diff --git a/test/adapters/dblite_test.js b/test/adapters/dblite_test.js index 690fc3e..638866d 100644 --- a/test/adapters/dblite_test.js +++ b/test/adapters/dblite_test.js @@ -15,8 +15,8 @@ const config = testBase.config; let nodeQuery = require('../../lib/NodeQuery')(config.dblite); let qb = nodeQuery.getQuery(); -suite('Dblite adapter tests -', () => { - suiteSetup(done => { +describe('Dblite adapter tests -', () => { + beforeAll(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);'; @@ -29,7 +29,7 @@ suite('Dblite adapter tests -', () => { }); testRunner(qb); - test('Promise - Select with function and argument in WHERE clause', () => { + it('Promise - Select with function and argument in WHERE clause', () => { let promise = qb.select('id') .from('create_test') .where('id', 'ABS(-88)') @@ -37,7 +37,7 @@ suite('Dblite adapter tests -', () => { expect(promise).to.be.fulfilled; }); - test('Promise - Test Insert Batch', () => { + it('Promise - Test Insert Batch', () => { let data = [ { id: 544, @@ -57,7 +57,7 @@ suite('Dblite adapter tests -', () => { let promise = qb.insertBatch('create_test', data); expect(promise).to.be.fulfilled; }); - suiteTeardown(() => { + afterAll(() => { qb.end(); }); }); diff --git a/test/adapters/mysql2_test.js b/test/adapters/mysql2_test.js index ea87a72..323937f 100644 --- a/test/adapters/mysql2_test.js +++ b/test/adapters/mysql2_test.js @@ -16,14 +16,14 @@ const config = testBase.config[adapterName]; let nodeQuery = reload('../../lib/NodeQuery')(config); let qb = nodeQuery.getQuery(); -suite('Mysql2 adapter tests -', () => { - test('nodeQuery.getQuery = nodeQuery.init', () => { +describe('Mysql2 adapter tests -', () => { + it('nodeQuery.getQuery = nodeQuery.init', () => { expect(nodeQuery.getQuery()) .to.be.deep.equal(qb); }); testRunner(qb); - test('Promise - Select with function and argument in WHERE clause', () => { + it('Promise - Select with function and argument in WHERE clause', () => { let promise = qb.select('id') .from('create_test') .where('id', 'CEILING(SQRT(88))') @@ -31,11 +31,11 @@ suite('Mysql2 adapter tests -', () => { return expect(promise).to.be.fulfilled; }); - test('Test Truncate', () => { + it('Test Truncate', () => { let promise = qb.truncate('create_test'); return expect(promise).to.be.fullfilled; }); - test('Test Insert Batch', () => { + it('Test Insert Batch', () => { let data = [ { id: 5442, @@ -55,7 +55,7 @@ suite('Mysql2 adapter tests -', () => { return expect(qb.insertBatch('create_test', data)).to.be.fulfilled; }); - /* suiteTeardown(() => { + /* describeTeardown(() => { qb.end(); }); */ }); diff --git a/test/adapters/00node-firebird_test.js b/test/adapters/node-firebird_test.js similarity index 83% rename from test/adapters/00node-firebird_test.js rename to test/adapters/node-firebird_test.js index 6a87c52..c56db1c 100644 --- a/test/adapters/00node-firebird_test.js +++ b/test/adapters/node-firebird_test.js @@ -18,12 +18,12 @@ if (!(process.env.CI || process.env.TRAVIS)) { let qb = nodeQuery.getQuery(); - suite('Firebird adapter tests -', () => { - test('nodeQuery.getQuery = nodeQuery.init', () => { + describe('Firebird adapter tests -', () => { + it('nodeQuery.getQuery = nodeQuery.init', () => { expect(nodeQuery.getQuery()) .to.be.deep.equal(qb); }); - test('insertBatch throws error', () => { + it('insertBatch throws error', () => { expect(() => { qb.driver.insertBatch('create_test', []); }).to.throw(Error, 'Not Implemented'); @@ -31,7 +31,7 @@ if (!(process.env.CI || process.env.TRAVIS)) { testRunner(qb); - suiteTeardown(() => { + afterAll(() => { qb.end(); }); }); diff --git a/test/adapters/pg_test.js b/test/adapters/pg_test.js index 3a6d3e6..4cfc2fb 100644 --- a/test/adapters/pg_test.js +++ b/test/adapters/pg_test.js @@ -18,21 +18,21 @@ let nodeQuery = reload('../../lib/NodeQuery')(config); let qb = nodeQuery.getQuery(); let qb2 = null; -suite('Pg adapter tests -', () => { - test('nodeQuery.getQuery = nodeQuery.init', () => { +describe('Pg adapter tests -', () => { + it('nodeQuery.getQuery = nodeQuery.init', () => { expect(nodeQuery.getQuery()) .to.be.deep.equal(qb); }); - test('Connecting with an object also works', () => { + it('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; + expect(qb2).to.be.ok; }); - test('Test Connection Error', done => { + it('Test Connection Error', done => { try { reload('../../lib/NodeQuery')({}); done(true); @@ -44,7 +44,7 @@ suite('Pg adapter tests -', () => { }); testRunner(qb); - test('Promise - Select with function and argument in WHERE clause', () => { + it('Promise - Select with function and argument in WHERE clause', () => { let promise = qb.select('id') .from('create_test') .where('id', 'CEILING(SQRT(88))') @@ -52,11 +52,11 @@ suite('Pg adapter tests -', () => { return expect(promise).to.be.fulfilled; }); - test('Promise - Test Truncate', () => { + it('Promise - Test Truncate', () => { let promise = qb.truncate('create_test'); return expect(promise).to.be.fulfilled; }); - test('Promise - Test Insert Batch', () => { + it('Promise - Test Insert Batch', () => { let data = [ { id: 544, @@ -76,7 +76,7 @@ suite('Pg adapter tests -', () => { let promise = qb.insertBatch('create_test', data); return expect(promise).to.be.fulfilled; }); - suiteTeardown(() => { + afterAll(() => { qb.end(); qb2.end(); }); diff --git a/test/adapters/sqlite3_test.js b/test/adapters/sqlite3_test.js index e86f1fc..05312fd 100644 --- a/test/adapters/sqlite3_test.js +++ b/test/adapters/sqlite3_test.js @@ -15,8 +15,8 @@ const config = testBase.config; let nodeQuery = require('../../lib/NodeQuery')(config.sqlite3); let qb = nodeQuery.getQuery(); -suite('Sqlite3 adapter tests -', () => { - suiteSetup(done => { +describe('Sqlite3 adapter tests -', () => { + beforeAll(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);'; @@ -29,7 +29,7 @@ suite('Sqlite3 adapter tests -', () => { }); testRunner(qb); - test('Promise - Select with function and argument in WHERE clause', () => { + it('Promise - Select with function and argument in WHERE clause', () => { let promise = qb.select('id') .from('create_test') .where('id', 'ABS(-88)') @@ -37,7 +37,7 @@ suite('Sqlite3 adapter tests -', () => { expect(promise).to.be.fulfilled; }); - test('Promise - Test Insert Batch', () => { + it('Promise - Test Insert Batch', () => { let data = [ { id: 544, @@ -57,7 +57,7 @@ suite('Sqlite3 adapter tests -', () => { let promise = qb.insertBatch('create_test', data); expect(promise).to.be.fulfilled; }); - suiteTeardown(() => { + afterAll(() => { qb.end(); }); }); diff --git a/test/base/adapterPromiseTestRunner.js b/test/base/adapterPromiseTestRunner.js index 58c391b..19556c3 100644 --- a/test/base/adapterPromiseTestRunner.js +++ b/test/base/adapterPromiseTestRunner.js @@ -11,11 +11,11 @@ const reload = require('require-reload')(require); const tests = reload('../base/tests'); module.exports = function promiseTestRunner (qb) { - Object.keys(tests).forEach(suiteName => { - suite(suiteName, () => { - let currentSuite = tests[suiteName]; + Object.keys(tests).forEach(describeName => { + describe(describeName, () => { + let currentSuite = tests[describeName]; Object.keys(currentSuite).forEach(testDesc => { - test(testDesc, done => { + it(testDesc, done => { const methodObj = currentSuite[testDesc]; const methodNames = Object.keys(methodObj); let results = []; @@ -45,13 +45,13 @@ module.exports = function promiseTestRunner (qb) { }); }); }); - suite('DB update tests -', () => { - suiteSetup(done => { + describe('DB update tests -', () => { + beforeAll(done => { let sql = qb.driver.truncate('create_test'); qb.query(sql).then(res => done()) .catch(err => done(err)); }); - test('Promise - Test Insert', () => { + it('Promise - Test Insert', () => { let promise = qb.set('id', 98) .set('key', '84') .set('val', Buffer.from('120')) @@ -59,7 +59,7 @@ module.exports = function promiseTestRunner (qb) { return expect(promise).to.be.fulfilled; }); - test('Promise - Test Insert Object', () => { + it('Promise - Test Insert Object', () => { let promise = qb.insert('create_test', { id: 587, key: 1, @@ -68,7 +68,7 @@ module.exports = function promiseTestRunner (qb) { return expect(promise).to.be.fulfilled; }); - test('Promise - Test Update', () => { + it('Promise - Test Update', () => { let promise = qb.where('id', 7) .update('create_test', { id: 7, @@ -78,7 +78,7 @@ module.exports = function promiseTestRunner (qb) { return expect(promise).to.be.fulfilled; }); - test('Promise - Test set Array Update', () => { + it('Promise - Test set Array Update', () => { let object = { id: 22, key: 'gogle', @@ -91,7 +91,7 @@ module.exports = function promiseTestRunner (qb) { return expect(promise).to.be.fulfilled; }); - test('Promise - Test where set update', () => { + it('Promise - Test where set update', () => { let promise = qb.where('id', 36) .set('id', 36) .set('key', 'gogle') @@ -100,17 +100,17 @@ module.exports = function promiseTestRunner (qb) { return expect(promise).to.be.fulfilled; }); - test('Promise - Test delete', () => { + it('Promise - Test delete', () => { let promise = qb.delete('create_test', {id: 5}); return expect(promise).to.be.fulfilled; }); - test('Promise - Delete with where', () => { + it('Promise - Delete with where', () => { let promise = qb.where('id', 5) .delete('create_test'); return expect(promise).to.be.fulfilled; }); - test('Promise - Delete multiple where values', () => { + it('Promise - Delete multiple where values', () => { let promise = qb.delete('create_test', { id: 5, key: 'gogle' @@ -119,8 +119,8 @@ module.exports = function promiseTestRunner (qb) { return expect(promise).to.be.fulfilled; }); }); - suite('Grouping tests -', () => { - test('Promise - Using grouping method', () => { + describe('Grouping tests -', () => { + it('Promise - Using grouping method', () => { let promise = qb.select('id, key as k, val') .from('create_test') .groupStart() @@ -132,7 +132,7 @@ module.exports = function promiseTestRunner (qb) { return expect(promise).to.be.fulfilled; }); - test('Promise - Using where first grouping', () => { + it('Promise - Using where first grouping', () => { let promise = qb.select('id, key as k, val') .from('create_test') .where('id !=', 5) @@ -145,7 +145,7 @@ module.exports = function promiseTestRunner (qb) { return expect(promise).to.be.fulfilled; }); - test('Promise - Using or grouping method', () => { + it('Promise - Using or grouping method', () => { let promise = qb.select('id, key as k, val') .from('create_test') .groupStart() @@ -160,7 +160,7 @@ module.exports = function promiseTestRunner (qb) { return expect(promise).to.be.fulfilled; }); - test('Promise - Using or not grouping method', () => { + it('Promise - Using or not grouping method', () => { let promise = qb.select('id, key as k, val') .from('create_test') .groupStart() diff --git a/test/base_test.js b/test/base_test.js index bcaab45..bbcd538 100644 --- a/test/base_test.js +++ b/test/base_test.js @@ -7,19 +7,19 @@ const glob = require('glob'); const nodeQuery = reload('../lib/NodeQuery')(); const Adapter = reload('../lib/Adapter'); -suite('Base tests -', () => { - suite('Sanity check', () => { +describe('Base tests -', () => { + describe('Sanity check', () => { let files = glob.sync(`${__dirname}/../lib/**/*.js`); files.forEach(mod => { let obj = require(mod); let shortName = mod.replace(/^\/(.*?)\/lib\/(.*?)\.js$/g, '$2'); - test(`${shortName} module is sane`, () => { + it(`${shortName} module is sane`, () => { expect(obj).to.be.ok; }); }); }); - test('NodeQuery.getQuery with no instance', () => { + it('NodeQuery.getQuery with no instance', () => { // Hack for testing to work around node // module caching let nodeQueryCopy = Object.create(nodeQuery); @@ -29,7 +29,7 @@ suite('Base tests -', () => { }).to.throw(Error, 'No Query Builder instance to return'); }); - test('Invalid driver type', () => { + it('Invalid driver type', () => { expect(() => { reload('../lib/NodeQuery')({ driver: 'Foo' @@ -37,14 +37,14 @@ suite('Base tests -', () => { }).to.throw(Error, 'Selected driver (Foo) does not exist!'); }); - test('Invalid adapter', () => { + it('Invalid adapter', () => { expect(() => { let a = new Adapter(); a.execute(); }).to.throw(Error, 'Correct adapter not defined for query execution'); }); - test('Invalid adapter - missing transformResult', () => { + it('Invalid adapter - missing transformResult', () => { expect(() => { let a = new Adapter(); a.transformResult([]); diff --git a/test/docker_install.sh b/test/docker_install.sh index 7009d6b..341b328 100644 --- a/test/docker_install.sh +++ b/test/docker_install.sh @@ -9,8 +9,8 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" set -xe # Install sqlite3 -apt-get update -yqq -apt-get install sqlite3 libsqlite3-dev -yqq +echo -e 'http://dl-cdn.alpinelinux.org/alpine/edge/main\nhttp://dl-cdn.alpinelinux.org/alpine/edge/community\nhttp://dl-cdn.alpinelinux.org/alpine/edge/testing' > /etc/apk/repositories +apk add --no-cache git yarn sqlite3 libsqlite3 # Replace test config with docker config file mv "$DIR/config-ci.json" "$DIR/config.json" \ No newline at end of file diff --git a/test/helpers_test.js b/test/helpers_test.js index f44d4e2..d94f6b7 100644 --- a/test/helpers_test.js +++ b/test/helpers_test.js @@ -7,23 +7,23 @@ const expect = chai.expect; 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\'', () => { +describe('Helper Module Tests -', () => { + describe('Type-checking methods -', () => { + describe('Object wrappers are listed as their native type', () => { + it('Boolean Wrapper returns \'boolean\' not \'object\'', () => { let item = Boolean(true); expect(helpers.type(item)).to.deep.equal('boolean'); }); - test('Number Wrapper returns \'number\' not \'object\'', () => { + it('Number Wrapper returns \'number\' not \'object\'', () => { let item = Number(4867); expect(helpers.type(item)).to.deep.equal('number'); }); - test('String Wrapper returns \'string\' not \'object\'', () => { + it('String Wrapper returns \'string\' not \'object\'', () => { let item = String('Foo'); expect(helpers.type(item)).to.deep.equal('string'); }); }); - suite('is..Method methods exist -', () => { + describe('is..Method methods exist -', () => { let types = [ 'Null', 'Undefined', @@ -39,19 +39,19 @@ suite('Helper Module Tests -', () => { ]; types.forEach(type => { - test(`is${type} method exists`, () => { + it(`is${type} method exists`, () => { assert.ok(helpers[`is${type}`]); }); }); }); - suite('isScalar -', () => { + describe('isScalar -', () => { let trueCases = { 'Strings are scalar': 'foo', 'Booleans are scalar': true, 'Numbers are scalar': 545 }; Object.keys(trueCases).forEach(desc => { - test(desc, () => { + it(desc, () => { expect(helpers.isScalar(trueCases[desc])).to.be.true; }); }); @@ -61,38 +61,38 @@ suite('Helper Module Tests -', () => { 'Objects are not scalar': [] }; Object.keys(falseCases).forEach(desc => { - test(desc, () => { + it(desc, () => { expect(helpers.isScalar(falseCases[desc])).to.be.false; }); }); }); - suite('isInfinity -', () => { - test('The type of 1/0 is infinity', () => { + describe('isInfinity -', () => { + it('The type of 1/0 is infinity', () => { expect(helpers.type(1 / 0)).to.equal('infinity'); }); - test('isInfinity is the same as isInfinite', () => { + it('isInfinity is the same as isInfinite', () => { expect(helpers.isInfinite(1 / 0)).to.be.true; }); }); - suite('isNaN -', () => { - test('The type of 0 / 0 is NaN', () => { + describe('isNaN -', () => { + it('The type of 0 / 0 is NaN', () => { expect(helpers.type(0 / 0)).to.equal('nan'); }); - test('isNaN method agrees with type', () => { + it('isNaN method agrees with type', () => { expect(helpers.isNaN(0 / 0)).to.be.true; }); }); }); - suite('Other helper methods -', () => { - suite('stringTrim -', () => { - test('stringTrim method works as expected', () => { + describe('Other helper methods -', () => { + describe('stringTrim -', () => { + it('stringTrim method works as expected', () => { let orig = [' x y ', 'z ', ' q']; let ret = ['x y', 'z', 'q']; expect(orig.map(helpers.stringTrim)).to.be.deep.equal(ret); }); }); - suite('arrayPluck -', () => { + describe('arrayPluck -', () => { let orig = [ { foo: 1 @@ -105,17 +105,17 @@ suite('Helper Module Tests -', () => { } ]; - test('Finding members in all objects', () => { + it('Finding members in all objects', () => { expect(helpers.arrayPluck(orig, 'foo')).to.be.deep.equal([1, 2, 3]); }); - test('Some members are missing in some objects', () => { + it('Some members are missing in some objects', () => { expect(helpers.arrayPluck(orig, 'bar')).to.be.deep.equal([10, 15]); }); - test('Empty case', () => { + it('Empty case', () => { expect(helpers.arrayPluck([], 'apple')).to.be.deep.equal([]); }); }); - suite('regexInArray -', () => { + describe('regexInArray -', () => { let orig = ['apple', ' string ', 6, 4, 7]; let cases = [ @@ -131,7 +131,7 @@ suite('Helper Module Tests -', () => { [0, 1].forEach(i => { let boolCase = cases[i]; Object.keys(boolCase).forEach(desc => { - test(desc, () => { + it(desc, () => { if (i) { expect(helpers.regexInArray(orig, boolCase[desc])).to.be.true; } else { @@ -141,15 +141,15 @@ suite('Helper Module Tests -', () => { }); }); - test('First argument is not an array', () => { + it('First argument is not an array', () => { expect(helpers.regexInArray(5, /5/)).to.be.false; }); - test('Array is empty', () => { + it('Array is empty', () => { expect(helpers.regexInArray([], /.*/)).to.be.false; }); }); - suite('upperCaseFirst -', () => { - test('Capitalizes only the first letter of the string', () => { + describe('upperCaseFirst -', () => { + it('Capitalizes only the first letter of the string', () => { expect(helpers.upperCaseFirst('foobar')).to.equal('Foobar'); expect(helpers.upperCaseFirst('FOOBAR')).to.equal('FOOBAR'); }); diff --git a/test/mocha.opts b/test/mocha.opts index 4065ed0..fedf205 100644 --- a/test/mocha.opts +++ b/test/mocha.opts @@ -1,9 +1,8 @@ ---ui tdd --bail --recursive --reporter nyan --slow 200 --timeout 25000 --check-leaks ---growl +--growl test/**/*_test.js \ No newline at end of file diff --git a/test/query-parser_test.js b/test/query-parser_test.js index 7e10dcb..b90bd8e 100644 --- a/test/query-parser_test.js +++ b/test/query-parser_test.js @@ -52,34 +52,34 @@ let whereMock = function (key, val) { // ! Start Tests // ----------------------------------------------------------------------------- -suite('Query Parser Tests', () => { - suite('Has operator tests', () => { - test('Has operator', () => { +describe('Query Parser Tests', () => { + describe('Has operator tests', () => { + it('Has operator', () => { let matches = parser.hasOperator('foo <> 2'); expect(matches).to.be.deep.equal(['<>']); }); - test('Has no operator', () => { + it('Has no operator', () => { let matches = parser.hasOperator('foo'); expect(matches).to.be.null; }); }); - suite('Where parser tests', () => { - setup(() => { + describe('Where parser tests', () => { + beforeAll(() => { state = new State(); }); - test('Has function full string', () => { + it('Has function full string', () => { whereMock('time < SUM(FOO(BAR()))'); parser.parseWhere(driver, state); expect(state.whereMap) .to.be.deep.equal(['"time" < SUM(FOO(BAR()))']); }); - test('Has function key/val', () => { + it('Has function key/val', () => { whereMock('time <', 'SUM(FOO(BAR()))'); parser.parseWhere(driver, state); expect(state.whereMap) .to.be.deep.equal(['"time" < SUM(FOO(BAR()))']); }); - test('Has function key/val object', () => { + it('Has function key/val object', () => { whereMock({ 'time <': 'SUM(FOO(BAR(\'x\')))' }); @@ -87,7 +87,7 @@ suite('Query Parser Tests', () => { expect(state.whereMap) .to.be.deep.equal(['"time" < SUM(FOO(BAR(\'x\')))']); }); - test('Has literal value', () => { + it('Has literal value', () => { whereMock({ foo: 3 }); @@ -97,7 +97,7 @@ suite('Query Parser Tests', () => { expect(state.whereValues) .to.be.deep.equal(['3']); }); - test('Has multiple literal values', () => { + it('Has multiple literal values', () => { whereMock({ foo: 3, bar: 5 @@ -109,7 +109,7 @@ suite('Query Parser Tests', () => { .to.be.deep.equal(['3', '5']); }); }); - suite('Parse join tests', () => { + describe('Parse join tests', () => { let data = [ { desc: 'Simple equals condition', @@ -131,13 +131,13 @@ suite('Query Parser Tests', () => { ]; data.forEach(datum => { - test(datum.desc, () => { + it(datum.desc, () => { let matches = parser.parseJoin(datum.join); expect(matches.combined).to.be.deep.equal(datum.expected); }); }); }); - suite('Compile join tests', () => { + describe('Compile join tests', () => { let data = [ { desc: 'Simple equals condition', @@ -159,7 +159,7 @@ suite('Query Parser Tests', () => { ]; data.forEach(datum => { - test(datum.desc, () => { + it(datum.desc, () => { let join = parser.compileJoin(datum.clause); expect(join).to.be.deep.equal(datum.expected); }); From a5eb0795c9973b32661fba4b132288cb48dc8ed1 Mon Sep 17 00:00:00 2001 From: Timothy J Warren Date: Tue, 28 Feb 2017 16:11:34 -0500 Subject: [PATCH 05/16] Fix reference to Helpers, other updates --- .gitlab-ci.yml | 10 +++++----- lib/Driver.js | 8 ++++---- lib/QueryBuilderBase.js | 14 +++++++------- lib/QueryParser.js | 14 +++++++------- lib/Result.js | 4 ++-- lib/drivers/Firebird.js | 4 ++-- lib/drivers/MSSQLDriver.js | 2 +- lib/drivers/Mysql.js | 4 ++-- package.json | 16 +++++++++------- test/query-parser_test.js | 6 +++--- 10 files changed, 42 insertions(+), 40 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c559a5c..0f9dee5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,11 +1,11 @@ before_script: # Install dependencies - bash test/docker_install.sh > /dev/null - - npm install + - yarn services: - - mysql:latest - - postgres:latest + - mariadb:latest + - postgres: alpine variables: MYSQL_ROOT_PASSWORD: foo-bar-baz @@ -23,9 +23,9 @@ cache: - node_modules/ test:6: - image: node:6 + image: node:6-alpine script: npm run test test:latest: - image: node:latest + image: node:alpine script: npm run test diff --git a/lib/Driver.js b/lib/Driver.js index 058224a..e60bb2d 100755 --- a/lib/Driver.js +++ b/lib/Driver.js @@ -1,6 +1,6 @@ 'use strict'; -const helpers = require('./helpers'); +const Helpers = require('./Helpers'); /** * Base Database Driver @@ -21,7 +21,7 @@ const Driver = { * @private */ _quote (str) { - return (helpers.isString(str) && + return (Helpers.isString(str) && !(str.startsWith(Driver.identifierStartChar) || str.endsWith(Driver.identifierEndChar)) ) ? `${Driver.identifierStartChar}${str}${Driver.identifierEndChar}` @@ -39,7 +39,7 @@ const Driver = { limit (sql, limit, offset) { sql += ` LIMIT ${limit}`; - if (helpers.isNumber(offset)) { + if (Helpers.isNumber(offset)) { sql += ` OFFSET ${offset}`; } @@ -79,7 +79,7 @@ const Driver = { // Handle commas if (str.includes(',')) { - let parts = str.split(',').map(helpers.stringTrim); + let parts = str.split(',').map(Helpers.stringTrim); str = parts.map(Driver.quoteIdentifiers).join(','); } diff --git a/lib/QueryBuilderBase.js b/lib/QueryBuilderBase.js index e0f3332..4180ac5 100644 --- a/lib/QueryBuilderBase.js +++ b/lib/QueryBuilderBase.js @@ -1,6 +1,6 @@ 'use strict'; -const helpers = require('./helpers'); +const Helpers = require('./Helpers'); const QueryParser = require('./QueryParser'); const State = require('./State'); @@ -34,7 +34,7 @@ class QueryBuilderBase { ['queryMap', 'groupString', 'orderString', 'havingMap'].forEach(clause => { let param = this.state[clause]; - if (!helpers.isScalar(param)) { + if (!Helpers.isScalar(param)) { Object.keys(param).forEach(part => { sql += param[part].conjunction + param[part].string; }); @@ -44,7 +44,7 @@ class QueryBuilderBase { }); // Append the limit, if it exists - if (helpers.isNumber(this.state.limit)) { + if (Helpers.isNumber(this.state.limit)) { sql = this.driver.limit(sql, this.state.limit, this.state.offset); } @@ -132,10 +132,10 @@ class QueryBuilderBase { _mixedSet (letName, valType, key, val) { let obj = {}; - if (helpers.isScalar(key) && !helpers.isUndefined(val)) { + 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)) { + } 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 { @@ -165,9 +165,9 @@ class QueryBuilderBase { _fixConjunction (conj) { let lastItem = this.state.queryMap[this.state.queryMap.length - 1]; - let conjunctionList = helpers.arrayPluck(this.state.queryMap, 'conjunction'); + let conjunctionList = Helpers.arrayPluck(this.state.queryMap, 'conjunction'); - if (this.state.queryMap.length === 0 || (!helpers.regexInArray(conjunctionList, /^ ?WHERE/i))) { + if (this.state.queryMap.length === 0 || (!Helpers.regexInArray(conjunctionList, /^ ?WHERE/i))) { conj = ' WHERE '; } else if (lastItem.type === 'groupStart') { conj = ''; diff --git a/lib/QueryParser.js b/lib/QueryParser.js index 27a140a..abf015c 100644 --- a/lib/QueryParser.js +++ b/lib/QueryParser.js @@ -1,7 +1,7 @@ 'use strict'; const XRegExp = require('xregexp'); -const helpers = require('./helpers'); +const Helpers = require('./Helpers'); // -------------------------------------------------------------------------- @@ -59,7 +59,7 @@ class QueryParser { let output = []; // Return non-array matches - if (helpers.isNull(array)) { + if (Helpers.isNull(array)) { return null; } @@ -123,7 +123,7 @@ class QueryParser { // Quote the identifiers parts.combined.forEach((part, i) => { - if (parts.identifiers.indexOf(part) !== -1 && !helpers.isNumber(part)) { + if (parts.identifiers.indexOf(part) !== -1 && !Helpers.isNumber(part)) { parts.combined[i] = this.driver.quoteIdentifiers(part); } }); @@ -166,9 +166,9 @@ class QueryParser { if (whereValues.indexOf(whereMap[key]) !== -1) { let value = whereMap[key]; let identIndex = parts.identifiers.indexOf(value); - let litIndex = (helpers.isArray(parts.literals)) ? parts.literals.indexOf(value) : -1; + let litIndex = (Helpers.isArray(parts.literals)) ? parts.literals.indexOf(value) : -1; let combIndex = parts.combined.indexOf(value); - let funcIndex = (helpers.isArray(parts.functions)) ? parts.functions.indexOf(value) : -1; + let funcIndex = (Helpers.isArray(parts.functions)) ? parts.functions.indexOf(value) : -1; let inOutputArray = outputValues.indexOf(value) !== -1; // Remove the identifier in question, @@ -210,7 +210,7 @@ class QueryParser { }, this); // Quote identifiers - if (helpers.isArray(parts.identifiers)) { + if (Helpers.isArray(parts.identifiers)) { parts.identifiers.forEach(ident => { let index = parts.combined.indexOf(ident); if (index !== -1) { @@ -224,7 +224,7 @@ class QueryParser { // This should only apply to literal values that are not // explicitly mapped to values, but have to be parsed from // a where condition, - if (helpers.isArray(parts.literals)) { + if (Helpers.isArray(parts.literals)) { parts.literals.forEach(lit => { let litIndex = parts.combined.indexOf(lit); diff --git a/lib/Result.js b/lib/Result.js index 4fb79a3..461b51a 100644 --- a/lib/Result.js +++ b/lib/Result.js @@ -1,6 +1,6 @@ 'use strict'; -const helpers = require('./helpers'); +const Helpers = require('./Helpers'); /** * Query result object @@ -25,7 +25,7 @@ class Result { if ( this._columns.length === 0 && this._rows.length > 0 && - helpers.isObject(rows[0]) + Helpers.isObject(rows[0]) ) { this.columns = Object.keys(rows[0]); } diff --git a/lib/drivers/Firebird.js b/lib/drivers/Firebird.js index 2c91827..ad7ad19 100644 --- a/lib/drivers/Firebird.js +++ b/lib/drivers/Firebird.js @@ -1,6 +1,6 @@ 'use strict'; -let helpers = require('../helpers'); +let Helpers = require('../Helpers'); /** * Driver for Firebird databases @@ -24,7 +24,7 @@ module.exports = (() => { driver.limit = (origSql, limit, offset) => { let sql = `FIRST ${limit}`; - if (helpers.isNumber(offset)) { + if (Helpers.isNumber(offset)) { sql += ` SKIP ${offset}`; } diff --git a/lib/drivers/MSSQLDriver.js b/lib/drivers/MSSQLDriver.js index 0deab0f..19ec4f5 100644 --- a/lib/drivers/MSSQLDriver.js +++ b/lib/drivers/MSSQLDriver.js @@ -8,7 +8,7 @@ module.exports = (() => { delete require.cache[require.resolve('../Driver')]; const driver = require('../Driver'); - const helpers = require('../helpers'); + const Helpers = require('../Helpers'); driver.identifierStartChar = '['; driver.identifierEndChar = ']'; diff --git a/lib/drivers/Mysql.js b/lib/drivers/Mysql.js index 0e91273..c8c2f45 100755 --- a/lib/drivers/Mysql.js +++ b/lib/drivers/Mysql.js @@ -8,7 +8,7 @@ module.exports = (() => { delete require.cache[require.resolve('../Driver')]; const driver = require('../Driver'); - const helpers = require('../helpers'); + const Helpers = require('../Helpers'); driver.identifierStartChar = '`'; driver.identifierEndChar = '`'; @@ -22,7 +22,7 @@ module.exports = (() => { * @return {String} - Modified SQL statement */ driver.limit = (sql, limit, offset) => { - sql += (helpers.isNumber(offset)) + sql += (Helpers.isNumber(offset)) ? ` LIMIT ${offset},${limit}` : ` LIMIT ${limit}`; diff --git a/package.json b/package.json index 67d0f06..02558a8 100644 --- a/package.json +++ b/package.json @@ -37,15 +37,9 @@ }, "main": "lib/NodeQuery.js", "dependencies": { - "dblite": "~0.7.6", "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", - "sqlite3": "^3.1.8", - "tedious": "^1.14.0", "xregexp": "^3.0.0" }, "devDependencies": { @@ -57,7 +51,7 @@ "happiness": "^7.1.2", "jest": "^19.0.2", "jsdoc": "^3.4.3", - "npm-run-all": "^3.0.0", + "npm-run-all": "^4.0.2", "nsp": "^2.2.1" }, "license": "MIT", @@ -89,5 +83,13 @@ "lint:src": "eslint ./lib", "lint:tests": "eslint ./test", "test": "jest" + }, + "peerDependencies": { + "dblite": "^0.7.8", + "mysql2": "^1.2.0", + "node-firebird": "^0.8.1", + "pg-native": "^1.10.0", + "sqlite3": "^3.1.8", + "tedious": "^1.14.0" } } diff --git a/test/query-parser_test.js b/test/query-parser_test.js index b90bd8e..3fa01c1 100644 --- a/test/query-parser_test.js +++ b/test/query-parser_test.js @@ -3,7 +3,7 @@ const expect = require('chai').expect; // Use the base driver as a mock for testing -const helpers = require('../lib/helpers'); +const Helpers = require('../lib/Helpers'); const driver = require('../lib/Driver'); const P = require('../lib/QueryParser'); @@ -17,10 +17,10 @@ let state = new State(); let mixedSet = function mixedSet (letName, valType, key, val) { let obj = {}; - if (helpers.isScalar(key) && !helpers.isUndefined(val)) { + 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)) { + } 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 { From 7edb5f1df0ad46dbb3cc31d83d0d35fc0d6769e9 Mon Sep 17 00:00:00 2001 From: Timothy J Warren Date: Tue, 28 Feb 2017 16:14:17 -0500 Subject: [PATCH 06/16] Fix ci yaml file --- .gitlab-ci.yml | 6 +++--- test/docker_install.sh | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0f9dee5..7e2a3da 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -5,7 +5,7 @@ before_script: services: - mariadb:latest - - postgres: alpine + - postgres:alpine variables: MYSQL_ROOT_PASSWORD: foo-bar-baz @@ -24,8 +24,8 @@ cache: test:6: image: node:6-alpine - script: npm run test + script: yarn run test test:latest: image: node:alpine - script: npm run test + script: yarn run test diff --git a/test/docker_install.sh b/test/docker_install.sh index 341b328..ec098af 100644 --- a/test/docker_install.sh +++ b/test/docker_install.sh @@ -11,6 +11,7 @@ set -xe # Install sqlite3 echo -e 'http://dl-cdn.alpinelinux.org/alpine/edge/main\nhttp://dl-cdn.alpinelinux.org/alpine/edge/community\nhttp://dl-cdn.alpinelinux.org/alpine/edge/testing' > /etc/apk/repositories apk add --no-cache git yarn sqlite3 libsqlite3 +npm install pg sqlite3 dblite mysql2 # Replace test config with docker config file mv "$DIR/config-ci.json" "$DIR/config.json" \ No newline at end of file From 32b2a68b51c5a0e9db6abcef8289f03b227fe197 Mon Sep 17 00:00:00 2001 From: Timothy J Warren Date: Tue, 28 Feb 2017 16:16:12 -0500 Subject: [PATCH 07/16] Remove reference to bash --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7e2a3da..3c61561 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,6 +1,6 @@ before_script: # Install dependencies - - bash test/docker_install.sh > /dev/null + - sh test/docker_install.sh > /dev/null - yarn services: From 22f047b5ed367edab96c46a31df9f4b07fc3528f Mon Sep 17 00:00:00 2001 From: Timothy J Warren Date: Tue, 28 Feb 2017 16:20:04 -0500 Subject: [PATCH 08/16] Remove bashism --- test/docker_install.sh | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/docker_install.sh b/test/docker_install.sh index ec098af..e86d897 100644 --- a/test/docker_install.sh +++ b/test/docker_install.sh @@ -3,9 +3,6 @@ # We need to install dependencies only for Docker [[ ! -e /.dockerenv ]] && [[ ! -e /.dockerinit ]] && exit 0 -# Where am I? -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - set -xe # Install sqlite3 @@ -14,4 +11,4 @@ apk add --no-cache git yarn sqlite3 libsqlite3 npm install pg sqlite3 dblite mysql2 # Replace test config with docker config file -mv "$DIR/config-ci.json" "$DIR/config.json" \ No newline at end of file +mv "./config-ci.json" "./config.json" \ No newline at end of file From 5479a71614dc8dba6cec389a1db059dd0c7105bd Mon Sep 17 00:00:00 2001 From: Timothy J Warren Date: Tue, 28 Feb 2017 16:25:31 -0500 Subject: [PATCH 09/16] Use packages that actually exist --- test/docker_install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/docker_install.sh b/test/docker_install.sh index e86d897..e697d38 100644 --- a/test/docker_install.sh +++ b/test/docker_install.sh @@ -7,7 +7,7 @@ set -xe # Install sqlite3 echo -e 'http://dl-cdn.alpinelinux.org/alpine/edge/main\nhttp://dl-cdn.alpinelinux.org/alpine/edge/community\nhttp://dl-cdn.alpinelinux.org/alpine/edge/testing' > /etc/apk/repositories -apk add --no-cache git yarn sqlite3 libsqlite3 +apk add --no-cache git yarn sqlite npm install pg sqlite3 dblite mysql2 # Replace test config with docker config file From 15371e49e812ae7702dae2a29f38328d7743b344 Mon Sep 17 00:00:00 2001 From: Timothy J Warren Date: Tue, 28 Feb 2017 16:28:30 -0500 Subject: [PATCH 10/16] Attempt to create test config file --- test/docker_install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/docker_install.sh b/test/docker_install.sh index e697d38..ffc6099 100644 --- a/test/docker_install.sh +++ b/test/docker_install.sh @@ -11,4 +11,4 @@ apk add --no-cache git yarn sqlite npm install pg sqlite3 dblite mysql2 # Replace test config with docker config file -mv "./config-ci.json" "./config.json" \ No newline at end of file +mv "./test/config-ci.json" "./test/config.json" \ No newline at end of file From 806a1e1702cc66a4688ba9a4251e839421128f41 Mon Sep 17 00:00:00 2001 From: Timothy J Warren Date: Tue, 28 Feb 2017 16:43:09 -0500 Subject: [PATCH 11/16] Try gitlab ci setup again --- package.json | 13 +++++++------ test/docker_install.sh | 1 - 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 02558a8..6245523 100644 --- a/package.json +++ b/package.json @@ -37,9 +37,15 @@ }, "main": "lib/NodeQuery.js", "dependencies": { + "dblite": "^0.7.8", "getargs": "~0.0.8", "glob": "^7.0.3", + "mysql2": "^1.2.0", + "node-firebird": "^0.8.1", + "pg": "^6.1.2", "require-reload": "~0.2.2", + "sqlite3": "^3.1.8", + "tedious": "^1.14.0", "xregexp": "^3.0.0" }, "devDependencies": { @@ -85,11 +91,6 @@ "test": "jest" }, "peerDependencies": { - "dblite": "^0.7.8", - "mysql2": "^1.2.0", - "node-firebird": "^0.8.1", - "pg-native": "^1.10.0", - "sqlite3": "^3.1.8", - "tedious": "^1.14.0" + "pg-native": "^1.10.0" } } diff --git a/test/docker_install.sh b/test/docker_install.sh index ffc6099..fc17261 100644 --- a/test/docker_install.sh +++ b/test/docker_install.sh @@ -8,7 +8,6 @@ set -xe # Install sqlite3 echo -e 'http://dl-cdn.alpinelinux.org/alpine/edge/main\nhttp://dl-cdn.alpinelinux.org/alpine/edge/community\nhttp://dl-cdn.alpinelinux.org/alpine/edge/testing' > /etc/apk/repositories apk add --no-cache git yarn sqlite -npm install pg sqlite3 dblite mysql2 # Replace test config with docker config file mv "./test/config-ci.json" "./test/config.json" \ No newline at end of file From a94038cd47c42e640a9ff3de31c87ad31a9fe7f7 Mon Sep 17 00:00:00 2001 From: Timothy J Warren Date: Fri, 2 Feb 2018 11:50:29 -0500 Subject: [PATCH 12/16] Update dependencies, and fix linting issues --- .eslintrc | 5 +- lib/Adapter.js | 2 - lib/Driver.js | 2 - lib/Helpers.js | 2 - lib/NodeQuery.js | 2 - lib/QueryBuilder.js | 2 - lib/QueryBuilderBase.js | 6 +- lib/QueryParser.js | 4 +- lib/Result.js | 4 +- lib/State.js | 2 - lib/adapters/Firebird/index.js | 8 - lib/adapters/Firebird/node-firebird.js | 61 -------- lib/adapters/MariaDB/index.js | 2 - lib/adapters/Mysql/index.js | 2 - lib/adapters/Mysql/mysql2.js | 3 - lib/adapters/Pg/Pg.js | 2 - lib/adapters/Pg/PgNative.js | 7 +- lib/adapters/Pg/index.js | 4 +- lib/adapters/Sqlite/dblite.js | 2 - lib/adapters/Sqlite/index.js | 3 - lib/adapters/Sqlite/sqlite3.js | 2 - lib/drivers/Firebird.js | 45 ------ lib/drivers/MariaDB.js | 2 - lib/drivers/Mysql.js | 2 - lib/drivers/Pg.js | 2 - lib/drivers/Sqlite.js | 2 - package.json | 194 +++++++++++++------------ test/FB_TEST_DB.FDB | Bin 1040384 -> 0 bytes test/adapters/dblite_test.js | 16 +- test/adapters/mysql2_test.js | 24 ++- test/adapters/node-firebird_test.js | 38 ----- test/adapters/pg_test.js | 29 ++-- test/adapters/sqlite3_test.js | 16 +- test/base.js | 2 - test/base/adapterPromiseTestRunner.js | 94 ++++++------ test/base/tests.js | 2 - test/base_test.js | 14 +- test/helpers_test.js | 49 +++---- test/query-parser_test.js | 26 ++-- 39 files changed, 226 insertions(+), 458 deletions(-) delete mode 100644 lib/adapters/Firebird/index.js delete mode 100644 lib/adapters/Firebird/node-firebird.js delete mode 100644 lib/drivers/Firebird.js delete mode 100755 test/FB_TEST_DB.FDB delete mode 100644 test/adapters/node-firebird_test.js diff --git a/.eslintrc b/.eslintrc index 2116a09..d230c37 100644 --- a/.eslintrc +++ b/.eslintrc @@ -34,5 +34,6 @@ "callback-return": [1], "object-shorthand": [1, "methods"], "prefer-template": [1] - } -} \ No newline at end of file + }, + "parser": "babel-eslint" +} diff --git a/lib/Adapter.js b/lib/Adapter.js index 772e341..8a0b993 100755 --- a/lib/Adapter.js +++ b/lib/Adapter.js @@ -1,5 +1,3 @@ -'use strict'; - /** * Class that wraps database connection libraries * diff --git a/lib/Driver.js b/lib/Driver.js index e60bb2d..19ac734 100755 --- a/lib/Driver.js +++ b/lib/Driver.js @@ -1,5 +1,3 @@ -'use strict'; - const Helpers = require('./Helpers'); /** diff --git a/lib/Helpers.js b/lib/Helpers.js index f5ff917..627babf 100755 --- a/lib/Helpers.js +++ b/lib/Helpers.js @@ -1,5 +1,3 @@ -'use strict'; - const fs = require('fs'); /** diff --git a/lib/NodeQuery.js b/lib/NodeQuery.js index 8af3938..54086b0 100755 --- a/lib/NodeQuery.js +++ b/lib/NodeQuery.js @@ -1,5 +1,3 @@ -'use strict'; - const QueryBuilder = require('./QueryBuilder'); // Map config driver name to code class name diff --git a/lib/QueryBuilder.js b/lib/QueryBuilder.js index 03f69e5..da3a8a6 100755 --- a/lib/QueryBuilder.js +++ b/lib/QueryBuilder.js @@ -1,5 +1,3 @@ -'use strict'; - const Helpers = require('./Helpers'); const QueryBuilderBase = require('./QueryBuilderBase'); diff --git a/lib/QueryBuilderBase.js b/lib/QueryBuilderBase.js index 4180ac5..c4e9d7f 100644 --- a/lib/QueryBuilderBase.js +++ b/lib/QueryBuilderBase.js @@ -1,5 +1,3 @@ -'use strict'; - const Helpers = require('./Helpers'); const QueryParser = require('./QueryParser'); const State = require('./State'); @@ -127,6 +125,10 @@ class QueryBuilderBase { * when appending to state * * @private + * @param {mixed} letName Lorem Ipsum + * @param {mixed} valType Lorem Ipsum + * @param {mixed} key Lorem Ipsum + * @param {mixed} val Lorem Ipsum * @return {Array} - modified state array */ _mixedSet (letName, valType, key, val) { diff --git a/lib/QueryParser.js b/lib/QueryParser.js index abf015c..b59c044 100644 --- a/lib/QueryParser.js +++ b/lib/QueryParser.js @@ -1,5 +1,3 @@ -'use strict'; - const XRegExp = require('xregexp'); const Helpers = require('./Helpers'); @@ -35,7 +33,7 @@ class QueryParser { ${matchPatterns['function'].source}| ${matchPatterns.literal.source} ) - ([a-z_\-]+[0-9]*\\.?) + ([a-z_-]+[0-9]*\\.?) )+`, 'igx'); // Full pattern for determining ordering of the pieces diff --git a/lib/Result.js b/lib/Result.js index 461b51a..6fbc140 100644 --- a/lib/Result.js +++ b/lib/Result.js @@ -1,5 +1,3 @@ -'use strict'; - const Helpers = require('./Helpers'); /** @@ -16,7 +14,7 @@ class Result { * @param {Array} [rows] - the data rows of the result * @param {Array} [columns] - the column names in the result */ - constructor (rows=[], columns=[]) { + constructor (rows = [], columns = []) { this._rows = rows; this._columns = columns; diff --git a/lib/State.js b/lib/State.js index eec885c..c9aaafb 100644 --- a/lib/State.js +++ b/lib/State.js @@ -1,5 +1,3 @@ -'use strict'; - /** * Class for objects containing the query builder state * @private diff --git a/lib/adapters/Firebird/index.js b/lib/adapters/Firebird/index.js deleted file mode 100644 index 1d93c17..0000000 --- a/lib/adapters/Firebird/index.js +++ /dev/null @@ -1,8 +0,0 @@ -'use strict'; - -const NodeFirebird = require('./node-firebird'); - -module.exports = config => { - return new NodeFirebird(config.connection); -}; - diff --git a/lib/adapters/Firebird/node-firebird.js b/lib/adapters/Firebird/node-firebird.js deleted file mode 100644 index e62e7d5..0000000 --- a/lib/adapters/Firebird/node-firebird.js +++ /dev/null @@ -1,61 +0,0 @@ -'use strict'; - -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) => { - fb.attach(config, (err, instance) => { - if (err) { - return reject(err); - } - - return resolve(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 => { - return new Promise((resolve, reject) => { - conn.query(sql, params, (err, result) => { - if (err) { - return reject(err); - } - - 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} - */ - close () { - this.instance.then(conn => conn.detach()); - } -} - -module.exports = Firebird; diff --git a/lib/adapters/MariaDB/index.js b/lib/adapters/MariaDB/index.js index f351d4d..a4fa937 100644 --- a/lib/adapters/MariaDB/index.js +++ b/lib/adapters/MariaDB/index.js @@ -1,3 +1 @@ -'use strict'; - module.exports = require('../Mysql'); diff --git a/lib/adapters/Mysql/index.js b/lib/adapters/Mysql/index.js index 985c3f4..a46cd5c 100644 --- a/lib/adapters/Mysql/index.js +++ b/lib/adapters/Mysql/index.js @@ -1,5 +1,3 @@ -'use strict'; - const Mysql2 = require('./mysql2'); module.exports = config => { diff --git a/lib/adapters/Mysql/mysql2.js b/lib/adapters/Mysql/mysql2.js index c13483c..b86d01e 100644 --- a/lib/adapters/Mysql/mysql2.js +++ b/lib/adapters/Mysql/mysql2.js @@ -1,12 +1,9 @@ -'use strict'; - const Adapter = require('../../Adapter'); const Result = require('../../Result'); const Helpers = require('../../Helpers'); const mysql2 = require('mysql2/promise'); class Mysql extends Adapter { - constructor (config) { const instance = mysql2.createConnection(config); super(instance); diff --git a/lib/adapters/Pg/Pg.js b/lib/adapters/Pg/Pg.js index 4d50492..4a9803b 100644 --- a/lib/adapters/Pg/Pg.js +++ b/lib/adapters/Pg/Pg.js @@ -1,5 +1,3 @@ -'use strict'; - const Adapter = require('../../Adapter'); const Result = require('../../Result'); const Helpers = require('../../Helpers'); diff --git a/lib/adapters/Pg/PgNative.js b/lib/adapters/Pg/PgNative.js index c341d26..906dbe0 100644 --- a/lib/adapters/Pg/PgNative.js +++ b/lib/adapters/Pg/PgNative.js @@ -1,9 +1,5 @@ -'use strict'; - const Pg = require('./Pg'); -const Result = require('../../Result'); const pg = require('pg').native; -const url = require('url'); class PgNative extends Pg { constructor (config) { @@ -21,7 +17,8 @@ class PgNative extends Pg { instance = Promise.resolve(conn); } - super(instance); + + super.instance = instance; } } diff --git a/lib/adapters/Pg/index.js b/lib/adapters/Pg/index.js index 5a940e7..a7d7d22 100644 --- a/lib/adapters/Pg/index.js +++ b/lib/adapters/Pg/index.js @@ -1,7 +1,5 @@ -'use strict'; - const Pg = require('./Pg'); -const PgNative = require('./PgNative') +const PgNative = require('./PgNative'); module.exports = config => { return (config.native) diff --git a/lib/adapters/Sqlite/dblite.js b/lib/adapters/Sqlite/dblite.js index 21af28b..0629151 100644 --- a/lib/adapters/Sqlite/dblite.js +++ b/lib/adapters/Sqlite/dblite.js @@ -1,5 +1,3 @@ -'use strict'; - const Adapter = require('../../Adapter'); const Result = require('../../Result'); const Helpers = require('../../Helpers'); diff --git a/lib/adapters/Sqlite/index.js b/lib/adapters/Sqlite/index.js index b59bcf2..554e381 100644 --- a/lib/adapters/Sqlite/index.js +++ b/lib/adapters/Sqlite/index.js @@ -1,5 +1,3 @@ -'use strict'; - module.exports = config => { const Implementation = (config.native) ? require('./sqlite3') @@ -7,4 +5,3 @@ module.exports = config => { return new Implementation(config.connection); }; - diff --git a/lib/adapters/Sqlite/sqlite3.js b/lib/adapters/Sqlite/sqlite3.js index 4c0529a..6fe1391 100644 --- a/lib/adapters/Sqlite/sqlite3.js +++ b/lib/adapters/Sqlite/sqlite3.js @@ -1,5 +1,3 @@ -'use strict'; - const Adapter = require('../../Adapter'); const Result = require('../../Result'); const Helpers = require('../../Helpers'); diff --git a/lib/drivers/Firebird.js b/lib/drivers/Firebird.js deleted file mode 100644 index ad7ad19..0000000 --- a/lib/drivers/Firebird.js +++ /dev/null @@ -1,45 +0,0 @@ -'use strict'; - -let Helpers = require('../Helpers'); - -/** - * Driver for Firebird databases - * - * @module drivers/firebird - */ -module.exports = (() => { - delete require.cache[require.resolve('../Driver')]; - let driver = require('../Driver'); - - driver.hasTruncate = false; - - /** - * Set the limit clause - - * @param {String} origSql - SQL statement to modify - * @param {Number} limit - Maximum number of rows to fetch - * @param {Number|null} offset - Number of rows to skip - * @return {String} - Modified SQL statement - */ - driver.limit = (origSql, limit, offset) => { - let sql = `FIRST ${limit}`; - - if (Helpers.isNumber(offset)) { - sql += ` SKIP ${offset}`; - } - - return origSql.replace(/SELECT/i, `SELECT ${sql}`); - }; - - /** - * SQL to insert a group of rows - * - * @return {void} - * @throws {Error} - */ - driver.insertBatch = () => { - throw new Error('Not Implemented'); - }; - - return driver; -})(); diff --git a/lib/drivers/MariaDB.js b/lib/drivers/MariaDB.js index 8d6df4e..e100b26 100644 --- a/lib/drivers/MariaDB.js +++ b/lib/drivers/MariaDB.js @@ -1,5 +1,3 @@ -'use strict'; - /** * Driver for MariaDB databases * diff --git a/lib/drivers/Mysql.js b/lib/drivers/Mysql.js index c8c2f45..72bad31 100755 --- a/lib/drivers/Mysql.js +++ b/lib/drivers/Mysql.js @@ -1,5 +1,3 @@ -'use strict'; - /** * Driver for MySQL databases * diff --git a/lib/drivers/Pg.js b/lib/drivers/Pg.js index 61adf4f..3ec19cb 100755 --- a/lib/drivers/Pg.js +++ b/lib/drivers/Pg.js @@ -1,5 +1,3 @@ -'use strict'; - /** * Driver for PostgreSQL databases * diff --git a/lib/drivers/Sqlite.js b/lib/drivers/Sqlite.js index 0ae0c1a..cd69ab2 100644 --- a/lib/drivers/Sqlite.js +++ b/lib/drivers/Sqlite.js @@ -1,5 +1,3 @@ -'use strict'; - /** * Driver for SQLite databases * diff --git a/package.json b/package.json index 6245523..4fcfafc 100644 --- a/package.json +++ b/package.json @@ -1,96 +1,102 @@ { - "name": "ci-node-query", - "version": "5.0.0", - "description": "A query builder for node based on the one in CodeIgniter", - "author": "Timothy J Warren ", - "engines": { - "node": ">=6.0.0" - }, - "files": [ - "lib/" - ], - "contributors": [ - { - "name": "Timothy J Warren", - "email": "tim@timshomepage.net" - } - ], - "repository": { - "type": "git", - "url": "https://git.timshomepage.net/timw4mail/node-query.git" - }, - "keywords": [ - "codeigniter", - "mariadb", - "mysql", - "mssql", - "query builder", - "postgres", - "postgresql", - "sql", - "sqlite", - "sqlite3", - "sqlserver" - ], - "bugs": { - "url": "https://git.timshomepage.net/timw4mail/node-query/issues" - }, - "main": "lib/NodeQuery.js", - "dependencies": { - "dblite": "^0.7.8", - "getargs": "~0.0.8", - "glob": "^7.0.3", - "mysql2": "^1.2.0", - "node-firebird": "^0.8.1", - "pg": "^6.1.2", - "require-reload": "~0.2.2", - "sqlite3": "^3.1.8", - "tedious": "^1.14.0", - "xregexp": "^3.0.0" - }, - "devDependencies": { - "chai": "^3.5.0", - "chai-as-promised": "^6.0.0", - "documentation": "latest", - "eslint": "^3.5.0", - "globstar": "^1.0.0", - "happiness": "^7.1.2", - "jest": "^19.0.2", - "jsdoc": "^3.4.3", - "npm-run-all": "^4.0.2", - "nsp": "^2.2.1" - }, - "license": "MIT", - "jest": { - "coverageDirectory": "coverage", - "coverageReporters": [ - "html", - "json", - "lcov", - "text-summary" - ], - "testEnvironment": "node", - "testMatch": [ - "**/test/**/*_test.js" - ] - }, - "scripts": { - "audit": "nsp check", - "build": "npm-run-all --parallel lint:src lint:tests docs coverage", - "coverage": "jest --coverage", - "default": "npm-run-all --parallel audit lint:src lint:tests && npm run test", - "predocs": "globstar -- documentation build -f md -o API.md \"lib/*.js\"", - "docs": "globstar -- documentation build -f html -o docs \"lib/*.js\"", - "postdocs": "jsdoc lib -r -d documentation", - "happy": "happiness \"lib/**/*.js\" \"test/**/*.js\"", - "happy:src": "happiness \"lib/**/*.js\"", - "happy:tests": "happiness \"test/**/*.js\"", - "lint": "npm-run-all lint:tests lint:src happy", - "lint:src": "eslint ./lib", - "lint:tests": "eslint ./test", - "test": "jest" - }, - "peerDependencies": { - "pg-native": "^1.10.0" - } + "name": "ci-node-query", + "version": "6.0.0", + "description": "A query builder for node based on the one in CodeIgniter", + "author": "Timothy J Warren ", + "engines": { + "node": ">=8.0.0" + }, + "files": [ + "lib/" + ], + "contributors": [ + { + "name": "Timothy J Warren", + "email": "tim@timshomepage.net" + } + ], + "repository": { + "type": "git", + "url": "https://git.timshomepage.net/timw4mail/node-query.git" + }, + "keywords": [ + "codeigniter", + "mariadb", + "mysql", + "mssql", + "query builder", + "postgres", + "postgresql", + "sql", + "sqlite", + "sqlite3", + "sqlserver" + ], + "bugs": { + "url": "https://git.timshomepage.net/timw4mail/node-query/issues" + }, + "main": "lib/NodeQuery.js", + "dependencies": { + "dblite": "^0.8.0", + "getargs": "~0.0.8", + "glob": "^7.0.3", + "mysql2": "^1.2.0", + "pg": "^7.4", + "require-reload": "~0.2.2", + "sqlite3": "^3.1.8", + "tedious": "^2.0.0", + "xregexp": "^4.0.0" + }, + "devDependencies": { + "babel-eslint": "^8.2.1", + "chai": "^4.1", + "chai-as-promised": "^7.1", + "documentation": "latest", + "eslint": "^4.16.0", + "globstar": "^1.0.0", + "happiness": "^10.0", + "jest": "^22.0.0", + "jsdoc": "^3.4.3", + "npm-run-all": "^4.0.2", + "nsp": "^3.1", + "pg-native": "^2.2" + }, + "license": "MIT", + "jest": { + "coverageDirectory": "coverage", + "coverageReporters": [ + "html", + "json", + "lcov", + "text-summary" + ], + "testEnvironment": "node", + "testMatch": [ + "**/test/**/*_test.js" + ] + }, + "scripts": { + "audit": "nsp check", + "build": "npm-run-all --parallel lint:src lint:tests docs coverage", + "coverage": "jest --coverage", + "default": "npm-run-all --parallel audit lint:src lint:tests && npm run test", + "predocs": "globstar -- documentation build -f md -o API.md \"lib/*.js\"", + "docs": "globstar -- documentation build -f html -o docs \"lib/*.js\"", + "fix": "happiness --fix \"lib/**/*.js\" \"test/**/*.js\"", + "postdocs": "jsdoc lib -r -d documentation", + "happy": "happiness \"lib/**/*.js\" \"test/**/*.js\"", + "happy:src": "happiness \"lib/**/*.js\"", + "happy:tests": "happiness \"test/**/*.js\"", + "lint": "npm-run-all lint:tests lint:src happy", + "lint:src": "eslint ./lib", + "lint:tests": "eslint ./test", + "test": "jest" + }, + "happiness": { + "env": { + "es6": true, + "jest": true + }, + "parser": "babel-eslint" + } } diff --git a/test/FB_TEST_DB.FDB b/test/FB_TEST_DB.FDB deleted file mode 100755 index 0a7051b295bbb59880c94a78282a0faf5b5f82be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1040384 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

dj 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