ueberdb2 4.0.11 → 4.0.15

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.
Files changed (47) hide show
  1. package/.eslintignore +2 -0
  2. package/.eslintrc.cjs +44 -5
  3. package/databases/{cassandra_db.js → cassandra_db.ts} +45 -30
  4. package/databases/{couch_db.js → couch_db.ts} +78 -31
  5. package/databases/{dirty_db.js → dirty_db.ts} +19 -14
  6. package/databases/{dirty_git_db.js → dirty_git_db.ts} +19 -15
  7. package/databases/{elasticsearch_db.js → elasticsearch_db.ts} +30 -21
  8. package/databases/{memory_db.js → memory_db.ts} +8 -8
  9. package/databases/mock_db.ts +43 -0
  10. package/databases/{mongodb_db.js → mongodb_db.ts} +22 -16
  11. package/databases/{mssql_db.js → mssql_db.ts} +29 -21
  12. package/databases/{mysql_db.js → mysql_db.ts} +20 -15
  13. package/databases/{postgres_db.js → postgres_db.ts} +37 -22
  14. package/databases/{postgrespool_db.js → postgrespool_db.ts} +3 -3
  15. package/databases/redis_db.ts +129 -0
  16. package/databases/{rethink_db.js → rethink_db.ts} +35 -19
  17. package/databases/{sqlite_db.js → sqlite_db.ts} +37 -36
  18. package/docker-compose.yml +44 -0
  19. package/{index.js → index.ts} +76 -25
  20. package/lib/AbstractDatabase.ts +79 -0
  21. package/lib/{CacheAndBufferLayer.js → CacheAndBufferLayer.ts} +17 -16
  22. package/lib/{logging.js → logging.ts} +10 -6
  23. package/package.json +17 -3
  24. package/test/lib/{databases.js → databases.ts} +8 -5
  25. package/test/test.ts +328 -0
  26. package/test/test_bulk.ts +69 -0
  27. package/test/{test_elasticsearch.js → test_elasticsearch.ts} +48 -53
  28. package/test/{test_findKeys.js → test_findKeys.ts} +15 -17
  29. package/test/{test_flush.js → test_flush.ts} +16 -22
  30. package/test/test_getSub.ts +28 -0
  31. package/test/test_lru.ts +151 -0
  32. package/test/test_memory.ts +32 -0
  33. package/test/{test_metrics.js → test_metrics.ts} +73 -68
  34. package/test/{test_mysql.js → test_mysql.ts} +16 -22
  35. package/test/test_postgres.ts +16 -0
  36. package/test/{test_setSub.js → test_setSub.ts} +8 -12
  37. package/test/test_tojson.ts +34 -0
  38. package/databases/mock_db.js +0 -42
  39. package/databases/redis_db.js +0 -96
  40. package/lib/AbstractDatabase.js +0 -37
  41. package/test/test.js +0 -328
  42. package/test/test_bulk.js +0 -69
  43. package/test/test_getSub.js +0 -31
  44. package/test/test_lru.js +0 -145
  45. package/test/test_memory.js +0 -31
  46. package/test/test_postgres.js +0 -16
  47. package/test/test_tojson.js +0 -37
@@ -1,46 +1,42 @@
1
+ import assert$0 from 'assert';
2
+ import {databases} from './lib/databases';
3
+ import * as mysql from '../databases/mysql_db';
1
4
  'use strict';
2
-
3
- const assert = require('assert').strict;
4
- const {databases} = require('./lib/databases');
5
- const mysql = require('../databases/mysql_db');
6
-
7
- describe(__filename, function () {
8
- beforeEach(async function () {
5
+ const assert = assert$0.strict;
6
+ describe(__filename, () => {
7
+ beforeEach(async function (this: any) {
9
8
  if (databases.mysql == null) return this.skip();
10
9
  });
11
-
12
- it('connect error is detected during init()', async function () {
10
+ it('connect error is detected during init()', async () => {
13
11
  // Use an invalid TCP port to force a connection error.
14
12
  const db = new mysql.Database({...databases.mysql, port: 65536});
15
13
  // An error is expected; prevent it from being logged.
16
- db.logger = Object.setPrototypeOf({error() {}}, db.logger);
14
+ db.logger = Object.setPrototypeOf({error() { }}, db.logger);
17
15
  await assert.rejects(db.init());
18
16
  });
19
-
20
- it('query after fatal error works', async function () {
17
+ it('query after fatal error works', async () => {
21
18
  const db = new mysql.Database(databases.mysql);
22
19
  await db.init();
23
20
  // An error is expected; prevent it from being logged.
24
- db.logger = Object.setPrototypeOf({error() {}}, db.logger);
21
+ db.logger = Object.setPrototypeOf({error() { }}, db.logger);
25
22
  // Sleep longer than the timeout to force a fatal error.
26
23
  await assert.rejects(db._query({sql: 'DO SLEEP(1);', timeout: 1}), {fatal: true});
27
24
  await assert.doesNotReject(db._query({sql: 'SELECT 1;'}));
28
25
  await db.close();
29
26
  });
30
-
31
- it('query times out', async function () {
27
+ it('query times out', async () => {
32
28
  const db = new mysql.Database(databases.mysql);
33
29
  await db.init();
34
30
  // Timeout error messages are expected; prevent them from being logged.
35
- db.logger = Object.setPrototypeOf({error() {}}, db.logger);
31
+ db.logger = Object.setPrototypeOf({error() { }}, db.logger);
36
32
  db.settings.queryTimeout = 100;
37
33
  await assert.doesNotReject(db._query({sql: 'DO SLEEP(0.090);'}));
38
34
  await assert.rejects(db._query({sql: 'DO SLEEP(0.110);'}));
39
35
  await db.close();
40
36
  });
41
-
42
- it('queries run concurrently and are queued when pool is busy', async function () {
37
+ it('queries run concurrently and are queued when pool is busy', async () => {
43
38
  const connectionLimit = 10;
39
+ // @ts-expect-error TS(2339): Property 'Database' does not exist on type 'typeof... Remove this comment to see the full error message
44
40
  const db = new mysql.Database({...databases.mysql, connectionLimit});
45
41
  await db.init();
46
42
  // Set the query duration high enough to avoid flakiness on slow machines but low enough to keep
@@ -48,21 +44,19 @@ describe(__filename, function () {
48
44
  const queryDuration = 100;
49
45
  db.settings.queryTimeout = queryDuration + 100;
50
46
  const enqueueQuery = () => db._query({sql: `DO SLEEP(${queryDuration / 1000});`});
51
-
52
47
  // Reduce test flakiness by using slow queries to warm up the pool's connections.
53
48
  await Promise.all([...Array(connectionLimit)].map(enqueueQuery));
54
-
55
49
  // Time how long it takes to run just under 2 * connectionLimit queries.
56
50
  const nQueries = 2 * connectionLimit - 1;
57
51
  const start = Date.now();
58
52
  await Promise.all([...Array(nQueries)].map(enqueueQuery));
59
53
  const duration = Date.now() - start;
60
-
61
54
  const wantDurationLower = Math.ceil(nQueries / connectionLimit) * queryDuration;
55
+ // @ts-expect-error TS(2775): Assertions require every name in the call target t... Remove this comment to see the full error message
62
56
  assert(duration >= wantDurationLower, `took ${duration}ms, want >= ${wantDurationLower}ms`);
63
57
  const wantDurationUpper = wantDurationLower + queryDuration;
58
+ // @ts-expect-error TS(2775): Assertions require every name in the call target t... Remove this comment to see the full error message
64
59
  assert(duration < wantDurationUpper, `took ${duration}ms, want < ${wantDurationUpper}ms`);
65
-
66
60
  await db.close();
67
61
  });
68
62
  });
@@ -0,0 +1,16 @@
1
+ import {databases} from './lib/databases';
2
+ import * as ueberdb from '../index';
3
+ 'use strict';
4
+ import {equal} from 'assert';
5
+
6
+ describe(__filename, () => {
7
+ it('connection string instead of settings object', async () => {
8
+ const {user, password, host, database} = databases.postgres;
9
+ console.log(`postgres://${user}:${password}@${host}/${database}`);
10
+ const db = new ueberdb.Database('postgres', `postgres://${user}:${password}@${host}/${database}`);
11
+ await db.init();
12
+ await db.set('key', 'val');
13
+ const val = await db.get('key') as string;
14
+ equal(val, 'val');
15
+ });
16
+ });
@@ -1,22 +1,18 @@
1
+ import assert$0 from 'assert';
2
+ import * as ueberdb from '../index';
1
3
  'use strict';
2
-
3
- const assert = require('assert').strict;
4
- const ueberdb = require('../index');
5
-
6
- describe(__filename, function () {
7
- let db;
8
-
9
- beforeEach(async function () {
4
+ const assert = assert$0.strict;
5
+ describe(__filename, () => {
6
+ let db: any;
7
+ beforeEach(async () => {
10
8
  db = new ueberdb.Database('memory', {}, {});
11
9
  await db.init();
12
10
  });
13
-
14
- afterEach(async function () {
11
+ afterEach(async () => {
15
12
  if (db != null) await db.close();
16
13
  db = null;
17
14
  });
18
-
19
- it('setSub rejects __proto__', async function () {
15
+ it('setSub rejects __proto__', async () => {
20
16
  await db.set('k', {});
21
17
  await assert.rejects(db.setSub('k', ['__proto__'], 'v'));
22
18
  });
@@ -0,0 +1,34 @@
1
+ import assert$0 from 'assert';
2
+ import * as ueberdb from '../index';
3
+ 'use strict';
4
+ const assert = assert$0.strict;
5
+ describe(__filename, () => {
6
+ let db: any = null;
7
+ before(async () => {
8
+ db = new ueberdb.Database('memory', {}, {});
9
+ await db.init();
10
+ });
11
+ after(async () => {
12
+ await db.close();
13
+ });
14
+ it('no .toJSON method', async () => {
15
+ await db.set('key', {prop: 'value'});
16
+ // @ts-expect-error TS(2775): Assertions require every name in the call target t... Remove this comment to see the full error message
17
+ assert.deepEqual(await db.get('key'), {prop: 'value'});
18
+ });
19
+ it('direct', async () => {
20
+ await db.set('key', {toJSON: (arg: any) => `toJSON ${arg}`});
21
+ // @ts-expect-error TS(2775): Assertions require every name in the call target t... Remove this comment to see the full error message
22
+ assert.equal(await db.get('key'), 'toJSON ');
23
+ });
24
+ it('object property', async () => {
25
+ await db.set('key', {prop: {toJSON: (arg: any) => `toJSON ${arg}`}});
26
+ // @ts-expect-error TS(2775): Assertions require every name in the call target t... Remove this comment to see the full error message
27
+ assert.deepEqual(await db.get('key'), {prop: 'toJSON prop'});
28
+ });
29
+ it('array entry', async () => {
30
+ await db.set('key', [{toJSON: (arg: any) => `toJSON ${arg}`}]);
31
+ // @ts-expect-error TS(2775): Assertions require every name in the call target t... Remove this comment to see the full error message
32
+ assert.deepEqual(await db.get('key'), ['toJSON 0']);
33
+ });
34
+ });
@@ -1,42 +0,0 @@
1
- 'use strict';
2
-
3
- const events = require('events');
4
-
5
- exports.Database = class extends events.EventEmitter {
6
- constructor(settings) {
7
- super();
8
- this.settings = {
9
- writeInterval: 1,
10
- ...settings,
11
- };
12
- settings.mock = this;
13
- }
14
-
15
- close(cb) {
16
- this.emit('close', cb);
17
- }
18
-
19
- doBulk(ops, cb) {
20
- this.emit('doBulk', ops, cb);
21
- }
22
-
23
- findKeys(key, notKey, cb) {
24
- this.emit('findKeys', key, notKey, cb);
25
- }
26
-
27
- get(key, cb) {
28
- this.emit('get', key, cb);
29
- }
30
-
31
- init(cb) {
32
- this.emit('init', cb);
33
- }
34
-
35
- remove(key, cb) {
36
- this.emit('remove', key, cb);
37
- }
38
-
39
- set(key, value, cb) {
40
- this.emit('set', key, value, cb);
41
- }
42
- };
@@ -1,96 +0,0 @@
1
- 'use strict';
2
-
3
- /**
4
- * 2011 Peter 'Pita' Martischka
5
- *
6
- * Licensed under the Apache License, Version 2.0 (the "License");
7
- * you may not use this file except in compliance with the License.
8
- * You may obtain a copy of the License at
9
- *
10
- * http://www.apache.org/licenses/LICENSE-2.0
11
- *
12
- * Unless required by applicable law or agreed to in writing, software
13
- * distributed under the License is distributed on an "AS-IS" BASIS,
14
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
- * See the License for the specific language governing permissions and
16
- * limitations under the License.
17
- */
18
-
19
- const AbstractDatabase = require('../lib/AbstractDatabase');
20
- const redis = require('redis');
21
-
22
- exports.Database = class extends AbstractDatabase {
23
- constructor(settings) {
24
- super();
25
- this._client = null;
26
- this.settings = settings || {};
27
- }
28
-
29
- get isAsync() { return true; }
30
-
31
- async init() {
32
- this._client = redis.createClient(this.settings);
33
- await this._client.connect();
34
- await this._client.ping();
35
- }
36
-
37
- async get(key) {
38
- return await this._client.get(key);
39
- }
40
-
41
- async findKeys(key, notKey) {
42
- const [type] = /^([^:*]+):\*$/.exec(key) || [];
43
- if (type != null && ['*:*:*', `${key}:*`].includes(notKey)) {
44
- // Performance optimization for a common Etherpad case.
45
- return await this._client.sMembers(`ueberDB:keys:${type}`);
46
- }
47
- let keys = await this._client.keys(key.replace(/[?[\]\\]/g, '\\$&'));
48
- if (notKey != null) {
49
- const regex = this.createFindRegex(key, notKey);
50
- keys = keys.filter((k) => regex.test(k));
51
- }
52
- return keys;
53
- }
54
-
55
- async set(key, value) {
56
- const matches = /^([^:]+):([^:]+)$/.exec(key);
57
- await Promise.all([
58
- matches && this._client.sAdd(`ueberDB:keys:${matches[1]}`, matches[0]),
59
- this._client.set(key, value),
60
- ]);
61
- }
62
-
63
- async remove(key) {
64
- const matches = /^([^:]+):([^:]+)$/.exec(key);
65
- await Promise.all([
66
- matches && this._client.sRem(`ueberDB:keys:${matches[1]}`, matches[0]),
67
- this._client.del(key),
68
- ]);
69
- }
70
-
71
- async doBulk(bulk) {
72
- const multi = this._client.multi();
73
-
74
- for (const {key, type, value} of bulk) {
75
- const matches = /^([^:]+):([^:]+)$/.exec(key);
76
- if (type === 'set') {
77
- if (matches) {
78
- multi.sAdd(`ueberDB:keys:${matches[1]}`, matches[0]);
79
- }
80
- multi.set(key, value);
81
- } else if (type === 'remove') {
82
- if (matches) {
83
- multi.sRem(`ueberDB:keys:${matches[1]}`, matches[0]);
84
- }
85
- multi.del(key);
86
- }
87
- }
88
-
89
- await multi.exec();
90
- }
91
-
92
- async close() {
93
- await this._client.quit();
94
- this._client = null;
95
- }
96
- };
@@ -1,37 +0,0 @@
1
- 'use strict';
2
-
3
- const logging = require('./logging');
4
-
5
- const nullLogger = logging.normalizeLogger(null);
6
-
7
- // Format: All characters match themselves except * matches any zero or more characters. No
8
- // backslash escaping is supported, so it is impossible to create a pattern that matches only the
9
- // '*' character.
10
- const simpleGlobToRegExp = (s) => s.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*');
11
-
12
- module.exports = class AbstractDatabase {
13
- constructor() {
14
- if (new.target === module.exports) {
15
- throw new TypeError('cannot instantiate Abstract Database directly');
16
- }
17
- for (const fn of ['init', 'close', 'get', 'findKeys', 'remove', 'set']) {
18
- if (typeof this[fn] !== 'function') throw new TypeError(`method ${fn} not defined`);
19
- }
20
- this.logger = nullLogger;
21
- }
22
-
23
- /**
24
- * For findKey regex. Used by document dbs like mongodb or dirty.
25
- */
26
- createFindRegex(key, notKey) {
27
- let regex = `^(?=${simpleGlobToRegExp(key)}$)`;
28
- if (notKey != null) regex += `(?!${simpleGlobToRegExp(notKey)}$)`;
29
- return new RegExp(regex);
30
- }
31
-
32
- doBulk(operations, cb) {
33
- throw new Error('the doBulk method must be implemented if write caching is enabled');
34
- }
35
-
36
- get isAsync() { return false; }
37
- };
package/test/test.js DELETED
@@ -1,328 +0,0 @@
1
- 'use strict';
2
-
3
- const wtfnode = require('wtfnode'); // This should be first so that it can instrument everything.
4
-
5
- const Clitable = require('cli-table');
6
- const Randexp = require('randexp');
7
- const assert = require('assert').strict;
8
- const databases = require('./lib/databases').databases;
9
- const fs = require('fs').promises;
10
- const logging = require('../lib/logging');
11
- const ueberdb = require('../index');
12
-
13
- const maxKeyLength = 100;
14
- const randomString = (length = maxKeyLength) => new Randexp(new RegExp(`.{${length}}`)).gen();
15
-
16
- // eslint-disable-next-line mocha/no-top-level-hooks
17
- after(async function () {
18
- // Add a timeout to forcibly exit if something is keeping node from exiting cleanly.
19
- // The timeout is unref()ed so that it doesn't prevent node from exiting when done.
20
- setTimeout(() => {
21
- console.error('node should have exited by now but something is keeping it open ' +
22
- 'such as an open connection or active timer');
23
- wtfnode.dump();
24
- process.exit(1); // eslint-disable-line n/no-process-exit
25
- }, 5000).unref();
26
- });
27
-
28
- describe(__filename, function () {
29
- let speedTable;
30
- let db;
31
-
32
- before(async function () {
33
- speedTable = new Clitable({
34
- head: [
35
- 'Database',
36
- 'read cache',
37
- 'write buffer',
38
- '#',
39
- 'ms/set',
40
- 'ms/get',
41
- 'ms/findKeys',
42
- 'ms/remove',
43
- 'total ms',
44
- 'total ms/#',
45
- ],
46
- colWidths: [15, 15, 15, 8, 13, 13, 13, 13, 13, 13],
47
- });
48
- });
49
-
50
- after(async function () {
51
- console.log(speedTable.toString());
52
- });
53
-
54
- for (const database of Object.keys(databases)) {
55
- const dbSettings = databases[database];
56
- describe(database, function () {
57
- for (const readCache of [false, true]) {
58
- describe(`${readCache ? '' : 'no '}read cache`, function () {
59
- for (const writeBuffer of [false, true]) {
60
- describe(`${writeBuffer ? '' : 'no '}write buffer`, function () {
61
- this.timeout(5000);
62
-
63
- before(async function () {
64
- if (dbSettings.filename) await fs.unlink(dbSettings.filename).catch(() => {});
65
- db = new ueberdb.Database(database, dbSettings, {
66
- ...(readCache ? {} : {cache: 0}),
67
- ...(writeBuffer ? {} : {writeInterval: 0}),
68
- }, new logging.ConsoleLogger());
69
- await db.init();
70
- });
71
-
72
- after(async function () {
73
- await db.close();
74
- if (dbSettings.filename) await fs.unlink(dbSettings.filename).catch(() => {});
75
- });
76
-
77
- describe('white space in key is not ignored', function () {
78
- for (const space of [false, true]) {
79
- describe(`key ${space ? 'has' : 'does not have'} a trailing space`, function () {
80
- let input;
81
- let key;
82
-
83
- before(async function () {
84
- input = {a: 1, b: new Randexp(/.+/).gen()};
85
- key = randomString(maxKeyLength - 1) + (space ? ' ' : '');
86
- await db.set(key, input);
87
- });
88
-
89
- it('get(key) -> record', async function () {
90
- const output = await db.get(key);
91
- assert.equal(JSON.stringify(output), JSON.stringify(input));
92
- });
93
-
94
- it('get(`${key} `) -> nullish', async function () {
95
- const output = await db.get(`${key} `);
96
- assert(output == null);
97
- });
98
-
99
- if (space) {
100
- it('get(key.slice(0, -1)) -> nullish', async function () {
101
- const output = await db.get(key.slice(0, -1));
102
- assert(output == null);
103
- });
104
- }
105
- });
106
- }
107
- });
108
-
109
- it('get of unknown key -> nullish', async function () {
110
- const key = randomString();
111
- assert(await db.get(key) == null);
112
- });
113
-
114
- it('set+get works', async function () {
115
- const input = {a: 1, b: new Randexp(/.+/).gen()};
116
- const key = randomString();
117
- await db.set(key, input);
118
- const output = await db.get(key);
119
- assert.equal(JSON.stringify(output), JSON.stringify(input));
120
- });
121
-
122
- it('set+get with random key/value works', async function () {
123
- const input = {testLongString: new Randexp(/[a-f0-9]{50000}/).gen()};
124
- const key = randomString();
125
- await db.set(key, input);
126
- const output = await db.get(key);
127
- assert.equal(JSON.stringify(output), JSON.stringify(input));
128
- });
129
-
130
- it('findKeys works', async function () {
131
- if (database === 'mongodb') this.skip(); // TODO: Fix mongodb.
132
- // TODO setting a key with non ascii chars
133
- const key = new Randexp(/([a-z]\w{0,20})foo\1/).gen();
134
- await Promise.all([
135
- db.set(key, true),
136
- db.set(`${key}a`, true),
137
- db.set(`nonmatching_${key}`, false),
138
- ]);
139
- const keys = await db.findKeys(`${key}*`, null);
140
- assert.deepEqual(keys.sort(), [key, `${key}a`]);
141
- });
142
-
143
- it('findKeys with exclusion works', async function () {
144
- if (database === 'mongodb') this.skip(); // TODO: Fix mongodb.
145
- const key = new Randexp(/([a-z]\w{0,20})foo\1/).gen();
146
- await Promise.all([
147
- db.set(key, true),
148
- db.set(`${key}a`, true),
149
- db.set(`${key}b`, false),
150
- db.set(`${key}b2`, false),
151
- db.set(`nonmatching_${key}`, false),
152
- ]);
153
- const keys = await db.findKeys(`${key}*`, `${key}b*`);
154
- assert.deepEqual(keys.sort(), [key, `${key}a`].sort());
155
- });
156
-
157
- it('findKeys with no matches works', async function () {
158
- const key = new Randexp(/([a-z]\w{0,20})foo\1/).gen();
159
- await db.set(key, true);
160
- const keys = await db.findKeys(`${key}_nomatch_*`, null);
161
- assert.deepEqual(keys, []);
162
- });
163
-
164
- it('findKeys with no wildcard works', async function () {
165
- const key = new Randexp(/([a-z]\w{0,20})foo\1/).gen();
166
- await db.set(key, true);
167
- const keys = await db.findKeys(key, null);
168
- assert.deepEqual(keys, [key]);
169
- });
170
-
171
- it('remove works', async function () {
172
- const input = {a: 1, b: new Randexp(/.+/).gen()};
173
- const key = randomString();
174
- await db.set(key, input);
175
- assert.equal(JSON.stringify(await db.get(key)), JSON.stringify(input));
176
- await db.remove(key);
177
- assert(await db.get(key) == null);
178
- });
179
-
180
- it('getSub of existing property works', async function () {
181
- await db.set('k', {sub1: {sub2: 'v'}});
182
- assert.equal(await db.getSub('k', ['sub1', 'sub2']), 'v');
183
- assert.deepEqual(await db.getSub('k', ['sub1']), {sub2: 'v'});
184
- assert.deepEqual(await db.getSub('k', []), {sub1: {sub2: 'v'}});
185
- });
186
-
187
- it('getSub of missing property returns nullish', async function () {
188
- await db.set('k', {sub1: {}});
189
- assert(await db.getSub('k', ['sub1', 'sub2']) == null);
190
-
191
- await db.set('k', {});
192
- assert(await db.getSub('k', ['sub1', 'sub2']) == null);
193
- assert(await db.getSub('k', ['sub1']) == null);
194
-
195
- await db.remove('k');
196
- assert(await db.getSub('k', ['sub1', 'sub2']) == null);
197
- assert(await db.getSub('k', ['sub1']) == null);
198
- assert(await db.getSub('k', []) == null);
199
- });
200
-
201
- it('setSub can modify an existing property', async function () {
202
- await db.set('k', {sub1: {sub2: 'v'}});
203
- await db.setSub('k', ['sub1', 'sub2'], 'v2');
204
- assert.deepEqual(await db.get('k'), {sub1: {sub2: 'v2'}});
205
-
206
- await db.setSub('k', ['sub1'], 'v2');
207
- assert.deepEqual(await db.get('k'), {sub1: 'v2'});
208
-
209
- await db.setSub('k', [], 'v3');
210
- assert.equal(await db.get('k'), 'v3');
211
- });
212
-
213
- it('setSub can add a new property', async function () {
214
- await db.remove('k');
215
- await db.setSub('k', [], {});
216
- assert.deepEqual(await db.get('k'), {});
217
- await db.setSub('k', ['sub1'], {});
218
- assert.deepEqual(await db.get('k'), {sub1: {}});
219
- await db.setSub('k', ['sub1', 'sub2'], 'v');
220
- assert.deepEqual(await db.get('k'), {sub1: {sub2: 'v'}});
221
-
222
- await db.remove('k');
223
- await db.setSub('k', ['sub1', 'sub2'], 'v');
224
- assert.deepEqual(await db.get('k'), {sub1: {sub2: 'v'}});
225
- });
226
-
227
- it('setSub rejects attempts to set properties on primitives', async function () {
228
- for (const v of ['hello world', 42, true]) {
229
- await db.set('k', v);
230
- assert.rejects(db.setSub('k', ['sub'], 'x'), {
231
- name: 'TypeError',
232
- message: /property "sub" on non-object/,
233
- });
234
- assert.deepEqual(await db.get('k'), v);
235
- }
236
- });
237
-
238
- it('speed is acceptable', async function () {
239
- this.timeout(180000);
240
-
241
- const {speeds: {
242
- count = 1000,
243
- setMax = 3,
244
- getMax = 0.1,
245
- findKeysMax = 3,
246
- removeMax = 1,
247
- } = {}} = dbSettings || {};
248
-
249
- const input = {a: 1, b: new Randexp(/.+/).gen()};
250
- // TODO setting a key with non ascii chars
251
- const key = new Randexp(/([a-z]\w{0,20})foo\1/).gen();
252
- // Pre-allocate an array before starting the timer so that time spent growing the
253
- // array doesn't throw off the benchmarks.
254
- const promises = [...Array(count + 1)].map(() => null);
255
-
256
- const timers = {start: Date.now()};
257
-
258
- for (let i = 0; i < count; ++i) promises[i] = db.set(key + i, input);
259
- promises[count] = db.flush();
260
- await Promise.all(promises);
261
- timers.set = Date.now();
262
-
263
- for (let i = 0; i < count; ++i) promises[i] = db.get(key + i);
264
- await Promise.all(promises);
265
- timers.get = Date.now();
266
-
267
- for (let i = 0; i < count; ++i) promises[i] = db.findKeys(key + i, null);
268
- await Promise.all(promises);
269
- timers.findKeys = Date.now();
270
-
271
- for (let i = 0; i < count; ++i) promises[i] = db.remove(key + i);
272
- promises[count] = db.flush();
273
- await Promise.all(promises);
274
- timers.remove = Date.now();
275
-
276
- const timePerOp = {
277
- set: (timers.set - timers.start) / count,
278
- get: (timers.get - timers.set) / count,
279
- findKeys: (timers.findKeys - timers.get) / count,
280
- remove: (timers.remove - timers.findKeys) / count,
281
- };
282
- speedTable.push([
283
- database,
284
- readCache ? 'yes' : 'no',
285
- writeBuffer ? 'yes' : 'no',
286
- count,
287
- timePerOp.set,
288
- timePerOp.get,
289
- timePerOp.findKeys,
290
- timePerOp.remove,
291
- timers.remove - timers.start,
292
- (timers.remove - timers.start) / count,
293
- ]);
294
-
295
- // Removes the "Acceptable ms/op" column if there is no enforced limit.
296
- const filterColumn = (row) => {
297
- if (readCache && writeBuffer) return row;
298
- row.splice(1, 1);
299
- return row;
300
- };
301
- const acceptableTable = new Clitable({
302
- head: filterColumn(['op', 'Acceptable ms/op', 'Actual ms/op']),
303
- colWidths: filterColumn([10, 18, 18]),
304
- });
305
- acceptableTable.push(...[
306
- ['set', setMax, timePerOp.set],
307
- ['get', getMax, timePerOp.get],
308
- ['findKeys', findKeysMax, timePerOp.findKeys],
309
- ['remove', removeMax, timePerOp.remove],
310
- ].map(filterColumn));
311
- console.log(acceptableTable.toString());
312
-
313
- if (readCache && writeBuffer) {
314
- assert(setMax >= timePerOp.set);
315
- assert(getMax >= timePerOp.get);
316
- assert(findKeysMax >= timePerOp.findKeys);
317
- assert(removeMax >= timePerOp.remove);
318
- }
319
- });
320
- });
321
- }
322
- });
323
- }
324
- });
325
- }
326
- });
327
-
328
- // TODO: Need test which prefills with 1e7 of data then does a get.