jexidb 2.1.1 → 2.1.2
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/dist/Database.cjs +7621 -113
- package/package.json +9 -2
- package/src/Database.mjs +244 -79
- package/src/SchemaManager.mjs +325 -268
- package/src/Serializer.mjs +20 -1
- package/src/managers/QueryManager.mjs +74 -18
- package/.babelrc +0 -13
- package/.gitattributes +0 -2
- package/CHANGELOG.md +0 -140
- package/babel.config.json +0 -5
- package/docs/API.md +0 -1057
- package/docs/EXAMPLES.md +0 -701
- package/docs/README.md +0 -194
- package/examples/iterate-usage-example.js +0 -157
- package/examples/simple-iterate-example.js +0 -115
- package/jest.config.js +0 -24
- package/scripts/README.md +0 -47
- package/scripts/benchmark-array-serialization.js +0 -108
- package/scripts/clean-test-files.js +0 -75
- package/scripts/prepare.js +0 -31
- package/scripts/run-tests.js +0 -80
- package/scripts/score-mode-demo.js +0 -45
- package/test/$not-operator-with-and.test.js +0 -282
- package/test/README.md +0 -8
- package/test/close-init-cycle.test.js +0 -256
- package/test/coverage-method.test.js +0 -93
- package/test/critical-bugs-fixes.test.js +0 -1069
- package/test/deserialize-corruption-fixes.test.js +0 -296
- package/test/exists-method.test.js +0 -318
- package/test/explicit-indexes-comparison.test.js +0 -219
- package/test/filehandler-non-adjacent-ranges-bug.test.js +0 -175
- package/test/index-line-number-regression.test.js +0 -100
- package/test/index-missing-index-data.test.js +0 -91
- package/test/index-persistence.test.js +0 -491
- package/test/index-serialization.test.js +0 -314
- package/test/indexed-query-mode.test.js +0 -360
- package/test/insert-session-auto-flush.test.js +0 -353
- package/test/iterate-method.test.js +0 -272
- package/test/legacy-operator-compat.test.js +0 -154
- package/test/query-operators.test.js +0 -238
- package/test/regex-array-fields.test.js +0 -129
- package/test/score-method.test.js +0 -298
- package/test/setup.js +0 -17
- package/test/term-mapping-minimal.test.js +0 -154
- package/test/term-mapping-simple.test.js +0 -257
- package/test/term-mapping.test.js +0 -514
- package/test/writebuffer-flush-resilience.test.js +0 -204
|
@@ -1,1069 +0,0 @@
|
|
|
1
|
-
import { Database } from '../src/Database.mjs'
|
|
2
|
-
import fs from 'fs'
|
|
3
|
-
import path from 'path'
|
|
4
|
-
|
|
5
|
-
describe('Critical Bugs Fixes', () => {
|
|
6
|
-
let testDir
|
|
7
|
-
let db
|
|
8
|
-
|
|
9
|
-
beforeEach(() => {
|
|
10
|
-
testDir = path.join(process.cwd(), 'test-files', 'critical-bugs')
|
|
11
|
-
fs.mkdirSync(testDir, { recursive: true })
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
afterEach(async () => {
|
|
15
|
-
if (db) {
|
|
16
|
-
await db.close()
|
|
17
|
-
}
|
|
18
|
-
// Clean up test files with retry mechanism
|
|
19
|
-
if (fs.existsSync(testDir)) {
|
|
20
|
-
try {
|
|
21
|
-
fs.rmSync(testDir, { recursive: true, force: true })
|
|
22
|
-
} catch (error) {
|
|
23
|
-
// If removal fails, try again after a short delay
|
|
24
|
-
await new Promise(resolve => setTimeout(resolve, 100))
|
|
25
|
-
try {
|
|
26
|
-
fs.rmSync(testDir, { recursive: true, force: true })
|
|
27
|
-
} catch (retryError) {
|
|
28
|
-
console.warn('Could not clean up test directory:', testDir)
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
describe('Bug #1: File Descriptor Exhaustion (EMFILE)', () => {
|
|
35
|
-
test('should handle concurrent operations without EMFILE errors', async () => {
|
|
36
|
-
const dbPath = path.join(testDir, 'concurrent-test.jdb')
|
|
37
|
-
|
|
38
|
-
// Create database with connection pooling enabled
|
|
39
|
-
db = new Database(dbPath, {
|
|
40
|
-
clear: true,
|
|
41
|
-
create: true,
|
|
42
|
-
indexes: {
|
|
43
|
-
nameTerms: 'array:string',
|
|
44
|
-
groupTerms: 'array:string'
|
|
45
|
-
},
|
|
46
|
-
debugMode: false
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
await db.init()
|
|
50
|
-
|
|
51
|
-
// Test concurrent inserts
|
|
52
|
-
const insertPromises = []
|
|
53
|
-
for (let i = 0; i < 100; i++) {
|
|
54
|
-
insertPromises.push(
|
|
55
|
-
db.insert({
|
|
56
|
-
name: `Channel ${i}`,
|
|
57
|
-
nameTerms: [`channel`, `${i}`],
|
|
58
|
-
url: `http://example.com/${i}`
|
|
59
|
-
})
|
|
60
|
-
)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// This should complete successfully
|
|
64
|
-
await expect(Promise.all(insertPromises)).resolves.not.toThrow()
|
|
65
|
-
|
|
66
|
-
// Verify data was inserted
|
|
67
|
-
expect(db.length).toBe(100)
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
test('should handle concurrent queries without EMFILE errors', async () => {
|
|
71
|
-
const dbPath = path.join(testDir, 'concurrent-queries.jdb')
|
|
72
|
-
|
|
73
|
-
db = new Database(dbPath, {
|
|
74
|
-
clear: true,
|
|
75
|
-
create: true,
|
|
76
|
-
indexes: {
|
|
77
|
-
nameTerms: 'array:string'
|
|
78
|
-
},
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
await db.init()
|
|
82
|
-
|
|
83
|
-
// Insert test data
|
|
84
|
-
for (let i = 0; i < 50; i++) {
|
|
85
|
-
await db.insert({
|
|
86
|
-
name: `Channel ${i}`,
|
|
87
|
-
nameTerms: [`channel`, `${i}`],
|
|
88
|
-
url: `http://example.com/${i}`
|
|
89
|
-
})
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Test concurrent queries
|
|
93
|
-
const queryPromises = []
|
|
94
|
-
for (let i = 0; i < 100; i++) {
|
|
95
|
-
queryPromises.push(
|
|
96
|
-
db.findOne({ nameTerms: { $in: ['channel'] } })
|
|
97
|
-
)
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// This should complete successfully
|
|
101
|
-
await expect(Promise.all(queryPromises)).resolves.not.toThrow()
|
|
102
|
-
})
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
describe('Bug #2: Object Reference Corruption', () => {
|
|
106
|
-
test('should not corrupt object references during insert', async () => {
|
|
107
|
-
const dbPath = path.join(testDir, 'reference-corruption.jdb')
|
|
108
|
-
|
|
109
|
-
db = new Database(dbPath, {
|
|
110
|
-
clear: true,
|
|
111
|
-
create: true,
|
|
112
|
-
indexes: {
|
|
113
|
-
nameTerms: 'array:string'
|
|
114
|
-
}
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
await db.init()
|
|
118
|
-
|
|
119
|
-
// Create test entry
|
|
120
|
-
const entry = {
|
|
121
|
-
name: 'Test Channel',
|
|
122
|
-
nameTerms: ['test', 'channel'],
|
|
123
|
-
url: 'http://example.com'
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Store original values
|
|
127
|
-
const originalNameTerms = [...entry.nameTerms]
|
|
128
|
-
const originalName = entry.name
|
|
129
|
-
|
|
130
|
-
// Insert entry
|
|
131
|
-
await db.insert(entry)
|
|
132
|
-
|
|
133
|
-
// Verify original object was not corrupted
|
|
134
|
-
expect(entry.nameTerms).toEqual(originalNameTerms)
|
|
135
|
-
expect(entry.name).toBe(originalName)
|
|
136
|
-
expect(entry.nameTerms).not.toBeUndefined()
|
|
137
|
-
expect(entry.nameTerms.length).toBe(2)
|
|
138
|
-
})
|
|
139
|
-
|
|
140
|
-
test('should handle concurrent inserts without reference corruption', async () => {
|
|
141
|
-
const dbPath = path.join(testDir, 'concurrent-reference.jdb')
|
|
142
|
-
|
|
143
|
-
db = new Database(dbPath, {
|
|
144
|
-
clear: true,
|
|
145
|
-
create: true,
|
|
146
|
-
indexes: {
|
|
147
|
-
nameTerms: 'array:string'
|
|
148
|
-
},
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
await db.init()
|
|
152
|
-
|
|
153
|
-
// Create multiple entries with same reference structure
|
|
154
|
-
const entries = []
|
|
155
|
-
for (let i = 0; i < 20; i++) {
|
|
156
|
-
entries.push({
|
|
157
|
-
name: `Channel ${i}`,
|
|
158
|
-
nameTerms: ['test', 'channel', `${i}`],
|
|
159
|
-
url: `http://example.com/${i}`
|
|
160
|
-
})
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Insert all entries concurrently
|
|
164
|
-
const insertPromises = entries.map(entry => db.insert(entry))
|
|
165
|
-
await Promise.all(insertPromises)
|
|
166
|
-
|
|
167
|
-
// Verify all original entries are intact
|
|
168
|
-
entries.forEach((entry, index) => {
|
|
169
|
-
expect(entry.nameTerms).toEqual(['test', 'channel', `${index}`])
|
|
170
|
-
expect(entry.name).toBe(`Channel ${index}`)
|
|
171
|
-
expect(entry.nameTerms.length).toBe(3)
|
|
172
|
-
})
|
|
173
|
-
})
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
describe('Bug #3: Array Query Syntax Issues', () => {
|
|
177
|
-
test('should handle direct array field queries automatically', async () => {
|
|
178
|
-
const dbPath = path.join(testDir, 'array-query.jdb')
|
|
179
|
-
|
|
180
|
-
db = new Database(dbPath, {
|
|
181
|
-
clear: true,
|
|
182
|
-
create: true,
|
|
183
|
-
debugMode: false,
|
|
184
|
-
indexes: {
|
|
185
|
-
nameTerms: 'array:string',
|
|
186
|
-
tags: 'array:string'
|
|
187
|
-
}
|
|
188
|
-
})
|
|
189
|
-
|
|
190
|
-
await db.init()
|
|
191
|
-
|
|
192
|
-
// Insert test data
|
|
193
|
-
await db.insert({
|
|
194
|
-
name: 'CNN International',
|
|
195
|
-
nameTerms: ['cnn', 'international', 'news'],
|
|
196
|
-
tags: ['news', 'international']
|
|
197
|
-
})
|
|
198
|
-
|
|
199
|
-
// Test direct value queries (should work automatically)
|
|
200
|
-
const result1 = await db.findOne({ nameTerms: 'cnn' })
|
|
201
|
-
expect(result1).toBeTruthy()
|
|
202
|
-
expect(result1.name).toBe('CNN International')
|
|
203
|
-
|
|
204
|
-
// Test array value queries (should work automatically)
|
|
205
|
-
const result2 = await db.findOne({ nameTerms: ['cnn'] })
|
|
206
|
-
expect(result2).toBeTruthy()
|
|
207
|
-
expect(result2.name).toBe('CNN International')
|
|
208
|
-
|
|
209
|
-
// Test $in queries (should still work)
|
|
210
|
-
const result3 = await db.findOne({ nameTerms: { $in: ['cnn'] } })
|
|
211
|
-
expect(result3).toBeTruthy()
|
|
212
|
-
expect(result3.name).toBe('CNN International')
|
|
213
|
-
|
|
214
|
-
// Test multiple values
|
|
215
|
-
const result4 = await db.findOne({ tags: 'news' })
|
|
216
|
-
expect(result4).toBeTruthy()
|
|
217
|
-
expect(result4.name).toBe('CNN International')
|
|
218
|
-
})
|
|
219
|
-
|
|
220
|
-
test('should provide clear error messages for invalid array queries', async () => {
|
|
221
|
-
const dbPath = path.join(testDir, 'array-query-error.jdb')
|
|
222
|
-
|
|
223
|
-
db = new Database(dbPath, {
|
|
224
|
-
clear: true,
|
|
225
|
-
create: true,
|
|
226
|
-
indexes: {
|
|
227
|
-
nameTerms: 'array:string'
|
|
228
|
-
}
|
|
229
|
-
})
|
|
230
|
-
|
|
231
|
-
await db.init()
|
|
232
|
-
|
|
233
|
-
// Insert test data
|
|
234
|
-
await db.insert({
|
|
235
|
-
name: 'Test Channel',
|
|
236
|
-
nameTerms: ['test', 'channel']
|
|
237
|
-
})
|
|
238
|
-
|
|
239
|
-
// Test invalid query (should throw clear error message)
|
|
240
|
-
await expect(db.findOne({ nameTerms: null })).rejects.toThrow(
|
|
241
|
-
"Invalid query for array field 'nameTerms'. Use { $in: [value] } syntax or direct value."
|
|
242
|
-
)
|
|
243
|
-
})
|
|
244
|
-
})
|
|
245
|
-
|
|
246
|
-
describe('Bug #4: Memory Leaks and Performance Issues', () => {
|
|
247
|
-
test('should properly clean up resources on destroy', async () => {
|
|
248
|
-
const dbPath = path.join(testDir, 'memory-cleanup.jdb')
|
|
249
|
-
|
|
250
|
-
db = new Database(dbPath, {
|
|
251
|
-
clear: true,
|
|
252
|
-
create: true,
|
|
253
|
-
indexes: {
|
|
254
|
-
nameTerms: 'array:string'
|
|
255
|
-
},
|
|
256
|
-
debugMode: false
|
|
257
|
-
})
|
|
258
|
-
|
|
259
|
-
await db.init()
|
|
260
|
-
|
|
261
|
-
// Insert some data
|
|
262
|
-
for (let i = 0; i < 100; i++) {
|
|
263
|
-
await db.insert({
|
|
264
|
-
name: `Channel ${i}`,
|
|
265
|
-
nameTerms: [`channel`, `${i}`]
|
|
266
|
-
})
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// Get memory usage before save
|
|
270
|
-
const memBefore = db.getMemoryUsage()
|
|
271
|
-
expect(memBefore.writeBufferSize).toBe(100) // Records should be in writeBuffer
|
|
272
|
-
expect(memBefore.total).toBe(100) // Total should be 100 records
|
|
273
|
-
|
|
274
|
-
// Save data before destroy
|
|
275
|
-
await db.save()
|
|
276
|
-
|
|
277
|
-
// Verify writeBuffer is cleared after save
|
|
278
|
-
const memAfter = db.getMemoryUsage()
|
|
279
|
-
expect(memAfter.writeBufferSize).toBe(0) // writeBuffer should be empty after save
|
|
280
|
-
|
|
281
|
-
// Destroy database
|
|
282
|
-
await db.destroy()
|
|
283
|
-
|
|
284
|
-
// Verify database is destroyed
|
|
285
|
-
expect(db.destroyed).toBe(true)
|
|
286
|
-
expect(db.operationQueue).toBeNull()
|
|
287
|
-
})
|
|
288
|
-
|
|
289
|
-
test('should handle auto-save functionality', async () => {
|
|
290
|
-
const dbPath = path.join(testDir, 'auto-save.jdb')
|
|
291
|
-
|
|
292
|
-
db = new Database(dbPath, {
|
|
293
|
-
clear: true,
|
|
294
|
-
create: true,
|
|
295
|
-
indexes: {
|
|
296
|
-
nameTerms: 'array:string'
|
|
297
|
-
},
|
|
298
|
-
debugMode: false
|
|
299
|
-
})
|
|
300
|
-
|
|
301
|
-
await db.init()
|
|
302
|
-
|
|
303
|
-
// Insert data to trigger auto-save
|
|
304
|
-
for (let i = 0; i < 10; i++) {
|
|
305
|
-
await db.insert({
|
|
306
|
-
name: `Channel ${i}`,
|
|
307
|
-
nameTerms: [`channel`, `${i}`]
|
|
308
|
-
})
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// Wait for auto-save to complete (it's async via setImmediate)
|
|
312
|
-
await new Promise(resolve => setTimeout(resolve, 2000))
|
|
313
|
-
|
|
314
|
-
// Verify data was saved
|
|
315
|
-
expect(db.length).toBe(10)
|
|
316
|
-
})
|
|
317
|
-
})
|
|
318
|
-
|
|
319
|
-
describe('Bug #5: Incomplete Database Structure', () => {
|
|
320
|
-
test('should create complete database structure even when empty', async () => {
|
|
321
|
-
const dbPath = path.join(testDir, 'empty-structure.jdb')
|
|
322
|
-
|
|
323
|
-
db = new Database(dbPath, {
|
|
324
|
-
clear: true,
|
|
325
|
-
create: true,
|
|
326
|
-
indexes: {
|
|
327
|
-
nameTerms: 'array:string'
|
|
328
|
-
}
|
|
329
|
-
})
|
|
330
|
-
|
|
331
|
-
await db.init()
|
|
332
|
-
|
|
333
|
-
// Don't insert any data
|
|
334
|
-
expect(db.length).toBe(0)
|
|
335
|
-
|
|
336
|
-
// Save empty database
|
|
337
|
-
await db.save()
|
|
338
|
-
|
|
339
|
-
// Check if all required files were created
|
|
340
|
-
const mainFile = dbPath
|
|
341
|
-
const indexFile = dbPath.replace(/\.jdb$/, '.idx.jdb')
|
|
342
|
-
|
|
343
|
-
expect(fs.existsSync(mainFile)).toBe(true)
|
|
344
|
-
expect(fs.existsSync(indexFile)).toBe(true)
|
|
345
|
-
// Note: offsets file might be combined with index file in newer versions
|
|
346
|
-
})
|
|
347
|
-
|
|
348
|
-
test('should maintain structure consistency after operations', async () => {
|
|
349
|
-
const dbPath = path.join(testDir, 'structure-consistency.jdb')
|
|
350
|
-
|
|
351
|
-
db = new Database(dbPath, {
|
|
352
|
-
clear: true,
|
|
353
|
-
create: true,
|
|
354
|
-
indexes: {
|
|
355
|
-
nameTerms: 'array:string'
|
|
356
|
-
}
|
|
357
|
-
})
|
|
358
|
-
|
|
359
|
-
await db.init()
|
|
360
|
-
|
|
361
|
-
// Insert some data
|
|
362
|
-
await db.insert({
|
|
363
|
-
name: 'Test Channel',
|
|
364
|
-
nameTerms: ['test', 'channel']
|
|
365
|
-
})
|
|
366
|
-
|
|
367
|
-
await db.save()
|
|
368
|
-
|
|
369
|
-
// Verify structure
|
|
370
|
-
expect(db.length).toBe(1)
|
|
371
|
-
expect(fs.existsSync(dbPath)).toBe(true)
|
|
372
|
-
expect(fs.existsSync(dbPath.replace(/\.jdb$/, '.idx.jdb'))).toBe(true)
|
|
373
|
-
})
|
|
374
|
-
})
|
|
375
|
-
|
|
376
|
-
describe('Integration Tests', () => {
|
|
377
|
-
test('should handle real-world scenario similar to update-list-index.js', async () => {
|
|
378
|
-
const dbPath = path.join(testDir, 'real-world.jdb')
|
|
379
|
-
|
|
380
|
-
// Configuration similar to the workaround
|
|
381
|
-
db = new Database(dbPath, {
|
|
382
|
-
clear: true,
|
|
383
|
-
create: true,
|
|
384
|
-
indexes: {
|
|
385
|
-
nameTerms: 'array:string',
|
|
386
|
-
groupTerms: 'array:string'
|
|
387
|
-
},
|
|
388
|
-
maxWriteBufferSize: 256 * 1024, // 256KB
|
|
389
|
-
integrityCheck: 'none',
|
|
390
|
-
streamingThreshold: 0.8,
|
|
391
|
-
indexedQueryMode: 'strict',
|
|
392
|
-
debugMode: false,
|
|
393
|
-
})
|
|
394
|
-
|
|
395
|
-
await db.init()
|
|
396
|
-
|
|
397
|
-
// Simulate the insert pattern from the workaround
|
|
398
|
-
const entries = []
|
|
399
|
-
for (let i = 0; i < 1000; i++) {
|
|
400
|
-
entries.push({
|
|
401
|
-
name: `Channel ${i}`,
|
|
402
|
-
nameTerms: ['channel', `${i}`, 'test'],
|
|
403
|
-
groupTerms: ['group1', 'group2'],
|
|
404
|
-
url: `http://example.com/${i}`
|
|
405
|
-
})
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
// Insert with controlled concurrency (similar to the workaround)
|
|
409
|
-
const batchSize = 50
|
|
410
|
-
for (let i = 0; i < entries.length; i += batchSize) {
|
|
411
|
-
const batch = entries.slice(i, i + batchSize)
|
|
412
|
-
const promises = batch.map(entry => db.insert(entry))
|
|
413
|
-
await Promise.all(promises)
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
// Verify all data was inserted correctly
|
|
417
|
-
expect(db.length).toBe(1000)
|
|
418
|
-
|
|
419
|
-
// Test queries work correctly
|
|
420
|
-
const result = await db.findOne({ nameTerms: 'channel' })
|
|
421
|
-
expect(result).toBeTruthy()
|
|
422
|
-
expect(result.name).toMatch(/^Channel \d+$/)
|
|
423
|
-
|
|
424
|
-
// Test array queries work
|
|
425
|
-
const results = await db.find({ nameTerms: 'test' })
|
|
426
|
-
expect(results.length).toBe(1000)
|
|
427
|
-
|
|
428
|
-
// Verify no object corruption occurred
|
|
429
|
-
entries.forEach((entry, index) => {
|
|
430
|
-
expect(entry.nameTerms).toEqual(['channel', `${index}`, 'test'])
|
|
431
|
-
expect(entry.name).toBe(`Channel ${index}`)
|
|
432
|
-
})
|
|
433
|
-
})
|
|
434
|
-
})
|
|
435
|
-
|
|
436
|
-
describe('🚨 CRITICAL: Save Buffer Regression Tests', () => {
|
|
437
|
-
test('save() should automatically flush writeBuffer and ensure it is empty', async () => {
|
|
438
|
-
const dbPath = path.join(testDir, 'test-flush.jdb')
|
|
439
|
-
db = new Database(dbPath, { clear: true, create: true })
|
|
440
|
-
await db.init()
|
|
441
|
-
|
|
442
|
-
// Insert data to populate writeBuffer
|
|
443
|
-
await db.insert({ name: "Test", url: "http://test.com" })
|
|
444
|
-
await db.insert({ name: "Test2", url: "http://test2.com" })
|
|
445
|
-
|
|
446
|
-
// Verify writeBuffer has data before save
|
|
447
|
-
expect(db.writeBuffer.length).toBeGreaterThan(0)
|
|
448
|
-
|
|
449
|
-
// Save should flush the buffer
|
|
450
|
-
await db.save()
|
|
451
|
-
|
|
452
|
-
// CRITICAL: writeBuffer must be empty after save
|
|
453
|
-
expect(db.writeBuffer.length).toBe(0)
|
|
454
|
-
})
|
|
455
|
-
|
|
456
|
-
test('save() should persist data correctly after flushing buffer', async () => {
|
|
457
|
-
const dbPath = path.join(testDir, 'test-persist.jdb')
|
|
458
|
-
db = new Database(dbPath, { clear: true, create: true })
|
|
459
|
-
|
|
460
|
-
await db.init()
|
|
461
|
-
|
|
462
|
-
// Insert test data
|
|
463
|
-
const testData = { name: "RTP1", url: "http://example.com" }
|
|
464
|
-
await db.insert(testData)
|
|
465
|
-
|
|
466
|
-
// Save should flush and persist data
|
|
467
|
-
await db.save()
|
|
468
|
-
|
|
469
|
-
// Query should return the actual data, not empty entries
|
|
470
|
-
const results = await db.find()
|
|
471
|
-
expect(results).toHaveLength(1)
|
|
472
|
-
expect(results[0].name).toBe("RTP1")
|
|
473
|
-
expect(results[0].url).toBe("http://example.com")
|
|
474
|
-
})
|
|
475
|
-
|
|
476
|
-
test('save() should work with immediate destroy without data loss', async () => {
|
|
477
|
-
const dbPath = path.join(testDir, 'test-immediate-destroy.jdb')
|
|
478
|
-
db = new Database(dbPath, { clear: true, create: true })
|
|
479
|
-
await db.init()
|
|
480
|
-
|
|
481
|
-
// Insert data
|
|
482
|
-
await db.insert({ name: "DestroyTest", url: "http://destroy.com" })
|
|
483
|
-
|
|
484
|
-
// Save and immediately destroy
|
|
485
|
-
await db.close()
|
|
486
|
-
|
|
487
|
-
// Create new database instance and verify data persisted
|
|
488
|
-
const db2 = new Database(dbPath, { create: true })
|
|
489
|
-
await db2.init()
|
|
490
|
-
|
|
491
|
-
const results = await db2.find()
|
|
492
|
-
expect(results).toHaveLength(1)
|
|
493
|
-
expect(results[0].name).toBe("DestroyTest")
|
|
494
|
-
expect(results[0].url).toBe("http://destroy.com")
|
|
495
|
-
|
|
496
|
-
await db2.destroy()
|
|
497
|
-
})
|
|
498
|
-
|
|
499
|
-
test('save() should handle multiple inserts correctly', async () => {
|
|
500
|
-
const dbPath = path.join(testDir, 'test-multiple.jdb')
|
|
501
|
-
db = new Database(dbPath, { clear: true, create: true })
|
|
502
|
-
|
|
503
|
-
await db.init()
|
|
504
|
-
|
|
505
|
-
// Insert multiple records
|
|
506
|
-
const testData = [
|
|
507
|
-
{ name: "Channel1", url: "http://channel1.com" },
|
|
508
|
-
{ name: "Channel2", url: "http://channel2.com" },
|
|
509
|
-
{ name: "Channel3", url: "http://channel3.com" }
|
|
510
|
-
]
|
|
511
|
-
|
|
512
|
-
for (const data of testData) {
|
|
513
|
-
await db.insert(data)
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
// Save should flush all data
|
|
517
|
-
await db.save()
|
|
518
|
-
|
|
519
|
-
// All data should be persisted
|
|
520
|
-
const results = await db.find()
|
|
521
|
-
expect(results).toHaveLength(3)
|
|
522
|
-
|
|
523
|
-
// Verify no empty entries
|
|
524
|
-
results.forEach(result => {
|
|
525
|
-
expect(result.name).not.toBe("")
|
|
526
|
-
expect(result.url).not.toBe("")
|
|
527
|
-
})
|
|
528
|
-
})
|
|
529
|
-
|
|
530
|
-
test('save() should not return before file operations are complete', async () => {
|
|
531
|
-
const dbPath = path.join(testDir, 'test-io-completion.jdb')
|
|
532
|
-
db = new Database(dbPath, { clear: true, create: true })
|
|
533
|
-
|
|
534
|
-
await db.init()
|
|
535
|
-
|
|
536
|
-
// Insert data
|
|
537
|
-
await db.insert({ name: "IOTest", url: "http://iotest.com" })
|
|
538
|
-
|
|
539
|
-
// Ensure data is in writeBuffer
|
|
540
|
-
expect(db.writeBuffer.length).toBeGreaterThan(0)
|
|
541
|
-
|
|
542
|
-
// Save should wait for I/O completion
|
|
543
|
-
const saveStart = Date.now()
|
|
544
|
-
await db.save()
|
|
545
|
-
const saveEnd = Date.now()
|
|
546
|
-
|
|
547
|
-
// Verify save actually took time (indicating I/O operations)
|
|
548
|
-
expect(saveEnd - saveStart).toBeGreaterThan(0)
|
|
549
|
-
|
|
550
|
-
// Verify data is actually persisted by checking file system
|
|
551
|
-
const dataFile = dbPath
|
|
552
|
-
|
|
553
|
-
// Data file should exist and have content
|
|
554
|
-
const dataStats = fs.statSync(dataFile)
|
|
555
|
-
expect(dataStats.size).toBeGreaterThan(0)
|
|
556
|
-
|
|
557
|
-
// Verify data can be queried (proving persistence)
|
|
558
|
-
const results = await db.find()
|
|
559
|
-
expect(results).toHaveLength(1)
|
|
560
|
-
expect(results[0].name).toBe("IOTest")
|
|
561
|
-
})
|
|
562
|
-
|
|
563
|
-
test('flushInsertionBuffer() should work for backward compatibility', async () => {
|
|
564
|
-
const dbPath = path.join(testDir, 'test-backward-compat.jdb')
|
|
565
|
-
db = new Database(dbPath, { clear: true, create: true })
|
|
566
|
-
|
|
567
|
-
await db.init()
|
|
568
|
-
|
|
569
|
-
// Insert data
|
|
570
|
-
await db.insert({ name: "BackwardTest", url: "http://backward.com" })
|
|
571
|
-
|
|
572
|
-
// Use deprecated method (should show warning but work)
|
|
573
|
-
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {})
|
|
574
|
-
await db.flushInsertionBuffer()
|
|
575
|
-
consoleSpy.mockRestore()
|
|
576
|
-
|
|
577
|
-
// Data should be persisted
|
|
578
|
-
const results = await db.find()
|
|
579
|
-
expect(results).toHaveLength(1)
|
|
580
|
-
expect(results[0].name).toBe("BackwardTest")
|
|
581
|
-
})
|
|
582
|
-
|
|
583
|
-
test('should handle rapid insert-save cycles', async () => {
|
|
584
|
-
const dbPath = path.join(testDir, 'test-rapid-cycles.jdb')
|
|
585
|
-
db = new Database(dbPath, { clear: true, create: true })
|
|
586
|
-
|
|
587
|
-
await db.init()
|
|
588
|
-
|
|
589
|
-
// Rapid insert-save cycles
|
|
590
|
-
for (let i = 0; i < 10; i++) {
|
|
591
|
-
await db.insert({ name: `Cycle${i}`, url: `http://cycle${i}.com` })
|
|
592
|
-
await db.save()
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
// Wait for all operations to complete
|
|
596
|
-
await db.waitForOperations()
|
|
597
|
-
|
|
598
|
-
// Force a final save to ensure all data is persisted
|
|
599
|
-
await db.save()
|
|
600
|
-
|
|
601
|
-
// All data should be persisted
|
|
602
|
-
const results = await db.find()
|
|
603
|
-
expect(results).toHaveLength(10)
|
|
604
|
-
|
|
605
|
-
// Verify data integrity
|
|
606
|
-
results.forEach((result, index) => {
|
|
607
|
-
expect(result.name).toBe(`Cycle${index}`)
|
|
608
|
-
expect(result.url).toBe(`http://cycle${index}.com`)
|
|
609
|
-
})
|
|
610
|
-
})
|
|
611
|
-
})
|
|
612
|
-
|
|
613
|
-
describe('Bug #6: db.walk() returns arrays instead of objects', () => {
|
|
614
|
-
test('should return objects consistently in db.walk() method', async () => {
|
|
615
|
-
const dbPath = path.join(testDir, 'walk-bug-test.jdb')
|
|
616
|
-
|
|
617
|
-
// Create database with EPG-like schema (similar to the bug report)
|
|
618
|
-
db = new Database(dbPath, {
|
|
619
|
-
clear: true,
|
|
620
|
-
create: true,
|
|
621
|
-
fields: {
|
|
622
|
-
ch: 'string', // Channel name
|
|
623
|
-
start: 'number', // Start timestamp
|
|
624
|
-
e: 'number', // End timestamp
|
|
625
|
-
t: 'string', // Programme title
|
|
626
|
-
i: 'string', // Icon URL
|
|
627
|
-
desc: 'string', // Description
|
|
628
|
-
c: 'array:string', // Categories
|
|
629
|
-
terms: 'array:string', // Search terms
|
|
630
|
-
age: 'number', // Age rating
|
|
631
|
-
lang: 'string', // Language
|
|
632
|
-
country: 'string', // Country
|
|
633
|
-
rating: 'string', // Rating
|
|
634
|
-
parental: 'string', // Parental control
|
|
635
|
-
genre: 'string', // Genre
|
|
636
|
-
contentType: 'string', // Content type
|
|
637
|
-
parentalLock: 'string', // Parental lock
|
|
638
|
-
geo: 'string', // Geographic restriction
|
|
639
|
-
ageRestriction: 'string' // Age restriction
|
|
640
|
-
},
|
|
641
|
-
indexes: ['ch', 'start', 'e', 'c', 'terms']
|
|
642
|
-
})
|
|
643
|
-
|
|
644
|
-
await db.init()
|
|
645
|
-
|
|
646
|
-
// Insert test data that mimics EPG programme data
|
|
647
|
-
const testProgrammes = [
|
|
648
|
-
{
|
|
649
|
-
ch: 'Canal Sony FHD H.265',
|
|
650
|
-
start: 1759628040,
|
|
651
|
-
e: 1759639740,
|
|
652
|
-
t: 'À Espera de um Milagre',
|
|
653
|
-
i: '',
|
|
654
|
-
desc: '',
|
|
655
|
-
c: ['Filme', 'Drama'],
|
|
656
|
-
terms: ['à', 'espera', 'de', 'um', 'milagre', 'canal', 'sony', 'h', '265', 'filme', 'drama'],
|
|
657
|
-
age: 0,
|
|
658
|
-
lang: '',
|
|
659
|
-
country: '',
|
|
660
|
-
rating: '',
|
|
661
|
-
parental: 'no',
|
|
662
|
-
genre: 'Filme, Drama',
|
|
663
|
-
contentType: '',
|
|
664
|
-
parentalLock: 'false',
|
|
665
|
-
geo: '',
|
|
666
|
-
ageRestriction: ''
|
|
667
|
-
},
|
|
668
|
-
{
|
|
669
|
-
ch: 'GLOBO',
|
|
670
|
-
start: 1759628040,
|
|
671
|
-
e: 1759639740,
|
|
672
|
-
t: 'Jornal da Globo',
|
|
673
|
-
i: '',
|
|
674
|
-
desc: '',
|
|
675
|
-
c: ['Jornalismo'],
|
|
676
|
-
terms: ['jornal', 'globo', 'noticias', 'jornalismo'],
|
|
677
|
-
age: 0,
|
|
678
|
-
lang: '',
|
|
679
|
-
country: '',
|
|
680
|
-
rating: '',
|
|
681
|
-
parental: 'no',
|
|
682
|
-
genre: 'Jornalismo',
|
|
683
|
-
contentType: '',
|
|
684
|
-
parentalLock: 'false',
|
|
685
|
-
geo: '',
|
|
686
|
-
ageRestriction: ''
|
|
687
|
-
},
|
|
688
|
-
{
|
|
689
|
-
ch: 'ESPN',
|
|
690
|
-
start: 1759630000,
|
|
691
|
-
e: 1759640000,
|
|
692
|
-
t: 'ESPN SportsCenter',
|
|
693
|
-
i: '',
|
|
694
|
-
desc: '',
|
|
695
|
-
c: ['Esporte'],
|
|
696
|
-
terms: ['espn', 'sportscenter', 'esporte', 'futebol'],
|
|
697
|
-
age: 0,
|
|
698
|
-
lang: '',
|
|
699
|
-
country: '',
|
|
700
|
-
rating: '',
|
|
701
|
-
parental: 'no',
|
|
702
|
-
genre: 'Esporte',
|
|
703
|
-
contentType: '',
|
|
704
|
-
parentalLock: 'false',
|
|
705
|
-
geo: '',
|
|
706
|
-
ageRestriction: ''
|
|
707
|
-
}
|
|
708
|
-
]
|
|
709
|
-
|
|
710
|
-
// Insert data using InsertSession (as used in EPG applications)
|
|
711
|
-
const insertSession = db.beginInsertSession({
|
|
712
|
-
batchSize: 500,
|
|
713
|
-
enableAutoSave: true
|
|
714
|
-
})
|
|
715
|
-
|
|
716
|
-
for (const programme of testProgrammes) {
|
|
717
|
-
await insertSession.add(programme)
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
await insertSession.commit()
|
|
721
|
-
await db.save()
|
|
722
|
-
|
|
723
|
-
// Test queries that previously caused the bug
|
|
724
|
-
const testQueries = [
|
|
725
|
-
{ terms: ['à', 'espera', 'de', 'um', 'milagre'] },
|
|
726
|
-
{ terms: ['globo', 'jornal'] },
|
|
727
|
-
{ terms: ['espn'] }
|
|
728
|
-
]
|
|
729
|
-
|
|
730
|
-
for (const query of testQueries) {
|
|
731
|
-
// Test 1: db.find() - should work correctly
|
|
732
|
-
const findResults = await db.find(query)
|
|
733
|
-
expect(findResults.length).toBeGreaterThan(0)
|
|
734
|
-
|
|
735
|
-
// Verify all results are objects
|
|
736
|
-
findResults.forEach(result => {
|
|
737
|
-
expect(Array.isArray(result)).toBe(false)
|
|
738
|
-
expect(typeof result).toBe('object')
|
|
739
|
-
expect(result.ch).toBeDefined()
|
|
740
|
-
expect(result.t).toBeDefined()
|
|
741
|
-
})
|
|
742
|
-
|
|
743
|
-
// Test 2: db.walk() without options - should return objects
|
|
744
|
-
const walkResults = []
|
|
745
|
-
for await (const programme of db.walk(query)) {
|
|
746
|
-
walkResults.push(programme)
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
expect(walkResults.length).toBeGreaterThan(0)
|
|
750
|
-
|
|
751
|
-
// CRITICAL: Verify all results are objects, not arrays
|
|
752
|
-
walkResults.forEach(result => {
|
|
753
|
-
expect(Array.isArray(result)).toBe(false)
|
|
754
|
-
expect(typeof result).toBe('object')
|
|
755
|
-
expect(result.ch).toBeDefined()
|
|
756
|
-
expect(result.t).toBeDefined()
|
|
757
|
-
})
|
|
758
|
-
|
|
759
|
-
// Test 3: db.walk() with caseInsensitive option - should return objects
|
|
760
|
-
const walkCaseInsensitiveResults = []
|
|
761
|
-
for await (const programme of db.walk(query, { caseInsensitive: true })) {
|
|
762
|
-
walkCaseInsensitiveResults.push(programme)
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
walkCaseInsensitiveResults.forEach(result => {
|
|
766
|
-
expect(Array.isArray(result)).toBe(false)
|
|
767
|
-
expect(typeof result).toBe('object')
|
|
768
|
-
expect(result.ch).toBeDefined()
|
|
769
|
-
expect(result.t).toBeDefined()
|
|
770
|
-
})
|
|
771
|
-
|
|
772
|
-
// Test 4: db.walk() with matchAny option - should return objects
|
|
773
|
-
const walkMatchAnyResults = []
|
|
774
|
-
for await (const programme of db.walk(query, { matchAny: true })) {
|
|
775
|
-
walkMatchAnyResults.push(programme)
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
walkMatchAnyResults.forEach(result => {
|
|
779
|
-
expect(Array.isArray(result)).toBe(false)
|
|
780
|
-
expect(typeof result).toBe('object')
|
|
781
|
-
expect(result.ch).toBeDefined()
|
|
782
|
-
expect(result.t).toBeDefined()
|
|
783
|
-
})
|
|
784
|
-
|
|
785
|
-
// Test 5: db.walk() with both options - should return objects
|
|
786
|
-
const walkBothOptionsResults = []
|
|
787
|
-
for await (const programme of db.walk(query, { caseInsensitive: true, matchAny: true })) {
|
|
788
|
-
walkBothOptionsResults.push(programme)
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
walkBothOptionsResults.forEach(result => {
|
|
792
|
-
expect(Array.isArray(result)).toBe(false)
|
|
793
|
-
expect(typeof result).toBe('object')
|
|
794
|
-
expect(result.ch).toBeDefined()
|
|
795
|
-
expect(result.t).toBeDefined()
|
|
796
|
-
})
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
// Test 6: db.walk() with empty query - should return objects
|
|
800
|
-
const emptyQueryResults = []
|
|
801
|
-
for await (const programme of db.walk({})) {
|
|
802
|
-
emptyQueryResults.push(programme)
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
expect(emptyQueryResults.length).toBe(3) // Should return all 3 programmes
|
|
806
|
-
|
|
807
|
-
emptyQueryResults.forEach(result => {
|
|
808
|
-
expect(Array.isArray(result)).toBe(false)
|
|
809
|
-
expect(typeof result).toBe('object')
|
|
810
|
-
expect(result.ch).toBeDefined()
|
|
811
|
-
expect(result.t).toBeDefined()
|
|
812
|
-
})
|
|
813
|
-
})
|
|
814
|
-
|
|
815
|
-
test('should maintain consistency between db.find() and db.walk() results', async () => {
|
|
816
|
-
const dbPath = path.join(testDir, 'walk-consistency-test.jdb')
|
|
817
|
-
|
|
818
|
-
db = new Database(dbPath, {
|
|
819
|
-
clear: true,
|
|
820
|
-
create: true,
|
|
821
|
-
fields: {
|
|
822
|
-
name: 'string',
|
|
823
|
-
category: 'string',
|
|
824
|
-
tags: 'array:string'
|
|
825
|
-
},
|
|
826
|
-
indexes: ['name', 'category', 'tags']
|
|
827
|
-
})
|
|
828
|
-
|
|
829
|
-
await db.init()
|
|
830
|
-
|
|
831
|
-
// Insert test data
|
|
832
|
-
const testData = [
|
|
833
|
-
{ name: 'Movie A', category: 'Entertainment', tags: ['action', 'thriller'] },
|
|
834
|
-
{ name: 'Movie B', category: 'Entertainment', tags: ['comedy', 'romance'] },
|
|
835
|
-
{ name: 'News Show', category: 'News', tags: ['current', 'events'] }
|
|
836
|
-
]
|
|
837
|
-
|
|
838
|
-
for (const item of testData) {
|
|
839
|
-
await db.insert(item)
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
await db.save()
|
|
843
|
-
|
|
844
|
-
// Test various queries
|
|
845
|
-
const queries = [
|
|
846
|
-
{ category: 'Entertainment' },
|
|
847
|
-
{ tags: ['action'] },
|
|
848
|
-
{ name: 'News Show' },
|
|
849
|
-
{ category: 'News', tags: ['current'] }
|
|
850
|
-
]
|
|
851
|
-
|
|
852
|
-
for (const query of queries) {
|
|
853
|
-
// Get results from both methods
|
|
854
|
-
const findResults = await db.find(query)
|
|
855
|
-
const walkResults = []
|
|
856
|
-
|
|
857
|
-
for await (const item of db.walk(query)) {
|
|
858
|
-
walkResults.push(item)
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
// Both should return the same number of results
|
|
862
|
-
expect(walkResults.length).toBe(findResults.length)
|
|
863
|
-
|
|
864
|
-
// Both should return objects with the same structure
|
|
865
|
-
findResults.forEach((findResult, index) => {
|
|
866
|
-
const walkResult = walkResults[index]
|
|
867
|
-
|
|
868
|
-
// Both should be objects
|
|
869
|
-
expect(Array.isArray(findResult)).toBe(false)
|
|
870
|
-
expect(Array.isArray(walkResult)).toBe(false)
|
|
871
|
-
|
|
872
|
-
// Both should have the same properties
|
|
873
|
-
expect(walkResult.name).toBe(findResult.name)
|
|
874
|
-
expect(walkResult.category).toBe(findResult.category)
|
|
875
|
-
expect(walkResult.tags).toEqual(findResult.tags)
|
|
876
|
-
})
|
|
877
|
-
}
|
|
878
|
-
})
|
|
879
|
-
|
|
880
|
-
test('length should be correct after close() + init() cycle', async () => {
|
|
881
|
-
const dbPath = path.join(testDir, 'test-close-init-length.jdb')
|
|
882
|
-
db = new Database(dbPath, { clear: true, create: true })
|
|
883
|
-
|
|
884
|
-
await db.init()
|
|
885
|
-
|
|
886
|
-
// Insert test data
|
|
887
|
-
const testData = [
|
|
888
|
-
{ name: "Channel1", url: "http://channel1.com" },
|
|
889
|
-
{ name: "Channel2", url: "http://channel2.com" },
|
|
890
|
-
{ name: "Channel3", url: "http://channel3.com" }
|
|
891
|
-
]
|
|
892
|
-
|
|
893
|
-
for (const data of testData) {
|
|
894
|
-
await db.insert(data)
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
// Verify data is inserted
|
|
898
|
-
expect(db.length).toBe(3)
|
|
899
|
-
|
|
900
|
-
// Save data to disk
|
|
901
|
-
await db.save()
|
|
902
|
-
|
|
903
|
-
// Verify data is still there after save
|
|
904
|
-
expect(db.length).toBe(3)
|
|
905
|
-
|
|
906
|
-
// Close database
|
|
907
|
-
await db.close()
|
|
908
|
-
|
|
909
|
-
// Create new database instance
|
|
910
|
-
const db2 = new Database(dbPath, { create: true })
|
|
911
|
-
await db2.init()
|
|
912
|
-
|
|
913
|
-
// CRITICAL: length should be correct after init()
|
|
914
|
-
expect(db2.length).toBe(3)
|
|
915
|
-
|
|
916
|
-
// Verify data is accessible
|
|
917
|
-
const results = await db2.find()
|
|
918
|
-
expect(results).toHaveLength(3)
|
|
919
|
-
expect(results[0].name).toBe("Channel1")
|
|
920
|
-
expect(results[1].name).toBe("Channel2")
|
|
921
|
-
expect(results[2].name).toBe("Channel3")
|
|
922
|
-
|
|
923
|
-
await db2.destroy()
|
|
924
|
-
})
|
|
925
|
-
|
|
926
|
-
test('EPGManager-like scenario: database state after close + init', async () => {
|
|
927
|
-
const dbPath = path.join(testDir, 'test-epg-manager-scenario.jdb')
|
|
928
|
-
db = new Database(dbPath, { clear: true, create: true })
|
|
929
|
-
|
|
930
|
-
await db.init()
|
|
931
|
-
|
|
932
|
-
// Insert EPG-like data
|
|
933
|
-
const epgData = [
|
|
934
|
-
{ ch: "Channel1", t: "Program 1", start: 1640995200, e: 1640998800, terms: ["news", "sports"] },
|
|
935
|
-
{ ch: "Channel2", t: "Program 2", start: 1640995200, e: 1640998800, terms: ["movie", "action"] },
|
|
936
|
-
{ ch: "Channel3", t: "Program 3", start: 1640995200, e: 1640998800, terms: ["comedy", "show"] }
|
|
937
|
-
]
|
|
938
|
-
|
|
939
|
-
for (const data of epgData) {
|
|
940
|
-
await db.insert(data)
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
// Verify data is inserted
|
|
944
|
-
expect(db.length).toBe(3)
|
|
945
|
-
|
|
946
|
-
// Save data to disk
|
|
947
|
-
await db.save()
|
|
948
|
-
|
|
949
|
-
// Close database
|
|
950
|
-
await db.close()
|
|
951
|
-
|
|
952
|
-
// Create new database instance (like EPGManager does)
|
|
953
|
-
const db2 = new Database(dbPath, { create: true })
|
|
954
|
-
|
|
955
|
-
// Simulate EPGManager initialization sequence
|
|
956
|
-
await db2.init()
|
|
957
|
-
|
|
958
|
-
// Add delay like EPGManager does
|
|
959
|
-
await new Promise(resolve => setTimeout(resolve, 500))
|
|
960
|
-
|
|
961
|
-
// Test database accessibility like EPGManager does
|
|
962
|
-
const testCount = await db2.count()
|
|
963
|
-
console.log(`Database test successful, total records: ${testCount}`)
|
|
964
|
-
|
|
965
|
-
// CRITICAL: length should be correct after init()
|
|
966
|
-
expect(db2.length).toBe(3)
|
|
967
|
-
expect(testCount).toBe(3)
|
|
968
|
-
|
|
969
|
-
// Verify data is accessible with EPG-like queries
|
|
970
|
-
const results = await db2.find({ terms: { $in: ["news"] } })
|
|
971
|
-
expect(results).toHaveLength(1)
|
|
972
|
-
expect(results[0].t).toBe("Program 1")
|
|
973
|
-
|
|
974
|
-
await db2.destroy()
|
|
975
|
-
})
|
|
976
|
-
|
|
977
|
-
test('IMMEDIATE ACCESS: No delay should be needed after init()', async () => {
|
|
978
|
-
const dbPath = path.join(testDir, 'test-immediate-access.jdb')
|
|
979
|
-
db = new Database(dbPath, { clear: true, create: true })
|
|
980
|
-
|
|
981
|
-
await db.init()
|
|
982
|
-
|
|
983
|
-
// Insert test data
|
|
984
|
-
const testData = [
|
|
985
|
-
{ name: "Test1", value: 1 },
|
|
986
|
-
{ name: "Test2", value: 2 },
|
|
987
|
-
{ name: "Test3", value: 3 }
|
|
988
|
-
]
|
|
989
|
-
|
|
990
|
-
for (const data of testData) {
|
|
991
|
-
await db.insert(data)
|
|
992
|
-
}
|
|
993
|
-
|
|
994
|
-
// Save data
|
|
995
|
-
await db.save()
|
|
996
|
-
|
|
997
|
-
// Close database
|
|
998
|
-
await db.close()
|
|
999
|
-
|
|
1000
|
-
// Create new database instance
|
|
1001
|
-
const db2 = new Database(dbPath, { create: true })
|
|
1002
|
-
|
|
1003
|
-
// CRITICAL: init() should return only when everything is ready
|
|
1004
|
-
await db2.init()
|
|
1005
|
-
|
|
1006
|
-
// NO DELAY - immediate access should work
|
|
1007
|
-
console.log(`IMMEDIATE ACCESS: length=${db2.length}, count=${await db2.count()}`)
|
|
1008
|
-
|
|
1009
|
-
// These should work immediately without any delay
|
|
1010
|
-
expect(db2.length).toBe(3)
|
|
1011
|
-
expect(await db2.count()).toBe(3)
|
|
1012
|
-
|
|
1013
|
-
// Verify data is immediately accessible
|
|
1014
|
-
const results = await db2.find({ name: "Test1" })
|
|
1015
|
-
expect(results).toHaveLength(1)
|
|
1016
|
-
expect(results[0].value).toBe(1)
|
|
1017
|
-
|
|
1018
|
-
await db2.destroy()
|
|
1019
|
-
})
|
|
1020
|
-
|
|
1021
|
-
test('PERFORMANCE: length vs count() performance comparison', async () => {
|
|
1022
|
-
const dbPath = path.join(testDir, 'test-performance-comparison.jdb')
|
|
1023
|
-
db = new Database(dbPath, { clear: true, create: true })
|
|
1024
|
-
|
|
1025
|
-
await db.init()
|
|
1026
|
-
|
|
1027
|
-
// Insert more data for performance test
|
|
1028
|
-
const testData = []
|
|
1029
|
-
for (let i = 0; i < 1000; i++) {
|
|
1030
|
-
testData.push({ name: `Test${i}`, value: i })
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
|
-
for (const data of testData) {
|
|
1034
|
-
await db.insert(data)
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
// Save data
|
|
1038
|
-
await db.save()
|
|
1039
|
-
|
|
1040
|
-
// Close database
|
|
1041
|
-
await db.close()
|
|
1042
|
-
|
|
1043
|
-
// Create new database instance
|
|
1044
|
-
const db2 = new Database(dbPath, { create: true })
|
|
1045
|
-
await db2.init()
|
|
1046
|
-
|
|
1047
|
-
// Test performance of length vs count()
|
|
1048
|
-
const lengthStart = Date.now()
|
|
1049
|
-
const lengthResult = db2.length
|
|
1050
|
-
const lengthTime = Date.now() - lengthStart
|
|
1051
|
-
|
|
1052
|
-
const countStart = Date.now()
|
|
1053
|
-
const countResult = await db2.count()
|
|
1054
|
-
const countTime = Date.now() - countStart
|
|
1055
|
-
|
|
1056
|
-
console.log(`PERFORMANCE: length=${lengthResult} (${lengthTime}ms) vs count=${countResult} (${countTime}ms)`)
|
|
1057
|
-
|
|
1058
|
-
// Both should return the same result
|
|
1059
|
-
expect(lengthResult).toBe(1000)
|
|
1060
|
-
expect(countResult).toBe(1000)
|
|
1061
|
-
|
|
1062
|
-
// length should be much faster (uses index)
|
|
1063
|
-
// count should be slower (executes full query)
|
|
1064
|
-
expect(lengthTime).toBeLessThan(countTime)
|
|
1065
|
-
|
|
1066
|
-
await db2.destroy()
|
|
1067
|
-
})
|
|
1068
|
-
})
|
|
1069
|
-
})
|