mirror of
https://github.com/morluto/rea.git
synced 2026-10-31 03:45:39 +00:00
fix(environment): stop identity checks at the first missing or repeated value
identifies built every value before checking, so a wide first item followed by many sparse items cost a full scan per suffix-named field. It now returns at the first missing or repeated value; a regression test counts reads over a 2,000-field item and 5,000 empty ones. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
aa48c746d8
commit
5bb7ca421c
2 files changed
+41
-6
No files matched your search
@@ -398,11 +398,14 @@ export const identifies = (
|
||||
items: readonly JsonObject[],
|
||||
name: string,
|
||||
): boolean => {
|
||||
const values = items.map((item) => scalarText(item[name]));
|
||||
return (
|
||||
values.every((value) => value !== undefined) &&
|
||||
new Set(values).size === values.length
|
||||
);
|
||||
// Stop at the first missing or repeated value, so a sparse list costs one read per field.
|
||||
const seen = new Set<string>();
|
||||
for (const item of items) {
|
||||
const value = scalarText(item[name]);
|
||||
if (value === undefined || seen.has(value)) return false;
|
||||
seen.add(value);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/** Fields ending in one of these may identify listed items that no identity name identifies. */
|
||||
|
||||
@@ -17,7 +17,12 @@ import {
|
||||
type AnalysisViewResult,
|
||||
} from "./analysisView.js";
|
||||
import { EnvironmentStateAccumulator } from "./EnvironmentStateAccumulator.js";
|
||||
import { at, replaceAt } from "./environmentStateRules.js";
|
||||
import {
|
||||
at,
|
||||
identityCandidates,
|
||||
replaceAt,
|
||||
type JsonObject,
|
||||
} from "./environmentStateRules.js";
|
||||
|
||||
type State = Extract<AnalysisViewResult, { kind: "environment-state" }>;
|
||||
const ALL_RULES = [
|
||||
@@ -1007,3 +1012,30 @@ it("reads only item totals and next-page markers as signs of a partial list", ()
|
||||
["recorded", []],
|
||||
]);
|
||||
});
|
||||
|
||||
it("reads a sparse list once per candidate field when inferring identity", () => {
|
||||
const fields = 2_000;
|
||||
const wide: JsonObject = Object.fromEntries(
|
||||
Array.from({ length: fields }, (_, index) => [
|
||||
`f${String(index)}Id`,
|
||||
index,
|
||||
]),
|
||||
);
|
||||
let reads = 0;
|
||||
const sparse = Array.from(
|
||||
{ length: 5_000 },
|
||||
() =>
|
||||
new Proxy<JsonObject>(
|
||||
{},
|
||||
{
|
||||
get: (target, key, receiver) => {
|
||||
reads += 1;
|
||||
return Reflect.get(target, key, receiver) as unknown;
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
expect(identityCandidates([wide, ...sparse])).toEqual([]);
|
||||
// Each identity name and suffix field stops at the first sparse item.
|
||||
expect(reads).toBeLessThanOrEqual(fields + 20);
|
||||
});
|
||||
Reference in new issue
Block a user