/** * Real-data performance test — uses exported applogs from a real note3 thread (47K applogs). * Tests real query patterns from note3's reactive.ts. */ import { describe, it, expect, beforeAll } from 'vitest' import { existsSync, readFileSync } from 'fs' import type { Applog } from '../../applog/datom-types.ts' import { sortApplogsByTs } from '../../applog/applog-utils.ts' import { ThreadInMemory } from '../../thread/writeable.ts' import { liveQuery, liveQueryNot, query, queryNot, queryAndMap, lastWriteWins, withoutDeleted } from '../../query/basic.ts' // ─── Real-data fixture ────────────────────────────────────────── // Opt-in: requires `tmp/real-applogs.jsonl` (an exported real note3 thread, // ~47K applogs). Suite is skipped when the fixture is missing. const REAL_APPLOGS_PATH = '/repo/tmp/real-applogs.jsonl' const hasRealData = existsSync(REAL_APPLOGS_PATH) describe.skipIf(!hasRealData)('real note3 data — query performance', () => { let raw: Applog[] let db: ThreadInMemory let lww: ReturnType let clean: ReturnType beforeAll(() => { const lines = readFileSync(REAL_APPLOGS_PATH, 'utf-8').trim().split('\n') raw = lines.map(l => JSON.parse(l)) as Applog[] sortApplogsByTs(raw) console.log(`\n [REAL] Loaded ${raw.length.toLocaleString()} real applogs from note3 thread (sorted)`) const attrCounts = new Map() for (const log of raw) { attrCounts.set(log.at, (attrCounts.get(log.at) || 0) + 1) } const top = [...attrCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 15) console.log(` [REAL] Top attributes:`, top.map(([at, n]) => `${at}(${n})`).join(', ')) }) it('setup: load + lastWriteWins + withoutDeleted', () => { const t0 = performance.now() db = ThreadInMemory.fromArray([...raw], 'real-note3') const loadTime = performance.now() - t0 const t1 = performance.now() lww = lastWriteWins(db) const lwwTime = performance.now() - t1 const t2 = performance.now() clean = withoutDeleted(lww) const wodTime = performance.now() - t2 console.log(` [REAL] Load: ${loadTime.toFixed(1)}ms (${db.size.toLocaleString()} applogs)`) console.log(` [REAL] lastWriteWins: ${lwwTime.toFixed(1)}ms (${db.size.toLocaleString()} → ${lww.size.toLocaleString()})`) console.log(` [REAL] withoutDeleted: ${wodTime.toFixed(1)}ms (${lww.size.toLocaleString()} → ${clean.size.toLocaleString()})`) }) // ── Real query 1: all blocks ─────────────────────────────── it('all blocks with content (1-step)', () => { const t0 = performance.now() const blocks = query(clean, [ { en: '?blockID', at: 'block/content' }, ]) const elapsed = performance.now() - t0 console.log(` [REAL] all blocks: ${elapsed.toFixed(3)}ms → ${blocks.size} blocks`) }) // ── Real query 2: useParents (2-step) ────────────────────── it('useParents (2-step): find parent of a block', () => { // Find a block that actually has a parent const relations = query(clean, [ { en: '?relID', at: 'relation/block', vl: '?blockID' }, { en: '?relID', at: 'relation/childOf', vl: '?parentID' }, ]) console.log(` [REAL] total relations: ${relations.size}`) if (relations.size > 0) { const blockID = relations.records[0].blockID const t0 = performance.now() const parents = queryAndMap(clean, [ { en: '?relID', at: 'relation/block', vl: blockID }, { en: '?relID', at: 'relation/childOf', vl: '?parentID' }, ], 'parentID') const elapsed = performance.now() - t0 console.log(` [REAL] useParents (block=${blockID}): ${elapsed.toFixed(3)}ms → ${(parents as any[]).length} parents`) } }) // ── Real query 3: useRoots (1-step + queryNot) ───────────── it('useRoots: all blocks, then queryNot(has parent)', () => { const t0 = performance.now() const blocks = query(clean, [ { en: '?blockID', at: 'block/content' }, ]) const queryTime = performance.now() - t0 const t1 = performance.now() // Single-step suffices: relation entities are deleted when unparenting, // so any relation/block pointing to blockID means it has a parent. const roots = queryNot(clean, blocks, { en: '?relID', at: 'relation/block', vl: '?blockID' }) const notTime = performance.now() - t1 console.log(` [REAL] useRoots — query blocks: ${queryTime.toFixed(3)}ms → ${blocks.size} blocks`) console.log(` [REAL] useRoots — queryNot(parent): ${notTime.toFixed(3)}ms → ${roots.size} roots (of ${blocks.size} blocks)`) // Debug: if roots == 0 or roots == blocks, investigate if (roots.size === 0 || roots.size === blocks.size) { // Check how many blocks actually have relations const blockIDs = blocks.records.map(r => r.blockID) as string[] const relBlocks = query(clean, [ { en: '?relID', at: 'relation/block', vl: blockIDs.slice(0, 5) }, ]) console.log(` [REAL] DEBUG: first 5 blocks have ${relBlocks.size} relations`) // Check what relation/childOf values exist const childOfs = query(clean, [{ at: 'relation/childOf' }]) console.log(` [REAL] DEBUG: total relation/childOf applogs: ${childOfs.size}`) if (childOfs.size > 0) { const sample = childOfs.nodes.slice(0, 3).map(n => `en=${n.logsOfThisNode.applogs[0]?.en} vl=${n.logsOfThisNode.applogs[0]?.vl}`) console.log(` [REAL] DEBUG: sample childOf:`, sample) } } }) // ── Real query 4: 3-step query ──────────────────────────── it('3-step: block → relation → parent name', () => { const t0 = performance.now() const result = query(clean, [ { en: '?blockID', at: 'block/content', vl: '?content' }, { en: '?relID', at: 'relation/block', vl: '?blockID' }, { en: '?relID', at: 'relation/childOf', vl: '?parentID' }, ]) const elapsed = performance.now() - t0 console.log(` [REAL] 3-step (block→relation→parent): ${elapsed.toFixed(3)}ms → ${result.size} results`) }) // ── Live: useRoots reactive + insert ────────────────────── it('liveQuery useRoots + insert', () => { const t0 = performance.now() const liveBlocks = liveQuery(clean, [ { en: '?blockID', at: 'block/content' }, ]) const setupTime = performance.now() - t0 const initialBlocks = liveBlocks.size const t1 = performance.now() // Single-step: relation entities are deleted on unparent (see useRoots comment above) const liveRoots = liveQueryNot(clean, liveBlocks, { en: '?relID', at: 'relation/block', vl: '?blockID' }) const notSetupTime = performance.now() - t1 const initialRoots = liveRoots.size console.log(` [REAL] liveQuery blocks setup: ${setupTime.toFixed(1)}ms → ${initialBlocks} blocks`) console.log(` [REAL] liveQueryNot roots setup: ${notSetupTime.toFixed(1)}ms → ${initialRoots} roots`) // Insert a new block const t2 = performance.now() db.insert([{ en: 'new-block-perf', at: 'block/content', vl: 'Perf test block', ag: 'perf-test' }]) const insertBlockTime = performance.now() - t2 console.log(` [REAL] Insert block: ${insertBlockTime.toFixed(3)}ms`) console.log(` [REAL] Blocks: ${initialBlocks} → ${liveBlocks.size}`) console.log(` [REAL] Roots: ${initialRoots} → ${liveRoots.size}`) // Insert a relation for the new block (should remove from roots) // Note: vl must be a real parent ID (string), not null. // queryNot treats each pattern independently — step 1 ({relation/block, vl: ?blockID}) // does the actual exclusion. vl:null in step 2 is a no-op (matches only literal null). const rootsBefore = liveRoots.size const t3 = performance.now() db.insert([ { en: 'rel-perf-1', at: 'relation/block', vl: 'new-block-perf', ag: 'perf-test' }, { en: 'rel-perf-1', at: 'relation/childOf', vl: 'some-parent-id', ag: 'perf-test' }, ]) const insertRelTime = performance.now() - t3 console.log(` [REAL] Insert parent relation: ${insertRelTime.toFixed(3)}ms`) console.log(` [REAL] Roots: ${rootsBefore} → ${liveRoots.size}`) expect(liveRoots.size).toBe(rootsBefore - 1) liveRoots.dispose() liveBlocks.dispose() }) }, { timeout: 60_000 })