jexidb 2.0.2 → 2.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/.babelrc +13 -0
- package/.gitattributes +2 -0
- package/CHANGELOG.md +140 -0
- package/LICENSE +21 -21
- package/README.md +301 -527
- package/babel.config.json +5 -0
- package/dist/Database.cjs +3896 -0
- package/docs/API.md +1051 -0
- package/docs/EXAMPLES.md +701 -0
- package/docs/README.md +194 -0
- package/examples/iterate-usage-example.js +157 -0
- package/examples/simple-iterate-example.js +115 -0
- package/jest.config.js +24 -0
- package/package.json +63 -51
- package/scripts/README.md +47 -0
- package/scripts/clean-test-files.js +75 -0
- package/scripts/prepare.js +31 -0
- package/scripts/run-tests.js +80 -0
- package/src/Database.mjs +4130 -0
- package/src/FileHandler.mjs +1101 -0
- package/src/OperationQueue.mjs +279 -0
- package/src/SchemaManager.mjs +268 -0
- package/src/Serializer.mjs +511 -0
- package/src/managers/ConcurrencyManager.mjs +257 -0
- package/src/managers/IndexManager.mjs +1403 -0
- package/src/managers/QueryManager.mjs +1273 -0
- package/src/managers/StatisticsManager.mjs +262 -0
- package/src/managers/StreamingProcessor.mjs +429 -0
- package/src/managers/TermManager.mjs +278 -0
- package/test/$not-operator-with-and.test.js +282 -0
- package/test/README.md +8 -0
- package/test/close-init-cycle.test.js +256 -0
- package/test/critical-bugs-fixes.test.js +1069 -0
- package/test/index-persistence.test.js +306 -0
- package/test/index-serialization.test.js +314 -0
- package/test/indexed-query-mode.test.js +360 -0
- package/test/iterate-method.test.js +272 -0
- package/test/query-operators.test.js +238 -0
- package/test/regex-array-fields.test.js +129 -0
- package/test/score-method.test.js +238 -0
- package/test/setup.js +17 -0
- package/test/term-mapping-minimal.test.js +154 -0
- package/test/term-mapping-simple.test.js +257 -0
- package/test/term-mapping.test.js +514 -0
- package/test/writebuffer-flush-resilience.test.js +204 -0
- package/dist/FileHandler.js +0 -688
- package/dist/IndexManager.js +0 -353
- package/dist/IntegrityChecker.js +0 -364
- package/dist/JSONLDatabase.js +0 -1194
- package/dist/index.js +0 -617
- package/src/FileHandler.js +0 -674
- package/src/IndexManager.js +0 -363
- package/src/IntegrityChecker.js +0 -379
- package/src/JSONLDatabase.js +0 -1248
- package/src/index.js +0 -608
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import { Database } from '../src/Database.mjs'
|
|
4
|
+
import TermManager from '../src/managers/TermManager.mjs'
|
|
5
|
+
|
|
6
|
+
// Add Mocha-style matchers to Jest
|
|
7
|
+
const originalExpect = expect
|
|
8
|
+
global.expect = function(actual) {
|
|
9
|
+
const matcher = originalExpect(actual)
|
|
10
|
+
matcher.to = {
|
|
11
|
+
deep: {
|
|
12
|
+
equal: (expected) => {
|
|
13
|
+
return originalExpect(actual).toEqual(expected)
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
equal: (expected) => {
|
|
17
|
+
return originalExpect(actual).toBe(expected)
|
|
18
|
+
},
|
|
19
|
+
have: {
|
|
20
|
+
length: (expected) => {
|
|
21
|
+
return originalExpect(actual).toHaveLength(expected)
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
be: {
|
|
25
|
+
false: () => {
|
|
26
|
+
return originalExpect(actual).toBe(false)
|
|
27
|
+
},
|
|
28
|
+
true: () => {
|
|
29
|
+
return originalExpect(actual).toBe(true)
|
|
30
|
+
},
|
|
31
|
+
greaterThan: (expected) => {
|
|
32
|
+
return originalExpect(actual).toBeGreaterThan(expected)
|
|
33
|
+
},
|
|
34
|
+
lessThan: (expected) => {
|
|
35
|
+
return originalExpect(actual).toBeLessThan(expected)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return matcher
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
describe('Term Mapping Tests', () => {
|
|
43
|
+
let db
|
|
44
|
+
let testDbPath
|
|
45
|
+
let testIdxPath
|
|
46
|
+
|
|
47
|
+
beforeEach(async () => {
|
|
48
|
+
// Use unique test file names to avoid interference between tests
|
|
49
|
+
const testId = Math.random().toString(36).substring(7)
|
|
50
|
+
testDbPath = path.join(process.cwd(), `test-term-mapping-${testId}.jdb`)
|
|
51
|
+
testIdxPath = path.join(process.cwd(), `test-term-mapping-${testId}.idx.jdb`)
|
|
52
|
+
|
|
53
|
+
// Clean up any existing test files
|
|
54
|
+
if (fs.existsSync(testDbPath)) fs.unlinkSync(testDbPath)
|
|
55
|
+
if (fs.existsSync(testIdxPath)) fs.unlinkSync(testIdxPath)
|
|
56
|
+
if (fs.existsSync(testDbPath.replace('.jdb', '.terms.jdb'))) fs.unlinkSync(testDbPath.replace('.jdb', '.terms.jdb'))
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
afterEach(async () => {
|
|
60
|
+
if (db) {
|
|
61
|
+
await db.close()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Clean up test files with error handling
|
|
65
|
+
try {
|
|
66
|
+
if (fs.existsSync(testDbPath)) fs.unlinkSync(testDbPath)
|
|
67
|
+
if (fs.existsSync(testIdxPath)) fs.unlinkSync(testIdxPath)
|
|
68
|
+
if (fs.existsSync(testDbPath.replace('.jdb', '.terms.jdb'))) fs.unlinkSync(testDbPath.replace('.jdb', '.terms.jdb'))
|
|
69
|
+
} catch (err) {
|
|
70
|
+
// Ignore cleanup errors to prevent test failures
|
|
71
|
+
console.warn('Warning: Could not clean up test files:', err.message)
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
describe('TermManager', () => {
|
|
76
|
+
let termManager
|
|
77
|
+
|
|
78
|
+
beforeEach(() => {
|
|
79
|
+
termManager = new TermManager()
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('should create and retrieve term IDs', () => {
|
|
83
|
+
const term1 = termManager.getTermId('bra')
|
|
84
|
+
const term2 = termManager.getTermId('globo')
|
|
85
|
+
const term3 = termManager.getTermId('bra') // Duplicate
|
|
86
|
+
|
|
87
|
+
expect(term1).toBe(1)
|
|
88
|
+
expect(term2).toBe(2)
|
|
89
|
+
expect(term3).toBe(1) // Should return same ID
|
|
90
|
+
|
|
91
|
+
expect(termManager.getTerm(1)).toBe('bra')
|
|
92
|
+
expect(termManager.getTerm(2)).toBe('globo')
|
|
93
|
+
expect(termManager.getTerm(999)).toBeNull()
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('should track term usage counts', () => {
|
|
97
|
+
const termId = termManager.getTermId('test')
|
|
98
|
+
expect(termManager.termCounts.get(termId)).toBe(1)
|
|
99
|
+
|
|
100
|
+
termManager.incrementTermCount(termId)
|
|
101
|
+
expect(termManager.termCounts.get(termId)).toBe(2)
|
|
102
|
+
|
|
103
|
+
termManager.decrementTermCount(termId)
|
|
104
|
+
expect(termManager.termCounts.get(termId)).toBe(1)
|
|
105
|
+
|
|
106
|
+
termManager.decrementTermCount(termId)
|
|
107
|
+
expect(termManager.termCounts.get(termId)).toBe(0)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('should clean up orphaned terms', async () => {
|
|
111
|
+
const term1 = termManager.getTermId('orphan1')
|
|
112
|
+
const term2 = termManager.getTermId('orphan2')
|
|
113
|
+
const term3 = termManager.getTermId('active')
|
|
114
|
+
|
|
115
|
+
// Make term1 and term2 orphaned
|
|
116
|
+
termManager.decrementTermCount(term1)
|
|
117
|
+
termManager.decrementTermCount(term2)
|
|
118
|
+
|
|
119
|
+
const orphanedCount = await termManager.cleanupOrphanedTerms(true)
|
|
120
|
+
expect(orphanedCount).toBe(2)
|
|
121
|
+
|
|
122
|
+
expect(termManager.hasTerm('orphan1')).toBe(false)
|
|
123
|
+
expect(termManager.hasTerm('orphan2')).toBe(false)
|
|
124
|
+
expect(termManager.hasTerm('active')).toBe(true)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('should load and save terms', async () => {
|
|
128
|
+
// Add some terms
|
|
129
|
+
termManager.getTermId('bra')
|
|
130
|
+
termManager.getTermId('globo')
|
|
131
|
+
termManager.getTermId('brasil')
|
|
132
|
+
|
|
133
|
+
const termsData = await termManager.saveTerms()
|
|
134
|
+
expect(termsData).to.deep.equal({
|
|
135
|
+
'1': 'bra',
|
|
136
|
+
'2': 'globo',
|
|
137
|
+
'3': 'brasil'
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
// Create new manager and load terms
|
|
141
|
+
const newManager = new TermManager()
|
|
142
|
+
await newManager.loadTerms(termsData)
|
|
143
|
+
|
|
144
|
+
expect(newManager.getTerm(1)).to.equal('bra')
|
|
145
|
+
expect(newManager.getTerm(2)).to.equal('globo')
|
|
146
|
+
expect(newManager.getTerm(3)).to.equal('brasil')
|
|
147
|
+
expect(newManager.nextId).to.equal(4)
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
it('should provide statistics', () => {
|
|
151
|
+
termManager.getTermId('term1')
|
|
152
|
+
termManager.getTermId('term2')
|
|
153
|
+
|
|
154
|
+
const stats = termManager.getStats()
|
|
155
|
+
expect(stats.totalTerms).to.equal(2)
|
|
156
|
+
expect(stats.nextId).to.equal(3)
|
|
157
|
+
expect(stats.orphanedTerms).to.equal(0)
|
|
158
|
+
})
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
describe('Database with Term Mapping', () => {
|
|
162
|
+
beforeEach(async () => {
|
|
163
|
+
db = new Database(testDbPath, {
|
|
164
|
+
indexes: { nameTerms: 'array:string', groupTerms: 'array:string' },
|
|
165
|
+
termMappingCleanup: true,
|
|
166
|
+
debugMode: false
|
|
167
|
+
})
|
|
168
|
+
await db.initialize()
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it('should insert records with term mapping', async () => {
|
|
172
|
+
const record = {
|
|
173
|
+
name: 'Test Record',
|
|
174
|
+
nameTerms: ['bra', 'globo', 'brasil'],
|
|
175
|
+
groupTerms: ['channel', 'discovery']
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const inserted = await db.insert(record)
|
|
179
|
+
expect(inserted.nameTerms).to.deep.equal(['bra', 'globo', 'brasil'])
|
|
180
|
+
expect(inserted.groupTerms).to.deep.equal(['channel', 'discovery'])
|
|
181
|
+
|
|
182
|
+
// Check that terms are mapped (order may vary)
|
|
183
|
+
const allTerms = db.termManager.getAllTerms()
|
|
184
|
+
expect(allTerms).toContain('bra')
|
|
185
|
+
expect(allTerms).toContain('globo')
|
|
186
|
+
expect(allTerms).toContain('brasil')
|
|
187
|
+
expect(allTerms).toContain('channel')
|
|
188
|
+
expect(allTerms).toContain('discovery')
|
|
189
|
+
|
|
190
|
+
// Check that we have the expected number of terms
|
|
191
|
+
expect(allTerms).to.have.length(5)
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it('should save and load with term mapping', async () => {
|
|
195
|
+
// Insert test data
|
|
196
|
+
await db.insert({
|
|
197
|
+
name: 'Record 1',
|
|
198
|
+
nameTerms: ['bra', 'globo'],
|
|
199
|
+
groupTerms: ['channel']
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
await db.insert({
|
|
203
|
+
name: 'Record 2',
|
|
204
|
+
nameTerms: ['brasil', 'discovery'],
|
|
205
|
+
groupTerms: ['channel', 'discovery']
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
await db.save()
|
|
209
|
+
|
|
210
|
+
// Small delay to ensure file operations are complete
|
|
211
|
+
await new Promise(resolve => setTimeout(resolve, 10))
|
|
212
|
+
|
|
213
|
+
// Create new database instance and load
|
|
214
|
+
const newDb = new Database(testDbPath, {
|
|
215
|
+
indexes: { nameTerms: 'array:string', groupTerms: 'array:string' },
|
|
216
|
+
termMappingCleanup: true,
|
|
217
|
+
debugMode: false
|
|
218
|
+
})
|
|
219
|
+
await newDb.initialize()
|
|
220
|
+
|
|
221
|
+
// Check that terms were loaded (order may vary)
|
|
222
|
+
const allTerms = newDb.termManager.getAllTerms()
|
|
223
|
+
expect(allTerms).toContain('bra')
|
|
224
|
+
expect(allTerms).toContain('globo')
|
|
225
|
+
expect(allTerms).toContain('channel')
|
|
226
|
+
expect(allTerms).toContain('brasil')
|
|
227
|
+
expect(allTerms).toContain('discovery')
|
|
228
|
+
|
|
229
|
+
// Check that data was loaded
|
|
230
|
+
const records = await newDb.find({})
|
|
231
|
+
expect(records).to.have.length(2)
|
|
232
|
+
expect(records[0].name).to.equal('Record 1')
|
|
233
|
+
expect(records[1].name).to.equal('Record 2')
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
it('should query with term mapping', async () => {
|
|
237
|
+
// Insert test data
|
|
238
|
+
await db.insert({
|
|
239
|
+
name: 'Record 1',
|
|
240
|
+
nameTerms: ['bra', 'globo'],
|
|
241
|
+
groupTerms: ['channel']
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
await db.insert({
|
|
245
|
+
name: 'Record 2',
|
|
246
|
+
nameTerms: ['brasil', 'discovery'],
|
|
247
|
+
groupTerms: ['channel', 'discovery']
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
// Query by term
|
|
251
|
+
const results1 = await db.find({ nameTerms: 'bra' })
|
|
252
|
+
console.log('TEST DEBUG: results1 =', results1, 'type:', typeof results1, 'isArray:', Array.isArray(results1))
|
|
253
|
+
expect(results1).to.have.length(1)
|
|
254
|
+
expect(results1[0].name).to.equal('Record 1')
|
|
255
|
+
|
|
256
|
+
const results2 = await db.find({ groupTerms: 'channel' })
|
|
257
|
+
expect(results2).to.have.length(2)
|
|
258
|
+
|
|
259
|
+
const results3 = await db.find({ nameTerms: 'nonexistent' })
|
|
260
|
+
expect(results3).to.have.length(0)
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
it('should update records with term mapping', async () => {
|
|
264
|
+
const record = await db.insert({
|
|
265
|
+
name: 'Test Record',
|
|
266
|
+
nameTerms: ['bra', 'globo'],
|
|
267
|
+
groupTerms: ['channel']
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
// Update with new terms
|
|
271
|
+
await db.update({ id: record.id }, {
|
|
272
|
+
nameTerms: ['brasil', 'discovery'],
|
|
273
|
+
groupTerms: ['discovery']
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
// Check that old terms are decremented and new terms are added
|
|
277
|
+
expect(db.termManager.termCounts.get(1)).to.equal(0) // 'bra' should be orphaned
|
|
278
|
+
expect(db.termManager.termCounts.get(2)).to.equal(0) // 'globo' should be orphaned
|
|
279
|
+
expect(db.termManager.termCounts.get(3)).to.equal(0) // 'channel' should be orphaned
|
|
280
|
+
expect(db.termManager.termCounts.get(4)).to.equal(1) // 'brasil' should be active
|
|
281
|
+
expect(db.termManager.termCounts.get(5)).to.equal(2) // 'discovery' should be active (used in both nameTerms and groupTerms)
|
|
282
|
+
|
|
283
|
+
// Query to verify update
|
|
284
|
+
const results = await db.find({ nameTerms: 'brasil' })
|
|
285
|
+
expect(results).to.have.length(1)
|
|
286
|
+
expect(results[0].nameTerms).to.deep.equal(['brasil', 'discovery'])
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
it('should delete records with term mapping', async () => {
|
|
290
|
+
const record = await db.insert({
|
|
291
|
+
name: 'Test Record',
|
|
292
|
+
nameTerms: ['bra', 'globo'],
|
|
293
|
+
groupTerms: ['channel']
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
await db.delete({ id: record.id })
|
|
297
|
+
|
|
298
|
+
// Check that terms are decremented
|
|
299
|
+
expect(db.termManager.termCounts.get(1)).to.equal(0) // 'bra' should be orphaned
|
|
300
|
+
expect(db.termManager.termCounts.get(2)).to.equal(0) // 'globo' should be orphaned
|
|
301
|
+
expect(db.termManager.termCounts.get(3)).to.equal(0) // 'channel' should be orphaned
|
|
302
|
+
|
|
303
|
+
// Verify record is deleted
|
|
304
|
+
const results = await db.find({ id: record.id })
|
|
305
|
+
expect(results).to.have.length(0)
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
it('should clean up orphaned terms on save', async () => {
|
|
309
|
+
// Insert and then delete to create orphaned terms
|
|
310
|
+
const record = await db.insert({
|
|
311
|
+
name: 'Test Record',
|
|
312
|
+
nameTerms: ['bra', 'globo'],
|
|
313
|
+
groupTerms: ['channel']
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
await db.delete({ id: record.id })
|
|
317
|
+
await db.save()
|
|
318
|
+
|
|
319
|
+
// Check that orphaned terms were cleaned up
|
|
320
|
+
expect(db.termManager.hasTerm('bra')).to.be.false
|
|
321
|
+
expect(db.termManager.hasTerm('globo')).to.be.false
|
|
322
|
+
expect(db.termManager.hasTerm('channel')).to.be.false
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
it('should handle complex queries with term mapping', async () => {
|
|
326
|
+
// Insert test data
|
|
327
|
+
await db.insert({
|
|
328
|
+
name: 'Record 1',
|
|
329
|
+
nameTerms: ['bra', 'globo'],
|
|
330
|
+
groupTerms: ['channel']
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
await db.insert({
|
|
334
|
+
name: 'Record 2',
|
|
335
|
+
nameTerms: ['brasil', 'discovery'],
|
|
336
|
+
groupTerms: ['channel', 'discovery']
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
await db.insert({
|
|
340
|
+
name: 'Record 3',
|
|
341
|
+
nameTerms: ['bra', 'brasil'],
|
|
342
|
+
groupTerms: ['discovery']
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
// Test $in queries
|
|
346
|
+
const results1 = await db.find({ nameTerms: { $in: ['bra', 'discovery'] } })
|
|
347
|
+
expect(results1).to.have.length(3)
|
|
348
|
+
|
|
349
|
+
// Test $all queries
|
|
350
|
+
const results2 = await db.find({ groupTerms: { $all: ['channel', 'discovery'] } })
|
|
351
|
+
expect(results2).to.have.length(1)
|
|
352
|
+
expect(results2[0].name).to.equal('Record 2')
|
|
353
|
+
|
|
354
|
+
// Test multiple criteria
|
|
355
|
+
const results3 = await db.find({
|
|
356
|
+
nameTerms: 'bra',
|
|
357
|
+
groupTerms: 'channel'
|
|
358
|
+
})
|
|
359
|
+
expect(results3).to.have.length(1)
|
|
360
|
+
expect(results3[0].name).to.equal('Record 1')
|
|
361
|
+
})
|
|
362
|
+
|
|
363
|
+
it('should maintain compatibility with non-term-mapping fields', async () => {
|
|
364
|
+
const record = await db.insert({
|
|
365
|
+
name: 'Test Record',
|
|
366
|
+
nameTerms: ['bra', 'globo'], // Term mapping field
|
|
367
|
+
groupTerms: ['channel'], // Term mapping field
|
|
368
|
+
category: 'news', // Non-term mapping field
|
|
369
|
+
tags: ['urgent', 'breaking'] // Non-term mapping field
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
// Query by non-term-mapping field
|
|
373
|
+
const results = await db.find({ category: 'news' })
|
|
374
|
+
expect(results).to.have.length(1)
|
|
375
|
+
expect(results[0].name).to.equal('Test Record')
|
|
376
|
+
|
|
377
|
+
// Query by tags (non-term-mapping array field)
|
|
378
|
+
const results2 = await db.find({ tags: 'urgent' })
|
|
379
|
+
expect(results2).to.have.length(1)
|
|
380
|
+
})
|
|
381
|
+
|
|
382
|
+
it('should handle empty term arrays', async () => {
|
|
383
|
+
const record = await db.insert({
|
|
384
|
+
name: 'Test Record',
|
|
385
|
+
nameTerms: [],
|
|
386
|
+
groupTerms: ['channel']
|
|
387
|
+
})
|
|
388
|
+
|
|
389
|
+
// Query should work with empty arrays
|
|
390
|
+
const results = await db.find({ nameTerms: 'bra' })
|
|
391
|
+
expect(results).to.have.length(0)
|
|
392
|
+
|
|
393
|
+
const results2 = await db.find({ groupTerms: 'channel' })
|
|
394
|
+
expect(results2).to.have.length(1)
|
|
395
|
+
})
|
|
396
|
+
|
|
397
|
+
it('should provide correct statistics', async () => {
|
|
398
|
+
await db.insert({
|
|
399
|
+
name: 'Record 1',
|
|
400
|
+
nameTerms: ['bra', 'globo'],
|
|
401
|
+
groupTerms: ['channel']
|
|
402
|
+
})
|
|
403
|
+
|
|
404
|
+
await db.insert({
|
|
405
|
+
name: 'Record 2',
|
|
406
|
+
nameTerms: ['brasil', 'discovery'],
|
|
407
|
+
groupTerms: ['channel', 'discovery']
|
|
408
|
+
})
|
|
409
|
+
|
|
410
|
+
const stats = db.getStats()
|
|
411
|
+
expect(stats.records).to.equal(2)
|
|
412
|
+
|
|
413
|
+
const termStats = db.termManager.getStats()
|
|
414
|
+
expect(termStats.totalTerms).to.equal(5) // bra, globo, brasil, discovery, channel
|
|
415
|
+
})
|
|
416
|
+
})
|
|
417
|
+
|
|
418
|
+
describe('Performance Tests', () => {
|
|
419
|
+
beforeEach(async () => {
|
|
420
|
+
db = new Database(testDbPath, {
|
|
421
|
+
indexes: { nameTerms: 'array:string', groupTerms: 'array:string' },
|
|
422
|
+
termMapping: true,
|
|
423
|
+
termMappingFields: ['nameTerms', 'groupTerms'],
|
|
424
|
+
debugMode: false
|
|
425
|
+
})
|
|
426
|
+
await db.initialize()
|
|
427
|
+
})
|
|
428
|
+
|
|
429
|
+
it('should handle large datasets efficiently', async () => {
|
|
430
|
+
const startTime = Date.now()
|
|
431
|
+
|
|
432
|
+
// OPTIMIZATION: Use InsertSession for batch operations
|
|
433
|
+
const session = db.beginInsertSession({
|
|
434
|
+
batchSize: 100,
|
|
435
|
+
enableAutoSave: false
|
|
436
|
+
})
|
|
437
|
+
|
|
438
|
+
// Insert 1000 records with term mapping in batches
|
|
439
|
+
for (let i = 0; i < 1000; i++) {
|
|
440
|
+
await session.add({
|
|
441
|
+
name: `Record ${i}`,
|
|
442
|
+
nameTerms: [`term${i % 100}`, `word${i % 50}`],
|
|
443
|
+
groupTerms: [`group${i % 20}`]
|
|
444
|
+
})
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
await session.commit()
|
|
448
|
+
const insertTime = Date.now() - startTime
|
|
449
|
+
console.log(`Inserted 1000 records in ${insertTime}ms`)
|
|
450
|
+
|
|
451
|
+
// Save the database
|
|
452
|
+
const saveStart = Date.now()
|
|
453
|
+
await db.save()
|
|
454
|
+
const saveTime = Date.now() - saveStart
|
|
455
|
+
console.log(`Saved database in ${saveTime}ms`)
|
|
456
|
+
|
|
457
|
+
// Query performance
|
|
458
|
+
const queryStart = Date.now()
|
|
459
|
+
const results = await db.find({ nameTerms: 'term1' })
|
|
460
|
+
const queryTime = Date.now() - queryStart
|
|
461
|
+
console.log(`Query completed in ${queryTime}ms, found ${results.length} results`)
|
|
462
|
+
|
|
463
|
+
expect(results.length).to.be.greaterThan(0)
|
|
464
|
+
expect(insertTime).to.be.lessThan(500) // OPTIMIZED: Should complete within 500ms
|
|
465
|
+
expect(saveTime).to.be.lessThan(1000) // OPTIMIZED: Save should be fast
|
|
466
|
+
expect(queryTime).to.be.lessThan(300) // OPTIMIZED: Query should be very fast
|
|
467
|
+
})
|
|
468
|
+
|
|
469
|
+
it('should save and load large datasets efficiently', async () => {
|
|
470
|
+
// OPTIMIZATION: Use InsertSession for batch operations
|
|
471
|
+
const session = db.beginInsertSession({
|
|
472
|
+
batchSize: 50,
|
|
473
|
+
enableAutoSave: false
|
|
474
|
+
})
|
|
475
|
+
|
|
476
|
+
// Insert test data in batches
|
|
477
|
+
for (let i = 0; i < 100; i++) {
|
|
478
|
+
await session.add({
|
|
479
|
+
name: `Record ${i}`,
|
|
480
|
+
nameTerms: [`term${i % 20}`, `word${i % 10}`],
|
|
481
|
+
groupTerms: [`group${i % 5}`]
|
|
482
|
+
})
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
await session.commit()
|
|
486
|
+
|
|
487
|
+
const saveStart = Date.now()
|
|
488
|
+
await db.save()
|
|
489
|
+
const saveTime = Date.now() - saveStart
|
|
490
|
+
console.log(`Saved database in ${saveTime}ms`)
|
|
491
|
+
|
|
492
|
+
// Create new database and load
|
|
493
|
+
const newDb = new Database(testDbPath, {
|
|
494
|
+
indexes: { nameTerms: 'array:string', groupTerms: 'array:string' },
|
|
495
|
+
debugMode: false
|
|
496
|
+
})
|
|
497
|
+
|
|
498
|
+
const loadStart = Date.now()
|
|
499
|
+
await newDb.initialize()
|
|
500
|
+
const loadTime = Date.now() - loadStart
|
|
501
|
+
console.log(`Loaded database in ${loadTime}ms`)
|
|
502
|
+
|
|
503
|
+
// Verify data integrity
|
|
504
|
+
const records = await newDb.find({})
|
|
505
|
+
expect(records).to.have.length(100)
|
|
506
|
+
|
|
507
|
+
const termStats = newDb.termManager.getStats()
|
|
508
|
+
expect(termStats.totalTerms).to.equal(35) // 20 + 10 + 5 unique terms
|
|
509
|
+
|
|
510
|
+
expect(saveTime).to.be.lessThan(1500) // OPTIMIZED: Save should be fast
|
|
511
|
+
expect(loadTime).to.be.lessThan(1000) // OPTIMIZED: Load should be very fast
|
|
512
|
+
})
|
|
513
|
+
})
|
|
514
|
+
})
|