@objectstack/driver-memory 4.0.4 → 4.1.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 +89 -8
- package/dist/index.d.mts +44 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.js +178 -12
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +178 -12
- package/dist/index.mjs.map +1 -1
- package/package.json +31 -6
- package/.turbo/turbo-build.log +0 -22
- package/CHANGELOG.md +0 -617
- package/objectstack.config.ts +0 -260
- package/src/in-memory-strategy.ts +0 -47
- package/src/index.ts +0 -32
- package/src/memory-analytics.test.ts +0 -346
- package/src/memory-analytics.ts +0 -518
- package/src/memory-driver.test.ts +0 -722
- package/src/memory-driver.ts +0 -1206
- package/src/memory-matcher.ts +0 -177
- package/src/persistence/file-adapter.ts +0 -103
- package/src/persistence/index.ts +0 -4
- package/src/persistence/local-storage-adapter.ts +0 -60
- package/src/persistence/persistence.test.ts +0 -298
- package/tsconfig.json +0 -27
- package/vitest.config.ts +0 -22
|
@@ -1,722 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
-
import { InMemoryDriver } from './memory-driver.js';
|
|
3
|
-
|
|
4
|
-
describe('InMemoryDriver', () => {
|
|
5
|
-
let driver: InMemoryDriver;
|
|
6
|
-
const testTable = 'test_table';
|
|
7
|
-
|
|
8
|
-
beforeEach(async () => {
|
|
9
|
-
driver = new InMemoryDriver({ persistence: false });
|
|
10
|
-
await driver.connect();
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
describe('Lifecycle', () => {
|
|
14
|
-
it('should connect successfully', async () => {
|
|
15
|
-
expect(driver.checkHealth()).resolves.toBe(true);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it('should clear data on disconnect', async () => {
|
|
19
|
-
await driver.create(testTable, { id: '1', name: 'test' });
|
|
20
|
-
await driver.disconnect();
|
|
21
|
-
const results = await driver.find(testTable, { fields: ['id'], object: testTable });
|
|
22
|
-
expect(results).toHaveLength(0);
|
|
23
|
-
});
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
describe('Plugin Installation', () => {
|
|
27
|
-
it('should register driver with engine', () => {
|
|
28
|
-
const registerDriverFn = vi.fn();
|
|
29
|
-
const mockEngine = {
|
|
30
|
-
ql: {
|
|
31
|
-
registerDriver: registerDriverFn
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
driver.install({ engine: mockEngine });
|
|
36
|
-
expect(registerDriverFn).toHaveBeenCalledWith(driver);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('should handle missing engine gracefully', () => {
|
|
40
|
-
const mockCtx = {}; // No engine
|
|
41
|
-
// Should not throw
|
|
42
|
-
driver.install(mockCtx);
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
describe('CRUD Operations', () => {
|
|
47
|
-
it('should create and find records', async () => {
|
|
48
|
-
const data = { id: '1', name: 'Alice', age: 30 };
|
|
49
|
-
const created = await driver.create(testTable, data);
|
|
50
|
-
expect(created.id).toBe('1');
|
|
51
|
-
|
|
52
|
-
const results = await driver.find(testTable, {
|
|
53
|
-
fields: ['id', 'name', 'age'],
|
|
54
|
-
object: testTable
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
expect(results).toHaveLength(1);
|
|
58
|
-
expect(results[0].id).toBe('1');
|
|
59
|
-
expect(results[0].name).toBe('Alice');
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('should support updating records by ID', async () => {
|
|
63
|
-
await driver.create(testTable, { id: '1', name: 'Bob', active: true });
|
|
64
|
-
|
|
65
|
-
const updateResult = await driver.update(
|
|
66
|
-
testTable,
|
|
67
|
-
'1',
|
|
68
|
-
{ active: false }
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
expect(updateResult.active).toBe(false);
|
|
72
|
-
|
|
73
|
-
const results = await driver.find(testTable, { fields: ['active'], object: testTable });
|
|
74
|
-
expect(results[0].active).toBe(false);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it('should support deleting records by ID', async () => {
|
|
78
|
-
await driver.create(testTable, { id: '1', name: 'Charlie' });
|
|
79
|
-
await driver.create(testTable, { id: '2', name: 'David' });
|
|
80
|
-
|
|
81
|
-
const deleteResult = await driver.delete(testTable, '1');
|
|
82
|
-
expect(deleteResult).toBe(true);
|
|
83
|
-
|
|
84
|
-
const results = await driver.find(testTable, { fields: ['name'], object: testTable });
|
|
85
|
-
expect(results).toHaveLength(1);
|
|
86
|
-
expect(results[0].name).toBe('David');
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('should return a copy of created record (immutability)', async () => {
|
|
90
|
-
const created = await driver.create(testTable, { id: '1', name: 'Alice' });
|
|
91
|
-
created.name = 'Modified';
|
|
92
|
-
|
|
93
|
-
const found = await driver.find(testTable, { object: testTable });
|
|
94
|
-
expect(found[0].name).toBe('Alice');
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it('should preserve created_at on update', async () => {
|
|
98
|
-
const created = await driver.create(testTable, { id: '1', name: 'Alice' });
|
|
99
|
-
const originalCreatedAt = created.created_at;
|
|
100
|
-
|
|
101
|
-
const updated = await driver.update(testTable, '1', { name: 'Alice Updated' });
|
|
102
|
-
expect(updated.created_at).toBe(originalCreatedAt);
|
|
103
|
-
expect(updated.name).toBe('Alice Updated');
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
describe('Query Capability', () => {
|
|
108
|
-
it('should filter results', async () => {
|
|
109
|
-
await driver.create(testTable, { id: '1', role: 'admin' });
|
|
110
|
-
await driver.create(testTable, { id: '2', role: 'user' });
|
|
111
|
-
await driver.create(testTable, { id: '3', role: 'user' });
|
|
112
|
-
|
|
113
|
-
const results = await driver.find(testTable, {
|
|
114
|
-
fields: ['id'],
|
|
115
|
-
object: testTable,
|
|
116
|
-
where: { role: 'user' }
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
expect(results).toHaveLength(2);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it('should limit results', async () => {
|
|
123
|
-
await driver.create(testTable, { id: '1' });
|
|
124
|
-
await driver.create(testTable, { id: '2' });
|
|
125
|
-
await driver.create(testTable, { id: '3' });
|
|
126
|
-
|
|
127
|
-
const results = await driver.find(testTable, {
|
|
128
|
-
fields: ['id'],
|
|
129
|
-
object: testTable,
|
|
130
|
-
limit: 2
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
expect(results).toHaveLength(2);
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it('should project specific fields', async () => {
|
|
137
|
-
await driver.create(testTable, { id: '1', name: 'Alice', age: 30, role: 'admin' });
|
|
138
|
-
|
|
139
|
-
const results = await driver.find(testTable, {
|
|
140
|
-
fields: ['name', 'age'],
|
|
141
|
-
object: testTable,
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
expect(results).toHaveLength(1);
|
|
145
|
-
expect(results[0].name).toBe('Alice');
|
|
146
|
-
expect(results[0].age).toBe(30);
|
|
147
|
-
expect(results[0].id).toBe('1'); // id always included
|
|
148
|
-
expect(results[0].role).toBeUndefined();
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
describe('Initial Data', () => {
|
|
153
|
-
it('should load initial data on connect', async () => {
|
|
154
|
-
const driverWithData = new InMemoryDriver({
|
|
155
|
-
persistence: false,
|
|
156
|
-
initialData: {
|
|
157
|
-
users: [
|
|
158
|
-
{ id: '1', name: 'Alice' },
|
|
159
|
-
{ id: '2', name: 'Bob' },
|
|
160
|
-
],
|
|
161
|
-
posts: [
|
|
162
|
-
{ id: '1', title: 'Hello World' },
|
|
163
|
-
],
|
|
164
|
-
},
|
|
165
|
-
});
|
|
166
|
-
await driverWithData.connect();
|
|
167
|
-
|
|
168
|
-
const users = await driverWithData.find('users', { object: 'users' });
|
|
169
|
-
expect(users).toHaveLength(2);
|
|
170
|
-
expect(users[0].name).toBe('Alice');
|
|
171
|
-
|
|
172
|
-
const posts = await driverWithData.find('posts', { object: 'posts' });
|
|
173
|
-
expect(posts).toHaveLength(1);
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
it('should generate IDs for initial data without IDs', async () => {
|
|
177
|
-
const driverWithData = new InMemoryDriver({
|
|
178
|
-
persistence: false,
|
|
179
|
-
initialData: {
|
|
180
|
-
items: [{ name: 'Widget' }],
|
|
181
|
-
},
|
|
182
|
-
});
|
|
183
|
-
await driverWithData.connect();
|
|
184
|
-
|
|
185
|
-
const items = await driverWithData.find('items', { object: 'items' });
|
|
186
|
-
expect(items).toHaveLength(1);
|
|
187
|
-
expect(items[0].id).toBeDefined();
|
|
188
|
-
expect(typeof items[0].id).toBe('string');
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
describe('Strict Mode', () => {
|
|
193
|
-
it('should throw on update of missing record in strict mode', async () => {
|
|
194
|
-
const strictDriver = new InMemoryDriver({ strictMode: true });
|
|
195
|
-
await strictDriver.connect();
|
|
196
|
-
|
|
197
|
-
await expect(
|
|
198
|
-
strictDriver.update(testTable, 'non-existent', { name: 'Test' })
|
|
199
|
-
).rejects.toThrow('Record with ID non-existent not found');
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
it('should throw on delete of missing record in strict mode', async () => {
|
|
203
|
-
const strictDriver = new InMemoryDriver({ strictMode: true });
|
|
204
|
-
await strictDriver.connect();
|
|
205
|
-
|
|
206
|
-
await expect(
|
|
207
|
-
strictDriver.delete(testTable, 'non-existent')
|
|
208
|
-
).rejects.toThrow('Record with ID non-existent not found');
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
it('should return null on update of missing record in default mode', async () => {
|
|
212
|
-
const result = await driver.update(testTable, 'non-existent', { name: 'Test' });
|
|
213
|
-
expect(result).toBeNull();
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
it('should return false on delete of missing record in default mode', async () => {
|
|
217
|
-
const result = await driver.delete(testTable, 'non-existent');
|
|
218
|
-
expect(result).toBe(false);
|
|
219
|
-
});
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
describe('Transaction Support', () => {
|
|
223
|
-
it('should begin and commit a transaction', async () => {
|
|
224
|
-
await driver.create(testTable, { id: '1', name: 'Alice' });
|
|
225
|
-
|
|
226
|
-
const tx = await driver.beginTransaction();
|
|
227
|
-
await driver.create(testTable, { id: '2', name: 'Bob' });
|
|
228
|
-
await driver.commit(tx);
|
|
229
|
-
|
|
230
|
-
const results = await driver.find(testTable, { object: testTable });
|
|
231
|
-
expect(results).toHaveLength(2);
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
it('should rollback a transaction', async () => {
|
|
235
|
-
await driver.create(testTable, { id: '1', name: 'Alice' });
|
|
236
|
-
|
|
237
|
-
const tx = await driver.beginTransaction();
|
|
238
|
-
await driver.create(testTable, { id: '2', name: 'Bob' });
|
|
239
|
-
|
|
240
|
-
// Verify Bob exists before rollback
|
|
241
|
-
let results = await driver.find(testTable, { object: testTable });
|
|
242
|
-
expect(results).toHaveLength(2);
|
|
243
|
-
|
|
244
|
-
await driver.rollback(tx);
|
|
245
|
-
|
|
246
|
-
// After rollback, Bob should be gone
|
|
247
|
-
results = await driver.find(testTable, { object: testTable });
|
|
248
|
-
expect(results).toHaveLength(1);
|
|
249
|
-
expect(results[0].name).toBe('Alice');
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
it('should handle rollback of updates', async () => {
|
|
253
|
-
await driver.create(testTable, { id: '1', name: 'Alice' });
|
|
254
|
-
|
|
255
|
-
const tx = await driver.beginTransaction();
|
|
256
|
-
await driver.update(testTable, '1', { name: 'Alice Modified' });
|
|
257
|
-
await driver.rollback(tx);
|
|
258
|
-
|
|
259
|
-
const results = await driver.find(testTable, { object: testTable });
|
|
260
|
-
expect(results[0].name).toBe('Alice');
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
it('should support capabilities.transactions = true', () => {
|
|
264
|
-
expect(driver.supports.transactions).toBe(true);
|
|
265
|
-
});
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
describe('Utility Methods', () => {
|
|
269
|
-
it('should clear all data', async () => {
|
|
270
|
-
await driver.create(testTable, { id: '1', name: 'Alice' });
|
|
271
|
-
await driver.create('other_table', { id: '1', name: 'Bob' });
|
|
272
|
-
|
|
273
|
-
expect(driver.getSize()).toBe(2);
|
|
274
|
-
|
|
275
|
-
await driver.clear();
|
|
276
|
-
|
|
277
|
-
expect(driver.getSize()).toBe(0);
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
it('should return correct size', async () => {
|
|
281
|
-
expect(driver.getSize()).toBe(0);
|
|
282
|
-
|
|
283
|
-
await driver.create(testTable, { id: '1' });
|
|
284
|
-
expect(driver.getSize()).toBe(1);
|
|
285
|
-
|
|
286
|
-
await driver.create(testTable, { id: '2' });
|
|
287
|
-
expect(driver.getSize()).toBe(2);
|
|
288
|
-
|
|
289
|
-
await driver.create('other', { id: '1' });
|
|
290
|
-
expect(driver.getSize()).toBe(3);
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
it('should return distinct values for a field', async () => {
|
|
294
|
-
await driver.create(testTable, { id: '1', role: 'admin' });
|
|
295
|
-
await driver.create(testTable, { id: '2', role: 'user' });
|
|
296
|
-
await driver.create(testTable, { id: '3', role: 'user' });
|
|
297
|
-
await driver.create(testTable, { id: '4', role: 'moderator' });
|
|
298
|
-
|
|
299
|
-
const roles = await driver.distinct(testTable, 'role');
|
|
300
|
-
expect(roles).toHaveLength(3);
|
|
301
|
-
expect(roles).toContain('admin');
|
|
302
|
-
expect(roles).toContain('user');
|
|
303
|
-
expect(roles).toContain('moderator');
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
it('should return distinct values with filter', async () => {
|
|
307
|
-
await driver.create(testTable, { id: '1', role: 'admin', active: true });
|
|
308
|
-
await driver.create(testTable, { id: '2', role: 'user', active: false });
|
|
309
|
-
await driver.create(testTable, { id: '3', role: 'user', active: true });
|
|
310
|
-
|
|
311
|
-
const roles = await driver.distinct(testTable, 'role', {
|
|
312
|
-
object: testTable,
|
|
313
|
-
where: { active: true },
|
|
314
|
-
});
|
|
315
|
-
expect(roles).toHaveLength(2);
|
|
316
|
-
expect(roles).toContain('admin');
|
|
317
|
-
expect(roles).toContain('user');
|
|
318
|
-
});
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
describe('Schema Management', () => {
|
|
322
|
-
it('should create table on syncSchema', async () => {
|
|
323
|
-
await driver.syncSchema('new_table', {});
|
|
324
|
-
const results = await driver.find('new_table', { object: 'new_table' });
|
|
325
|
-
expect(results).toHaveLength(0);
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
it('should drop table', async () => {
|
|
329
|
-
await driver.create(testTable, { id: '1', name: 'test' });
|
|
330
|
-
await driver.dropTable(testTable);
|
|
331
|
-
const results = await driver.find(testTable, { object: testTable });
|
|
332
|
-
expect(results).toHaveLength(0);
|
|
333
|
-
});
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
describe('Bulk Operations', () => {
|
|
337
|
-
it('should bulk create records', async () => {
|
|
338
|
-
const records = [
|
|
339
|
-
{ name: 'Alice' },
|
|
340
|
-
{ name: 'Bob' },
|
|
341
|
-
{ name: 'Charlie' },
|
|
342
|
-
];
|
|
343
|
-
const results = await driver.bulkCreate(testTable, records);
|
|
344
|
-
expect(results).toHaveLength(3);
|
|
345
|
-
expect(results[0].name).toBe('Alice');
|
|
346
|
-
expect(results[1].name).toBe('Bob');
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
it('should count records', async () => {
|
|
350
|
-
await driver.create(testTable, { id: '1', role: 'admin' });
|
|
351
|
-
await driver.create(testTable, { id: '2', role: 'user' });
|
|
352
|
-
await driver.create(testTable, { id: '3', role: 'user' });
|
|
353
|
-
|
|
354
|
-
const total = await driver.count(testTable);
|
|
355
|
-
expect(total).toBe(3);
|
|
356
|
-
|
|
357
|
-
const userCount = await driver.count(testTable, {
|
|
358
|
-
object: testTable,
|
|
359
|
-
where: { role: 'user' },
|
|
360
|
-
});
|
|
361
|
-
expect(userCount).toBe(2);
|
|
362
|
-
});
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
describe('ID Generation', () => {
|
|
366
|
-
it('should generate unique counter-based IDs', async () => {
|
|
367
|
-
const r1 = await driver.create(testTable, { name: 'A' });
|
|
368
|
-
const r2 = await driver.create(testTable, { name: 'B' });
|
|
369
|
-
|
|
370
|
-
expect(r1.id).toBeDefined();
|
|
371
|
-
expect(r2.id).toBeDefined();
|
|
372
|
-
expect(r1.id).not.toBe(r2.id);
|
|
373
|
-
// Counter-based IDs include the table name
|
|
374
|
-
expect(r1.id).toContain(testTable);
|
|
375
|
-
});
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
describe('Version', () => {
|
|
379
|
-
it('should report version 1.0.0', () => {
|
|
380
|
-
expect(driver.version).toBe('1.0.0');
|
|
381
|
-
});
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
describe('Mingo Aggregation Pipeline', () => {
|
|
385
|
-
beforeEach(async () => {
|
|
386
|
-
// Seed order data for aggregation tests
|
|
387
|
-
await driver.create('orders', { id: '1', customer: 'alice', amount: 100, status: 'completed' });
|
|
388
|
-
await driver.create('orders', { id: '2', customer: 'bob', amount: 200, status: 'completed' });
|
|
389
|
-
await driver.create('orders', { id: '3', customer: 'alice', amount: 150, status: 'pending' });
|
|
390
|
-
await driver.create('orders', { id: '4', customer: 'bob', amount: 300, status: 'completed' });
|
|
391
|
-
await driver.create('orders', { id: '5', customer: 'charlie', amount: 50, status: 'cancelled' });
|
|
392
|
-
});
|
|
393
|
-
|
|
394
|
-
it('should $group and $sum', async () => {
|
|
395
|
-
const results = await driver.aggregate('orders', [
|
|
396
|
-
{ $match: { status: 'completed' } },
|
|
397
|
-
{ $group: { _id: '$customer', totalAmount: { $sum: '$amount' } } },
|
|
398
|
-
{ $sort: { _id: 1 } },
|
|
399
|
-
]);
|
|
400
|
-
|
|
401
|
-
expect(results).toHaveLength(2);
|
|
402
|
-
expect(results[0]._id).toBe('alice');
|
|
403
|
-
expect(results[0].totalAmount).toBe(100);
|
|
404
|
-
expect(results[1]._id).toBe('bob');
|
|
405
|
-
expect(results[1].totalAmount).toBe(500);
|
|
406
|
-
});
|
|
407
|
-
|
|
408
|
-
it('should $group with $avg', async () => {
|
|
409
|
-
const results = await driver.aggregate('orders', [
|
|
410
|
-
{ $group: { _id: null, avgAmount: { $avg: '$amount' } } },
|
|
411
|
-
]);
|
|
412
|
-
|
|
413
|
-
expect(results).toHaveLength(1);
|
|
414
|
-
expect(results[0].avgAmount).toBe(160); // (100+200+150+300+50)/5
|
|
415
|
-
});
|
|
416
|
-
|
|
417
|
-
it('should $group with $min and $max', async () => {
|
|
418
|
-
const results = await driver.aggregate('orders', [
|
|
419
|
-
{ $group: { _id: null, minAmount: { $min: '$amount' }, maxAmount: { $max: '$amount' } } },
|
|
420
|
-
]);
|
|
421
|
-
|
|
422
|
-
expect(results).toHaveLength(1);
|
|
423
|
-
expect(results[0].minAmount).toBe(50);
|
|
424
|
-
expect(results[0].maxAmount).toBe(300);
|
|
425
|
-
});
|
|
426
|
-
|
|
427
|
-
it('should $group with $count', async () => {
|
|
428
|
-
const results = await driver.aggregate('orders', [
|
|
429
|
-
{ $match: { status: 'completed' } },
|
|
430
|
-
{ $count: 'completedCount' },
|
|
431
|
-
]);
|
|
432
|
-
|
|
433
|
-
expect(results).toHaveLength(1);
|
|
434
|
-
expect(results[0].completedCount).toBe(3);
|
|
435
|
-
});
|
|
436
|
-
|
|
437
|
-
it('should $sort pipeline stage', async () => {
|
|
438
|
-
const results = await driver.aggregate('orders', [
|
|
439
|
-
{ $sort: { amount: -1 } },
|
|
440
|
-
{ $limit: 3 },
|
|
441
|
-
]);
|
|
442
|
-
|
|
443
|
-
expect(results).toHaveLength(3);
|
|
444
|
-
expect(results[0].amount).toBe(300);
|
|
445
|
-
expect(results[1].amount).toBe(200);
|
|
446
|
-
expect(results[2].amount).toBe(150);
|
|
447
|
-
});
|
|
448
|
-
|
|
449
|
-
it('should $project pipeline stage', async () => {
|
|
450
|
-
const results = await driver.aggregate('orders', [
|
|
451
|
-
{ $match: { customer: 'alice' } },
|
|
452
|
-
{ $project: { customer: 1, amount: 1, _id: 0 } },
|
|
453
|
-
{ $sort: { amount: 1 } },
|
|
454
|
-
]);
|
|
455
|
-
|
|
456
|
-
expect(results).toHaveLength(2);
|
|
457
|
-
expect(results[0]).toEqual({ customer: 'alice', amount: 100 });
|
|
458
|
-
expect(results[1]).toEqual({ customer: 'alice', amount: 150 });
|
|
459
|
-
});
|
|
460
|
-
|
|
461
|
-
it('should $group with $push (collect values)', async () => {
|
|
462
|
-
const results = await driver.aggregate('orders', [
|
|
463
|
-
{ $match: { status: 'completed' } },
|
|
464
|
-
{ $group: { _id: '$customer', amounts: { $push: '$amount' } } },
|
|
465
|
-
{ $sort: { _id: 1 } },
|
|
466
|
-
]);
|
|
467
|
-
|
|
468
|
-
expect(results).toHaveLength(2);
|
|
469
|
-
expect(results[0]._id).toBe('alice');
|
|
470
|
-
expect(results[0].amounts).toEqual([100]);
|
|
471
|
-
expect(results[1]._id).toBe('bob');
|
|
472
|
-
expect(results[1].amounts).toEqual([200, 300]);
|
|
473
|
-
});
|
|
474
|
-
});
|
|
475
|
-
|
|
476
|
-
describe('Mingo Query Operators', () => {
|
|
477
|
-
beforeEach(async () => {
|
|
478
|
-
await driver.create(testTable, { id: '1', name: 'Alice', age: 30, score: 85 });
|
|
479
|
-
await driver.create(testTable, { id: '2', name: 'Bob', age: 25, score: 92 });
|
|
480
|
-
await driver.create(testTable, { id: '3', name: 'Charlie', age: 35, score: 78 });
|
|
481
|
-
await driver.create(testTable, { id: '4', name: 'Diana', age: 28, score: 95 });
|
|
482
|
-
});
|
|
483
|
-
|
|
484
|
-
it('should filter with $gt operator', async () => {
|
|
485
|
-
const results = await driver.find(testTable, {
|
|
486
|
-
object: testTable,
|
|
487
|
-
where: { age: { $gt: 28 } },
|
|
488
|
-
});
|
|
489
|
-
expect(results).toHaveLength(2);
|
|
490
|
-
expect(results.map((r: any) => r.name).sort()).toEqual(['Alice', 'Charlie']);
|
|
491
|
-
});
|
|
492
|
-
|
|
493
|
-
it('should filter with $in operator', async () => {
|
|
494
|
-
const results = await driver.find(testTable, {
|
|
495
|
-
object: testTable,
|
|
496
|
-
where: { name: { $in: ['Alice', 'Diana'] } },
|
|
497
|
-
});
|
|
498
|
-
expect(results).toHaveLength(2);
|
|
499
|
-
});
|
|
500
|
-
|
|
501
|
-
it('should filter with $and operator', async () => {
|
|
502
|
-
const results = await driver.find(testTable, {
|
|
503
|
-
object: testTable,
|
|
504
|
-
where: { $and: [{ age: { $gte: 28 } }, { score: { $gt: 80 } }] },
|
|
505
|
-
});
|
|
506
|
-
expect(results).toHaveLength(2);
|
|
507
|
-
expect(results.map((r: any) => r.name).sort()).toEqual(['Alice', 'Diana']);
|
|
508
|
-
});
|
|
509
|
-
|
|
510
|
-
it('should filter with $or operator', async () => {
|
|
511
|
-
const results = await driver.find(testTable, {
|
|
512
|
-
object: testTable,
|
|
513
|
-
where: { $or: [{ age: { $lt: 26 } }, { score: { $gte: 95 } }] },
|
|
514
|
-
});
|
|
515
|
-
expect(results).toHaveLength(2);
|
|
516
|
-
expect(results.map((r: any) => r.name).sort()).toEqual(['Bob', 'Diana']);
|
|
517
|
-
});
|
|
518
|
-
|
|
519
|
-
it('should filter with $regex operator', async () => {
|
|
520
|
-
const results = await driver.find(testTable, {
|
|
521
|
-
object: testTable,
|
|
522
|
-
where: { name: { $regex: /^[AB]/ } },
|
|
523
|
-
});
|
|
524
|
-
expect(results).toHaveLength(2);
|
|
525
|
-
expect(results.map((r: any) => r.name).sort()).toEqual(['Alice', 'Bob']);
|
|
526
|
-
});
|
|
527
|
-
|
|
528
|
-
it('should count with complex filter', async () => {
|
|
529
|
-
const count = await driver.count(testTable, {
|
|
530
|
-
object: testTable,
|
|
531
|
-
where: { age: { $gte: 30 } },
|
|
532
|
-
});
|
|
533
|
-
expect(count).toBe(2);
|
|
534
|
-
});
|
|
535
|
-
});
|
|
536
|
-
|
|
537
|
-
describe('Aggregation Edge Cases', () => {
|
|
538
|
-
it('should aggregate empty collection', async () => {
|
|
539
|
-
const results = await driver.aggregate('empty_table', [
|
|
540
|
-
{ $group: { _id: null, total: { $sum: '$amount' } } },
|
|
541
|
-
]);
|
|
542
|
-
expect(results).toHaveLength(0);
|
|
543
|
-
});
|
|
544
|
-
|
|
545
|
-
it('should handle pipeline with only $match', async () => {
|
|
546
|
-
await driver.create(testTable, { id: '1', status: 'active' });
|
|
547
|
-
await driver.create(testTable, { id: '2', status: 'inactive' });
|
|
548
|
-
|
|
549
|
-
const results = await driver.aggregate(testTable, [
|
|
550
|
-
{ $match: { status: 'active' } },
|
|
551
|
-
]);
|
|
552
|
-
expect(results).toHaveLength(1);
|
|
553
|
-
expect(results[0].status).toBe('active');
|
|
554
|
-
});
|
|
555
|
-
});
|
|
556
|
-
|
|
557
|
-
describe('AST Filter Conversion', () => {
|
|
558
|
-
beforeEach(async () => {
|
|
559
|
-
await driver.create(testTable, { id: '1', name: 'Alice', age: 30 });
|
|
560
|
-
await driver.create(testTable, { id: '2', name: 'Bob', age: 25 });
|
|
561
|
-
await driver.create(testTable, { id: '3', name: 'Charlie', age: 35 });
|
|
562
|
-
});
|
|
563
|
-
|
|
564
|
-
it('should handle AST comparison node filter', async () => {
|
|
565
|
-
const results = await driver.find(testTable, {
|
|
566
|
-
object: testTable,
|
|
567
|
-
where: { type: 'comparison', field: 'age', operator: '>', value: 28 },
|
|
568
|
-
});
|
|
569
|
-
expect(results).toHaveLength(2);
|
|
570
|
-
});
|
|
571
|
-
|
|
572
|
-
it('should handle AST logical node filter', async () => {
|
|
573
|
-
const results = await driver.find(testTable, {
|
|
574
|
-
object: testTable,
|
|
575
|
-
where: {
|
|
576
|
-
type: 'logical',
|
|
577
|
-
operator: 'or',
|
|
578
|
-
conditions: [
|
|
579
|
-
{ type: 'comparison', field: 'name', operator: '=', value: 'Alice' },
|
|
580
|
-
{ type: 'comparison', field: 'name', operator: '=', value: 'Charlie' },
|
|
581
|
-
],
|
|
582
|
-
},
|
|
583
|
-
});
|
|
584
|
-
expect(results).toHaveLength(2);
|
|
585
|
-
});
|
|
586
|
-
|
|
587
|
-
it('should handle empty where clause', async () => {
|
|
588
|
-
const results = await driver.find(testTable, {
|
|
589
|
-
object: testTable,
|
|
590
|
-
});
|
|
591
|
-
expect(results).toHaveLength(3);
|
|
592
|
-
});
|
|
593
|
-
});
|
|
594
|
-
|
|
595
|
-
describe('FilterCondition Object Format (from parseFilterAST)', () => {
|
|
596
|
-
beforeEach(async () => {
|
|
597
|
-
await driver.create(testTable, { id: '1', name: 'Alice Johnson', age: 30, email: 'alice@example.com', bio: null });
|
|
598
|
-
await driver.create(testTable, { id: '2', name: 'Bob Smith', age: 25, email: 'bob@test.com', bio: 'Developer' });
|
|
599
|
-
await driver.create(testTable, { id: '3', name: 'Charlie Brown', age: 35, email: 'charlie@example.com', bio: '' });
|
|
600
|
-
await driver.create(testTable, { id: '4', name: 'Evan Davis', age: 28, email: 'evan@test.com', bio: 'Designer' });
|
|
601
|
-
});
|
|
602
|
-
|
|
603
|
-
it('should filter with $contains operator', async () => {
|
|
604
|
-
const results = await driver.find(testTable, {
|
|
605
|
-
object: testTable,
|
|
606
|
-
where: { name: { $contains: 'Evan' } },
|
|
607
|
-
});
|
|
608
|
-
expect(results).toHaveLength(1);
|
|
609
|
-
expect(results[0].name).toBe('Evan Davis');
|
|
610
|
-
});
|
|
611
|
-
|
|
612
|
-
it('should filter with $contains case-insensitively', async () => {
|
|
613
|
-
const results = await driver.find(testTable, {
|
|
614
|
-
object: testTable,
|
|
615
|
-
where: { name: { $contains: 'alice' } },
|
|
616
|
-
});
|
|
617
|
-
expect(results).toHaveLength(1);
|
|
618
|
-
expect(results[0].name).toBe('Alice Johnson');
|
|
619
|
-
});
|
|
620
|
-
|
|
621
|
-
it('should filter with $notContains operator', async () => {
|
|
622
|
-
const results = await driver.find(testTable, {
|
|
623
|
-
object: testTable,
|
|
624
|
-
where: { email: { $notContains: 'example' } },
|
|
625
|
-
});
|
|
626
|
-
expect(results).toHaveLength(2);
|
|
627
|
-
expect(results.map((r: any) => r.name).sort()).toEqual(['Bob Smith', 'Evan Davis']);
|
|
628
|
-
});
|
|
629
|
-
|
|
630
|
-
it('should filter with $startsWith operator', async () => {
|
|
631
|
-
const results = await driver.find(testTable, {
|
|
632
|
-
object: testTable,
|
|
633
|
-
where: { name: { $startsWith: 'Ch' } },
|
|
634
|
-
});
|
|
635
|
-
expect(results).toHaveLength(1);
|
|
636
|
-
expect(results[0].name).toBe('Charlie Brown');
|
|
637
|
-
});
|
|
638
|
-
|
|
639
|
-
it('should filter with $endsWith operator', async () => {
|
|
640
|
-
const results = await driver.find(testTable, {
|
|
641
|
-
object: testTable,
|
|
642
|
-
where: { email: { $endsWith: '.com' } },
|
|
643
|
-
});
|
|
644
|
-
expect(results).toHaveLength(4);
|
|
645
|
-
});
|
|
646
|
-
|
|
647
|
-
it('should filter with $between operator', async () => {
|
|
648
|
-
const results = await driver.find(testTable, {
|
|
649
|
-
object: testTable,
|
|
650
|
-
where: { age: { $between: [26, 32] } },
|
|
651
|
-
});
|
|
652
|
-
expect(results).toHaveLength(2);
|
|
653
|
-
expect(results.map((r: any) => r.name).sort()).toEqual(['Alice Johnson', 'Evan Davis']);
|
|
654
|
-
});
|
|
655
|
-
|
|
656
|
-
it('should filter with $null: true', async () => {
|
|
657
|
-
const results = await driver.find(testTable, {
|
|
658
|
-
object: testTable,
|
|
659
|
-
where: { bio: { $null: true } },
|
|
660
|
-
});
|
|
661
|
-
expect(results).toHaveLength(1);
|
|
662
|
-
expect(results[0].name).toBe('Alice Johnson');
|
|
663
|
-
});
|
|
664
|
-
|
|
665
|
-
it('should filter with $null: false', async () => {
|
|
666
|
-
const results = await driver.find(testTable, {
|
|
667
|
-
object: testTable,
|
|
668
|
-
where: { bio: { $null: false } },
|
|
669
|
-
});
|
|
670
|
-
expect(results).toHaveLength(3);
|
|
671
|
-
});
|
|
672
|
-
|
|
673
|
-
it('should handle $contains inside $and', async () => {
|
|
674
|
-
const results = await driver.find(testTable, {
|
|
675
|
-
object: testTable,
|
|
676
|
-
where: { $and: [{ name: { $contains: 'a' } }, { age: { $gte: 30 } }] },
|
|
677
|
-
});
|
|
678
|
-
expect(results).toHaveLength(2);
|
|
679
|
-
expect(results.map((r: any) => r.name).sort()).toEqual(['Alice Johnson', 'Charlie Brown']);
|
|
680
|
-
});
|
|
681
|
-
|
|
682
|
-
it('should handle $startsWith inside $or', async () => {
|
|
683
|
-
const results = await driver.find(testTable, {
|
|
684
|
-
object: testTable,
|
|
685
|
-
where: { $or: [{ name: { $startsWith: 'Al' } }, { name: { $startsWith: 'Ev' } }] },
|
|
686
|
-
});
|
|
687
|
-
expect(results).toHaveLength(2);
|
|
688
|
-
expect(results.map((r: any) => r.name).sort()).toEqual(['Alice Johnson', 'Evan Davis']);
|
|
689
|
-
});
|
|
690
|
-
|
|
691
|
-
it('should handle AST-converted notcontains via convertConditionToMongo', async () => {
|
|
692
|
-
const results = await driver.find(testTable, {
|
|
693
|
-
object: testTable,
|
|
694
|
-
where: { type: 'comparison', field: 'name', operator: 'notcontains', value: 'Bob' },
|
|
695
|
-
});
|
|
696
|
-
expect(results).toHaveLength(3);
|
|
697
|
-
});
|
|
698
|
-
|
|
699
|
-
it('should handle combined $startsWith + $endsWith on same field', async () => {
|
|
700
|
-
const results = await driver.find(testTable, {
|
|
701
|
-
object: testTable,
|
|
702
|
-
where: { name: { $startsWith: 'A', $endsWith: 'son' } },
|
|
703
|
-
});
|
|
704
|
-
expect(results).toHaveLength(1);
|
|
705
|
-
expect(results[0].name).toBe('Alice Johnson');
|
|
706
|
-
});
|
|
707
|
-
|
|
708
|
-
it('should filter with $null: true on missing fields', async () => {
|
|
709
|
-
// bio is null for Alice Johnson — should match
|
|
710
|
-
// Records without bio field at all should also match $null: true
|
|
711
|
-
await driver.create(testTable, { id: '5', name: 'Frank', age: 40, email: 'frank@test.com' });
|
|
712
|
-
const results = await driver.find(testTable, {
|
|
713
|
-
object: testTable,
|
|
714
|
-
where: { bio: { $null: true } },
|
|
715
|
-
});
|
|
716
|
-
// Alice (bio: null), Frank (bio: missing)
|
|
717
|
-
expect(results.length).toBeGreaterThanOrEqual(2);
|
|
718
|
-
expect(results.map((r: any) => r.name)).toContain('Alice Johnson');
|
|
719
|
-
expect(results.map((r: any) => r.name)).toContain('Frank');
|
|
720
|
-
});
|
|
721
|
-
});
|
|
722
|
-
});
|