Browse Source

Make helpers a class of static functions, add helper methods to run a full file of sql queries

Timothy J. Warren 1 year ago
parent
commit
7f22eee84d

+ 33
- 0
docker-compose.yml View File

@@ -0,0 +1,33 @@
1
+mariadb:
2
+  image: mariadb:latest
3
+  environment:
4
+    - MYSQL_USER=test
5
+    - MYSQL_PASSWORD=test
6
+    - MYSQL_DATABASE=test
7
+    - MYSQL_RANDOM_ROOT_PASSWORD=yes
8
+  ports:
9
+    - 3306:3306
10
+
11
+postgresql:
12
+  image: postgres:latest
13
+  environment:
14
+    - POSTGRES_USER=test
15
+    - POSTGRES_PASSWORD=test
16
+    - POSTGRES_DB=test
17
+  ports:
18
+    - 5432:5432
19
+
20
+sqlserver:
21
+  image: microsoft/mssql-server-linux
22
+  environment:
23
+    - ACCEPT_EULA=Y
24
+    - SA_PASSWORD=t3571ng0n1y
25
+  ports:
26
+    - 1433:1433
27
+
28
+firebird:
29
+  image: itherz/firebird3:latest
30
+  ports:
31
+    - 5040:5040
32
+  volumes:
33
+    - ./test:/databases

lib/helpers.js → lib/Helpers.js View File

@@ -1,18 +1,40 @@
1 1
 'use strict';
2 2
 
3
+const fs = require('fs');
4
+
3 5
 /**
4 6
  * Various internal helper functions
5 7
  *
6 8
  * @private
7 9
  */
8
-const helpers = {
10
+class Helpers {
11
+	/**
12
+	 * Get the contents of a file
13
+	 *
14
+	 * @param {string} file - The path to the file
15
+	 * @return {Promise<string>} - Promise resolving to the contents of the file
16
+	 */
17
+	static readFile (file) {
18
+		return new Promise((resolve, reject) => {
19
+			fs.readFile(file, (err, data) => {
20
+				if (err) {
21
+					return reject(err);
22
+				}
23
+				return resolve(Buffer.from(data).toString());
24
+			});
25
+		});
26
+	}
27
+
9 28
 	/**
10 29
 	 * Wrap String.prototype.trim in a way that is easily mappable
11 30
 	 *
12 31
 	 * @param {String} str - The string to trim
13 32
 	 * @return {String} - The trimmed string
14 33
 	 */
15
-	stringTrim: str => str.trim(),
34
+	static stringTrim (str) {
35
+		return str.trim();
36
+	}
37
+
16 38
 	/**
17 39
 	 * Get the type of the variable passed
18 40
 	 *
@@ -21,7 +43,7 @@ const helpers = {
21 43
 	 * @param {mixed} o - Object to type check
22 44
 	 * @return {String} - Type of the object
23 45
 	 */
24
-	type: o => {
46
+	static type (o) {
25 47
 		let type = Object.prototype.toString.call(o).slice(8, -1).toLowerCase();
26 48
 
27 49
 		// handle NaN and Infinity
@@ -36,17 +58,19 @@ const helpers = {
36 58
 		}
37 59
 
38 60
 		return type;
39
-	},
61
+	}
62
+
40 63
 	/**
41 64
 	 * Determine whether an object is scalar
42 65
 	 *
43 66
 	 * @param {mixed} obj - Object to test
44 67
 	 * @return {bool} - Is object scalar
45 68
 	 */
46
-	isScalar: obj => {
69
+	static isScalar (obj) {
47 70
 		let scalar = ['string', 'number', 'boolean'];
48
-		return scalar.indexOf(helpers.type(obj)) !== -1;
49
-	},
71
+		return scalar.indexOf(Helpers.type(obj)) !== -1;
72
+	}
73
+
50 74
 	/**
51 75
 	 * Get a list of values with a common key from an array of objects
52 76
 	 *
@@ -54,7 +78,7 @@ const helpers = {
54 78
 	 * @param {String} key - The key of the object to get
55 79
 	 * @return {Array} - The new array of plucked values
56 80
 	 */
57
-	arrayPluck: (arr, key) => {
81
+	static arrayPluck (arr, key) {
58 82
 		let output = [];
59 83
 
60 84
 		// Empty case
@@ -63,13 +87,14 @@ const helpers = {
63 87
 		}
64 88
 
65 89
 		arr.forEach(obj => {
66
-			if (!helpers.isUndefined(obj[key])) {
90
+			if (!Helpers.isUndefined(obj[key])) {
67 91
 				output.push(obj[key]);
68 92
 			}
69 93
 		});
70 94
 
71 95
 		return output;
72
-	},
96
+	}
97
+
73 98
 	/**
74 99
 	 * Determine if a value matching the passed regular expression is
75 100
 	 * in the passed array
@@ -78,9 +103,9 @@ const helpers = {
78 103
 	 * @param {RegExp} pattern - The pattern to match
79 104
 	 * @return {Boolean} - If an array item matches the pattern
80 105
 	 */
81
-	regexInArray: (arr, pattern) => {
106
+	static regexInArray (arr, pattern) {
82 107
 		// Empty case(s)
83
-		if (!helpers.isArray(arr) || arr.length === 0) {
108
+		if (!Helpers.isArray(arr) || arr.length === 0) {
84 109
 			return false;
85 110
 		}
86 111
 
@@ -93,19 +118,20 @@ const helpers = {
93 118
 		}
94 119
 
95 120
 		return false;
96
-	},
121
+	}
122
+
97 123
 	/**
98 124
 	 * Make the first letter of the string uppercase
99 125
 	 *
100 126
 	 * @param {String} str - The string to modify
101 127
 	 * @return {String} - The modified string
102 128
 	 */
103
-	upperCaseFirst: str => {
129
+	static upperCaseFirst (str) {
104 130
 		str += '';
105 131
 		let first = str.charAt(0).toUpperCase();
106 132
 		return first + str.substr(1);
107 133
 	}
108
-};
134
+}
109 135
 
110 136
 // Define an 'is' method for each type
111 137
 let types = [
@@ -119,7 +145,8 @@ let types = [
119 145
 	'Function',
120 146
 	'RegExp',
121 147
 	'NaN',
122
-	'Infinite'
148
+	'Infinite',
149
+	'Promise'
123 150
 ];
124 151
 types.forEach(t => {
125 152
 	/**
@@ -127,19 +154,19 @@ types.forEach(t => {
127 154
 	 * function name, eg isNumber
128 155
 	 *
129 156
 	 * Types available are Null, Undefined, Object, Array, String, Number,
130
-	 * Boolean, Function, RegExp, NaN and Infinite
157
+	 * Boolean, Function, RegExp, NaN, Infinite, Promise
131 158
 	 *
132 159
 	 * @private
133 160
 	 * @param {mixed} o - The object to check its type
134 161
 	 * @return {Boolean} - If the type matches
135 162
 	 */
136
-	helpers[`is${t}`] = function (o) {
163
+	Helpers[`is${t}`] = function (o) {
137 164
 		if (t.toLowerCase() === 'infinite') {
138 165
 			t = 'infinity';
139 166
 		}
140 167
 
141
-		return helpers.type(o) === t.toLowerCase();
168
+		return Helpers.type(o) === t.toLowerCase();
142 169
 	};
143 170
 });
144 171
 
145
-module.exports = helpers;
172
+module.exports = Helpers;

+ 26
- 6
lib/QueryBuilder.js View File

@@ -1,6 +1,6 @@
1 1
 'use strict';
2 2
 
3
-const helpers = require('./helpers');
3
+const Helpers = require('./Helpers');
4 4
 const QueryBuilderBase = require('./QueryBuilderBase');
5 5
 
6 6
 /**
@@ -15,6 +15,26 @@ class QueryBuilder extends QueryBuilderBase {
15 15
 	// ! Miscellaneous Methods
16 16
 	// ----------------------------------------------------------------------------
17 17
 
18
+	/**
19
+	 * Run a set of queries from a file
20
+	 *
21
+	 * @param {string} file - The path to the sql file
22
+	 * @param {string} [separator=';'] - The character separating each query
23
+	 * @return {Promise} - The result of all the queries
24
+	 */
25
+	queryFile (file, separator = ';') {
26
+		return Helpers.readFile(file).then(sqlFile => {
27
+			const queries = sqlFile.split(separator);
28
+			const results = [];
29
+
30
+			queries.forEach(sql => {
31
+				results.push(this.query(sql));
32
+			});
33
+
34
+			return Promise.all(results);
35
+		});
36
+	}
37
+
18 38
 	/**
19 39
 	 * Run an arbitrary sql query. Run as a prepared statement.
20 40
 	 *
@@ -80,12 +100,12 @@ class QueryBuilder extends QueryBuilderBase {
80 100
 		// Split/trim fields by comma
81 101
 		fields = (Array.isArray(fields))
82 102
 			? fields
83
-			: fields.split(',').map(helpers.stringTrim);
103
+			: fields.split(',').map(Helpers.stringTrim);
84 104
 
85 105
 		// Split on 'As'
86 106
 		fields.forEach((field, index) => {
87 107
 			if (/as/i.test(field)) {
88
-				fields[index] = field.split(/ as /i).map(helpers.stringTrim);
108
+				fields[index] = field.split(/ as /i).map(Helpers.stringTrim);
89 109
 			}
90 110
 		});
91 111
 
@@ -113,7 +133,7 @@ class QueryBuilder extends QueryBuilderBase {
113 133
 	 */
114 134
 	from (tableName) {
115 135
 		// Split identifiers on spaces
116
-		let identArray = tableName.trim().split(' ').map(helpers.stringTrim);
136
+		let identArray = tableName.trim().split(' ').map(Helpers.stringTrim);
117 137
 
118 138
 		// Quote/prefix identifiers
119 139
 		identArray[0] = this.driver.quoteTable(identArray[0]);
@@ -354,7 +374,7 @@ class QueryBuilder extends QueryBuilderBase {
354 374
 		type = type || 'inner';
355 375
 
356 376
 		// Prefix/quote table name
357
-		table = table.split(' ').map(helpers.stringTrim);
377
+		table = table.split(' ').map(Helpers.stringTrim);
358 378
 		table[0] = this.driver.quoteTable(table[0]);
359 379
 		table = table.map(this.driver.quoteIdentifiers);
360 380
 		table = table.join(' ');
@@ -376,7 +396,7 @@ class QueryBuilder extends QueryBuilderBase {
376 396
 	 * @return {QueryBuilder} - The Query Builder object, for chaining
377 397
 	 */
378 398
 	groupBy (field) {
379
-		if (!helpers.isScalar(field)) {
399
+		if (!Helpers.isScalar(field)) {
380 400
 			let newGroupArray = field.map(this.driver.quoteIdentifiers);
381 401
 			this.state.groupArray = this.state.groupArray.concat(newGroupArray);
382 402
 		} else {

+ 2
- 2
lib/adapters/Mysql/mysql2.js View File

@@ -2,7 +2,7 @@
2 2
 
3 3
 const Adapter = require('../../Adapter');
4 4
 const Result = require('../../Result');
5
-const helpers = require('../../helpers');
5
+const Helpers = require('../../Helpers');
6 6
 const mysql2 = require('mysql2/promise');
7 7
 
8 8
 class Mysql extends Adapter {
@@ -22,7 +22,7 @@ class Mysql extends Adapter {
22 22
 		// For insert and update queries, the result object
23 23
 		// works differently. Just apply the properties of
24 24
 		// this special result to the standard result object.
25
-		if (helpers.type(result) === 'object') {
25
+		if (Helpers.type(result) === 'object') {
26 26
 			let r = new Result();
27 27
 
28 28
 			Object.keys(result).forEach(key => {

+ 3
- 3
lib/adapters/Pg/Pg.js View File

@@ -2,7 +2,7 @@
2 2
 
3 3
 const Adapter = require('../../Adapter');
4 4
 const Result = require('../../Result');
5
-const helpers = require('../../helpers');
5
+const Helpers = require('../../Helpers');
6 6
 const pg = require('pg');
7 7
 const url = require('url');
8 8
 
@@ -10,7 +10,7 @@ class Pg extends Adapter {
10 10
 	constructor (config) {
11 11
 		let instance = null;
12 12
 		let connectionString = '';
13
-		if (helpers.isObject(config)) {
13
+		if (Helpers.isObject(config)) {
14 14
 			let host = config.host || 'localhost';
15 15
 			let user = config.user || 'postgres';
16 16
 			let password = `:${config.password}` || '';
@@ -25,7 +25,7 @@ class Pg extends Adapter {
25 25
 			};
26 26
 
27 27
 			connectionString = url.format(conn);
28
-		} else if (helpers.isString(config)) {
28
+		} else if (Helpers.isString(config)) {
29 29
 			connectionString = config;
30 30
 		}
31 31
 

+ 2
- 2
lib/adapters/Sqlite/dblite.js View File

@@ -2,12 +2,12 @@
2 2
 
3 3
 const Adapter = require('../../Adapter');
4 4
 const Result = require('../../Result');
5
-const helpers = require('../../helpers');
5
+const Helpers = require('../../Helpers');
6 6
 const dbliteAdapter = require('dblite');
7 7
 
8 8
 class SqliteDblite extends Adapter {
9 9
 	constructor (config) {
10
-		let file = (helpers.isString(config)) ? config : config.file;
10
+		let file = (Helpers.isString(config)) ? config : config.file;
11 11
 
12 12
 		const instance = new Promise((resolve, reject) => {
13 13
 			let conn = dbliteAdapter(file);

+ 2
- 2
lib/adapters/Sqlite/sqlite3.js View File

@@ -2,12 +2,12 @@
2 2
 
3 3
 const Adapter = require('../../Adapter');
4 4
 const Result = require('../../Result');
5
-const helpers = require('../../helpers');
5
+const Helpers = require('../../Helpers');
6 6
 const sqlite3 = require('sqlite3').verbose();
7 7
 
8 8
 class SqliteSqlite3 extends Adapter {
9 9
 	constructor (config) {
10
-		let file = (helpers.isString(config)) ? config : config.file;
10
+		let file = (Helpers.isString(config)) ? config : config.file;
11 11
 
12 12
 		const instance = new Promise((resolve, reject) => {
13 13
 			let conn = new sqlite3.Database(file, sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE, err => {

test/adapters/00node-firebird_test.js → test/adapters/00node-firebirdtest.js View File


+ 6
- 0
test/adapters/mysql2_test.js View File

@@ -17,6 +17,12 @@ let nodeQuery = reload('../../lib/NodeQuery')(config);
17 17
 let qb = nodeQuery.getQuery();
18 18
 
19 19
 suite('Mysql2 adapter tests -', () => {
20
+	suiteSetup(done => {
21
+		qb.queryFile(`${__dirname}/../sql/mysql.sql`)
22
+			.then(() => done())
23
+			.catch(e => done(e));
24
+	});
25
+
20 26
 	test('nodeQuery.getQuery = nodeQuery.init', () => {
21 27
 		expect(nodeQuery.getQuery())
22 28
 			.to.be.deep.equal(qb);

+ 6
- 0
test/adapters/pg_test.js View File

@@ -19,6 +19,12 @@ let qb = nodeQuery.getQuery();
19 19
 let qb2 = null;
20 20
 
21 21
 suite('Pg adapter tests -', () => {
22
+	suiteSetup(done => {
23
+		qb.queryFile(`${__dirname}/../sql/pgsql.sql`)
24
+			.then(() => done())
25
+			.catch(e => done(e));
26
+	});
27
+
22 28
 	test('nodeQuery.getQuery = nodeQuery.init', () => {
23 29
 		expect(nodeQuery.getQuery())
24 30
 			.to.be.deep.equal(qb);

+ 21
- 21
test/helpers_test.js View File

@@ -5,22 +5,22 @@ const chai = require('chai');
5 5
 const assert = chai.assert;
6 6
 const expect = chai.expect;
7 7
 
8
-let helpers = require('../lib/helpers');
8
+let Helpers = require('../lib/Helpers');
9 9
 
10 10
 suite('Helper Module Tests -', () => {
11 11
 	suite('Type-checking methods -', () => {
12 12
 		suite('Object wrappers are listed as their native type', () => {
13 13
 			test('Boolean Wrapper returns \'boolean\' not \'object\'', () => {
14 14
 				let item = Boolean(true);
15
-				expect(helpers.type(item)).to.deep.equal('boolean');
15
+				expect(Helpers.type(item)).to.deep.equal('boolean');
16 16
 			});
17 17
 			test('Number Wrapper returns \'number\' not \'object\'', () => {
18 18
 				let item = Number(4867);
19
-				expect(helpers.type(item)).to.deep.equal('number');
19
+				expect(Helpers.type(item)).to.deep.equal('number');
20 20
 			});
21 21
 			test('String Wrapper returns \'string\' not \'object\'', () => {
22 22
 				let item = String('Foo');
23
-				expect(helpers.type(item)).to.deep.equal('string');
23
+				expect(Helpers.type(item)).to.deep.equal('string');
24 24
 			});
25 25
 		});
26 26
 		suite('is..Method methods exist -', () => {
@@ -40,7 +40,7 @@ suite('Helper Module Tests -', () => {
40 40
 
41 41
 			types.forEach(type => {
42 42
 				test(`is${type} method exists`, () => {
43
-					assert.ok(helpers[`is${type}`]);
43
+					assert.ok(Helpers[`is${type}`]);
44 44
 				});
45 45
 			});
46 46
 		});
@@ -52,7 +52,7 @@ suite('Helper Module Tests -', () => {
52 52
 			};
53 53
 			Object.keys(trueCases).forEach(desc => {
54 54
 				test(desc, () => {
55
-					expect(helpers.isScalar(trueCases[desc])).to.be.true;
55
+					expect(Helpers.isScalar(trueCases[desc])).to.be.true;
56 56
 				});
57 57
 			});
58 58
 
@@ -62,24 +62,24 @@ suite('Helper Module Tests -', () => {
62 62
 			};
63 63
 			Object.keys(falseCases).forEach(desc => {
64 64
 				test(desc, () => {
65
-					expect(helpers.isScalar(falseCases[desc])).to.be.false;
65
+					expect(Helpers.isScalar(falseCases[desc])).to.be.false;
66 66
 				});
67 67
 			});
68 68
 		});
69 69
 		suite('isInfinity -', () => {
70 70
 			test('The type of 1/0 is infinity', () => {
71
-				expect(helpers.type(1 / 0)).to.equal('infinity');
71
+				expect(Helpers.type(1 / 0)).to.equal('infinity');
72 72
 			});
73 73
 			test('isInfinity is the same as isInfinite', () => {
74
-				expect(helpers.isInfinite(1 / 0)).to.be.true;
74
+				expect(Helpers.isInfinite(1 / 0)).to.be.true;
75 75
 			});
76 76
 		});
77 77
 		suite('isNaN -', () => {
78 78
 			test('The type of 0 / 0 is NaN', () => {
79
-				expect(helpers.type(0 / 0)).to.equal('nan');
79
+				expect(Helpers.type(0 / 0)).to.equal('nan');
80 80
 			});
81 81
 			test('isNaN method agrees with type', () => {
82
-				expect(helpers.isNaN(0 / 0)).to.be.true;
82
+				expect(Helpers.isNaN(0 / 0)).to.be.true;
83 83
 			});
84 84
 		});
85 85
 	});
@@ -89,7 +89,7 @@ suite('Helper Module Tests -', () => {
89 89
 				let orig = ['  x y ', 'z   ', ' q'];
90 90
 				let ret = ['x y', 'z', 'q'];
91 91
 
92
-				expect(orig.map(helpers.stringTrim)).to.be.deep.equal(ret);
92
+				expect(orig.map(Helpers.stringTrim)).to.be.deep.equal(ret);
93 93
 			});
94 94
 		});
95 95
 		suite('arrayPluck -', () => {
@@ -106,13 +106,13 @@ suite('Helper Module Tests -', () => {
106 106
 			];
107 107
 
108 108
 			test('Finding members in all objects', () => {
109
-				expect(helpers.arrayPluck(orig, 'foo')).to.be.deep.equal([1, 2, 3]);
109
+				expect(Helpers.arrayPluck(orig, 'foo')).to.be.deep.equal([1, 2, 3]);
110 110
 			});
111 111
 			test('Some members are missing in some objects', () => {
112
-				expect(helpers.arrayPluck(orig, 'bar')).to.be.deep.equal([10, 15]);
112
+				expect(Helpers.arrayPluck(orig, 'bar')).to.be.deep.equal([10, 15]);
113 113
 			});
114 114
 			test('Empty case', () => {
115
-				expect(helpers.arrayPluck([], 'apple')).to.be.deep.equal([]);
115
+				expect(Helpers.arrayPluck([], 'apple')).to.be.deep.equal([]);
116 116
 			});
117 117
 		});
118 118
 		suite('regexInArray -', () => {
@@ -133,25 +133,25 @@ suite('Helper Module Tests -', () => {
133 133
 				Object.keys(boolCase).forEach(desc => {
134 134
 					test(desc, () => {
135 135
 						if (i) {
136
-							expect(helpers.regexInArray(orig, boolCase[desc])).to.be.true;
136
+							expect(Helpers.regexInArray(orig, boolCase[desc])).to.be.true;
137 137
 						} else {
138
-							expect(helpers.regexInArray(orig, boolCase[desc])).to.be.false;
138
+							expect(Helpers.regexInArray(orig, boolCase[desc])).to.be.false;
139 139
 						}
140 140
 					});
141 141
 				});
142 142
 			});
143 143
 
144 144
 			test('First argument is not an array', () => {
145
-				expect(helpers.regexInArray(5, /5/)).to.be.false;
145
+				expect(Helpers.regexInArray(5, /5/)).to.be.false;
146 146
 			});
147 147
 			test('Array is empty', () => {
148
-				expect(helpers.regexInArray([], /.*/)).to.be.false;
148
+				expect(Helpers.regexInArray([], /.*/)).to.be.false;
149 149
 			});
150 150
 		});
151 151
 		suite('upperCaseFirst -', () => {
152 152
 			test('Capitalizes only the first letter of the string', () => {
153
-				expect(helpers.upperCaseFirst('foobar')).to.equal('Foobar');
154
-				expect(helpers.upperCaseFirst('FOOBAR')).to.equal('FOOBAR');
153
+				expect(Helpers.upperCaseFirst('foobar')).to.equal('Foobar');
154
+				expect(Helpers.upperCaseFirst('FOOBAR')).to.equal('FOOBAR');
155 155
 			});
156 156
 		});
157 157
 	});

+ 7
- 11
test/sql/mysql.sql View File

@@ -3,25 +3,21 @@
3 3
 --
4 4
 -- Table structure for table `create_join`
5 5
 --
6
-
7 6
 DROP TABLE IF EXISTS `create_join` CASCADE;
8
-CREATE TABLE `create_join` (
9
-  `id` int(10) NOT NULL,
10
-  `key` text,
11
-  `val` text
7
+CREATE TABLE IF NOT EXISTS `create_join` (
8
+  `id` int(10) PRIMARY KEY NOT NULL,
9
+  `key` VARCHAR(128),
10
+  `val` MEDIUMTEXT
12 11
 );
13
-ALTER TABLE `create_join`
14
- ADD PRIMARY KEY (`id`);
15 12
 
16 13
 --
17 14
 -- Table structure for table `create_test`
18 15
 --
19
-
20 16
 DROP TABLE IF EXISTS `create_test` CASCADE;
21 17
 CREATE TABLE `create_test` (
22 18
   `id` int(10) NOT NULL,
23
-  `key` text,
24
-  `val` text
19
+  `key` TEXT,
20
+  `val` LONGTEXT
25 21
 );
26 22
 ALTER TABLE `create_test`
27
- ADD PRIMARY KEY (`id`);
23
+ ADD PRIMARY KEY (`id`)

+ 1
- 1
test/sql/pgsql.sql View File

@@ -13,4 +13,4 @@ CREATE TABLE "create_test" (
13 13
     id integer NOT NULL,
14 14
     key text,
15 15
     val text
16
-);
16
+)