diff --git a/hbrdd/dbf/indexer.go b/hbrdd/dbf/indexer.go index d65428c..8050505 100644 --- a/hbrdd/dbf/indexer.go +++ b/hbrdd/dbf/indexer.go @@ -412,6 +412,38 @@ func (a *DBFArea) Seek(key hbrt.Value, softSeek bool, findLast bool) (bool, erro if exactFound && recNo > 0 { a.GoTo(recNo) a.FEof = false + // SET DELETED ON: if found record is deleted, skip to next non-deleted with same key + if hbrdd.IsSetDeleted != nil && hbrdd.IsSetDeleted() && a.Deleted() { + // Skip forward through deleted records + for { + idx.SkipNext() + if idx.IsEOF() { + break + } + // Check if key still matches (partial or full) + curKey := idx.CurKey() + if actualLen < keyLen { + if !bytes.Equal(curKey[:actualLen], searchKey[:actualLen]) { + break + } + } else { + if !bytes.Equal(curKey, searchKey) { + break + } + } + a.GoTo(idx.CurRecNo()) + if !a.Deleted() { + a.SetFound(true) + return true, nil + } + } + // All matching records are deleted + rc, _ := a.RecCount() + a.GoTo(rc + 1) + a.FEof = true + a.SetFound(false) + return false, nil + } a.SetFound(true) return true, nil } diff --git a/hbrdd/ntx/ntx.go b/hbrdd/ntx/ntx.go index df74acc..b41bae5 100644 --- a/hbrdd/ntx/ntx.go +++ b/hbrdd/ntx/ntx.go @@ -244,6 +244,10 @@ func (idx *Index) Seek(searchKey []byte) (uint32, bool) { pageOffset := int64(idx.header.Root) // Phase 1: Traverse from root to leaf + // Harbour: always descend to leaf, even if match found in internal page. + // This ensures SEEK finds the FIRST (lowest RecNo) occurrence of duplicate keys. + // fStop tracks whether any page had an exact match along the path. + fStop := false for { page, err := LoadPage(idx.file, pageOffset) if err != nil { @@ -252,6 +256,9 @@ func (idx *Index) Seek(searchKey []byte) (uint32, bool) { } iKey, found := idx.pageKeyFind(page, searchKey, false, 0) + if found { + fStop = true + } // Push onto stack if idx.stackLevel < StackSize { @@ -262,24 +269,33 @@ func (idx *Index) Seek(searchKey []byte) (uint32, bool) { idx.stackLevel++ } - if found { - // Exact match found at this page - idx.curRecNo = page.KeyRecNo(iKey) - copy(idx.curKey, page.KeyValue(iKey, idx.keyLen)) - return idx.curRecNo, true - } - - // Follow child pointer + // Follow child pointer (always descend, even on match) childOffset := page.KeyChild(iKey) if childOffset == 0 { - // At leaf — no exact match - // Position at this key (next higher) for SOFTSEEK + // At leaf — check if the current position has a matching key if iKey < int(page.keyCount) { idx.curRecNo = page.KeyRecNo(iKey) copy(idx.curKey, page.KeyValue(iKey, idx.keyLen)) + // Check if this leaf key actually matches the search key + leafMatch := (bytes.Compare(searchKey, page.KeyValue(iKey, len(searchKey))) == 0) + if leafMatch || fStop { + // Verify it's a real match by comparing actual key content + if bytes.Compare(idx.curKey[:len(searchKey)], searchKey[:len(searchKey)]) == 0 { + return idx.curRecNo, true + } + } } else { // Past end of page — try next via stack - if idx.nextKey() { + if fStop { + // We matched on an internal page but descended to wrong child + // The match is the parent separator — go back via nextKey + if idx.nextKey() { + // Check if nextKey is actually a match + if bytes.Compare(idx.curKey[:len(searchKey)], searchKey[:len(searchKey)]) == 0 { + return idx.curRecNo, true + } + } + } else if idx.nextKey() { return idx.curRecNo, false } idx.tagEOF = true