knex 0.14.3 → 0.14.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/lib/client.js +0 -6
- package/lib/dialects/mysql/index.js +10 -6
- package/lib/dialects/mysql2/index.js +0 -59
- package/lib/dialects/sqlite3/query/compiler.js +11 -2
- package/lib/helpers.js +3 -3
- package/lib/migrate/index.js +2 -5
- package/lib/query/compiler.js +6 -5
- package/package.json +11 -9
- package/scripts/stress-test/README.txt +18 -0
- package/scripts/stress-test/debug.log +0 -0
- package/scripts/stress-test/docker-compose.yml +44 -0
- package/scripts/stress-test/knex-stress-test.js +162 -0
- package/scripts/stress-test/mysql2-random-hanging-every-now-and-then.js +132 -0
- package/scripts/stress-test/mysql2-sudden-exit-without-error.js +98 -0
- package/scripts/stress-test/reconnect-test-mysql-based-drivers.js +174 -0
- package/src/client.js +0 -6
- package/src/dialects/mysql/index.js +12 -9
- package/src/dialects/mysql2/index.js +1 -47
- package/src/dialects/sqlite3/query/compiler.js +7 -0
- package/src/helpers.js +11 -2
- package/src/migrate/index.js +2 -3
- package/src/query/compiler.js +10 -5
- package/scripts/stress-test.js +0 -93
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,28 @@
|
|
|
1
1
|
|
|
2
2
|
# Master (Unreleased)
|
|
3
3
|
|
|
4
|
+
# 0.14.4 - 19 Feb, 2018
|
|
5
|
+
|
|
6
|
+
### Bug fixes:
|
|
7
|
+
|
|
8
|
+
- containsUndefined only validate plain objects. Fixes #1898 (#2468)
|
|
9
|
+
- Add warning when using .returning() in sqlite3. Fixes #1660 (#2471)
|
|
10
|
+
- Throw an error if .update() results in an empty sql (#2472)
|
|
11
|
+
- Removed unnecessary createTableIfNotExist and replaced with createTable (#2473)
|
|
12
|
+
|
|
13
|
+
### New Features:
|
|
14
|
+
|
|
15
|
+
- Allow calling lock procedures (such as forUpdate) outside of transaction. Fixes #2403. (#2475)
|
|
16
|
+
- Added test and documentation for Event 'start' (#2488)
|
|
17
|
+
|
|
18
|
+
### Test / internal changes
|
|
19
|
+
|
|
20
|
+
- Added stress test, which uses TCP proxy to simulate flaky connection #2460
|
|
21
|
+
- Removed old docker tests, new stress test setup (#2474)
|
|
22
|
+
- Removed unused property __cid on the base client (#2481)
|
|
23
|
+
- Changed rm to rimraf in 'npm run dev' (#2483)
|
|
24
|
+
- Changed babel preset and use latest node as target when running dev (#2484)
|
|
25
|
+
|
|
4
26
|
# 0.14.3 - 8 Feb, 2018
|
|
5
27
|
|
|
6
28
|
### Bug fixes:
|
package/lib/client.js
CHANGED
|
@@ -96,11 +96,6 @@ var debug = require('debug')('knex:client');
|
|
|
96
96
|
var debugQuery = require('debug')('knex:query');
|
|
97
97
|
var debugBindings = require('debug')('knex:bindings');
|
|
98
98
|
|
|
99
|
-
var id = 0;
|
|
100
|
-
function clientId() {
|
|
101
|
-
return 'client' + id++;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
99
|
// The base client provides the general structure
|
|
105
100
|
// for a dialect specific client object.
|
|
106
101
|
function Client() {
|
|
@@ -119,7 +114,6 @@ function Client() {
|
|
|
119
114
|
if (this.driverName && config.connection) {
|
|
120
115
|
this.initializeDriver();
|
|
121
116
|
if (!config.pool || config.pool && config.pool.max !== 0) {
|
|
122
|
-
this.__cid = clientId();
|
|
123
117
|
this.initializePool(config);
|
|
124
118
|
}
|
|
125
119
|
}
|
|
@@ -103,11 +103,15 @@ function Client_MySQL(config) {
|
|
|
103
103
|
|
|
104
104
|
return new _bluebird2.default(function (resolver, rejecter) {
|
|
105
105
|
var connection = _this.driver.createConnection(_this.connectionSettings);
|
|
106
|
+
connection.on('error', function (err) {
|
|
107
|
+
connection.__knex__disposed = err;
|
|
108
|
+
});
|
|
106
109
|
connection.connect(function (err) {
|
|
107
|
-
if (err)
|
|
108
|
-
|
|
109
|
-
connection.
|
|
110
|
-
|
|
110
|
+
if (err) {
|
|
111
|
+
// if connection is rejected, remove listener that was registered above...
|
|
112
|
+
connection.removeAllListeners();
|
|
113
|
+
return rejecter(err);
|
|
114
|
+
}
|
|
111
115
|
resolver(connection);
|
|
112
116
|
});
|
|
113
117
|
});
|
|
@@ -117,16 +121,16 @@ function Client_MySQL(config) {
|
|
|
117
121
|
// Used to explicitly close a connection, called internally by the pool
|
|
118
122
|
// when a connection times out or the pool is shutdown.
|
|
119
123
|
destroyRawConnection: function destroyRawConnection(connection) {
|
|
120
|
-
connection.removeAllListeners();
|
|
121
124
|
return _bluebird2.default.fromCallback(connection.end.bind(connection)).catch(function (err) {
|
|
122
125
|
connection.__knex__disposed = err;
|
|
126
|
+
}).finally(function () {
|
|
127
|
+
return connection.removeAllListeners();
|
|
123
128
|
});
|
|
124
129
|
},
|
|
125
130
|
validateConnection: function validateConnection(connection) {
|
|
126
131
|
if (connection.state === 'connected' || connection.state === 'authenticated') {
|
|
127
132
|
return true;
|
|
128
133
|
}
|
|
129
|
-
|
|
130
134
|
return false;
|
|
131
135
|
},
|
|
132
136
|
|
|
@@ -6,10 +6,6 @@ var _assign2 = require('lodash/assign');
|
|
|
6
6
|
|
|
7
7
|
var _assign3 = _interopRequireDefault(_assign2);
|
|
8
8
|
|
|
9
|
-
var _map2 = require('lodash/map');
|
|
10
|
-
|
|
11
|
-
var _map3 = _interopRequireDefault(_map2);
|
|
12
|
-
|
|
13
9
|
var _inherits = require('inherits');
|
|
14
10
|
|
|
15
11
|
var _inherits2 = _interopRequireDefault(_inherits);
|
|
@@ -18,20 +14,10 @@ var _mysql = require('../mysql');
|
|
|
18
14
|
|
|
19
15
|
var _mysql2 = _interopRequireDefault(_mysql);
|
|
20
16
|
|
|
21
|
-
var _bluebird = require('bluebird');
|
|
22
|
-
|
|
23
|
-
var _bluebird2 = _interopRequireDefault(_bluebird);
|
|
24
|
-
|
|
25
|
-
var _helpers = require('../../helpers');
|
|
26
|
-
|
|
27
|
-
var helpers = _interopRequireWildcard(_helpers);
|
|
28
|
-
|
|
29
17
|
var _transaction = require('./transaction');
|
|
30
18
|
|
|
31
19
|
var _transaction2 = _interopRequireDefault(_transaction);
|
|
32
20
|
|
|
33
|
-
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
|
|
34
|
-
|
|
35
21
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
36
22
|
|
|
37
23
|
// Always initialize with the "QueryBuilder" and "QueryCompiler"
|
|
@@ -60,52 +46,7 @@ function Client_MySQL2(config) {
|
|
|
60
46
|
if (connection._fatalError) {
|
|
61
47
|
return false;
|
|
62
48
|
}
|
|
63
|
-
|
|
64
49
|
return true;
|
|
65
|
-
},
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
// Get a raw connection, called by the `pool` whenever a new
|
|
69
|
-
// connection needs to be added to the pool.
|
|
70
|
-
acquireRawConnection: function acquireRawConnection() {
|
|
71
|
-
var connection = this.driver.createConnection(this.connectionSettings);
|
|
72
|
-
connection.on('error', function (err) {
|
|
73
|
-
connection.__knex__disposed = err;
|
|
74
|
-
});
|
|
75
|
-
return new _bluebird2.default(function (resolver, rejecter) {
|
|
76
|
-
connection.connect(function (err) {
|
|
77
|
-
if (err) {
|
|
78
|
-
return rejecter(err);
|
|
79
|
-
}
|
|
80
|
-
resolver(connection);
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
},
|
|
84
|
-
processResponse: function processResponse(obj, runner) {
|
|
85
|
-
var response = obj.response;
|
|
86
|
-
var method = obj.method;
|
|
87
|
-
|
|
88
|
-
var rows = response[0];
|
|
89
|
-
var fields = response[1];
|
|
90
|
-
if (obj.output) return obj.output.call(runner, rows, fields);
|
|
91
|
-
switch (method) {
|
|
92
|
-
case 'select':
|
|
93
|
-
case 'pluck':
|
|
94
|
-
case 'first':
|
|
95
|
-
{
|
|
96
|
-
var resp = helpers.skim(rows);
|
|
97
|
-
if (method === 'pluck') return (0, _map3.default)(resp, obj.pluck);
|
|
98
|
-
return method === 'first' ? resp[0] : resp;
|
|
99
|
-
}
|
|
100
|
-
case 'insert':
|
|
101
|
-
return [rows.insertId];
|
|
102
|
-
case 'del':
|
|
103
|
-
case 'update':
|
|
104
|
-
case 'counter':
|
|
105
|
-
return rows.affectedRows;
|
|
106
|
-
default:
|
|
107
|
-
return response;
|
|
108
|
-
}
|
|
109
50
|
}
|
|
110
51
|
});
|
|
111
52
|
|
|
@@ -42,13 +42,22 @@ var _compiler = require('../../../query/compiler');
|
|
|
42
42
|
|
|
43
43
|
var _compiler2 = _interopRequireDefault(_compiler);
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
var _helpers = require('../../../helpers');
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
48
48
|
|
|
49
49
|
function QueryCompiler_SQLite3(client, builder) {
|
|
50
50
|
_compiler2.default.call(this, client, builder);
|
|
51
|
+
|
|
52
|
+
var returning = this.single.returning;
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
if (returning) {
|
|
56
|
+
(0, _helpers.warn)('.returning() is not supported by sqlite3 and will not have any effect.');
|
|
57
|
+
}
|
|
51
58
|
}
|
|
59
|
+
// SQLite3 Query Builder & Compiler
|
|
60
|
+
|
|
52
61
|
(0, _inherits2.default)(QueryCompiler_SQLite3, _compiler2.default);
|
|
53
62
|
|
|
54
63
|
(0, _assign3.default)(QueryCompiler_SQLite3.prototype, {
|
package/lib/helpers.js
CHANGED
|
@@ -10,9 +10,9 @@ var _isArray2 = require('lodash/isArray');
|
|
|
10
10
|
|
|
11
11
|
var _isArray3 = _interopRequireDefault(_isArray2);
|
|
12
12
|
|
|
13
|
-
var
|
|
13
|
+
var _isPlainObject2 = require('lodash/isPlainObject');
|
|
14
14
|
|
|
15
|
-
var
|
|
15
|
+
var _isPlainObject3 = _interopRequireDefault(_isPlainObject2);
|
|
16
16
|
|
|
17
17
|
var _isUndefined2 = require('lodash/isUndefined');
|
|
18
18
|
|
|
@@ -110,7 +110,7 @@ function containsUndefined(mixed) {
|
|
|
110
110
|
if (argContainsUndefined) break;
|
|
111
111
|
argContainsUndefined = this.containsUndefined(mixed[i]);
|
|
112
112
|
}
|
|
113
|
-
} else if ((0,
|
|
113
|
+
} else if ((0, _isPlainObject3.default)(mixed)) {
|
|
114
114
|
for (var key in mixed) {
|
|
115
115
|
if (mixed.hasOwnProperty(key)) {
|
|
116
116
|
if (argContainsUndefined) break;
|
package/lib/migrate/index.js
CHANGED
|
@@ -262,13 +262,10 @@ var Migrator = function () {
|
|
|
262
262
|
});
|
|
263
263
|
};
|
|
264
264
|
|
|
265
|
-
// Create the migration table, if it doesn't already exist.
|
|
266
|
-
|
|
267
|
-
|
|
268
265
|
Migrator.prototype._createMigrationTable = function _createMigrationTable(tableName) {
|
|
269
266
|
var trx = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.knex;
|
|
270
267
|
|
|
271
|
-
return trx.schema.
|
|
268
|
+
return trx.schema.createTable(tableName, function (t) {
|
|
272
269
|
t.increments();
|
|
273
270
|
t.string('name');
|
|
274
271
|
t.integer('batch');
|
|
@@ -279,7 +276,7 @@ var Migrator = function () {
|
|
|
279
276
|
Migrator.prototype._createMigrationLockTable = function _createMigrationLockTable(tableName) {
|
|
280
277
|
var trx = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.knex;
|
|
281
278
|
|
|
282
|
-
return trx.schema.
|
|
279
|
+
return trx.schema.createTable(tableName, function (t) {
|
|
283
280
|
t.integer('is_locked');
|
|
284
281
|
});
|
|
285
282
|
};
|
package/lib/query/compiler.js
CHANGED
|
@@ -468,11 +468,7 @@ var components = ['columns', 'join', 'where', 'union', 'group', 'having', 'order
|
|
|
468
468
|
// Compiles the "locks".
|
|
469
469
|
lock: function lock() {
|
|
470
470
|
if (this.single.lock) {
|
|
471
|
-
|
|
472
|
-
helpers.warn('You are attempting to perform a "lock" command outside of a transaction.');
|
|
473
|
-
} else {
|
|
474
|
-
return this[this.single.lock]();
|
|
475
|
-
}
|
|
471
|
+
return this[this.single.lock]();
|
|
476
472
|
}
|
|
477
473
|
},
|
|
478
474
|
|
|
@@ -644,6 +640,11 @@ var components = ['columns', 'join', 'where', 'union', 'group', 'having', 'order
|
|
|
644
640
|
while (++i < columns.length) {
|
|
645
641
|
vals.push(this.formatter.wrap(columns[i]) + ' = ' + this.formatter.parameter(data[columns[i]]));
|
|
646
642
|
}
|
|
643
|
+
|
|
644
|
+
if ((0, _isEmpty3.default)(vals)) {
|
|
645
|
+
throw new Error(['Empty .update() call detected!', 'Update data does not contain any values to update.', 'This will result in a faulty query.'].join(' '));
|
|
646
|
+
}
|
|
647
|
+
|
|
647
648
|
return vals;
|
|
648
649
|
},
|
|
649
650
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "knex",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.4",
|
|
4
4
|
"description": "A batteries-included SQL query & schema builder for Postgres, MySQL and SQLite3 and the Browser",
|
|
5
5
|
"main": "knex.js",
|
|
6
6
|
"dependencies": {
|
|
@@ -31,10 +31,9 @@
|
|
|
31
31
|
"babel-plugin-add-module-exports": "^0.2.1",
|
|
32
32
|
"babel-plugin-lodash": "3.3.2",
|
|
33
33
|
"babel-plugin-transform-runtime": "^6.23.0",
|
|
34
|
-
"babel-preset-
|
|
34
|
+
"babel-preset-env": "^1.6.1",
|
|
35
35
|
"chai": "^4.1.2",
|
|
36
36
|
"coveralls": "~3.0.0",
|
|
37
|
-
"dockerode": "^2.5.3",
|
|
38
37
|
"eslint": "4.16.0",
|
|
39
38
|
"eslint-plugin-import": "^2.8.0",
|
|
40
39
|
"estraverse": "^4.2.0",
|
|
@@ -45,7 +44,7 @@
|
|
|
45
44
|
"mock-fs": "^4.4.2",
|
|
46
45
|
"mssql": "^4.1.0",
|
|
47
46
|
"mysql": "^2.15.0",
|
|
48
|
-
"mysql2": "^1.5.
|
|
47
|
+
"mysql2": "^1.5.2",
|
|
49
48
|
"pg": "^7.4.1",
|
|
50
49
|
"pg-query-stream": "^1.1.1",
|
|
51
50
|
"rimraf": "^2.6.2",
|
|
@@ -55,7 +54,8 @@
|
|
|
55
54
|
"sqlite3": "^3.1.13",
|
|
56
55
|
"tap-spec": "^4.1.1",
|
|
57
56
|
"tape": "^4.8.0",
|
|
58
|
-
"through": "^2.3.8"
|
|
57
|
+
"through": "^2.3.8",
|
|
58
|
+
"toxiproxy-node-client": "^2.0.6"
|
|
59
59
|
},
|
|
60
60
|
"buildDependencies": [
|
|
61
61
|
"babel-cli",
|
|
@@ -71,15 +71,17 @@
|
|
|
71
71
|
"debug_test": "node-debug _mocha -t 0 test/index.js",
|
|
72
72
|
"babel": "rimraf ./lib && babel src --out-dir lib --copy-files",
|
|
73
73
|
"coveralls": "cat ./test/coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js",
|
|
74
|
-
"dev": "
|
|
74
|
+
"dev": "rimraf ./lib && babel -w src --out-dir lib --copy-files",
|
|
75
75
|
"lint": "eslint src/**",
|
|
76
76
|
"plaintest": "mocha --check-leaks -b -R spec test/index.js && npm run tape",
|
|
77
77
|
"prepublish": "npm run babel",
|
|
78
|
-
"
|
|
79
|
-
"pre_test": "npm run lint && npm run docker_test",
|
|
78
|
+
"pre_test": "npm run lint",
|
|
80
79
|
"tape": "node test/tape/index.js | tap-spec",
|
|
81
80
|
"debug_tape": "node-debug test/tape/index.js",
|
|
82
|
-
"test": "npm run pre_test && istanbul --config=test/.istanbul.yml cover node_modules/mocha/bin/_mocha -- --check-leaks -t 10000 -b -R spec test/index.js && npm run tape"
|
|
81
|
+
"test": "npm run pre_test && istanbul --config=test/.istanbul.yml cover node_modules/mocha/bin/_mocha -- --check-leaks -t 10000 -b -R spec test/index.js && npm run tape",
|
|
82
|
+
"stress:init": "docker-compose -f scripts/stress-test/docker-compose.yml up --no-start && docker-compose -f scripts/stress-test/docker-compose.yml start",
|
|
83
|
+
"stress:test": "node scripts/stress-test/knex-stress-test.js | grep -A 3 -- '- STATS '",
|
|
84
|
+
"stress:destroy": "docker-compose -f scripts/stress-test/docker-compose.yml stop"
|
|
83
85
|
},
|
|
84
86
|
"bin": {
|
|
85
87
|
"knex": "./bin/cli.js"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Test scripts to evaluate stability of drivers / pool etc.
|
|
2
|
+
|
|
3
|
+
# To run this test you need to be in this directory + have node >= 8
|
|
4
|
+
# and startup docker containers with proxy and sql servers
|
|
5
|
+
|
|
6
|
+
docker-compose up --no-start
|
|
7
|
+
docker-compose start
|
|
8
|
+
|
|
9
|
+
# Select different test script to run:
|
|
10
|
+
|
|
11
|
+
node mysql2-random-hanging-every-now-and-then.js 2> /dev/null | grep -B500 -A2 -- "- STATS"
|
|
12
|
+
node mysql2-sudden-exit-without-error
|
|
13
|
+
node knex-stress-test.js | grep -A 3 -- "- STATS "
|
|
14
|
+
node reconnect-test-mysql-based-drivers.js 2> /dev/null | grep -A 3 -- "- STATS "
|
|
15
|
+
|
|
16
|
+
# Shut down docker instances when done:
|
|
17
|
+
|
|
18
|
+
docker-compose down
|
|
File without changes
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
version: '3'
|
|
2
|
+
|
|
3
|
+
services:
|
|
4
|
+
toxiproxy:
|
|
5
|
+
image: shopify/toxiproxy
|
|
6
|
+
ports:
|
|
7
|
+
- "8474:8474"
|
|
8
|
+
- "23306:23306"
|
|
9
|
+
- "25432:25432"
|
|
10
|
+
- "21521:21521"
|
|
11
|
+
links:
|
|
12
|
+
- "mysql"
|
|
13
|
+
- "postgresql"
|
|
14
|
+
|
|
15
|
+
mysql:
|
|
16
|
+
image: mysql:5.7
|
|
17
|
+
ports:
|
|
18
|
+
- "33306:3306"
|
|
19
|
+
environment:
|
|
20
|
+
- TZ=UTC
|
|
21
|
+
- MYSQL_ROOT_PASSWORD=mysqlrootpassword
|
|
22
|
+
|
|
23
|
+
postgresql:
|
|
24
|
+
image: mdillon/postgis
|
|
25
|
+
ports:
|
|
26
|
+
- "35432:5432"
|
|
27
|
+
environment:
|
|
28
|
+
- POSTGRES_PASSWORD=postgresrootpassword
|
|
29
|
+
- POSTGRES_USER=postgres
|
|
30
|
+
|
|
31
|
+
oracledbxe:
|
|
32
|
+
image: wnameless/oracle-xe-11g
|
|
33
|
+
ports:
|
|
34
|
+
- "31521:1521"
|
|
35
|
+
environment:
|
|
36
|
+
- ORACLE_ALLOW_REMOTE=true
|
|
37
|
+
|
|
38
|
+
# mssql:
|
|
39
|
+
# image: microsoft/mssql-server-linux
|
|
40
|
+
# ports:
|
|
41
|
+
# - "31433:1433"
|
|
42
|
+
# environment:
|
|
43
|
+
# - ACCEPT_EULA=Y
|
|
44
|
+
# - SA_PASSWORD=mssqlpassword
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
const Knex = require('../../lib');
|
|
2
|
+
|
|
3
|
+
const Bluebird = require('bluebird');
|
|
4
|
+
const toxiproxy = require('toxiproxy-node-client');
|
|
5
|
+
const toxicli = new toxiproxy.Toxiproxy('http://localhost:8474');
|
|
6
|
+
const rp = require('request-promise-native');
|
|
7
|
+
|
|
8
|
+
// init instances
|
|
9
|
+
const pg = Knex({
|
|
10
|
+
client: 'pg',
|
|
11
|
+
connection: 'postgres://postgres:postgresrootpassword@localhost:25432/postgres',
|
|
12
|
+
pool: { max: 50 }
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const mysql2 = Knex({
|
|
16
|
+
client: 'mysql2',
|
|
17
|
+
connection: 'mysql://root:mysqlrootpassword@localhost:23306/?charset=utf8&connectTimeout=500',
|
|
18
|
+
pool: { max: 50 }
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const mysql = Knex({
|
|
22
|
+
client: 'mysql',
|
|
23
|
+
connection: 'mysql://root:mysqlrootpassword@localhost:23306/?charset=utf8&connectTimeout=500',
|
|
24
|
+
pool: { max: 50 }
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
/* TODO: figure out how to nicely install oracledb node driver on osx
|
|
28
|
+
const mysql = Knex({
|
|
29
|
+
client: 'oracledb',
|
|
30
|
+
connection: {
|
|
31
|
+
user : "travis",
|
|
32
|
+
password : "travis",
|
|
33
|
+
connectString : "localhost/XE",
|
|
34
|
+
// https://github.com/oracle/node-oracledb/issues/525
|
|
35
|
+
stmtCacheSize : 0
|
|
36
|
+
},
|
|
37
|
+
pool: { max: 50 }
|
|
38
|
+
});
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
const counters = {};
|
|
42
|
+
|
|
43
|
+
function setQueryCounters(instance, name) {
|
|
44
|
+
const counts = counters[name] = {queries: 0, results: 0, errors: 0};
|
|
45
|
+
instance.on('query', () => counts.queries += 1);
|
|
46
|
+
instance.on('query-response', () => counts.results += 1);
|
|
47
|
+
instance.on('query-error', () => counts.errors += 1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
setQueryCounters(pg, 'pg');
|
|
51
|
+
setQueryCounters(mysql, 'mysql');
|
|
52
|
+
setQueryCounters(mysql2, 'mysql2');
|
|
53
|
+
|
|
54
|
+
const _ = require('lodash');
|
|
55
|
+
|
|
56
|
+
// start printing out counters
|
|
57
|
+
let lastCounters = _.cloneDeep(counters);
|
|
58
|
+
|
|
59
|
+
setInterval(() => {
|
|
60
|
+
const reqsPerSec = {};
|
|
61
|
+
for (let key of Object.keys(counters)) {
|
|
62
|
+
reqsPerSec[key] = {
|
|
63
|
+
queries: (counters[key].queries - lastCounters[key].queries) / 2,
|
|
64
|
+
results: (counters[key].results - lastCounters[key].results) / 2,
|
|
65
|
+
errors: (counters[key].errors - lastCounters[key].errors) / 2,
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
console.log('------------------------ STATS PER SECOND ------------------------');
|
|
69
|
+
console.dir(reqsPerSec, {colors: true});
|
|
70
|
+
console.log('------------------------------- EOS ------------------------------');
|
|
71
|
+
lastCounters = _.cloneDeep(counters);
|
|
72
|
+
}, 2000);
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
async function killConnectionsPg() {
|
|
76
|
+
return pg.raw(`SELECT pg_terminate_backend(pg_stat_activity.pid)
|
|
77
|
+
FROM pg_stat_activity
|
|
78
|
+
WHERE pg_stat_activity.datname = 'postgres'
|
|
79
|
+
AND pid <> pg_backend_pid()`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function killConnectionsMyslq(client) {
|
|
83
|
+
const [rows, colDefs] = await client.raw(`SHOW FULL PROCESSLIST`);
|
|
84
|
+
await Promise.all(rows.map(row => client.raw(`KILL ${row.Id}`)));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function main() {
|
|
88
|
+
async function loopQueries(prefix, query) {
|
|
89
|
+
const queries = () => [
|
|
90
|
+
...Array(50).fill(query),
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
while(true) {
|
|
94
|
+
try {
|
|
95
|
+
await Promise.all(queries());
|
|
96
|
+
} catch (err) {
|
|
97
|
+
console.log(prefix, err);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function recreateProxy(serviceName, listenPort, proxyToPort) {
|
|
103
|
+
try {
|
|
104
|
+
await rp.delete({
|
|
105
|
+
url: `${toxicli.host}/proxies/${serviceName}`
|
|
106
|
+
});
|
|
107
|
+
} catch(err) {}
|
|
108
|
+
|
|
109
|
+
const proxy = await toxicli.createProxy({
|
|
110
|
+
name: serviceName,
|
|
111
|
+
listen: `0.0.0.0:${listenPort}`,
|
|
112
|
+
upstream: `${serviceName}:${proxyToPort}`
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// add some latency
|
|
116
|
+
await proxy.addToxic(new toxiproxy.Toxic(proxy, {
|
|
117
|
+
type: 'latency',
|
|
118
|
+
attributes: {latency: 1, jitter: 1}
|
|
119
|
+
}));
|
|
120
|
+
|
|
121
|
+
// cause connections to be closed every 500 bytes
|
|
122
|
+
await proxy.addToxic(new toxiproxy.Toxic(proxy, {
|
|
123
|
+
type: 'limit_data',
|
|
124
|
+
attributes: {bytes: 5000}
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// create TCP proxies for simulating bad connections etc.
|
|
129
|
+
async function recreateProxies() {
|
|
130
|
+
await recreateProxy('postgresql', 25432, 5432);
|
|
131
|
+
await recreateProxy('mysql', 23306, 3306);
|
|
132
|
+
await recreateProxy('oracledbxe', 21521, 1521);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
await recreateProxies();
|
|
136
|
+
|
|
137
|
+
loopQueries('PSQL:', pg.raw('select 1'));
|
|
138
|
+
loopQueries('PSQL TO:', pg.raw('select 1').timeout(20));
|
|
139
|
+
|
|
140
|
+
loopQueries('MYSQL:', mysql.raw('select 1'));
|
|
141
|
+
loopQueries('MYSQL TO:', mysql.raw('select 1').timeout(20));
|
|
142
|
+
|
|
143
|
+
loopQueries('MYSQL2:', mysql2.raw('select 1'));
|
|
144
|
+
loopQueries('MYSQL2 TO:', mysql2.raw('select 1').timeout(20));
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
while(true) {
|
|
148
|
+
await Bluebird.delay(20); // kill everything every quite often from server side
|
|
149
|
+
try {
|
|
150
|
+
await Promise.all([
|
|
151
|
+
killConnectionsPg(),
|
|
152
|
+
killConnectionsMyslq(mysql),
|
|
153
|
+
killConnectionsMyslq(mysql2),
|
|
154
|
+
]);
|
|
155
|
+
} catch (err) {
|
|
156
|
+
console.log('KILLER ERROR:', err);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
process.on('exit', () => console.log('- STATS PRINT NEAR END LOGS ')); // marker for grep...
|
|
162
|
+
main();
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test case for figuring out robust way to recognize if connection is dead
|
|
3
|
+
* for mysql based drivers.
|
|
4
|
+
*/
|
|
5
|
+
const Bluebird = require('bluebird');
|
|
6
|
+
const toxiproxy = require('toxiproxy-node-client');
|
|
7
|
+
const toxicli = new toxiproxy.Toxiproxy('http://localhost:8474');
|
|
8
|
+
const rp = require('request-promise-native');
|
|
9
|
+
const _ = require('lodash');
|
|
10
|
+
|
|
11
|
+
async function stdMysqlQuery(con, sql) {
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
try {
|
|
14
|
+
con.query({
|
|
15
|
+
sql,
|
|
16
|
+
timeout: 500
|
|
17
|
+
}, function (error, results, fields) {
|
|
18
|
+
if (error) {
|
|
19
|
+
reject(error);
|
|
20
|
+
} else {
|
|
21
|
+
resolve(results);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
} catch (err) {
|
|
25
|
+
reject(err); // double sure...
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const mysql2 = require('mysql2');
|
|
31
|
+
let mysql2Con = {_fatalError: 'initmefirst'};
|
|
32
|
+
async function mysql2Query(sql) {
|
|
33
|
+
// recreate connection on fatal error
|
|
34
|
+
if (mysql2Con._fatalError) {
|
|
35
|
+
console.log('========== Reconnecting mysql2');
|
|
36
|
+
mysql2Con = mysql2.createConnection({
|
|
37
|
+
host: 'localhost',
|
|
38
|
+
user: 'root',
|
|
39
|
+
password: 'mysqlrootpassword',
|
|
40
|
+
port: 23306,
|
|
41
|
+
connectTimeout: 500,
|
|
42
|
+
debug: true
|
|
43
|
+
});
|
|
44
|
+
mysql2Con.on('error', err => {
|
|
45
|
+
console.log('- STATS Mysql2 connection died:', err);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
console.log('================ MYSQL2 Running query ======');
|
|
49
|
+
const res = await stdMysqlQuery(mysql2Con, sql);
|
|
50
|
+
console.log('====================== done ================');
|
|
51
|
+
return res;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const counters = {};
|
|
55
|
+
function setMysqlQueryCounters(name) {
|
|
56
|
+
const counts = counters[name] = {queries: 0, results: 0, errors: 0};
|
|
57
|
+
}
|
|
58
|
+
setMysqlQueryCounters('mysql2');
|
|
59
|
+
|
|
60
|
+
// start printing out counters
|
|
61
|
+
let lastCounters = _.cloneDeep(counters);
|
|
62
|
+
setInterval(() => {
|
|
63
|
+
const reqsPerSec = {};
|
|
64
|
+
for (let key of Object.keys(counters)) {
|
|
65
|
+
reqsPerSec[key] = {
|
|
66
|
+
queries: (counters[key].queries - lastCounters[key].queries),
|
|
67
|
+
results: (counters[key].results - lastCounters[key].results),
|
|
68
|
+
errors: (counters[key].errors - lastCounters[key].errors),
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
console.log('------------------------ STATS PER SECOND ------------------------');
|
|
72
|
+
console.dir(reqsPerSec, {colors: true});
|
|
73
|
+
console.log('------------------------------- EOS ------------------------------');
|
|
74
|
+
lastCounters = _.cloneDeep(counters);
|
|
75
|
+
}, 1000);
|
|
76
|
+
|
|
77
|
+
async function recreateProxy(serviceName, listenPort, proxyToPort) {
|
|
78
|
+
try {
|
|
79
|
+
await rp.delete({
|
|
80
|
+
url: `${toxicli.host}/proxies/${serviceName}`
|
|
81
|
+
});
|
|
82
|
+
} catch(err) {}
|
|
83
|
+
|
|
84
|
+
const proxy = await toxicli.createProxy({
|
|
85
|
+
name: serviceName,
|
|
86
|
+
listen: `0.0.0.0:${listenPort}`,
|
|
87
|
+
upstream: `${serviceName}:${proxyToPort}`
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// add some latency
|
|
91
|
+
await proxy.addToxic(new toxiproxy.Toxic(proxy, {
|
|
92
|
+
type: 'latency',
|
|
93
|
+
attributes: {latency: 1, jitter: 1}
|
|
94
|
+
}));
|
|
95
|
+
|
|
96
|
+
// cause connections to be closed every some transferred bytes
|
|
97
|
+
await proxy.addToxic(new toxiproxy.Toxic(proxy, {
|
|
98
|
+
type: 'limit_data',
|
|
99
|
+
attributes: {bytes: 1000}
|
|
100
|
+
}));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function main() {
|
|
104
|
+
await recreateProxy('mysql', 23306, 3306);
|
|
105
|
+
setInterval(() => recreateProxy('mysql', 23306, 3306), 2000);
|
|
106
|
+
|
|
107
|
+
async function loopQueries(prefix, query) {
|
|
108
|
+
const counts = counters[prefix];
|
|
109
|
+
|
|
110
|
+
while(true) {
|
|
111
|
+
try {
|
|
112
|
+
counts.queries += 1;
|
|
113
|
+
// without this delay we might endup to busy failure loop
|
|
114
|
+
await Bluebird.delay(0);
|
|
115
|
+
await query();
|
|
116
|
+
counts.results += 1;
|
|
117
|
+
} catch (err) {
|
|
118
|
+
counts.errors += 1;
|
|
119
|
+
console.log(prefix, err);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
loopQueries('mysql2', () => mysql2Query('select 1'));
|
|
125
|
+
|
|
126
|
+
// wait forever
|
|
127
|
+
while(true) {
|
|
128
|
+
await Bluebird.delay(1000);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
main().then(() => console.log('DONE')).catch(err => console.log(err));
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test case when mysql2 driver strangely exits when one tries to send query
|
|
3
|
+
* to dead connection.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const Bluebird = require('bluebird');
|
|
7
|
+
const toxiproxy = require('toxiproxy-node-client');
|
|
8
|
+
const toxicli = new toxiproxy.Toxiproxy('http://localhost:8474');
|
|
9
|
+
const rp = require('request-promise-native');
|
|
10
|
+
|
|
11
|
+
// drops old toxicproxy and creates new
|
|
12
|
+
async function recreateProxy(serviceName, listenPort, proxyToPort) {
|
|
13
|
+
try {
|
|
14
|
+
await rp.delete({
|
|
15
|
+
url: `${toxicli.host}/proxies/${serviceName}`
|
|
16
|
+
});
|
|
17
|
+
} catch(err) {
|
|
18
|
+
// there was no proxy by that name... its ok
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const proxy = await toxicli.createProxy({
|
|
22
|
+
name: serviceName,
|
|
23
|
+
listen: `0.0.0.0:${listenPort}`,
|
|
24
|
+
upstream: `${serviceName}:${proxyToPort}`
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function insanelyParanoidQuery(con) {
|
|
29
|
+
console.log('sending query');
|
|
30
|
+
const res = await new Promise((resolve,reject) => {
|
|
31
|
+
try {
|
|
32
|
+
con.query('select 1', [], function(err, rows, fields) {
|
|
33
|
+
if (err) {
|
|
34
|
+
reject(err);
|
|
35
|
+
} else {
|
|
36
|
+
resolve(rows);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
} catch(err) {
|
|
40
|
+
console.log('Huh synchronous exception?! (shouldnt be possible)');
|
|
41
|
+
reject(err);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
console.log(res);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
async function main() {
|
|
49
|
+
// create proxy from localhost:23306 -> mysqldocker:3306
|
|
50
|
+
await recreateProxy('mysql', 23306, 3306);
|
|
51
|
+
|
|
52
|
+
// ------------- setup mysql2 db driver connection
|
|
53
|
+
const mysql2 = require('mysql2'); // with mysql this works...
|
|
54
|
+
const mysql2Con = await mysql2.createConnection({
|
|
55
|
+
host: 'localhost',
|
|
56
|
+
user: 'root',
|
|
57
|
+
password: 'mysqlrootpassword',
|
|
58
|
+
port: 23306
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
mysql2Con.on('error', function(err) {
|
|
62
|
+
console.log("I'm dead", err);
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
console.log('Going to cut connections');
|
|
66
|
+
|
|
67
|
+
// cut connection during recreate
|
|
68
|
+
await recreateProxy('mysql', 23306, 3306);
|
|
69
|
+
|
|
70
|
+
console.log('Proxy recreated... start waiting');
|
|
71
|
+
|
|
72
|
+
// wait forever
|
|
73
|
+
while(true) {
|
|
74
|
+
await Bluebird.delay(1000);
|
|
75
|
+
try {
|
|
76
|
+
await insanelyParanoidQuery(mysql2Con);
|
|
77
|
+
} catch (err) {
|
|
78
|
+
console.log('query failed:', err);
|
|
79
|
+
}
|
|
80
|
+
await recreateProxy('mysql', 23306, 3306);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
main()
|
|
85
|
+
.then(() => console.log('Process stopped normally'))
|
|
86
|
+
.catch(err => {
|
|
87
|
+
console.log('Process stopped to failure', err);
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
console.log('Waiting for eventloop to stop...');
|
|
91
|
+
|
|
92
|
+
process.on('uncaughtException', function (err) {
|
|
93
|
+
console.log('uncaughtException', err);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
process.on('exit', function () {
|
|
97
|
+
console.log('Did someone call process.exit() or wat? exitCode:', process.exitCode);
|
|
98
|
+
});
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test case for figuring out robust way to recognize if connection is dead
|
|
3
|
+
* for mysql based drivers.
|
|
4
|
+
*/
|
|
5
|
+
const Bluebird = require('bluebird');
|
|
6
|
+
const toxiproxy = require('toxiproxy-node-client');
|
|
7
|
+
const toxicli = new toxiproxy.Toxiproxy('http://localhost:8474');
|
|
8
|
+
const rp = require('request-promise-native');
|
|
9
|
+
|
|
10
|
+
async function stdMysqlQuery(con, sql) {
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
try {
|
|
13
|
+
con.query({
|
|
14
|
+
sql,
|
|
15
|
+
timeout: 4000
|
|
16
|
+
}, function (error, results, fields) {
|
|
17
|
+
if (error) {
|
|
18
|
+
reject(error);
|
|
19
|
+
} else {
|
|
20
|
+
resolve(results);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
} catch (err) {
|
|
24
|
+
reject(err); // double sure...
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ALL THE DRIVERS HAS DIFFERENT BAG OF TRICKS TO RECOVER AND
|
|
30
|
+
// RECOGNIZE WHEN CONNECTION HAS BEEN CLOSED
|
|
31
|
+
|
|
32
|
+
// ------------- setup mysql db driver connection
|
|
33
|
+
const mysql = require('mysql');
|
|
34
|
+
let mysqlCon = {state: 'disconnected'};
|
|
35
|
+
async function mysqlQuery(sql) {
|
|
36
|
+
// best way to check if connection is still alive
|
|
37
|
+
if (mysqlCon.state === 'disconnected') {
|
|
38
|
+
console.log('reconnecting mysql');
|
|
39
|
+
mysqlCon = mysql.createConnection({
|
|
40
|
+
host: 'localhost',
|
|
41
|
+
user: 'root',
|
|
42
|
+
password: 'mysqlrootpassword',
|
|
43
|
+
port: 23306,
|
|
44
|
+
connectTimeout: 500
|
|
45
|
+
});
|
|
46
|
+
// not always triggered, if this happens during query
|
|
47
|
+
mysqlCon.on('error', err => {
|
|
48
|
+
console.log('- STATS Mysql connection died:', err);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return stdMysqlQuery(mysqlCon, sql);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ------------- setup mysql2 db driver connection
|
|
55
|
+
const mysql2 = require('mysql2');
|
|
56
|
+
let mysql2Con = {_fatalError: 'initmefirst'};
|
|
57
|
+
async function mysql2Query(sql) {
|
|
58
|
+
if (mysql2Con._fatalError) {
|
|
59
|
+
console.log('reconnecting mysql2');
|
|
60
|
+
mysql2Con = mysql2.createConnection({
|
|
61
|
+
host: 'localhost',
|
|
62
|
+
user: 'root',
|
|
63
|
+
password: 'mysqlrootpassword',
|
|
64
|
+
port: 23306,
|
|
65
|
+
connectTimeout: 500
|
|
66
|
+
});
|
|
67
|
+
mysql2Con.on('error', err => {
|
|
68
|
+
console.log('- STATS Mysql2 connection died:', err);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
console.log('================ MYSQL2 Running query....');
|
|
72
|
+
const res = await stdMysqlQuery(mysql2Con, sql);
|
|
73
|
+
console.log('=========== done');
|
|
74
|
+
return res;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const counters = {};
|
|
78
|
+
function setMysqlQueryCounters(name) {
|
|
79
|
+
const counts = counters[name] = {queries: 0, results: 0, errors: 0};
|
|
80
|
+
}
|
|
81
|
+
setMysqlQueryCounters('mysql');
|
|
82
|
+
setMysqlQueryCounters('mysql2');
|
|
83
|
+
|
|
84
|
+
const _ = require('lodash');
|
|
85
|
+
|
|
86
|
+
// start printing out counters
|
|
87
|
+
let lastCounters = _.cloneDeep(counters);
|
|
88
|
+
|
|
89
|
+
setInterval(() => {
|
|
90
|
+
const reqsPerSec = {};
|
|
91
|
+
for (let key of Object.keys(counters)) {
|
|
92
|
+
reqsPerSec[key] = {
|
|
93
|
+
queries: (counters[key].queries - lastCounters[key].queries),
|
|
94
|
+
results: (counters[key].results - lastCounters[key].results),
|
|
95
|
+
errors: (counters[key].errors - lastCounters[key].errors),
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
console.log('------------------------ STATS PER SECOND ------------------------');
|
|
99
|
+
console.dir(reqsPerSec, {colors: true});
|
|
100
|
+
console.log('------------------------------- EOS ------------------------------');
|
|
101
|
+
lastCounters = _.cloneDeep(counters);
|
|
102
|
+
|
|
103
|
+
// if hang
|
|
104
|
+
///if (reqsPerSec.mysql2.queries === 0) process.exit(0);
|
|
105
|
+
}, 1000);
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
async function main() {
|
|
109
|
+
|
|
110
|
+
async function recreateProxy(serviceName, listenPort, proxyToPort) {
|
|
111
|
+
try {
|
|
112
|
+
await rp.delete({
|
|
113
|
+
url: `${toxicli.host}/proxies/${serviceName}`
|
|
114
|
+
});
|
|
115
|
+
} catch(err) {}
|
|
116
|
+
|
|
117
|
+
const proxy = await toxicli.createProxy({
|
|
118
|
+
name: serviceName,
|
|
119
|
+
listen: `0.0.0.0:${listenPort}`,
|
|
120
|
+
upstream: `${serviceName}:${proxyToPort}`
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// add some latency
|
|
124
|
+
await proxy.addToxic(new toxiproxy.Toxic(proxy, {
|
|
125
|
+
type: 'latency',
|
|
126
|
+
attributes: {latency: 1, jitter: 1}
|
|
127
|
+
}));
|
|
128
|
+
|
|
129
|
+
// cause connections to be closed every some transferred bytes
|
|
130
|
+
await proxy.addToxic(new toxiproxy.Toxic(proxy, {
|
|
131
|
+
type: 'limit_data',
|
|
132
|
+
attributes: {bytes: 1000}
|
|
133
|
+
}));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
// create TCP proxies for simulating bad connections etc.
|
|
138
|
+
async function recreateProxies() {
|
|
139
|
+
console.log('----- Recreating proxies -> cutting connections completely');
|
|
140
|
+
await recreateProxy('postgresql', 25432, 5432);
|
|
141
|
+
await recreateProxy('mysql', 23306, 3306);
|
|
142
|
+
await recreateProxy('oracledbxe', 21521, 1521);
|
|
143
|
+
}
|
|
144
|
+
setInterval(() => recreateProxies(), 2000);
|
|
145
|
+
|
|
146
|
+
async function loopQueries(prefix, query) {
|
|
147
|
+
const counts = counters[prefix];
|
|
148
|
+
|
|
149
|
+
while(true) {
|
|
150
|
+
try {
|
|
151
|
+
counts.queries += 1;
|
|
152
|
+
// without this delay we endup to busy failure loop
|
|
153
|
+
await Bluebird.delay(0);
|
|
154
|
+
await query();
|
|
155
|
+
counts.results += 1;
|
|
156
|
+
} catch (err) {
|
|
157
|
+
counts.errors += 1;
|
|
158
|
+
console.log(prefix, err);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
await recreateProxies();
|
|
164
|
+
|
|
165
|
+
loopQueries('mysql', () => mysqlQuery('select 1'));
|
|
166
|
+
loopQueries('mysql2', () => mysql2Query('select 1'));
|
|
167
|
+
|
|
168
|
+
// wait forever
|
|
169
|
+
while(true) {
|
|
170
|
+
await Bluebird.delay(1000);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
main().then(() => console.log('DONE')).catch(err => console.log(err));
|
package/src/client.js
CHANGED
|
@@ -27,11 +27,6 @@ const debug = require('debug')('knex:client')
|
|
|
27
27
|
const debugQuery = require('debug')('knex:query')
|
|
28
28
|
const debugBindings = require('debug')('knex:bindings')
|
|
29
29
|
|
|
30
|
-
let id = 0
|
|
31
|
-
function clientId() {
|
|
32
|
-
return `client${id++}`
|
|
33
|
-
}
|
|
34
|
-
|
|
35
30
|
// The base client provides the general structure
|
|
36
31
|
// for a dialect specific client object.
|
|
37
32
|
function Client(config = {}) {
|
|
@@ -48,7 +43,6 @@ function Client(config = {}) {
|
|
|
48
43
|
if (this.driverName && config.connection) {
|
|
49
44
|
this.initializeDriver()
|
|
50
45
|
if (!config.pool || (config.pool && config.pool.max !== 0)) {
|
|
51
|
-
this.__cid = clientId()
|
|
52
46
|
this.initializePool(config)
|
|
53
47
|
}
|
|
54
48
|
}
|
|
@@ -64,33 +64,36 @@ assign(Client_MySQL.prototype, {
|
|
|
64
64
|
// connection needs to be added to the pool.
|
|
65
65
|
acquireRawConnection() {
|
|
66
66
|
return new Promise((resolver, rejecter) => {
|
|
67
|
-
const connection = this.driver.createConnection(this.connectionSettings)
|
|
67
|
+
const connection = this.driver.createConnection(this.connectionSettings);
|
|
68
|
+
connection.on('error', err => {
|
|
69
|
+
connection.__knex__disposed = err;
|
|
70
|
+
});
|
|
68
71
|
connection.connect((err) => {
|
|
69
|
-
if (err)
|
|
70
|
-
|
|
71
|
-
connection.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
if (err) {
|
|
73
|
+
// if connection is rejected, remove listener that was registered above...
|
|
74
|
+
connection.removeAllListeners();
|
|
75
|
+
return rejecter(err);
|
|
76
|
+
}
|
|
77
|
+
resolver(connection);
|
|
78
|
+
});
|
|
75
79
|
})
|
|
76
80
|
},
|
|
77
81
|
|
|
78
82
|
// Used to explicitly close a connection, called internally by the pool
|
|
79
83
|
// when a connection times out or the pool is shutdown.
|
|
80
84
|
destroyRawConnection(connection) {
|
|
81
|
-
connection.removeAllListeners()
|
|
82
85
|
return Promise
|
|
83
86
|
.fromCallback(connection.end.bind(connection))
|
|
84
87
|
.catch(err => {
|
|
85
88
|
connection.__knex__disposed = err
|
|
86
89
|
})
|
|
90
|
+
.finally(() => connection.removeAllListeners());
|
|
87
91
|
},
|
|
88
92
|
|
|
89
93
|
validateConnection(connection) {
|
|
90
94
|
if (connection.state === 'connected' || connection.state === 'authenticated') {
|
|
91
95
|
return true
|
|
92
96
|
}
|
|
93
|
-
|
|
94
97
|
return false
|
|
95
98
|
},
|
|
96
99
|
|
|
@@ -3,9 +3,7 @@
|
|
|
3
3
|
// -------
|
|
4
4
|
import inherits from 'inherits';
|
|
5
5
|
import Client_MySQL from '../mysql';
|
|
6
|
-
import
|
|
7
|
-
import * as helpers from '../../helpers';
|
|
8
|
-
import { map, assign } from 'lodash'
|
|
6
|
+
import { assign } from 'lodash'
|
|
9
7
|
import Transaction from './transaction';
|
|
10
8
|
|
|
11
9
|
// Always initialize with the "QueryBuilder" and "QueryCompiler"
|
|
@@ -33,52 +31,8 @@ assign(Client_MySQL2.prototype, {
|
|
|
33
31
|
if (connection._fatalError) {
|
|
34
32
|
return false
|
|
35
33
|
}
|
|
36
|
-
|
|
37
34
|
return true
|
|
38
|
-
},
|
|
39
|
-
|
|
40
|
-
// Get a raw connection, called by the `pool` whenever a new
|
|
41
|
-
// connection needs to be added to the pool.
|
|
42
|
-
acquireRawConnection() {
|
|
43
|
-
const connection = this.driver.createConnection(this.connectionSettings)
|
|
44
|
-
connection.on('error', err => {
|
|
45
|
-
connection.__knex__disposed = err
|
|
46
|
-
})
|
|
47
|
-
return new Promise((resolver, rejecter) => {
|
|
48
|
-
connection.connect((err) => {
|
|
49
|
-
if (err) {
|
|
50
|
-
return rejecter(err)
|
|
51
|
-
}
|
|
52
|
-
resolver(connection)
|
|
53
|
-
})
|
|
54
|
-
})
|
|
55
|
-
},
|
|
56
|
-
|
|
57
|
-
processResponse(obj, runner) {
|
|
58
|
-
const { response } = obj
|
|
59
|
-
const { method } = obj
|
|
60
|
-
const rows = response[0]
|
|
61
|
-
const fields = response[1]
|
|
62
|
-
if (obj.output) return obj.output.call(runner, rows, fields)
|
|
63
|
-
switch (method) {
|
|
64
|
-
case 'select':
|
|
65
|
-
case 'pluck':
|
|
66
|
-
case 'first': {
|
|
67
|
-
const resp = helpers.skim(rows)
|
|
68
|
-
if (method === 'pluck') return map(resp, obj.pluck)
|
|
69
|
-
return method === 'first' ? resp[0] : resp
|
|
70
|
-
}
|
|
71
|
-
case 'insert':
|
|
72
|
-
return [rows.insertId]
|
|
73
|
-
case 'del':
|
|
74
|
-
case 'update':
|
|
75
|
-
case 'counter':
|
|
76
|
-
return rows.affectedRows
|
|
77
|
-
default:
|
|
78
|
-
return response
|
|
79
|
-
}
|
|
80
35
|
}
|
|
81
|
-
|
|
82
36
|
})
|
|
83
37
|
|
|
84
38
|
export default Client_MySQL2;
|
|
@@ -4,9 +4,16 @@
|
|
|
4
4
|
import inherits from 'inherits';
|
|
5
5
|
import QueryCompiler from '../../../query/compiler';
|
|
6
6
|
import { assign, each, isEmpty, isString, noop, reduce, identity } from 'lodash'
|
|
7
|
+
import { warn } from '../../../helpers';
|
|
7
8
|
|
|
8
9
|
function QueryCompiler_SQLite3(client, builder) {
|
|
9
10
|
QueryCompiler.call(this, client, builder)
|
|
11
|
+
|
|
12
|
+
const {returning} = this.single;
|
|
13
|
+
|
|
14
|
+
if(returning) {
|
|
15
|
+
warn('.returning() is not supported by sqlite3 and will not have any effect.');
|
|
16
|
+
}
|
|
10
17
|
}
|
|
11
18
|
inherits(QueryCompiler_SQLite3, QueryCompiler)
|
|
12
19
|
|
package/src/helpers.js
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
/* eslint no-console:0 */
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
map,
|
|
5
|
+
pick,
|
|
6
|
+
keys,
|
|
7
|
+
isFunction,
|
|
8
|
+
isUndefined,
|
|
9
|
+
isPlainObject,
|
|
10
|
+
isArray,
|
|
11
|
+
isTypedArray
|
|
12
|
+
} from 'lodash'
|
|
4
13
|
import chalk from 'chalk';
|
|
5
14
|
|
|
6
15
|
// Pick off the attributes from only the current layer of the object.
|
|
@@ -60,7 +69,7 @@ export function containsUndefined(mixed) {
|
|
|
60
69
|
if(argContainsUndefined) break;
|
|
61
70
|
argContainsUndefined = this.containsUndefined(mixed[i]);
|
|
62
71
|
}
|
|
63
|
-
} else if(
|
|
72
|
+
} else if(isPlainObject(mixed)) {
|
|
64
73
|
for(const key in mixed) {
|
|
65
74
|
if (mixed.hasOwnProperty(key)) {
|
|
66
75
|
if(argContainsUndefined) break;
|
package/src/migrate/index.js
CHANGED
|
@@ -152,9 +152,8 @@ export default class Migrator {
|
|
|
152
152
|
.then(data => !data.length && trx.into(lockTable).insert({ is_locked: 0 }));
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
// Create the migration table, if it doesn't already exist.
|
|
156
155
|
_createMigrationTable(tableName, trx = this.knex) {
|
|
157
|
-
return trx.schema.
|
|
156
|
+
return trx.schema.createTable(tableName, function(t) {
|
|
158
157
|
t.increments();
|
|
159
158
|
t.string('name');
|
|
160
159
|
t.integer('batch');
|
|
@@ -163,7 +162,7 @@ export default class Migrator {
|
|
|
163
162
|
}
|
|
164
163
|
|
|
165
164
|
_createMigrationLockTable(tableName, trx = this.knex) {
|
|
166
|
-
return trx.schema.
|
|
165
|
+
return trx.schema.createTable(tableName, function(t) {
|
|
167
166
|
t.integer('is_locked');
|
|
168
167
|
});
|
|
169
168
|
}
|
package/src/query/compiler.js
CHANGED
|
@@ -425,11 +425,7 @@ assign(QueryCompiler.prototype, {
|
|
|
425
425
|
// Compiles the "locks".
|
|
426
426
|
lock() {
|
|
427
427
|
if (this.single.lock) {
|
|
428
|
-
|
|
429
|
-
helpers.warn('You are attempting to perform a "lock" command outside of a transaction.')
|
|
430
|
-
} else {
|
|
431
|
-
return this[this.single.lock]()
|
|
432
|
-
}
|
|
428
|
+
return this[this.single.lock]()
|
|
433
429
|
}
|
|
434
430
|
},
|
|
435
431
|
|
|
@@ -618,6 +614,15 @@ assign(QueryCompiler.prototype, {
|
|
|
618
614
|
this.formatter.parameter(data[columns[i]])
|
|
619
615
|
);
|
|
620
616
|
}
|
|
617
|
+
|
|
618
|
+
if(isEmpty(vals)) {
|
|
619
|
+
throw new Error([
|
|
620
|
+
'Empty .update() call detected!',
|
|
621
|
+
'Update data does not contain any values to update.',
|
|
622
|
+
'This will result in a faulty query.',
|
|
623
|
+
].join(' '));
|
|
624
|
+
}
|
|
625
|
+
|
|
621
626
|
return vals;
|
|
622
627
|
},
|
|
623
628
|
|
package/scripts/stress-test.js
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
const Bluebird = require('bluebird');
|
|
2
|
-
|
|
3
|
-
// TODO: setup all DBs with docker
|
|
4
|
-
// TODO: setup proxy for closing connections
|
|
5
|
-
// TODO: use all dialects
|
|
6
|
-
|
|
7
|
-
const pg = require('../lib')({
|
|
8
|
-
client: 'pg',
|
|
9
|
-
connection: 'postgres:///knex_test',
|
|
10
|
-
pool: { max: 90 }
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
const mysql = require('../lib')({
|
|
14
|
-
dialect: 'mysql2',
|
|
15
|
-
connection: {
|
|
16
|
-
database: "knex_test",
|
|
17
|
-
user: "root",
|
|
18
|
-
charset: 'utf8'
|
|
19
|
-
},
|
|
20
|
-
pool: { max: 90 }
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
async function main() {
|
|
24
|
-
const pgQueries = [
|
|
25
|
-
...Array(40).fill(pg('accounts').timeout(100)), // these might eat memory
|
|
26
|
-
...Array(40).fill(pg('accounts')) // these might hang indefinitely
|
|
27
|
-
];
|
|
28
|
-
|
|
29
|
-
function killConnectionsPg() {
|
|
30
|
-
return pg.raw(`SELECT pg_terminate_backend(pg_stat_activity.pid)
|
|
31
|
-
FROM pg_stat_activity
|
|
32
|
-
WHERE pg_stat_activity.datname = 'knex_test'
|
|
33
|
-
AND pid <> pg_backend_pid()`);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async function killConnectionsMyslq() {
|
|
37
|
-
const [rows, colDefs] = await mysql.raw(`SHOW FULL PROCESSLIST`);
|
|
38
|
-
await Promise.all(rows.map(row => mysql.raw(`KILL ${row.Id}`)));
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const mysqlQueries = [
|
|
42
|
-
...Array(40).fill(mysql('accounts').timeout(100)),
|
|
43
|
-
...Array(40).fill(mysql('accounts'))
|
|
44
|
-
];
|
|
45
|
-
|
|
46
|
-
const counters = {
|
|
47
|
-
pgQueries: 0,
|
|
48
|
-
pgResults: 0,
|
|
49
|
-
pgErrors: 0,
|
|
50
|
-
mysqlQueries: 0,
|
|
51
|
-
mysqlResults: 0,
|
|
52
|
-
mysqlErrors: 0
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
let lastCounters = {...counters};
|
|
56
|
-
|
|
57
|
-
pg.on('query', () => counters.pgQueries += 1);
|
|
58
|
-
pg.on('query-response', () => counters.pgResults += 1);
|
|
59
|
-
pg.on('query-error', () => counters.pgErrors += 1);
|
|
60
|
-
mysql.on('query', () => counters.mysqlQueries += 1);
|
|
61
|
-
mysql.on('query-response', () => counters.mysqlResults += 1);
|
|
62
|
-
mysql.on('query-error', () => counters.mysqlErrors += 1);
|
|
63
|
-
|
|
64
|
-
setInterval(() => {
|
|
65
|
-
console.dir({
|
|
66
|
-
markerPerSecond: '-------------------------------------------------------------------------------------------',
|
|
67
|
-
pgQueriesPerSecond: (counters.pgQueries - lastCounters.pgQueries)/2,
|
|
68
|
-
pgResultsPerSecond: (counters.pgResults - lastCounters.pgResults)/2,
|
|
69
|
-
pgErrorsPerSecond: (counters.pgErrors - lastCounters.pgErrors)/2,
|
|
70
|
-
mysqlQueriesPerSecond: (counters.mysqlQueries - lastCounters.mysqlQueries)/2,
|
|
71
|
-
mysqlResultsPerSecond: (counters.mysqlResults - lastCounters.mysqlResults)/2,
|
|
72
|
-
mysqlErrorsPerSecond: (counters.mysqlErrors - lastCounters.mysqlErrors)/2,
|
|
73
|
-
}, {colors: true});
|
|
74
|
-
|
|
75
|
-
lastCounters = {...counters};
|
|
76
|
-
}, 2000);
|
|
77
|
-
|
|
78
|
-
while(true) {
|
|
79
|
-
try {
|
|
80
|
-
await Promise.all([
|
|
81
|
-
...pgQueries,
|
|
82
|
-
killConnectionsPg(),
|
|
83
|
-
// ...mysqlQueries,
|
|
84
|
-
// killConnectionsMyslq()
|
|
85
|
-
]);
|
|
86
|
-
} catch (err) {
|
|
87
|
-
console.log(err);
|
|
88
|
-
// pass on error and try again...
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
main();
|