File size: 8,348 Bytes
4e23b01
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
// Drives the query engine through the real db.query() path to cover the
// operators and path handling that the higher-level tests do not exercise.
import { test } from 'vitest';
import assert from 'node:assert/strict';
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import { MiniDb } from '../src/index.js';

async function tmpDir() {
  return fs.mkdtemp(path.join(os.tmpdir(), 'minidb-qe-'));
}

async function seed() {
  const dir = await tmpDir();
  const db = await MiniDb.open({ dir, valueCodec: 'json' });
  await db.set('a', { name: 'Ann', age: 30, city: 'Paris', tags: ['x', 'y'], score: 9.5, active: true });
  await db.set('b', { name: 'Bob', age: 17, city: 'London', tags: ['y'], score: 4.2, active: false });
  await db.set('c', { name: 'Eve', age: 25, city: 'Paris', tags: ['z'], score: 7.0 });
  await db.set('d', { name: 'Max', age: 40, city: 'Berlin', tags: [], score: 8.1, active: true });
  return { dir, db };
}

test('comparison operators $eq $ne $gt $gte $lt $lte', async () => {
  const { dir, db } = await seed();
  try {
    assert.deepEqual(db.query({ filter: { age: { $eq: 25 } } }).map((r) => r.key), ['c']);
    assert.deepEqual(db.query({ filter: { age: { $ne: 25 } } }).map((r) => r.key).sort(), ['a', 'b', 'd']);
    assert.deepEqual(db.query({ filter: { age: { $gte: 30 } } }).map((r) => r.key).sort(), ['a', 'd']);
    assert.deepEqual(db.query({ filter: { age: { $lte: 25 } } }).map((r) => r.key).sort(), ['b', 'c']);
    // Combine two operators in one cond object (both must hold).
    assert.deepEqual(
      db.query({ filter: { age: { $gt: 17, $lt: 30 } } }).map((r) => r.key),
      ['c'],
    );
    await db.close();
  } finally {
    await fs.rm(dir, { recursive: true, force: true });
  }
});

test('$in / $nin membership', async () => {
  const { dir, db } = await seed();
  try {
    assert.deepEqual(db.query({ filter: { city: { $in: ['Paris', 'Berlin'] } } }).map((r) => r.key).sort(), [
      'a',
      'c',
      'd',
    ]);
    assert.deepEqual(db.query({ filter: { city: { $nin: ['Paris'] } } }).map((r) => r.key).sort(), ['b', 'd']);
    // Non-array argument never matches.
    assert.deepEqual(db.query({ filter: { city: { $in: 'Paris' as unknown as string[] } } }).map((r) => r.key), []);
    assert.deepEqual(db.query({ filter: { city: { $nin: 'Paris' as unknown as string[] } } }).map((r) => r.key), []);
    await db.close();
  } finally {
    await fs.rm(dir, { recursive: true, force: true });
  }
});

test('$exists and $type', async () => {
  const { dir, db } = await seed();
  try {
    // 'active' is set on a, b, d but not c.
    assert.deepEqual(db.query({ filter: { active: { $exists: true } } }).map((r) => r.key).sort(), ['a', 'b', 'd']);
    assert.deepEqual(db.query({ filter: { active: { $exists: false } } }).map((r) => r.key), ['c']);
    assert.deepEqual(db.query({ filter: { name: { $type: 'string' } } }).map((r) => r.key).sort(), [
      'a',
      'b',
      'c',
      'd',
    ]);
    assert.deepEqual(db.query({ filter: { age: { $type: 'string' } } }).map((r) => r.key), []);
    await db.close();
  } finally {
    await fs.rm(dir, { recursive: true, force: true });
  }
});

test('$regex with string, [pattern, flags], RegExp and stateful global RegExp', async () => {
  const { dir, db } = await seed();
  try {
    assert.deepEqual(db.query({ filter: { name: { $regex: '^A' } } }).map((r) => r.key), ['a']);
    // Tuple form supplies flags.
    assert.deepEqual(
      db.query({ filter: { name: { $regex: ['^a', 'i'] as unknown as string } } }).map((r) => r.key),
      ['a'],
    );
    // RegExp instance form.
    assert.deepEqual(db.query({ filter: { name: { $regex: /^B/ } } }).map((r) => r.key), ['b']);
    // A global RegExp is stateful; the engine must reset lastIndex so every
    // document is tested from the start.
    const g = /^E/g;
    assert.deepEqual(db.query({ filter: { name: { $regex: g } } }).map((r) => r.key), ['c']);
    // Non-string field never matches a regex.
    assert.deepEqual(db.query({ filter: { age: { $regex: '3' } } }).map((r) => r.key), []);
    await db.close();
  } finally {
    await fs.rm(dir, { recursive: true, force: true });
  }
});

test('scalar RegExp value resets lastIndex between documents', async () => {
  const { dir, db } = await seed();
  try {
    const g = /^A/g;
    assert.deepEqual(db.query({ filter: { name: g } }).map((r) => r.key), ['a']);
    // Non-string value against a scalar RegExp never matches.
    assert.deepEqual(db.query({ filter: { age: /^3/ } }).map((r) => r.key), []);
    await db.close();
  } finally {
    await fs.rm(dir, { recursive: true, force: true });
  }
});

test('logical operators $and $or $nor $not', async () => {
  const { dir, db } = await seed();
  try {
    assert.deepEqual(
      db.query({ filter: { $and: [{ city: 'Paris' }, { age: { $gt: 20 } }] } }).map((r) => r.key).sort(),
      ['a', 'c'],
    );
    assert.deepEqual(
      db.query({ filter: { $or: [{ name: 'Ann' }, { name: 'Bob' }] } }).map((r) => r.key).sort(),
      ['a', 'b'],
    );
    // $nor: matches documents that satisfy NONE of the branches.
    assert.deepEqual(
      db.query({ filter: { $nor: [{ city: 'Paris' }, { city: 'London' }] } }).map((r) => r.key),
      ['d'],
    );
    assert.deepEqual(db.query({ filter: { $not: { city: 'Paris' } } }).map((r) => r.key).sort(), ['b', 'd']);
    await db.close();
  } finally {
    await fs.rm(dir, { recursive: true, force: true });
  }
});

test('unknown operator and non-array logical args never match', async () => {
  const { dir, db } = await seed();
  try {
    // Unknown operator falls through to the default case (no match).
    assert.deepEqual(db.query({ filter: { age: { $bogus: 1 } as never } }).map((r) => r.key), []);
    // $and/$or/$nor with a non-array argument are treated as non-matching.
    assert.deepEqual(db.query({ filter: { $and: { city: 'Paris' } as never } }).map((r) => r.key), []);
    assert.deepEqual(db.query({ filter: { $or: { city: 'Paris' } as never } }).map((r) => r.key), []);
    assert.deepEqual(db.query({ filter: { $nor: { city: 'Paris' } as never } }).map((r) => r.key), []);
    await db.close();
  } finally {
    await fs.rm(dir, { recursive: true, force: true });
  }
});

test('nested dot + bracket paths in filter and projection', async () => {
  const dir = await tmpDir();
  try {
    const db = await MiniDb.open({ dir, valueCodec: 'json' });
    await db.set('p1', { user: { name: 'Ann', addr: { zip: '75001' } }, items: [{ sku: 'A' }, { sku: 'B' }] });
    await db.set('p2', { user: { name: 'Bob', addr: { zip: '12345' } }, items: [{ sku: 'A' }] });
    await db.set('p3', { user: { name: 'Eve' }, items: [] });

    assert.deepEqual(db.query({ filter: { 'user.name': 'Bob' } }).map((r) => r.key), ['p2']);
    assert.deepEqual(db.query({ filter: { 'user.addr.zip': '75001' } }).map((r) => r.key), ['p1']);
    // Bracket index into an array element.
    assert.deepEqual(db.query({ filter: { 'items[1].sku': 'B' } }).map((r) => r.key), ['p1']);
    // Missing intermediate path does not match.
    assert.deepEqual(db.query({ filter: { 'user.addr.zip': 'x' } }).map((r) => r.key).length, 0);

    // Nested projection rebuilds the nested shape in the output.
    const projected = db.query({ filter: { 'user.name': 'Ann' }, project: ['user.addr.zip', 'items[0].sku'] });
    assert.deepEqual(projected[0]!.value, { user: { addr: { zip: '75001' } }, items: [{ sku: 'A' }] });

    // Projecting a missing path drops it; empty project list returns the doc.
    assert.deepEqual(db.query({ project: [] })[0]!.value, (await db.get('p1')));
    await db.close();
  } finally {
    await fs.rm(dir, { recursive: true, force: true });
  }
});

test('sort ascending/descending, skip and limit', async () => {
  const { dir, db } = await seed();
  try {
    assert.deepEqual(db.query({ sort: { age: 1 } }).map((r) => r.key), ['b', 'c', 'a', 'd']);
    assert.deepEqual(db.query({ sort: { age: -1 } }).map((r) => r.key), ['d', 'a', 'c', 'b']);
    assert.deepEqual(db.query({ sort: { age: 1 }, skip: 1, limit: 2 }).map((r) => r.key), ['c', 'a']);
    // No filter / sort returns every document.
    assert.equal(db.query().length, 4);
    await db.close();
  } finally {
    await fs.rm(dir, { recursive: true, force: true });
  }
});