better-sqlite3-multiple-ciphers 7.4.6-beta.0 → 7.5.0-beta.0
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/README.md +14 -7
- package/deps/download.sh +111 -108
- package/deps/setup.ps1 +10 -12
- package/deps/sqlite3/sqlite3.c +272560 -0
- package/deps/sqlite3/sqlite3.h +12770 -0
- package/deps/sqlite3/sqlite3ext.h +675 -0
- package/deps/sqlite3.gyp +13 -7
- package/deps/symlink.js +7 -4
- package/lib/database.js +17 -6
- package/lib/sqlite-error.js +1 -2
- package/package.json +10 -4
- package/src/better_sqlite3.cpp +46 -35
- package/src/better_sqlite3.hpp +40 -38
- package/.gitattributes +0 -1
- package/.github/workflows/prebuild.yml +0 -49
- package/.github/workflows/test.yml +0 -59
- package/benchmark/benchmark.js +0 -31
- package/benchmark/drivers.js +0 -21
- package/benchmark/index.js +0 -83
- package/benchmark/seed.js +0 -47
- package/benchmark/trials.js +0 -65
- package/benchmark/types/insert.js +0 -16
- package/benchmark/types/select-all.js +0 -14
- package/benchmark/types/select-iterate.js +0 -23
- package/benchmark/types/select.js +0 -14
- package/benchmark/types/transaction.js +0 -40
- package/deps/extract.js +0 -16
- package/deps/sqlite3.tar.gz +0 -0
- package/docs/api.md +0 -645
- package/docs/benchmark.md +0 -38
- package/docs/compilation.md +0 -76
- package/docs/integer.md +0 -79
- package/docs/performance.md +0 -39
- package/docs/threads.md +0 -97
- package/docs/tips.md +0 -35
- package/docs/troubleshooting.md +0 -23
- package/docs/unsafe.md +0 -16
- package/src/better_sqlite3.lzz +0 -88
- package/src/objects/backup.lzz +0 -138
- package/src/objects/database.lzz +0 -468
- package/src/objects/statement-iterator.lzz +0 -138
- package/src/objects/statement.lzz +0 -323
- package/src/util/bind-map.lzz +0 -73
- package/src/util/binder.lzz +0 -190
- package/src/util/constants.lzz +0 -151
- package/src/util/custom-aggregate.lzz +0 -121
- package/src/util/custom-function.lzz +0 -59
- package/src/util/custom-table.lzz +0 -397
- package/src/util/data-converter.lzz +0 -17
- package/src/util/data.lzz +0 -145
- package/src/util/macros.lzz +0 -159
- package/src/util/query-macros.lzz +0 -71
- package/test/00.setup.js +0 -25
- package/test/01.sqlite-error.js +0 -27
- package/test/10.database.open.js +0 -159
- package/test/11.database.close.js +0 -68
- package/test/12.database.pragma.js +0 -65
- package/test/13.database.prepare.js +0 -60
- package/test/14.database.exec.js +0 -46
- package/test/20.statement.run.js +0 -170
- package/test/21.statement.get.js +0 -109
- package/test/22.statement.all.js +0 -129
- package/test/23.statement.iterate.js +0 -223
- package/test/24.statement.bind.js +0 -107
- package/test/25.statement.columns.js +0 -46
- package/test/30.database.transaction.js +0 -157
- package/test/31.database.checkpoint.js +0 -62
- package/test/32.database.function.js +0 -211
- package/test/33.database.aggregate.js +0 -603
- package/test/34.database.table.js +0 -671
- package/test/35.database.load-extension.js +0 -75
- package/test/36.database.backup.js +0 -240
- package/test/37.database.serialize.js +0 -81
- package/test/40.bigints.js +0 -145
- package/test/41.at-exit.js +0 -52
- package/test/42.integrity.js +0 -531
- package/test/43.verbose.js +0 -100
- package/test/44.worker-threads.js +0 -66
- package/test/45.unsafe-mode.js +0 -52
- package/test/46.encryption.js +0 -31
- package/test/50.misc.js +0 -44
|
@@ -1,603 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
const Database = require('../.');
|
|
3
|
-
|
|
4
|
-
describe('Database#aggregate()', function () {
|
|
5
|
-
beforeEach(function () {
|
|
6
|
-
this.db = new Database(util.next());
|
|
7
|
-
this.db.prepare('CREATE TABLE empty (_)').run();
|
|
8
|
-
this.db.prepare('CREATE TABLE ints (_)').run();
|
|
9
|
-
this.db.prepare('CREATE TABLE texts (_)').run();
|
|
10
|
-
this.db.prepare('INSERT INTO ints VALUES (?), (?), (?), (?), (?), (?), (?)').run(3, 5, 7, 11, 13, 17, 19);
|
|
11
|
-
this.db.prepare('INSERT INTO texts VALUES (?), (?), (?), (?), (?), (?), (?)').run('a', 'b', 'c', 'd', 'e', 'f', 'g');
|
|
12
|
-
this.get = (SQL, ...args) => this.db.prepare(`SELECT ${SQL}`).pluck().get(args);
|
|
13
|
-
this.all = (SQL, ...args) => this.db.prepare(`SELECT ${SQL} WINDOW win AS (ORDER BY rowid ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) ORDER BY rowid`).pluck().all(args);
|
|
14
|
-
});
|
|
15
|
-
afterEach(function () {
|
|
16
|
-
this.db.close();
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it('should throw an exception if the correct arguments are not provided', function () {
|
|
20
|
-
expect(() => this.db.aggregate()).to.throw(TypeError);
|
|
21
|
-
expect(() => this.db.aggregate(null)).to.throw(TypeError);
|
|
22
|
-
expect(() => this.db.aggregate('a')).to.throw(TypeError);
|
|
23
|
-
expect(() => this.db.aggregate({})).to.throw(TypeError);
|
|
24
|
-
expect(() => this.db.aggregate({ step: () => {} })).to.throw(TypeError);
|
|
25
|
-
expect(() => this.db.aggregate({ name: 'b', step: function b() {} })).to.throw(TypeError);
|
|
26
|
-
expect(() => this.db.aggregate(() => {})).to.throw(TypeError);
|
|
27
|
-
expect(() => this.db.aggregate(function c() {})).to.throw(TypeError);
|
|
28
|
-
expect(() => this.db.aggregate({}, function d() {})).to.throw(TypeError);
|
|
29
|
-
expect(() => this.db.aggregate({ name: 'e', step: function e() {} }, function e() {})).to.throw(TypeError);
|
|
30
|
-
expect(() => this.db.aggregate('f')).to.throw(TypeError);
|
|
31
|
-
expect(() => this.db.aggregate('g', null)).to.throw(TypeError);
|
|
32
|
-
expect(() => this.db.aggregate('h', {})).to.throw(TypeError);
|
|
33
|
-
expect(() => this.db.aggregate('i', function i() {})).to.throw(TypeError);
|
|
34
|
-
expect(() => this.db.aggregate('j', {}, function j() {})).to.throw(TypeError);
|
|
35
|
-
expect(() => this.db.aggregate('k', { name: 'k' }, function k() {})).to.throw(TypeError);
|
|
36
|
-
expect(() => this.db.aggregate('l', { inverse: function l() {} })).to.throw(TypeError);
|
|
37
|
-
expect(() => this.db.aggregate('m', { result: function m() {} })).to.throw(TypeError);
|
|
38
|
-
expect(() => this.db.aggregate(new String('n'), { step: function n() {} })).to.throw(TypeError);
|
|
39
|
-
});
|
|
40
|
-
it('should throw an exception if boolean options are provided as non-booleans', function () {
|
|
41
|
-
expect(() => this.db.aggregate('a', { step: () => {}, varargs: undefined })).to.throw(TypeError);
|
|
42
|
-
expect(() => this.db.aggregate('b', { step: () => {}, deterministic: undefined })).to.throw(TypeError);
|
|
43
|
-
expect(() => this.db.aggregate('b', { step: () => {}, directOnly: undefined })).to.throw(TypeError);
|
|
44
|
-
expect(() => this.db.aggregate('c', { step: () => {}, safeIntegers: undefined })).to.throw(TypeError);
|
|
45
|
-
});
|
|
46
|
-
it('should throw an exception if function options are provided as non-fns', function () {
|
|
47
|
-
expect(() => this.db.aggregate('a', { step: undefined })).to.throw(TypeError);
|
|
48
|
-
expect(() => this.db.aggregate('b', { step: null })).to.throw(TypeError);
|
|
49
|
-
expect(() => this.db.aggregate('c', { step: false })).to.throw(TypeError);
|
|
50
|
-
expect(() => this.db.aggregate('d', { step: true })).to.throw(TypeError);
|
|
51
|
-
expect(() => this.db.aggregate('e', { step: Object.create(Function.prototype) })).to.throw(TypeError);
|
|
52
|
-
expect(() => this.db.aggregate('f', { step: () => {}, inverse: false })).to.throw(TypeError);
|
|
53
|
-
expect(() => this.db.aggregate('g', { step: () => {}, inverse: true })).to.throw(TypeError);
|
|
54
|
-
expect(() => this.db.aggregate('h', { step: () => {}, inverse: Object.create(Function.prototype) })).to.throw(TypeError);
|
|
55
|
-
expect(() => this.db.aggregate('i', { step: () => {}, result: false })).to.throw(TypeError);
|
|
56
|
-
expect(() => this.db.aggregate('j', { step: () => {}, result: true })).to.throw(TypeError);
|
|
57
|
-
expect(() => this.db.aggregate('k', { step: () => {}, result: Object.create(Function.prototype) })).to.throw(TypeError);
|
|
58
|
-
});
|
|
59
|
-
it('should throw an exception if the provided name is empty', function () {
|
|
60
|
-
expect(() => this.db.aggregate('', { step: () => {} })).to.throw(TypeError);
|
|
61
|
-
expect(() => this.db.aggregate('', { name: 'a', step: () => {} })).to.throw(TypeError);
|
|
62
|
-
expect(() => this.db.aggregate('', { name: 'b', step: function b() {} })).to.throw(TypeError);
|
|
63
|
-
});
|
|
64
|
-
it('should throw an exception if step.length or inverse.length is invalid', function () {
|
|
65
|
-
const length = x => Object.defineProperty(() => {}, 'length', { value: x });
|
|
66
|
-
expect(() => this.db.aggregate('a', { step: length(undefined) })).to.throw(TypeError);
|
|
67
|
-
expect(() => this.db.aggregate('b', { step: length(null) })).to.throw(TypeError);
|
|
68
|
-
expect(() => this.db.aggregate('c', { step: length('2') })).to.throw(TypeError);
|
|
69
|
-
expect(() => this.db.aggregate('d', { step: length(NaN) })).to.throw(TypeError);
|
|
70
|
-
expect(() => this.db.aggregate('e', { step: length(Infinity) })).to.throw(TypeError);
|
|
71
|
-
expect(() => this.db.aggregate('f', { step: length(2.000000001) })).to.throw(TypeError);
|
|
72
|
-
expect(() => this.db.aggregate('g', { step: length(-0.000000001) })).to.throw(TypeError);
|
|
73
|
-
expect(() => this.db.aggregate('h', { step: length(-2) })).to.throw(TypeError);
|
|
74
|
-
expect(() => this.db.aggregate('i', { step: length(100.000000001) })).to.throw(TypeError);
|
|
75
|
-
expect(() => this.db.aggregate('j', { step: length(102) })).to.throw(RangeError);
|
|
76
|
-
expect(() => this.db.aggregate('aa', { step: () => {}, inverse: length(undefined) })).to.throw(TypeError);
|
|
77
|
-
expect(() => this.db.aggregate('bb', { step: () => {}, inverse: length(null) })).to.throw(TypeError);
|
|
78
|
-
expect(() => this.db.aggregate('cc', { step: () => {}, inverse: length('2') })).to.throw(TypeError);
|
|
79
|
-
expect(() => this.db.aggregate('dd', { step: () => {}, inverse: length(NaN) })).to.throw(TypeError);
|
|
80
|
-
expect(() => this.db.aggregate('ee', { step: () => {}, inverse: length(Infinity) })).to.throw(TypeError);
|
|
81
|
-
expect(() => this.db.aggregate('ff', { step: () => {}, inverse: length(2.000000001) })).to.throw(TypeError);
|
|
82
|
-
expect(() => this.db.aggregate('gg', { step: () => {}, inverse: length(-0.000000001) })).to.throw(TypeError);
|
|
83
|
-
expect(() => this.db.aggregate('hh', { step: () => {}, inverse: length(-2) })).to.throw(TypeError);
|
|
84
|
-
expect(() => this.db.aggregate('ii', { step: () => {}, inverse: length(100.000000001) })).to.throw(TypeError);
|
|
85
|
-
expect(() => this.db.aggregate('jj', { step: () => {}, inverse: length(102) })).to.throw(RangeError);
|
|
86
|
-
});
|
|
87
|
-
it('should register an aggregate function and return the database object', function () {
|
|
88
|
-
const length = x => Object.defineProperty(() => {}, 'length', { value: x });
|
|
89
|
-
expect(this.db.aggregate('a', { step: () => {} })).to.equal(this.db);
|
|
90
|
-
expect(this.db.aggregate('b', { step: function x() {} })).to.equal(this.db);
|
|
91
|
-
expect(this.db.aggregate('c', { step: length(1) })).to.equal(this.db);
|
|
92
|
-
expect(this.db.aggregate('d', { step: length(101) })).to.equal(this.db);
|
|
93
|
-
});
|
|
94
|
-
it('should enable the registered aggregate function to be executed from SQL', function () {
|
|
95
|
-
// numbers
|
|
96
|
-
this.db.aggregate('a', { step: (ctx, a, b) => a * b + ctx });
|
|
97
|
-
expect(this.get('a(_, ?) FROM ints', 2)).to.equal(150);
|
|
98
|
-
|
|
99
|
-
// strings
|
|
100
|
-
this.db.aggregate('b', { step: (ctx, a, b) => a + b + ctx });
|
|
101
|
-
expect(this.get('b(_, ?) FROM texts', '!')).to.equal('g!f!e!d!c!b!a!null');
|
|
102
|
-
|
|
103
|
-
// starting value is null
|
|
104
|
-
this.db.aggregate('c', { step: (ctx, x) => null });
|
|
105
|
-
this.db.aggregate('d', { step: (ctx, x) => ctx });
|
|
106
|
-
this.db.aggregate('e', { step: (ctx, x) => {} });
|
|
107
|
-
expect(this.get('c(_) FROM ints')).to.equal(null);
|
|
108
|
-
expect(this.get('d(_) FROM ints')).to.equal(null);
|
|
109
|
-
expect(this.get('e(_) FROM ints')).to.equal(null);
|
|
110
|
-
|
|
111
|
-
// buffers
|
|
112
|
-
this.db.aggregate('f', { step: (ctx, x) => x });
|
|
113
|
-
const input = Buffer.alloc(8).fill(0xdd);
|
|
114
|
-
const output = this.get('f(?)', input);
|
|
115
|
-
expect(input).to.not.equal(output);
|
|
116
|
-
expect(input.equals(output)).to.be.true;
|
|
117
|
-
expect(output.equals(Buffer.alloc(8).fill(0xdd))).to.be.true;
|
|
118
|
-
|
|
119
|
-
// zero arguments
|
|
120
|
-
this.db.aggregate('g', { step: (ctx) => 'z' + ctx });
|
|
121
|
-
this.db.aggregate('h', { step: (ctx) => 12 });
|
|
122
|
-
this.db.aggregate('i', { step: () => 44 });
|
|
123
|
-
expect(this.get('g()')).to.equal('znull');
|
|
124
|
-
expect(this.get('h()')).to.equal(12);
|
|
125
|
-
expect(this.get('i()')).to.equal(44);
|
|
126
|
-
expect(this.get('g() FROM empty')).to.equal(null);
|
|
127
|
-
expect(this.get('h() FROM empty')).to.equal(null);
|
|
128
|
-
expect(this.get('i() FROM empty')).to.equal(null);
|
|
129
|
-
expect(this.get('g() FROM ints')).to.equal('zzzzzzznull');
|
|
130
|
-
expect(this.get('h() FROM ints')).to.equal(12);
|
|
131
|
-
expect(this.get('i() FROM ints')).to.equal(44);
|
|
132
|
-
expect(this.get('g(*) FROM ints')).to.equal('zzzzzzznull');
|
|
133
|
-
expect(this.get('h(*) FROM ints')).to.equal(12);
|
|
134
|
-
expect(this.get('i(*) FROM ints')).to.equal(44);
|
|
135
|
-
});
|
|
136
|
-
it('should use a strict number of arguments by default', function () {
|
|
137
|
-
this.db.aggregate('agg', { step: (ctx, a, b) => {} });
|
|
138
|
-
expect(() => this.get('agg()')).to.throw(Database.SqliteError).with.property('code', 'SQLITE_ERROR');
|
|
139
|
-
expect(() => this.get('agg(?)', 4)).to.throw(Database.SqliteError).with.property('code', 'SQLITE_ERROR');
|
|
140
|
-
expect(() => this.get('agg(?, ?, ?)', 4, 8, 3)).to.throw(Database.SqliteError).with.property('code', 'SQLITE_ERROR');
|
|
141
|
-
this.get('agg(?, ?)', 4, 8);
|
|
142
|
-
});
|
|
143
|
-
it('should accept a "varargs" option', function () {
|
|
144
|
-
const step = (ctx, ...args) => args.reduce((a, b) => a * b, 1) + ctx;
|
|
145
|
-
Object.defineProperty(step, 'length', { value: '-2' });
|
|
146
|
-
this.db.aggregate('agg', { varargs: true, step });
|
|
147
|
-
expect(this.get('agg()')).to.equal(1);
|
|
148
|
-
expect(this.get('agg(?)', 7)).to.equal(7);
|
|
149
|
-
expect(this.get('agg(?, ?)', 4, 8)).to.equal(32);
|
|
150
|
-
expect(this.get('agg(?, ?, ?, ?, ?, ?)', 2, 3, 4, 5, 6, 7)).to.equal(5040);
|
|
151
|
-
});
|
|
152
|
-
it('should accept an optional start value', function () {
|
|
153
|
-
this.db.aggregate('a', { start: 10000, step: (ctx, a, b) => a * b + ++ctx });
|
|
154
|
-
expect(this.get('a(_, ?) FROM ints', 2)).to.equal(10157);
|
|
155
|
-
expect(this.get('a(_, ?) FROM ints', 2)).to.equal(10157);
|
|
156
|
-
|
|
157
|
-
this.db.aggregate('b', { start: { foo: 1000 }, step: (ctx, a, b) => a * b + (ctx.foo ? ++ctx.foo : ++ctx) });
|
|
158
|
-
expect(this.get('b(_, ?) FROM ints', 2)).to.equal(1157);
|
|
159
|
-
expect(this.get('b(_, ?) FROM ints', 2)).to.equal(1158);
|
|
160
|
-
|
|
161
|
-
let ranOnce = false;
|
|
162
|
-
this.db.aggregate('c', { start: undefined, step: (ctx, a, b) => {
|
|
163
|
-
if (ranOnce) expect(ctx).to.be.NaN;
|
|
164
|
-
else expect(ctx).to.be.undefined;
|
|
165
|
-
ranOnce = true;
|
|
166
|
-
return a * b + ++ctx;
|
|
167
|
-
} });
|
|
168
|
-
expect(this.get('c(_, ?) FROM ints', 2)).to.equal(null);
|
|
169
|
-
expect(ranOnce).to.be.true;
|
|
170
|
-
ranOnce = false;
|
|
171
|
-
expect(this.get('c(_, ?) FROM ints', 2)).to.equal(null);
|
|
172
|
-
expect(ranOnce).to.be.true;
|
|
173
|
-
});
|
|
174
|
-
it('should accept an optional start() function', function () {
|
|
175
|
-
let start = 10000;
|
|
176
|
-
|
|
177
|
-
this.db.aggregate('a', { start: () => start++, step: (ctx, a, b) => a * b + ctx });
|
|
178
|
-
expect(this.get('a(_, ?) FROM ints', 2)).to.equal(10150);
|
|
179
|
-
expect(this.get('a(_, ?) FROM ints', 2)).to.equal(10151);
|
|
180
|
-
expect(this.get('a(_, ?) FROM ints', 2)).to.equal(10152);
|
|
181
|
-
|
|
182
|
-
this.db.aggregate('b', { start: () => ({ foo: start-- }), step: (ctx, a, b) => a * b + (ctx.foo || ctx) });
|
|
183
|
-
expect(this.get('b(_, ?) FROM ints', 2)).to.equal(10153);
|
|
184
|
-
expect(this.get('b(_, ?) FROM ints', 2)).to.equal(10152);
|
|
185
|
-
expect(this.get('b(_, ?) FROM ints', 2)).to.equal(10151);
|
|
186
|
-
|
|
187
|
-
let ranOnce = false;
|
|
188
|
-
this.db.aggregate('c', { start: () => undefined, step: (ctx, a, b) => {
|
|
189
|
-
if (ranOnce) expect(ctx).to.be.NaN;
|
|
190
|
-
else expect(ctx).to.be.undefined;
|
|
191
|
-
ranOnce = true;
|
|
192
|
-
return a * b + ++ctx;
|
|
193
|
-
} });
|
|
194
|
-
expect(this.get('c(_, ?) FROM ints', 2)).to.equal(null);
|
|
195
|
-
expect(ranOnce).to.be.true;
|
|
196
|
-
ranOnce = false;
|
|
197
|
-
expect(this.get('c(_, ?) FROM ints', 2)).to.equal(null);
|
|
198
|
-
expect(ranOnce).to.be.true;
|
|
199
|
-
});
|
|
200
|
-
it('should not change the aggregate value when step() returns undefined', function () {
|
|
201
|
-
this.db.aggregate('a', { start: 10000, step: (ctx, a, b) => a === 11 ? undefined : a * b + ctx });
|
|
202
|
-
expect(this.get('a(_, ?) FROM ints', 2)).to.equal(10128);
|
|
203
|
-
this.db.aggregate('b', { start: () => 1000, step: (ctx, a, b) => {} });
|
|
204
|
-
expect(this.get('b(_, ?) FROM ints', 2)).to.equal(1000);
|
|
205
|
-
this.db.aggregate('c', { start: () => 1000, step: (ctx, a, b) => null });
|
|
206
|
-
expect(this.get('c(_, ?) FROM ints', 2)).to.equal(null);
|
|
207
|
-
});
|
|
208
|
-
it('should accept a result() transformer function', function () {
|
|
209
|
-
this.db.aggregate('a', {
|
|
210
|
-
start: 10000,
|
|
211
|
-
step: (ctx, a, b) => a * b + ctx,
|
|
212
|
-
result: ctx => ctx / 2,
|
|
213
|
-
});
|
|
214
|
-
expect(this.get('a(_, ?) FROM ints', 2)).to.equal(5075);
|
|
215
|
-
this.db.aggregate('b', {
|
|
216
|
-
start: () => ({ foo: 1000 }),
|
|
217
|
-
step: (ctx, a, b) => { ctx.foo += a * b; },
|
|
218
|
-
result: ctx => ctx.foo,
|
|
219
|
-
});
|
|
220
|
-
expect(this.get('b(_, ?) FROM ints', 2)).to.equal(1150);
|
|
221
|
-
expect(this.get('b(_, ?) FROM ints', 2)).to.equal(1150); // should play well when ran multiple times
|
|
222
|
-
this.db.aggregate('c', {
|
|
223
|
-
start: () => ({ foo: 1000 }),
|
|
224
|
-
step: (ctx, a, b) => { ctx.foo += 1; },
|
|
225
|
-
result: ctx => ctx.foo,
|
|
226
|
-
});
|
|
227
|
-
expect(this.get('c(_, ?) FROM empty', 2)).to.equal(1000);
|
|
228
|
-
});
|
|
229
|
-
it('should interpret undefined as null within a result() function', function () {
|
|
230
|
-
this.db.aggregate('agg', {
|
|
231
|
-
start: 10000,
|
|
232
|
-
step: (ctx, a, b) => a * b + ctx,
|
|
233
|
-
result: () => {},
|
|
234
|
-
});
|
|
235
|
-
expect(this.get('agg(_, ?) FROM ints', 2)).to.equal(null);
|
|
236
|
-
});
|
|
237
|
-
it('should accept an inverse() function to support aggregate window functions', function () {
|
|
238
|
-
this.db.aggregate('agg', {
|
|
239
|
-
start: () => 10000,
|
|
240
|
-
step: (ctx, a, b) => a * b + ctx,
|
|
241
|
-
});
|
|
242
|
-
expect(() => this.all('agg(_, ?) OVER win FROM ints', 2))
|
|
243
|
-
.to.throw(Database.SqliteError).with.property('code', 'SQLITE_ERROR');
|
|
244
|
-
this.db.aggregate('wn', {
|
|
245
|
-
start: () => 10000,
|
|
246
|
-
step: (ctx, a, b) => a * b + ctx,
|
|
247
|
-
inverse: (ctx, a, b) => ctx - a * b,
|
|
248
|
-
});
|
|
249
|
-
expect(this.all('wn(_, ?) OVER win FROM ints', 2))
|
|
250
|
-
.to.deep.equal([10016, 10030, 10046, 10062, 10082, 10098, 10072]);
|
|
251
|
-
});
|
|
252
|
-
it('should not change the aggregate value when inverse() returns undefined', function () {
|
|
253
|
-
this.db.aggregate('a', {
|
|
254
|
-
start: () => 10000,
|
|
255
|
-
step: (ctx, a, b) => a * b + ctx,
|
|
256
|
-
inverse: (ctx, a, b) => a === 11 ? undefined : ctx - a * b,
|
|
257
|
-
});
|
|
258
|
-
expect(this.all('a(_, ?) OVER win FROM ints', 2))
|
|
259
|
-
.to.deep.equal([10016, 10030, 10046, 10062, 10082, 10120, 10094]);
|
|
260
|
-
this.db.aggregate('b', {
|
|
261
|
-
start: () => 10000,
|
|
262
|
-
step: (ctx, a, b) => ctx ? a * b + ctx : null,
|
|
263
|
-
inverse: (ctx, a, b) => null,
|
|
264
|
-
});
|
|
265
|
-
expect(this.all('b(_, ?) OVER win FROM ints', 2))
|
|
266
|
-
.to.deep.equal([10016, 10030, null, null, null, null, null]);
|
|
267
|
-
});
|
|
268
|
-
it('should potentially call result() multiple times for window functions', function () {
|
|
269
|
-
let startCount = 0;
|
|
270
|
-
let stepCount = 0;
|
|
271
|
-
let inverseCount = 0;
|
|
272
|
-
let resultCount = 0;
|
|
273
|
-
this.db.aggregate('wn', {
|
|
274
|
-
start: () => {
|
|
275
|
-
startCount += 1;
|
|
276
|
-
return { foo: 1000, results: 0 };
|
|
277
|
-
},
|
|
278
|
-
step: (ctx, a, b) => {
|
|
279
|
-
stepCount += 1;
|
|
280
|
-
ctx.foo += a * b;
|
|
281
|
-
},
|
|
282
|
-
inverse: (ctx, a, b) => {
|
|
283
|
-
inverseCount += 1;
|
|
284
|
-
ctx.foo -= a * b;
|
|
285
|
-
},
|
|
286
|
-
result: (ctx) => {
|
|
287
|
-
resultCount += 1;
|
|
288
|
-
return ctx.foo + ctx.results++ * 10000;
|
|
289
|
-
},
|
|
290
|
-
});
|
|
291
|
-
expect(this.all('wn(_, ?) OVER win FROM ints', 2))
|
|
292
|
-
.to.deep.equal([1016, 11030, 21046, 31062, 41082, 51098, 61072]);
|
|
293
|
-
expect(startCount).to.equal(1);
|
|
294
|
-
expect(stepCount).to.equal(7);
|
|
295
|
-
expect(inverseCount).to.equal(5);
|
|
296
|
-
expect(resultCount).to.equal(7);
|
|
297
|
-
expect(this.all('wn(_, ?) OVER win FROM ints', 2)) // should play well when ran multiple times
|
|
298
|
-
.to.deep.equal([1016, 11030, 21046, 31062, 41082, 51098, 61072]);
|
|
299
|
-
expect(startCount).to.equal(2);
|
|
300
|
-
expect(stepCount).to.equal(14);
|
|
301
|
-
expect(inverseCount).to.equal(10);
|
|
302
|
-
expect(resultCount).to.equal(14);
|
|
303
|
-
expect(this.all('wn(_, ?) OVER win FROM empty', 2))
|
|
304
|
-
.to.deep.equal([]);
|
|
305
|
-
expect(startCount).to.equal(2);
|
|
306
|
-
expect(stepCount).to.equal(14);
|
|
307
|
-
expect(inverseCount).to.equal(10);
|
|
308
|
-
expect(resultCount).to.equal(14);
|
|
309
|
-
});
|
|
310
|
-
it('should infer argument count from the greater of step() and inverse()', function () {
|
|
311
|
-
this.db.aggregate('a', {
|
|
312
|
-
start: () => 10000,
|
|
313
|
-
step: (ctx, a) => a + ctx,
|
|
314
|
-
inverse: (ctx, a, b) => ctx - a,
|
|
315
|
-
});
|
|
316
|
-
expect(this.all('a(_, ?) OVER win FROM ints', 2))
|
|
317
|
-
.to.deep.equal([10008, 10015, 10023, 10031, 10041, 10049, 10036]);
|
|
318
|
-
expect(() => this.all('a(_) OVER win FROM ints'))
|
|
319
|
-
.to.throw(Database.SqliteError).with.property('code', 'SQLITE_ERROR');
|
|
320
|
-
this.db.aggregate('b', {
|
|
321
|
-
start: () => 10000,
|
|
322
|
-
step: (ctx, a, b) => a + ctx,
|
|
323
|
-
inverse: (ctx, a) => ctx - a,
|
|
324
|
-
});
|
|
325
|
-
expect(this.all('b(_, ?) OVER win FROM ints', 2))
|
|
326
|
-
.to.deep.equal([10008, 10015, 10023, 10031, 10041, 10049, 10036]);
|
|
327
|
-
expect(() => this.all('b(_) OVER win FROM ints'))
|
|
328
|
-
.to.throw(Database.SqliteError).with.property('code', 'SQLITE_ERROR');
|
|
329
|
-
this.db.aggregate('c', {
|
|
330
|
-
start: (a, b, c, d, e) => 10000,
|
|
331
|
-
step: () => {},
|
|
332
|
-
inverse: (ctx, a) => --ctx,
|
|
333
|
-
result: (ctx, a, b, c, d, e) => ctx,
|
|
334
|
-
});
|
|
335
|
-
expect(this.all('c(_) OVER win FROM ints'))
|
|
336
|
-
.to.deep.equal([10000, 10000, 9999, 9998, 9997, 9996, 9995]);
|
|
337
|
-
expect(() => this.all('c() OVER win FROM ints'))
|
|
338
|
-
.to.throw(Database.SqliteError).with.property('code', 'SQLITE_ERROR');
|
|
339
|
-
expect(() => this.all('c(*) OVER win FROM ints'))
|
|
340
|
-
.to.throw(Database.SqliteError).with.property('code', 'SQLITE_ERROR');
|
|
341
|
-
expect(() => this.all('c(_, ?) OVER win FROM ints', 2))
|
|
342
|
-
.to.throw(Database.SqliteError).with.property('code', 'SQLITE_ERROR');
|
|
343
|
-
});
|
|
344
|
-
it('should throw an exception if the database is busy', function () {
|
|
345
|
-
let ranOnce = false;
|
|
346
|
-
for (const x of this.db.prepare('SELECT 2').pluck().iterate()) {
|
|
347
|
-
expect(x).to.equal(2);
|
|
348
|
-
ranOnce = true;
|
|
349
|
-
expect(() => this.db.aggregate('a', { step: () => {} })).to.throw(TypeError);
|
|
350
|
-
}
|
|
351
|
-
expect(ranOnce).to.be.true;
|
|
352
|
-
this.db.aggregate('b', { step: () => {} });
|
|
353
|
-
});
|
|
354
|
-
it('should cause the database to become busy when executing the aggregate', function () {
|
|
355
|
-
let checkCount = 0;
|
|
356
|
-
const expectBusy = () => {
|
|
357
|
-
expect(() => this.db.exec('SELECT a()')).to.throw(TypeError);
|
|
358
|
-
expect(() => this.db.prepare('SELECT 555')).to.throw(TypeError);
|
|
359
|
-
expect(() => this.db.pragma('cache_size')).to.throw(TypeError);
|
|
360
|
-
expect(() => this.db.function('x', () => {})).to.throw(TypeError);
|
|
361
|
-
expect(() => this.db.aggregate('y', { step: () => {} })).to.throw(TypeError);
|
|
362
|
-
checkCount += 1;
|
|
363
|
-
};
|
|
364
|
-
this.db.aggregate('a', { step: () => {} });
|
|
365
|
-
this.db.aggregate('b', { start: expectBusy, step: expectBusy, inverse: expectBusy, result: expectBusy });
|
|
366
|
-
|
|
367
|
-
expect(this.all('b(*) OVER win FROM ints')).to.deep.equal([null, null, null, null, null, null, null]);
|
|
368
|
-
expect(checkCount).to.equal(20);
|
|
369
|
-
checkCount = 0;
|
|
370
|
-
|
|
371
|
-
expect(this.db.exec('SELECT b(*) OVER win FROM ints WINDOW win AS (ORDER BY rowid ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) ORDER BY rowid')).to.equal(this.db);
|
|
372
|
-
expect(checkCount).to.equal(20);
|
|
373
|
-
|
|
374
|
-
this.db.exec('SELECT a()');
|
|
375
|
-
this.db.prepare('SELECT 555');
|
|
376
|
-
this.db.pragma('cache_size');
|
|
377
|
-
this.db.function('xx', () => {});
|
|
378
|
-
this.db.aggregate('yy', { step: () => {} });
|
|
379
|
-
});
|
|
380
|
-
it('should cause the aggregate to throw when returning an invalid value', function () {
|
|
381
|
-
this.db.aggregate('a', {
|
|
382
|
-
start: () => ({}),
|
|
383
|
-
step: () => ({}),
|
|
384
|
-
inverse: () => ({}),
|
|
385
|
-
result: () => 42,
|
|
386
|
-
});
|
|
387
|
-
this.db.aggregate('b', {
|
|
388
|
-
start: () => 42,
|
|
389
|
-
step: () => 42,
|
|
390
|
-
inverse: () => 42,
|
|
391
|
-
result: () => ({}),
|
|
392
|
-
});
|
|
393
|
-
this.db.aggregate('c', {
|
|
394
|
-
step: () => {},
|
|
395
|
-
result: () => 42,
|
|
396
|
-
});
|
|
397
|
-
this.db.aggregate('d', {
|
|
398
|
-
step: () => {},
|
|
399
|
-
result: () => ({}),
|
|
400
|
-
});
|
|
401
|
-
expect(this.all('a(*) OVER win FROM ints')).to.deep.equal([42, 42, 42, 42, 42, 42, 42]);
|
|
402
|
-
expect(() => this.all('b(*) OVER win FROM ints')).to.throw(TypeError);
|
|
403
|
-
expect(this.get('c(*) FROM ints')).to.equal(42);
|
|
404
|
-
expect(this.get('c(*) FROM empty')).to.equal(42);
|
|
405
|
-
expect(() => this.get('d(*) FROM ints')).to.throw(TypeError);
|
|
406
|
-
expect(() => this.get('d(*) FROM empty')).to.throw(TypeError);
|
|
407
|
-
});
|
|
408
|
-
it('should close a statement iterator that caused its aggregate to throw', function () {
|
|
409
|
-
this.db.prepare('CREATE TABLE iterable (value INTEGER)').run();
|
|
410
|
-
this.db.prepare('INSERT INTO iterable WITH RECURSIVE temp(x) AS (SELECT 1 UNION ALL SELECT x * 2 FROM temp LIMIT 10) SELECT * FROM temp').run();
|
|
411
|
-
|
|
412
|
-
let i = 0;
|
|
413
|
-
const err = new Error('foo');
|
|
414
|
-
this.db.aggregate('wn', {
|
|
415
|
-
step: (ctx, x) => { if (++i >= 5) throw err; return x; },
|
|
416
|
-
inverse: () => {},
|
|
417
|
-
});
|
|
418
|
-
const iterator = this.db.prepare('SELECT wn(value) OVER (ROWS CURRENT ROW) FROM iterable').pluck().iterate();
|
|
419
|
-
|
|
420
|
-
let total = 0;
|
|
421
|
-
expect(() => {
|
|
422
|
-
for (const value of iterator) {
|
|
423
|
-
total += value;
|
|
424
|
-
expect(() => this.db.exec('SELECT wn(value) OVER (ROWS CURRENT ROW) FROM iterable LIMIT 4')).to.throw(TypeError);
|
|
425
|
-
}
|
|
426
|
-
}).to.throw(err);
|
|
427
|
-
|
|
428
|
-
expect(total).to.equal(1 + 2 + 4 + 8);
|
|
429
|
-
expect(iterator.next()).to.deep.equal({ value: undefined, done: true });
|
|
430
|
-
expect(total).to.equal(1 + 2 + 4 + 8);
|
|
431
|
-
|
|
432
|
-
i = 0;
|
|
433
|
-
this.db.exec('SELECT wn(value) OVER (ROWS CURRENT ROW) FROM iterable LIMIT 4');
|
|
434
|
-
expect(i).to.equal(4);
|
|
435
|
-
});
|
|
436
|
-
it('should be able to register multiple aggregates with the same name', function () {
|
|
437
|
-
this.db.aggregate('agg', { step: (ctx) => 0 });
|
|
438
|
-
this.db.aggregate('agg', { step: (ctx, a) => 1 });
|
|
439
|
-
this.db.aggregate('agg', { step: (ctx, a, b) => 2 });
|
|
440
|
-
this.db.aggregate('agg', { step: (ctx, a, b, c) => 3, inverse: () => {} });
|
|
441
|
-
this.db.aggregate('agg', { step: (ctx, a, b, c, d) => 4 });
|
|
442
|
-
expect(this.get('agg()')).to.equal(0);
|
|
443
|
-
expect(this.get('agg(555)')).to.equal(1);
|
|
444
|
-
expect(this.get('agg(555, 555)')).to.equal(2);
|
|
445
|
-
expect(this.get('agg(555, 555, 555)')).to.equal(3);
|
|
446
|
-
expect(this.get('agg(555, 555, 555, 555)')).to.equal(4);
|
|
447
|
-
this.db.aggregate('agg', { step: (ctx, a, b) => 'foo', inverse: () => {} });
|
|
448
|
-
this.db.aggregate('agg', { step: (ctx, a, b, c) => 'bar' });
|
|
449
|
-
expect(this.get('agg()')).to.equal(0);
|
|
450
|
-
expect(this.get('agg(555)')).to.equal(1);
|
|
451
|
-
expect(this.get('agg(555, 555)')).to.equal('foo');
|
|
452
|
-
expect(this.get('agg(555, 555, 555)')).to.equal('bar');
|
|
453
|
-
expect(this.get('agg(555, 555, 555, 555)')).to.equal(4);
|
|
454
|
-
});
|
|
455
|
-
it('should not be able to affect bound buffers mid-query', function () {
|
|
456
|
-
const input = Buffer.alloc(1024 * 8).fill(0xbb);
|
|
457
|
-
let startCalled = false;
|
|
458
|
-
let stepCalled = false;
|
|
459
|
-
this.db.aggregate('agg', {
|
|
460
|
-
start: () => {
|
|
461
|
-
startCalled = true;
|
|
462
|
-
input[0] = 2;
|
|
463
|
-
},
|
|
464
|
-
step: () => {
|
|
465
|
-
stepCalled = true;
|
|
466
|
-
input[0] = 2;
|
|
467
|
-
},
|
|
468
|
-
});
|
|
469
|
-
const output = this.get('?, agg(*) FROM ints', input);
|
|
470
|
-
expect(startCalled).to.be.true;
|
|
471
|
-
expect(stepCalled).to.be.true;
|
|
472
|
-
expect(output.equals(Buffer.alloc(1024 * 8).fill(0xbb))).to.be.true;
|
|
473
|
-
});
|
|
474
|
-
describe('should propagate exceptions', function () {
|
|
475
|
-
const exceptions = [new TypeError('foobar'), new Error('baz'), { yup: 'ok' }, 'foobarbazqux', '', null, 123.4];
|
|
476
|
-
const expectError = (exception, fn) => {
|
|
477
|
-
try { fn(); } catch (ex) {
|
|
478
|
-
expect(ex).to.equal(exception);
|
|
479
|
-
return;
|
|
480
|
-
}
|
|
481
|
-
throw new TypeError('Expected aggregate to throw an exception');
|
|
482
|
-
};
|
|
483
|
-
|
|
484
|
-
specify('thrown in the start() function', function () {
|
|
485
|
-
exceptions.forEach((exception, index) => {
|
|
486
|
-
const calls = [];
|
|
487
|
-
this.db.aggregate(`wn${index}`, {
|
|
488
|
-
start: () => { calls.push('a'); throw exception; },
|
|
489
|
-
step: () => { calls.push('b'); },
|
|
490
|
-
inverse: () => { calls.push('c'); },
|
|
491
|
-
result: () => { calls.push('d'); },
|
|
492
|
-
});
|
|
493
|
-
expectError(exception, () => this.get(`wn${index}() FROM empty`));
|
|
494
|
-
expect(calls.splice(0)).to.deep.equal(['a']);
|
|
495
|
-
expectError(exception, () => this.get(`wn${index}() FROM ints`));
|
|
496
|
-
expect(calls.splice(0)).to.deep.equal(['a']);
|
|
497
|
-
expectError(exception, () => this.all(`wn${index}() OVER win FROM ints`));
|
|
498
|
-
expect(calls.splice(0)).to.deep.equal(['a']);
|
|
499
|
-
});
|
|
500
|
-
});
|
|
501
|
-
specify('thrown in the step() function', function () {
|
|
502
|
-
exceptions.forEach((exception, index) => {
|
|
503
|
-
const calls = [];
|
|
504
|
-
this.db.aggregate(`wn${index}`, {
|
|
505
|
-
start: () => { calls.push('a'); },
|
|
506
|
-
step: () => { calls.push('b'); throw exception; },
|
|
507
|
-
inverse: () => { calls.push('c'); },
|
|
508
|
-
result: () => { calls.push('d'); },
|
|
509
|
-
});
|
|
510
|
-
expect(this.get(`wn${index}() FROM empty`)).to.equal(null);
|
|
511
|
-
expect(calls.splice(0)).to.deep.equal(['a', 'd']);
|
|
512
|
-
expectError(exception, () => this.get(`wn${index}() FROM ints`));
|
|
513
|
-
expect(calls.splice(0)).to.deep.equal(['a', 'b']);
|
|
514
|
-
expectError(exception, () => this.all(`wn${index}() OVER win FROM ints`));
|
|
515
|
-
expect(calls.splice(0)).to.deep.equal(['a', 'b']);
|
|
516
|
-
});
|
|
517
|
-
});
|
|
518
|
-
specify('thrown in the inverse() function', function () {
|
|
519
|
-
exceptions.forEach((exception, index) => {
|
|
520
|
-
const calls = [];
|
|
521
|
-
this.db.aggregate(`wn${index}`, {
|
|
522
|
-
start: () => { calls.push('a'); },
|
|
523
|
-
step: () => { calls.push('b'); },
|
|
524
|
-
inverse: () => { calls.push('c'); throw exception; },
|
|
525
|
-
result: () => { calls.push('d'); },
|
|
526
|
-
});
|
|
527
|
-
expect(this.get(`wn${index}() FROM empty`)).to.equal(null);
|
|
528
|
-
expect(calls.splice(0)).to.deep.equal(['a', 'd']);
|
|
529
|
-
expect(this.get(`wn${index}() FROM ints`)).to.equal(null);
|
|
530
|
-
expect(calls.splice(0)).to.deep.equal(['a', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'd']);
|
|
531
|
-
expectError(exception, () => this.all(`wn${index}() OVER win FROM ints`));
|
|
532
|
-
expect(calls.length).to.be.above(2);
|
|
533
|
-
expect(calls.indexOf('c')).to.equal(calls.length - 1);
|
|
534
|
-
});
|
|
535
|
-
});
|
|
536
|
-
specify('thrown in the result() function', function () {
|
|
537
|
-
exceptions.forEach((exception, index) => {
|
|
538
|
-
const calls = [];
|
|
539
|
-
this.db.aggregate(`wn${index}`, {
|
|
540
|
-
start: () => { calls.push('a'); },
|
|
541
|
-
step: () => { calls.push('b'); },
|
|
542
|
-
inverse: () => { calls.push('c'); },
|
|
543
|
-
result: () => { calls.push('d'); throw exception; },
|
|
544
|
-
});
|
|
545
|
-
expectError(exception, () => this.get(`wn${index}() FROM empty`));
|
|
546
|
-
expect(calls.splice(0)).to.deep.equal(['a', 'd']);
|
|
547
|
-
expectError(exception, () => this.get(`wn${index}() FROM ints`));
|
|
548
|
-
expect(calls.splice(0)).to.deep.equal(['a', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'd']);
|
|
549
|
-
expectError(exception, () => this.all(`wn${index}() OVER win FROM ints`));
|
|
550
|
-
expect(calls.splice(0)).to.deep.equal(['a', 'b', 'b', 'd']);
|
|
551
|
-
});
|
|
552
|
-
});
|
|
553
|
-
specify('thrown due to returning an invalid value', function () {
|
|
554
|
-
const calls = [];
|
|
555
|
-
this.db.aggregate('wn', {
|
|
556
|
-
start: () => { calls.push('a'); },
|
|
557
|
-
step: () => { calls.push('b'); },
|
|
558
|
-
inverse: () => { calls.push('c'); },
|
|
559
|
-
result: () => { calls.push('d'); return {}; },
|
|
560
|
-
});
|
|
561
|
-
expect(() => this.get('wn() FROM empty')).to.throw(TypeError);
|
|
562
|
-
expect(calls.splice(0)).to.deep.equal(['a', 'd']);
|
|
563
|
-
expect(() => this.get('wn() FROM ints')).to.throw(TypeError);;
|
|
564
|
-
expect(calls.splice(0)).to.deep.equal(['a', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'd']);
|
|
565
|
-
expect(() => this.all('wn() OVER win FROM ints')).to.throw(TypeError);;
|
|
566
|
-
expect(calls.splice(0)).to.deep.equal(['a', 'b', 'b', 'd']);
|
|
567
|
-
});
|
|
568
|
-
});
|
|
569
|
-
describe('should not affect external environment', function () {
|
|
570
|
-
specify('busy state', function () {
|
|
571
|
-
this.db.aggregate('agg', { step: (ctx, x) => {
|
|
572
|
-
expect(() => this.db.exec('SELECT 555')).to.throw(TypeError);
|
|
573
|
-
return x * 2 + ctx;
|
|
574
|
-
} });
|
|
575
|
-
let ranOnce = false;
|
|
576
|
-
for (const x of this.db.prepare('SELECT agg(555)').pluck().iterate()) {
|
|
577
|
-
ranOnce = true;
|
|
578
|
-
expect(x).to.equal(1110);
|
|
579
|
-
expect(() => this.db.exec('SELECT 555')).to.throw(TypeError);
|
|
580
|
-
}
|
|
581
|
-
expect(ranOnce).to.be.true;
|
|
582
|
-
this.db.exec('SELECT 555');
|
|
583
|
-
});
|
|
584
|
-
specify('was_js_error state', function () {
|
|
585
|
-
this.db.prepare('CREATE TABLE data (value INTEGER)').run();
|
|
586
|
-
const stmt = this.db.prepare('SELECT value FROM data');
|
|
587
|
-
this.db.prepare('DROP TABLE data').run();
|
|
588
|
-
|
|
589
|
-
const err = new Error('foo');
|
|
590
|
-
this.db.aggregate('agg', { step: () => { throw err; } });
|
|
591
|
-
|
|
592
|
-
expect(() => this.db.prepare('SELECT agg()').get()).to.throw(err);
|
|
593
|
-
try { stmt.get(); } catch (ex) {
|
|
594
|
-
expect(ex).to.be.an.instanceof(Error);
|
|
595
|
-
expect(ex).to.not.equal(err);
|
|
596
|
-
expect(ex.message).to.not.equal(err.message);
|
|
597
|
-
expect(ex).to.be.an.instanceof(Database.SqliteError);
|
|
598
|
-
return;
|
|
599
|
-
}
|
|
600
|
-
throw new TypeError('Expected the statement to throw an exception');
|
|
601
|
-
});
|
|
602
|
-
});
|
|
603
|
-
});
|