83 lines
3.7 KiB
TypeScript
83 lines
3.7 KiB
TypeScript
|
|
/**
|
||
|
|
* @module tests/conformance/sparse-store-cut
|
||
|
|
* @description THE SPARSE-STORE CUT (ruled 2026-08-12) — the shared
|
||
|
|
* conformance rows both engines run: a WHERE filter naming a field NO row
|
||
|
|
* carries is SERVED OPERATOR-TRUTHFULLY, never refused:
|
||
|
|
* eq / in / range / contains → [] (nothing carries it → nothing matches)
|
||
|
|
* ne / exists:false → ALL rows (equally true — blanket-empty here
|
||
|
|
* would be the outlawed silent wrong)
|
||
|
|
* exists:true → []
|
||
|
|
* orderBy on an unresolvable field KEEPS the hard refusal (no truthful
|
||
|
|
* order exists). The did-you-mean demotes to a throttled WARN on the serve.
|
||
|
|
* A fresh tenant's first filtered read is a correct empty answer — the
|
||
|
|
* 341-red first-adopter class, closed.
|
||
|
|
*/
|
||
|
|
import { describe, it, expect, afterEach } from 'vitest'
|
||
|
|
import { Brainy, UnresolvableFieldError } from '../../src/index.js'
|
||
|
|
import { NounType } from '../../src/types/graphTypes.js'
|
||
|
|
|
||
|
|
const brains: Brainy[] = []
|
||
|
|
afterEach(async () => {
|
||
|
|
for (const b of brains.splice(0)) await b.close().catch(() => {})
|
||
|
|
})
|
||
|
|
|
||
|
|
async function corpus(): Promise<{ brain: Brainy; ids: string[] }> {
|
||
|
|
const b = new Brainy({ storage: { type: 'memory' }, requireSubtype: false })
|
||
|
|
await b.init()
|
||
|
|
brains.push(b)
|
||
|
|
const ids: string[] = []
|
||
|
|
for (let i = 0; i < 4; i++) {
|
||
|
|
ids.push(
|
||
|
|
await b.add({ data: `row ${i}`, type: NounType.Document, metadata: { carried: i } })
|
||
|
|
)
|
||
|
|
}
|
||
|
|
return { brain: b, ids }
|
||
|
|
}
|
||
|
|
|
||
|
|
describe('sparse-store cut — operator-truthful serve on never-carried fields', () => {
|
||
|
|
it('positive matches serve EMPTY: eq, in, range, contains', async () => {
|
||
|
|
const { brain } = await corpus()
|
||
|
|
expect(await brain.find({ where: { ghost: 'x' }, limit: 10 })).toEqual([])
|
||
|
|
expect(await brain.find({ where: { ghost: { in: ['a', 'b'] } }, limit: 10 })).toEqual([])
|
||
|
|
expect(await brain.find({ where: { ghost: { gt: 5 } }, limit: 10 })).toEqual([])
|
||
|
|
expect(await brain.find({ where: { ghost: { exists: true } }, limit: 10 })).toEqual([])
|
||
|
|
})
|
||
|
|
|
||
|
|
it('negative matches serve ALL rows: ne and exists:false (the truth, not blanket-empty)', async () => {
|
||
|
|
const { brain, ids } = await corpus()
|
||
|
|
const ne = await brain.find({ where: { ghost: { ne: 'x' } }, limit: 10 })
|
||
|
|
expect(ne.map((r) => r.id).sort()).toEqual([...ids].sort())
|
||
|
|
const absent = await brain.find({ where: { ghost: { exists: false } }, limit: 10 })
|
||
|
|
expect(absent.map((r) => r.id).sort()).toEqual([...ids].sort())
|
||
|
|
})
|
||
|
|
|
||
|
|
it('the fresh-tenant day-one shape: an EMPTY store answers its first filtered read with [], never a refusal', async () => {
|
||
|
|
const b = new Brainy({ storage: { type: 'memory' }, requireSubtype: false })
|
||
|
|
await b.init()
|
||
|
|
brains.push(b)
|
||
|
|
expect(await b.find({ where: { status: 'open' }, limit: 50 })).toEqual([])
|
||
|
|
expect(await b.find({ where: { date: { gte: '2026-01-01' } }, limit: 50 })).toEqual([])
|
||
|
|
})
|
||
|
|
|
||
|
|
it('orderBy on an unresolvable field KEEPS the typed refusal', async () => {
|
||
|
|
const { brain } = await corpus()
|
||
|
|
await expect(
|
||
|
|
brain.find({ where: { carried: { gte: 0 } }, orderBy: 'system.notAScalar', limit: 10 })
|
||
|
|
).rejects.toThrow(UnresolvableFieldError)
|
||
|
|
})
|
||
|
|
|
||
|
|
it('compound filters: the never-carried clause composes truthfully with carried clauses', async () => {
|
||
|
|
const { brain, ids } = await corpus()
|
||
|
|
// carried>=2 AND ghost ne 'x' → the carried>=2 rows (ne-clause = all).
|
||
|
|
const both = await brain.find({
|
||
|
|
where: { carried: { gte: 2 }, ghost: { ne: 'x' } },
|
||
|
|
limit: 10
|
||
|
|
})
|
||
|
|
expect(both.map((r) => r.id).sort()).toEqual([ids[2], ids[3]].sort())
|
||
|
|
// carried>=2 AND ghost eq 'x' → [] (eq-clause empties the intersection).
|
||
|
|
expect(
|
||
|
|
await brain.find({ where: { carried: { gte: 2 }, ghost: 'x' }, limit: 10 })
|
||
|
|
).toEqual([])
|
||
|
|
})
|
||
|
|
})
|