fix(find): the projection seam is ES-private, and document the projection
TypeScript's `private` is compile-time only, so the seam's helpers were real prototype methods and the generated contract manifest listed them as public DOORS — which would have obliged every other engine to implement an internal detail. They are `#`-private now and the manifest is unchanged by this branch. Found while checking that: docs/api-contract.json was ALREADY stale at v10.4.11 — promoteQueuedFlush and startFlushLeader are in src and absent from the manifest, so they leaked the same way and were never re-emitted. Left alone here rather than folded into this branch; it is someone's to fix deliberately, and the fix is the same # conversion. docs/FIND_SYSTEM.md gains the projection: the rules, why a missing field is absent rather than an error, where the values come from and what a field the column cannot serve costs.
This commit is contained in:
parent
ad0f493f7a
commit
be77a10bfe
3 changed files with 117 additions and 29 deletions
|
|
@ -172,21 +172,32 @@ describe('find/get({ fields }) — projection', () => {
|
|||
expect(reads).toBe(0)
|
||||
})
|
||||
|
||||
it('reads records only for the rows that owe an un-served field', async () => {
|
||||
// `body` is not a scalar the index serves, so the record must be opened —
|
||||
// but the projection still returns only the named fields.
|
||||
it('reads records only for the fields the column cannot serve', async () => {
|
||||
// `system.data` is NOT a column the store holds (verified against
|
||||
// getIndexedFields), so the record must be opened for it — while `title`,
|
||||
// which the column does hold, still comes from the index.
|
||||
const { out, reads } = await countingReads(() =>
|
||||
brain.find({ where: { kind: 'post' }, fields: ['title', 'body'], limit: 4 })
|
||||
brain.find({ where: { kind: 'post' }, fields: ['title', 'system.data'], limit: 4 })
|
||||
)
|
||||
expect(out).toHaveLength(4)
|
||||
expect(reads).toBe(4)
|
||||
for (const r of out) {
|
||||
const meta = (r.entity.metadata ?? {}) as Record<string, unknown>
|
||||
expect(meta.body).toBe(BODY)
|
||||
expect(Object.keys(meta).sort()).toEqual(['body', 'title'])
|
||||
expect(Object.keys(meta)).toEqual(['title'])
|
||||
expect(typeof (r.entity as any).data).toBe('string')
|
||||
}
|
||||
})
|
||||
|
||||
it('a large field the column DOES hold costs no record read', async () => {
|
||||
// Worth pinning because it is the venue case: the body is column-served on
|
||||
// this engine, so a list that projects around it pays nothing for it, and
|
||||
// a list that projects it still pays no record read.
|
||||
const { reads } = await countingReads(() =>
|
||||
brain.find({ where: { kind: 'post' }, fields: ['body'], limit: 4 })
|
||||
)
|
||||
expect(reads).toBe(0)
|
||||
})
|
||||
|
||||
it('get({ fields }) projects a single row through the same seam', async () => {
|
||||
const full = await brain.get(ids[0])
|
||||
const projected = await brain.get(ids[0], { fields: ['title', 'slug'] })
|
||||
|
|
@ -205,20 +216,32 @@ describe('find/get({ fields }) — projection', () => {
|
|||
expect(reads).toBe(0)
|
||||
})
|
||||
|
||||
it('the provider door serves only what it can serve EXACTLY', async () => {
|
||||
// The bucketed timestamps are indexed at minute precision for range
|
||||
// queries. The door must omit them rather than hand back a bucket that
|
||||
// differs from the record — omission costs a read, a wrong value is a wrong
|
||||
// answer nobody can see.
|
||||
it('the door serves EXACT values — the column, never the bucketed index', async () => {
|
||||
// The sparse index buckets `system.createdAt` to the minute for range
|
||||
// queries; the column store keeps raw ms. Serving a projection from the
|
||||
// former would hand back a value that differs from the record's, so the
|
||||
// door reads the column — and this pin is what proves which one it read.
|
||||
const index = (brain as any).metadataIndex
|
||||
const served = await index.getScalarsForIds(ids.slice(0, 3), [
|
||||
'title',
|
||||
'system.createdAt'
|
||||
])
|
||||
expect(served.size).toBeGreaterThan(0)
|
||||
const sample = ids.slice(0, 3)
|
||||
const served = await index.getScalarsForIds(sample, ['title', 'system.createdAt'])
|
||||
expect(served.size).toBe(sample.length)
|
||||
for (const id of sample) {
|
||||
const row = served.get(id)!
|
||||
const record = await brain.get(id)
|
||||
expect(row.title).toEqual((record!.metadata as any).title)
|
||||
// Exact to the millisecond — a bucketed value would be rounded down to
|
||||
// the minute and this would fail.
|
||||
expect(row['system.createdAt']).toEqual((record as any).createdAt)
|
||||
}
|
||||
})
|
||||
|
||||
it('a field the column store does not hold is OMITTED, not approximated', async () => {
|
||||
const index = (brain as any).metadataIndex
|
||||
const served = await index.getScalarsForIds(ids.slice(0, 2), ['title', 'system.data'])
|
||||
for (const [, row] of served) {
|
||||
expect('title' in row).toBe(true)
|
||||
expect('system.createdAt' in row).toBe(false)
|
||||
// Omission is what makes the caller read the record for it.
|
||||
expect('system.data' in row).toBe(false)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Reference in a new issue