diff --git a/hbrdd/dbf/indexer.go b/hbrdd/dbf/indexer.go index c60fdc4..d65428c 100644 --- a/hbrdd/dbf/indexer.go +++ b/hbrdd/dbf/indexer.go @@ -569,7 +569,6 @@ func (a *DBFArea) SkipIndexed(count int64) error { for i := int64(0); i > count; i-- { idx.SkipPrev() if idx.IsBOF() { - a.FBof = true // Stay at first record in scope if a.idxState.scopeTop != nil { idx.Seek(a.idxState.scopeTop) @@ -577,9 +576,12 @@ func (a *DBFArea) SkipIndexed(count int64) error { idx.GoTop() } if !idx.IsEOF() { - return a.GoTo(idx.CurRecNo()) + a.GoTo(idx.CurRecNo()) + } else { + a.GoTo(1) } - return a.GoTo(1) + a.FBof = true // set AFTER GoTo (GoTo resets FBof) + return nil } // Check top scope if hasScope && a.idxState.scopeTop != nil { diff --git a/hbrdd/ntx/build.go b/hbrdd/ntx/build.go index 6372c14..755e662 100644 --- a/hbrdd/ntx/build.go +++ b/hbrdd/ntx/build.go @@ -325,39 +325,31 @@ func (idx *Index) insertKeyBTree(key []byte, recNo uint32) error { } // Split propagated to root — create new root + // Harbour: new page with promoted key at pos 0 + // child[0] = promoteChild (newPage = LEFT half) + // child[1] = old root (RIGHT half) newRootOff := int64(idx.header.NextPage) idx.header.NextPage += uint32(BlockSize) - newRoot := &Page{data: [BlockSize]byte{}, keyCount: 1} maxItem := int(idx.header.MaxItem) itemSize := int(idx.header.ItemSize) dataStart := 2 + (maxItem+1)*2 - binary.LittleEndian.PutUint16(newRoot.data[0:2], 1) - // Initialize offset table for all slots + newRoot := &Page{data: [BlockSize]byte{}, keyCount: 0} for i := 0; i <= maxItem; i++ { binary.LittleEndian.PutUint16(newRoot.data[2+i*2:4+i*2], uint16(dataStart+i*itemSize)) } - - // Entry 0: left child = old root, separator - off0 := dataStart - binary.LittleEndian.PutUint16(newRoot.data[2:4], uint16(off0)) - binary.LittleEndian.PutUint32(newRoot.data[off0:off0+4], idx.header.Root) // old root - binary.LittleEndian.PutUint32(newRoot.data[off0+4:off0+8], promoteRecNo) - copy(newRoot.data[off0+8:off0+8+idx.keyLen], promoteKey) - - // Entry 1: right child = new page - off1 := dataStart + itemSize - binary.LittleEndian.PutUint16(newRoot.data[4:6], uint16(off1)) - binary.LittleEndian.PutUint32(newRoot.data[off1:off1+4], promoteChild) + // Insert promoted key at position 0 with child = promoteChild (left half) + idx.pageInsertKey(newRoot, 0, promoteKey, promoteRecNo, promoteChild) + // Set trailing child (position 1) = old root (right half) + trailOff := int(newRoot.keyOffset(1)) + binary.LittleEndian.PutUint32(newRoot.data[trailOff:trailOff+4], idx.header.Root) newRoot.writeTo(idx.file, newRootOff) idx.header.Root = uint32(newRootOff) - // Update header - f := idx.file - f.Seek(0, 0) - WriteHeader(f, &idx.header) + idx.file.Seek(0, 0) + WriteHeader(idx.file, &idx.header) return nil } @@ -416,91 +408,119 @@ func (idx *Index) pageInsertKey(page *Page, iKey int, key []byte, recNo uint32, binary.LittleEndian.PutUint16(page.data[0:2], page.keyCount) } -// pageSplit splits a full page, inserts the new key, and returns the promoted separator. +// pageSplit splits a full page — exact port of Harbour hb_ntxPageSplit. +// NewPage = LEFT (lower half), OldPage = RIGHT (upper half, offset-swapped). +// Returns: promoted key, its recNo, and newPage offset (left child of promoted key). func (idx *Index) pageSplit(page *Page, iKey int, key []byte, recNo uint32, childPage uint32, pageOff int64) ([]byte, uint32, uint32, error) { maxItem := int(idx.header.MaxItem) itemSize := int(idx.header.ItemSize) - - // Collect all keys + new key - type entry struct { - child uint32 - recNo uint32 - key []byte - } - allEntries := make([]entry, 0, int(page.keyCount)+1) - - for i := 0; i < int(page.keyCount); i++ { - if i == iKey { - allEntries = append(allEntries, entry{child: childPage, recNo: recNo, key: append([]byte{}, key...)}) - } - allEntries = append(allEntries, entry{ - child: page.KeyChild(i), - recNo: page.KeyRecNo(i), - key: append([]byte{}, page.KeyValue(i, idx.keyLen)...), - }) - } - if iKey == int(page.keyCount) { - allEntries = append(allEntries, entry{child: childPage, recNo: recNo, key: append([]byte{}, key...)}) - } - // Trailing child - trailingChild := page.KeyChild(int(page.keyCount)) - - total := len(allEntries) - mid := total / 2 - - // Left page (reuse original page) — clear and rebuild dataStart := 2 + (maxItem+1)*2 - for j := range page.data { - page.data[j] = 0 - } - page.keyCount = 0 - binary.LittleEndian.PutUint16(page.data[0:2], 0) - for i := 0; i <= maxItem; i++ { - binary.LittleEndian.PutUint16(page.data[2+i*2:4+i*2], uint16(dataStart+i*itemSize)) - } - for i := 0; i < mid; i++ { - idx.pageInsertKey(page, i, allEntries[i].key, allEntries[i].recNo, allEntries[i].child) - } - // Set trailing child pointer - trailOff := int(page.keyOffset(mid)) - binary.LittleEndian.PutUint32(page.data[trailOff:trailOff+4], allEntries[mid].child) - page.writeTo(idx.file, pageOff) - // Promoted separator - promKey := append([]byte{}, allEntries[mid].key...) - promRecNo := allEntries[mid].recNo - - // Right page (new page) — initialize offset table - rightOff := int64(idx.header.NextPage) + // Allocate new page (will be the LEFT / lower half) + newPageOff := int64(idx.header.NextPage) idx.header.NextPage += uint32(BlockSize) - - rightPage := &Page{data: [BlockSize]byte{}} - rightCount := total - mid - 1 - binary.LittleEndian.PutUint16(rightPage.data[0:2], uint16(rightCount)) + newPage := &Page{data: [BlockSize]byte{}} // Initialize offset table for i := 0; i <= maxItem; i++ { - binary.LittleEndian.PutUint16(rightPage.data[2+i*2:4+i*2], uint16(dataStart+i*itemSize)) + binary.LittleEndian.PutUint16(newPage.data[2+i*2:4+i*2], uint16(dataStart+i*itemSize)) } - rightPage.keyCount = 0 - for i := 0; i < rightCount; i++ { - srcIdx := mid + 1 + i - idx.pageInsertKey(rightPage, i, allEntries[srcIdx].key, allEntries[srcIdx].recNo, allEntries[srcIdx].child) + + uiKeys := int(page.keyCount) + 1 // total including new key + uiHalf := uiKeys / 2 + + // Phase 1: Copy lower half to newPage + j := 0 // index into old page entries + for newPageCount := 0; newPageCount < uiHalf; newPageCount++ { + if newPageCount == iKey { + // Insert the new key here + idx.setKeyEntry(newPage, newPageCount, childPage, recNo, key) + } else { + // Copy from old page entry j + idx.copyKeyEntry(newPage, newPageCount, page, j) + j++ + } + newPage.keyCount++ } - // Trailing child - rightTrailOff := int(rightPage.keyOffset(rightCount)) - if mid+1+rightCount < len(allEntries) { - binary.LittleEndian.PutUint32(rightPage.data[rightTrailOff:rightTrailOff+4], allEntries[mid+1+rightCount].child) + binary.LittleEndian.PutUint16(newPage.data[0:2], newPage.keyCount) + + // Phase 2: Extract promoted key (middle key) + var promKey []byte + var promRecNo uint32 + if uiHalf == iKey { + // The new key IS the promoted separator + promRecNo = recNo + promKey = append([]byte{}, key...) + // newPage's trailing child = the new key's child pointer + trailOff := int(newPage.keyOffset(int(newPage.keyCount))) + binary.LittleEndian.PutUint32(newPage.data[trailOff:trailOff+4], childPage) } else { - binary.LittleEndian.PutUint32(rightPage.data[rightTrailOff:rightTrailOff+4], trailingChild) + // Promoted key comes from old page entry j + promRecNo = page.KeyRecNo(j) + promKey = append([]byte{}, page.KeyValue(j, idx.keyLen)...) + // newPage's trailing child = old page entry j's child + trailOff := int(newPage.keyOffset(int(newPage.keyCount))) + binary.LittleEndian.PutUint32(newPage.data[trailOff:trailOff+4], page.KeyChild(j)) + j++ } - rightPage.writeTo(idx.file, rightOff) - // Update header on disk - f := idx.file - f.Seek(0, 0) - WriteHeader(f, &idx.header) + // Phase 3: Upper half stays in old page (offset swapping — Harbour style) + // Rearrange the old page's offset table so that entries j..keyCount-1 + // become positions 0..i-1, using offset swapping (no data copying). + i := 0 + for uiHalf++; uiHalf < uiKeys; uiHalf++ { + if uiHalf == iKey { + // Insert new key at position i in old page + idx.setKeyEntry(page, i, childPage, recNo, key) + } else { + // Swap offset[i] and offset[j] — data stays in place + offI := page.keyOffset(i) + offJ := page.keyOffset(j) + binary.LittleEndian.PutUint16(page.data[2+j*2:4+j*2], offI) + binary.LittleEndian.PutUint16(page.data[2+i*2:4+i*2], offJ) + j++ + } + i++ + } + // Move trailing child: old page's last child → position i + lastChild := page.KeyChild(int(page.keyCount)) + trailOff := int(page.keyOffset(int(page.keyCount))) + binary.LittleEndian.PutUint32(page.data[trailOff:trailOff+4], 0) // clear old + newTrailOff := int(page.keyOffset(i)) + binary.LittleEndian.PutUint32(page.data[newTrailOff:newTrailOff+4], lastChild) + page.keyCount = uint16(i) + binary.LittleEndian.PutUint16(page.data[0:2], page.keyCount) - return promKey, promRecNo, uint32(rightOff), nil + // Write both pages + newPage.writeTo(idx.file, newPageOff) + page.writeTo(idx.file, pageOff) + + // Update header + idx.file.Seek(0, 0) + WriteHeader(idx.file, &idx.header) + + // Harbour: pKeyNew->Tag = pNewPage->Page (left child = newPage) + return promKey, promRecNo, uint32(newPageOff), nil +} + +// setKeyEntry writes a key entry at position i in a page. +func (idx *Index) setKeyEntry(page *Page, i int, child uint32, recNo uint32, key []byte) { + off := int(page.keyOffset(i)) + binary.LittleEndian.PutUint32(page.data[off:off+4], child) + binary.LittleEndian.PutUint32(page.data[off+4:off+8], recNo) + padKey := make([]byte, idx.keyLen) + for j := range padKey { + padKey[j] = ' ' + } + copy(padKey, key) + copy(page.data[off+8:off+8+idx.keyLen], padKey) +} + +// copyKeyEntry copies a full key entry (child+recNo+key) from src page position srcI to dst position dstI. +func (idx *Index) copyKeyEntry(dst *Page, dstI int, src *Page, srcI int) { + itemSize := int(idx.header.ItemSize) + dstOff := int(dst.keyOffset(dstI)) + srcOff := int(src.keyOffset(srcI)) + copy(dst.data[dstOff:dstOff+itemSize], src.data[srcOff:srcOff+itemSize]) } // writeTo writes a page to file at the given offset.