Skip to content

Commit 5ce2322

Browse files
fix: prevent infinite loop in BTreeIndex.takeInternal when indexed value is undefined (issue #1186)
Co-Authored-By: Claude (claude-opus-4-5) <[email protected]>
1 parent faff15a commit 5ce2322

File tree

1 file changed

+21
-4
lines changed

1 file changed

+21
-4
lines changed

packages/db/src/indexes/btree-index.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ import type { CompareOptions } from '../query/builder/types.js'
66
import type { BasicExpression } from '../query/ir.js'
77
import type { IndexOperation } from './base-index.js'
88

9+
// Sentinel value to represent "start from beginning" in takeInternal,
10+
// distinct from an actual undefined indexed value.
11+
const START_ITERATION = Symbol(`START_ITERATION`)
12+
913
/**
1014
* Options for Ordered index
1115
*/
@@ -268,10 +272,23 @@ export class BTreeIndex<
268272
const keysInResult: Set<TKey> = new Set()
269273
const result: Array<TKey> = []
270274
let pair: [any, any] | undefined
271-
let key = normalizeValue(from)
272-
273-
while ((pair = nextPair(key)) !== undefined && result.length < n) {
274-
key = pair[0]
275+
// Use a sentinel to distinguish "start from beginning" (when from is undefined)
276+
// from "continue after the actual undefined key". The BTree's nextPair function
277+
// treats undefined specially as "return min/max pair", so we need this
278+
// distinction to avoid infinite loops when undefined is an actual key value.
279+
let key: any = from === undefined ? START_ITERATION : normalizeValue(from)
280+
281+
while ((pair = nextPair(key === START_ITERATION ? undefined : key)) !== undefined && result.length < n) {
282+
const newKey = pair[0]
283+
// When nextPair returns the same key we passed in, we've hit a cycle.
284+
// This happens when the indexed value is undefined because:
285+
// - nextPair(undefined) returns min/max pair instead of finding the next key
286+
// - If the min/max key is also undefined, we get the same pair back
287+
// In this case, we need to break out of the loop to prevent infinite iteration.
288+
if (key !== START_ITERATION && newKey === key) {
289+
break
290+
}
291+
key = newKey
275292
const keys = this.valueMap.get(key)
276293
if (keys && keys.size > 0) {
277294
// Sort keys for deterministic order, reverse if needed

0 commit comments

Comments
 (0)