fix: implement exists: false and missing operators in MetadataIndexManager
Fixes critical bug where excludeVFS: true excluded ALL entities including
non-VFS entities created with brain.add(). The MetadataIndexManager's
getIdsForFilter() only implemented exists: true, missing the else clause
for exists: false.
Changes:
- Added exists: false implementation (returns all IDs minus field IDs)
- Added missing operator (for API consistency with metadataFilter.ts)
- Both operators now match in-memory metadataFilter.ts behavior
Root Cause:
- brainy.ts sets filter.vfsType = { exists: false } for excludeVFS
- metadataIndex.ts case 'exists' only checked if (operand) with no else
- Missing else clause caused empty fieldResults, returning []
Impact:
- Fixes excludeVFS feature (used by Workshop team)
- Fixes any queries using exists: false operator
- Adds missing operator support for completeness
Testing:
- Build: passing
- Tests: passing
- Manual test: verified excludeVFS correctly excludes VFS entities only
Reported by: Workshop Team (Soulcraft)
Issue: BRAINY_V5_7_7_EXCLUDEVFS_BUG.md
This commit is contained in:
parent
4c1b60e2b1
commit
b0f72ef36f
1 changed files with 77 additions and 1 deletions
|
|
@ -1618,7 +1618,8 @@ export class MetadataIndexManager {
|
||||||
// exists: boolean - check if field exists (any value)
|
// exists: boolean - check if field exists (any value)
|
||||||
case 'exists':
|
case 'exists':
|
||||||
if (operand) {
|
if (operand) {
|
||||||
// Get all IDs that have this field (any value) from chunked sparse index with roaring bitmaps (v3.43.0)
|
// exists: true - Get all IDs that have this field (any value)
|
||||||
|
// v3.43.0: From chunked sparse index with roaring bitmaps
|
||||||
// v3.44.1: Now fully lazy-loaded via UnifiedCache (no local sparseIndices Map)
|
// v3.44.1: Now fully lazy-loaded via UnifiedCache (no local sparseIndices Map)
|
||||||
const allIntIds = new Set<number>()
|
const allIntIds = new Set<number>()
|
||||||
|
|
||||||
|
|
@ -1640,6 +1641,81 @@ export class MetadataIndexManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert integer IDs back to UUIDs
|
// Convert integer IDs back to UUIDs
|
||||||
|
fieldResults = this.idMapper.intsIterableToUuids(allIntIds)
|
||||||
|
} else {
|
||||||
|
// exists: false - Get all IDs that DON'T have this field
|
||||||
|
// v5.7.9: Fixed excludeVFS bug (was returning empty array)
|
||||||
|
const allItemIds = await this.getAllIds()
|
||||||
|
const existsIntIds = new Set<number>()
|
||||||
|
|
||||||
|
// Get IDs that HAVE this field
|
||||||
|
const sparseIndex = await this.loadSparseIndex(field)
|
||||||
|
if (sparseIndex) {
|
||||||
|
for (const chunkId of sparseIndex.getAllChunkIds()) {
|
||||||
|
const chunk = await this.chunkManager.loadChunk(field, chunkId)
|
||||||
|
if (chunk) {
|
||||||
|
for (const bitmap of chunk.entries.values()) {
|
||||||
|
for (const intId of bitmap) {
|
||||||
|
existsIntIds.add(intId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to UUIDs and subtract from all IDs
|
||||||
|
const existsUuids = this.idMapper.intsIterableToUuids(existsIntIds)
|
||||||
|
const existsSet = new Set(existsUuids)
|
||||||
|
fieldResults = allItemIds.filter(id => !existsSet.has(id))
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
// ===== MISSING OPERATOR =====
|
||||||
|
// missing: boolean - equivalent to exists: !boolean (for compatibility with metadataFilter.ts)
|
||||||
|
case 'missing':
|
||||||
|
// missing: true is equivalent to exists: false
|
||||||
|
// missing: false is equivalent to exists: true
|
||||||
|
// v5.7.9: Added for API consistency with in-memory metadataFilter
|
||||||
|
if (operand) {
|
||||||
|
// missing: true - field does NOT exist (same as exists: false)
|
||||||
|
const allItemIds = await this.getAllIds()
|
||||||
|
const existsIntIds = new Set<number>()
|
||||||
|
|
||||||
|
const sparseIndex = await this.loadSparseIndex(field)
|
||||||
|
if (sparseIndex) {
|
||||||
|
for (const chunkId of sparseIndex.getAllChunkIds()) {
|
||||||
|
const chunk = await this.chunkManager.loadChunk(field, chunkId)
|
||||||
|
if (chunk) {
|
||||||
|
for (const bitmap of chunk.entries.values()) {
|
||||||
|
for (const intId of bitmap) {
|
||||||
|
existsIntIds.add(intId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const existsUuids = this.idMapper.intsIterableToUuids(existsIntIds)
|
||||||
|
const existsSet = new Set(existsUuids)
|
||||||
|
fieldResults = allItemIds.filter(id => !existsSet.has(id))
|
||||||
|
} else {
|
||||||
|
// missing: false - field DOES exist (same as exists: true)
|
||||||
|
const allIntIds = new Set<number>()
|
||||||
|
|
||||||
|
const sparseIndex = await this.loadSparseIndex(field)
|
||||||
|
if (sparseIndex) {
|
||||||
|
for (const chunkId of sparseIndex.getAllChunkIds()) {
|
||||||
|
const chunk = await this.chunkManager.loadChunk(field, chunkId)
|
||||||
|
if (chunk) {
|
||||||
|
for (const bitmap of chunk.entries.values()) {
|
||||||
|
for (const intId of bitmap) {
|
||||||
|
allIntIds.add(intId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fieldResults = this.idMapper.intsIterableToUuids(allIntIds)
|
fieldResults = this.idMapper.intsIterableToUuids(allIntIds)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue