@@ -6,6 +6,10 @@ import type { CompareOptions } from '../query/builder/types.js'
66import type { BasicExpression } from '../query/ir.js'
77import 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