From faff15a0a6366f4158e2f168e8b35be3589b44b1 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Tue, 27 Jan 2026 16:05:42 +0000 Subject: [PATCH 1/3] test: assert BTreeIndex.take handles undefined indexed values correctly (issue #1186) Co-Authored-By: Claude (claude-opus-4-5) --- .../db/tests/deterministic-ordering.test.ts | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/packages/db/tests/deterministic-ordering.test.ts b/packages/db/tests/deterministic-ordering.test.ts index f25e3db93..09027251f 100644 --- a/packages/db/tests/deterministic-ordering.test.ts +++ b/packages/db/tests/deterministic-ordering.test.ts @@ -109,6 +109,40 @@ describe(`Deterministic Ordering`, () => { }) describe(`BTreeIndex`, () => { + it(`should handle undefined indexed values with take and limit`, () => { + const index = new BTreeIndex( + 1, + new PropRef([`priority`]), + `priority_index`, + ) + + // Add items where priority is undefined + index.add(`a`, { priority: undefined }) + index.add(`b`, { priority: undefined }) + index.add(`c`, { priority: 1 }) + + // take() with a limit should return results without hanging + const keys = index.take(2) + expect(keys).toEqual([`a`, `b`]) + }) + + it(`should handle undefined indexed values with takeReversed and limit`, () => { + const index = new BTreeIndex( + 1, + new PropRef([`priority`]), + `priority_index`, + ) + + // Add items where priority is undefined + index.add(`a`, { priority: undefined }) + index.add(`b`, { priority: undefined }) + index.add(`c`, { priority: 1 }) + + // takeReversed() with a limit should return results without hanging + const keys = index.takeReversed(2) + expect(keys).toEqual([`c`, `b`]) + }) + it(`should return keys in deterministic order when indexed values are equal`, () => { const index = new BTreeIndex( 1, From 5ce232201b0c7ce6cd514311ffda9865f7abec78 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Tue, 27 Jan 2026 16:13:27 +0000 Subject: [PATCH 2/3] fix: prevent infinite loop in BTreeIndex.takeInternal when indexed value is undefined (issue #1186) Co-Authored-By: Claude (claude-opus-4-5) --- packages/db/src/indexes/btree-index.ts | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/packages/db/src/indexes/btree-index.ts b/packages/db/src/indexes/btree-index.ts index 2392fbcad..9a11b1bb2 100644 --- a/packages/db/src/indexes/btree-index.ts +++ b/packages/db/src/indexes/btree-index.ts @@ -6,6 +6,10 @@ import type { CompareOptions } from '../query/builder/types.js' import type { BasicExpression } from '../query/ir.js' import type { IndexOperation } from './base-index.js' +// Sentinel value to represent "start from beginning" in takeInternal, +// distinct from an actual undefined indexed value. +const START_ITERATION = Symbol(`START_ITERATION`) + /** * Options for Ordered index */ @@ -268,10 +272,23 @@ export class BTreeIndex< const keysInResult: Set = new Set() const result: Array = [] let pair: [any, any] | undefined - let key = normalizeValue(from) - - while ((pair = nextPair(key)) !== undefined && result.length < n) { - key = pair[0] + // Use a sentinel to distinguish "start from beginning" (when from is undefined) + // from "continue after the actual undefined key". The BTree's nextPair function + // treats undefined specially as "return min/max pair", so we need this + // distinction to avoid infinite loops when undefined is an actual key value. + let key: any = from === undefined ? START_ITERATION : normalizeValue(from) + + while ((pair = nextPair(key === START_ITERATION ? undefined : key)) !== undefined && result.length < n) { + const newKey = pair[0] + // When nextPair returns the same key we passed in, we've hit a cycle. + // This happens when the indexed value is undefined because: + // - nextPair(undefined) returns min/max pair instead of finding the next key + // - If the min/max key is also undefined, we get the same pair back + // In this case, we need to break out of the loop to prevent infinite iteration. + if (key !== START_ITERATION && newKey === key) { + break + } + key = newKey const keys = this.valueMap.get(key) if (keys && keys.size > 0) { // Sort keys for deterministic order, reverse if needed From fa8457c785eb118a9bcbfa83a3d9f62032bbcb55 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 27 Jan 2026 16:15:06 +0000 Subject: [PATCH 3/3] ci: apply automated fixes --- packages/db/src/indexes/btree-index.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/db/src/indexes/btree-index.ts b/packages/db/src/indexes/btree-index.ts index 9a11b1bb2..dbd34e83f 100644 --- a/packages/db/src/indexes/btree-index.ts +++ b/packages/db/src/indexes/btree-index.ts @@ -278,7 +278,11 @@ export class BTreeIndex< // distinction to avoid infinite loops when undefined is an actual key value. let key: any = from === undefined ? START_ITERATION : normalizeValue(from) - while ((pair = nextPair(key === START_ITERATION ? undefined : key)) !== undefined && result.length < n) { + while ( + (pair = nextPair(key === START_ITERATION ? undefined : key)) !== + undefined && + result.length < n + ) { const newKey = pair[0] // When nextPair returns the same key we passed in, we've hit a cycle. // This happens when the indexed value is undefined because: