Skip to content

Commit 8315c0f

Browse files
committed
test(cli): the unmapped-columns reader double holds the caller's bound
check:objectql-double-limit could not seat the double: it read only the id seek. It now implements equality and $gt, refuses any other operator, applies limit by presence after the filter, and projects only when asked. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016GiHYRmLSNWTfbX9gVQkpz
1 parent d5b349d commit 8315c0f

1 file changed

Lines changed: 18 additions & 9 deletions

File tree

‎packages/cli/src/commands/migrate/unmapped-columns.test.ts‎

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,22 +49,31 @@ function finding(partial: Partial<ManagedDriftEntry> & { kind: ManagedDriftEntry
4949
/**
5050
* A reader over in-memory rows that honours what the keyset walk asks of a
5151
* driver: the `id > cursor` seek, ascending order, the page limit and the
52-
* projection. Every query it was asked is recorded.
52+
* projection. A condition is an equality or a `$gt`; any other operator is
53+
* refused rather than read as a match. Every query it was asked is recorded.
5354
*/
5455
function rowsReader(rows: Array<Record<string, unknown>>): UnmappedColumnReader & { queries: Array<Record<string, any>> } {
5556
const queries: Array<Record<string, any>> = [];
57+
const holds = (row: Record<string, unknown>, key: string, cond: unknown): boolean => {
58+
if (cond !== null && typeof cond === 'object') {
59+
const ops = Object.keys(cond);
60+
if (ops.length === 1 && ops[0] === '$gt') return String(row[key]) > String((cond as { $gt: unknown }).$gt);
61+
throw new Error(`this double implements equality and $gt only, not ${JSON.stringify(cond)}`);
62+
}
63+
return row[key] === cond;
64+
};
5665
return {
5766
queries,
5867
async find(_object, query) {
5968
queries.push(query);
60-
const where = query.where as { id?: { $gt?: string } } | undefined;
61-
const after = where?.id?.$gt;
62-
const fields = query.fields as string[];
63-
return rows
64-
.filter((r) => after === undefined || String(r.id) > after)
65-
.sort((a, b) => String(a.id).localeCompare(String(b.id)))
66-
.slice(0, query.limit as number)
67-
.map((r) => Object.fromEntries(fields.filter((f) => f in r).map((f) => [f, r[f]])));
69+
const where = (query?.where ?? {}) as Record<string, unknown>;
70+
const matched = rows
71+
.filter((r) => Object.entries(where).every(([key, cond]) => holds(r, key, cond)))
72+
.sort((a, b) => String(a.id).localeCompare(String(b.id)));
73+
// The caller's bound, by presence, after the filter: a driver applies it there too.
74+
const page = typeof query?.limit === 'number' ? matched.slice(0, query.limit) : matched;
75+
const fields = Array.isArray(query?.fields) ? (query.fields as string[]) : null;
76+
return fields ? page.map((r) => Object.fromEntries(fields.filter((f) => f in r).map((f) => [f, r[f]]))) : page;
6877
},
6978
};
7079
}

0 commit comments

Comments
 (0)