mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-29 06:32:28 +00:00
LadybugDB 0.18.3 (shipped in 1.6.12 and 1.6.13-rc.57) misreads string
columns after a selective write. The CodeEmbedding table is affected by
the embedding writeback's own statements, without any COPY: after
per-node DELETE + CREATE and a checkpoint, 3,936 of 8,192 tuples scan
wrong and the {nodeId} lookup misses a retained row. On a released
build this makes unchanged nodes look new (re-embedding) and puts blank
or truncated ids into the CodeEmbedding key ("duplicated primary key").
The 0.21.1 engine on main fixes it.
- reproduce-search.cjs gains an `embedding` scenario using the
production CREATE / DELETE-by-nodeId statements; it fails on 0.18.3
and passes on 0.21.1. The vitest wrapper runs it with the other
scenarios; per-scenario expectations are now data, not branches.
- incremental-method-integrity runs both analyses with a deterministic
loopback embedding endpoint and asserts that only the edited file is
re-embedded and every other CodeEmbedding row (4,096+) is unchanged
from a cold reader.
Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
68 lines
3 KiB
TypeScript
68 lines
3 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { spawnSync } from 'node:child_process';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const reproducer = fileURLToPath(
|
|
new URL('../../bench/incremental-write-integrity/reproduce-search.cjs', import.meta.url),
|
|
);
|
|
interface Check {
|
|
name: string;
|
|
ok: boolean;
|
|
error?: string;
|
|
rows?: number;
|
|
wrong?: number;
|
|
missing?: number;
|
|
ids?: string[];
|
|
indexes?: number;
|
|
}
|
|
|
|
const scenarios = ['context', 'property', 'method', 'embedding'] as const;
|
|
const lookups = (id: string) =>
|
|
Object.fromEntries(['primary-key', 'uid', 'name'].map((name) => [name, { ids: [id] }]));
|
|
// Per-stage expectations for the row retained through every DELETE/write.
|
|
const retainedChecks: Record<(typeof scenarios)[number], Record<string, Partial<Check>>> = {
|
|
context: lookups('Function:src/owner2.ts:unchangedSwiftTarget64'),
|
|
method: lookups('Method:checks/test_owner2.py:Checks.test_retained_method_64#0'),
|
|
property: {
|
|
catalog: { indexes: 1 },
|
|
'fts-query': { ids: ['Property:src/owner2.swift:property64'] },
|
|
...Object.fromEntries(
|
|
['name', 'content', 'description'].map((column) => [`lower-${column}`, { rows: 8192 }]),
|
|
),
|
|
},
|
|
// Stale-row DELETE and cached-hash lookups address embeddings by owner node id.
|
|
embedding: { 'node-id': { ids: ['Method:src/main/java/acme/Owner2.java:Owner2.method64#0:0'] } },
|
|
};
|
|
|
|
describe('native incremental search (#3421, #3423, #3564, #3569, #3474)', () => {
|
|
for (const scenario of scenarios) {
|
|
it(`preserves retained ${scenario} strings and search through COPY, checkpoint and reopen`, () => {
|
|
// Own process: isolate native lifetimes and allow old-engine diagnostic runs.
|
|
const result = spawnSync(process.execPath, [reproducer, scenario], {
|
|
encoding: 'utf8',
|
|
timeout: 120_000,
|
|
maxBuffer: 2 * 1024 * 1024,
|
|
});
|
|
expect(result.error, result.stderr).toBeUndefined();
|
|
expect(result.signal, result.stderr).toBeNull();
|
|
expect(result.status, result.stdout + result.stderr).toBe(0);
|
|
const report = JSON.parse(result.stdout.trim()) as { checks: Check[] };
|
|
expect(report.checks.filter((check) => !check.ok)).toEqual([]);
|
|
const check = (name: string) => report.checks.find((entry) => entry.name === name);
|
|
expect(check('after-delete:scan')).toMatchObject({ rows: 8128, wrong: 0, missing: 0 });
|
|
const stages = ['baseline', 'after-copy', 'after-checkpoint', 'reopened'];
|
|
if (scenario === 'property') stages.push('repair');
|
|
for (const stage of stages) {
|
|
expect(check(`${stage}:scan`)).toMatchObject({ rows: 8192, wrong: 0, missing: 0 });
|
|
for (const [name, expected] of Object.entries(retainedChecks[scenario])) {
|
|
expect(check(`${stage}:${name}`), name).toMatchObject(expected);
|
|
}
|
|
}
|
|
if (scenario === 'property') {
|
|
for (const name of ['baseline:build', 'after-copy:build', 'repair:drop', 'repair:build']) {
|
|
expect(check(name)?.ok).toBe(true);
|
|
}
|
|
}
|
|
}, 150_000);
|
|
}
|
|
});
|