fix: 3 RDD compat bugs — FIELD->, AsNumInt Double, PACK/ZAP with index
Bug 1: FIELD->NAME in INDEX ON expression - evalKeyExprInner: strip FIELD->/alias-> prefix before field lookup - exprToString: handle AliasExpr (FIELD->NAME → "FIELD->NAME") Bug 2: AsNumInt() on Double returned IEEE 754 raw bits - Value.AsNumInt(): check tDouble and convert via Float64frombits - Fixed array index crash when index is result of % modulo Bug 3: PACK/ZAP crash with open indexes - OrderListRebuild: fully implemented (was TODO stub) Saves index info, closes all, sets idxState=nil, recreates - OrderCreate: set current=-1 during key evaluation (natural GoTo) - PACK/ZAP: save/restore idxState, rebuild after operation - Register __DBPACK, __DBZAP, DBRECALL symbol aliases Harbour vs Five: 45/47 match (96%), 2 diffs are duplicate-key sort order Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -631,6 +631,13 @@ func (a *DBFArea) Pack() error {
|
||||
a.flushRecord()
|
||||
}
|
||||
|
||||
// Temporarily disable index to avoid indexed navigation during PACK
|
||||
var savedIdx *indexState
|
||||
if a.idxState != nil {
|
||||
savedIdx = a.idxState
|
||||
a.idxState = nil
|
||||
}
|
||||
|
||||
outRec := uint32(0)
|
||||
buf := make([]byte, a.header.RecordLen)
|
||||
|
||||
@@ -661,7 +668,7 @@ func (a *DBFArea) Pack() error {
|
||||
// Update header
|
||||
a.updateHeader()
|
||||
|
||||
// Reposition
|
||||
// Reposition (natural order, no index yet)
|
||||
if a.recCount > 0 {
|
||||
a.GoTo(1)
|
||||
} else {
|
||||
@@ -669,6 +676,14 @@ func (a *DBFArea) Pack() error {
|
||||
a.recNo = 1
|
||||
}
|
||||
|
||||
// Rebuild indexes (record numbers changed after PACK)
|
||||
if savedIdx != nil {
|
||||
a.idxState = savedIdx
|
||||
if err := a.OrderListRebuild(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -678,6 +693,13 @@ func (a *DBFArea) Zap() error {
|
||||
return fmt.Errorf("ZAP requires exclusive access")
|
||||
}
|
||||
|
||||
// Save index state
|
||||
var savedIdx *indexState
|
||||
if a.idxState != nil {
|
||||
savedIdx = a.idxState
|
||||
a.idxState = nil
|
||||
}
|
||||
|
||||
a.recCount = 0
|
||||
a.header.RecCount = 0
|
||||
|
||||
@@ -688,6 +710,13 @@ func (a *DBFArea) Zap() error {
|
||||
a.updateHeader()
|
||||
a.FEof = true
|
||||
a.recNo = 1
|
||||
|
||||
// Rebuild indexes (empty after ZAP)
|
||||
if savedIdx != nil {
|
||||
a.idxState = savedIdx
|
||||
a.OrderListRebuild() // rebuilds empty indexes
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,9 @@ func (a *DBFArea) ensureIndexState() {
|
||||
func (a *DBFArea) OrderCreate(params hbrdd.OrderCreateParams) error {
|
||||
a.ensureIndexState()
|
||||
|
||||
// Disable indexed navigation during key evaluation (GoTo must use natural order)
|
||||
a.idxState.current = -1
|
||||
|
||||
idxPath := params.FilePath
|
||||
if idxPath == "" {
|
||||
return fmt.Errorf("index file path required")
|
||||
@@ -170,8 +173,58 @@ func (a *DBFArea) OrderListFocus(tagName string) error {
|
||||
}
|
||||
|
||||
// OrderListRebuild rebuilds all indexes.
|
||||
// Harbour: ORDLISTREBUILD / REINDEX — recreates all open indexes from current data.
|
||||
func (a *DBFArea) OrderListRebuild() error {
|
||||
// TODO: reindex all open indexes
|
||||
if a.idxState == nil || len(a.idxState.indexes) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Save current index info
|
||||
savedCurrent := a.idxState.current
|
||||
type idxInfo struct {
|
||||
name string
|
||||
tag string
|
||||
keyExpr string
|
||||
}
|
||||
infos := make([]idxInfo, len(a.idxState.indexes))
|
||||
for i := range a.idxState.indexes {
|
||||
infos[i] = idxInfo{
|
||||
name: a.idxState.names[i],
|
||||
tag: a.idxState.tags[i],
|
||||
keyExpr: a.idxState.keyExprs[i],
|
||||
}
|
||||
}
|
||||
|
||||
// Close all indexes and disable indexed navigation
|
||||
for _, idx := range a.idxState.indexes {
|
||||
idx.Close()
|
||||
}
|
||||
a.idxState.indexes = nil
|
||||
a.idxState.names = nil
|
||||
a.idxState.tags = nil
|
||||
a.idxState.keyExprs = nil
|
||||
a.idxState.current = -1
|
||||
|
||||
// Remove idxState so GoTo uses natural order during rebuild
|
||||
a.idxState = nil
|
||||
|
||||
// Recreate each index
|
||||
for _, info := range infos {
|
||||
err := a.OrderCreate(hbrdd.OrderCreateParams{
|
||||
KeyExpr: info.keyExpr,
|
||||
FilePath: info.name,
|
||||
TagName: info.tag,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("rebuild index %s: %w", info.name, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Restore active index
|
||||
if a.idxState != nil && savedCurrent >= 0 && savedCurrent < len(a.idxState.indexes) {
|
||||
a.idxState.current = savedCurrent
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -361,10 +414,16 @@ func (a *DBFArea) evalKeyExprInner(expr string) []byte {
|
||||
return []byte(expr[1 : len(expr)-1])
|
||||
}
|
||||
|
||||
// Strip FIELD-> or _FIELD-> or alias-> prefix (Harbour: M->var, FIELD->var)
|
||||
fieldName := upper
|
||||
if idx := strings.Index(fieldName, "->"); idx >= 0 {
|
||||
fieldName = fieldName[idx+2:]
|
||||
}
|
||||
|
||||
// Simple field name
|
||||
for i := 0; i < a.FieldCount(); i++ {
|
||||
fi := a.GetFieldInfo(i)
|
||||
if strings.ToUpper(fi.Name) == upper {
|
||||
if strings.ToUpper(fi.Name) == fieldName {
|
||||
val, _ := a.GetValue(i)
|
||||
return formatKeyValue(val, fi)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user