feat: Harbour RDD parity — NTX/CDX 100% compatible, FIELD-> works
Five RDD engine now matches Harbour DBFNTX and DBFCDX byte-for-byte
in ordering, seek, navigation, and field access. Verified against
Harbour 3.2.0dev with a 281-line comparison test covering:
- Natural/NAME/CITY/AGE/SALARY/UPPER ordering
- SEEK (exact/not-found), GoTop/GoBottom per order
- DELETE/RECALL with SET DELETED
- CDX compound index read with 5 tags (BYNAME, BYCITY, BYAGE, BYSAL, BYUNAME)
- Reverse traversal
Fixes:
1. FIELD->NAME returned NIL
GetAliasField returned interface{} but runtime expected hbrt.Value,
so the type assertion in PushAliasField failed and pushed NIL.
- workarea.go: change return type to hbrt.Value, handle FIELD/_FIELD
as current-workarea alias, add SetAliasField
- gengo.go: emit SetAliasField() for alias->field := value in both
statement and expression contexts
2. OrdSetFocus(n) silently switched to natural order
v.AsString() returns "" for a numeric Value, so OrderListFocus("")
set current=-1.
- indexrtl.go: convert numeric param via fmt.Sprintf("%d", ...)
3. CDX compound tag order mismatched Harbour
Five decoded the structural B-tree which is alphabetical, but
Harbour sorts tags by TagBlock (file offset = creation order).
- cdx/cdx.go: sort tagEntries by offset ascending after decoding,
matching hb_cdxIndexLoadAvailTags in dbfcdx1.c
4. OutStd()/OutErr() not registered — caused panic on call
- hbrtl/console.go: add rtlOutStd/rtlOutErr implementations
- hbrtl/register.go: register OUTSTD and OUTERR
- analyzer.go: add OUTSTD/OUTERR to RTL known-functions
5. FIELD keyword triggered "undeclared variable" warnings
- analyzer.go: add FIELD, _FIELD, M, MEMVAR as builtin constants
Tests:
go test ./... — ALL PASS (17 packages)
FiveSql2 43/43 — 100%
compat_harbour 51/51 — 100%
Harbour diff — 0 lines differ (281-line comparison)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,7 @@ import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
@@ -376,6 +377,14 @@ func OpenIndex(path string) (*Index, error) {
|
||||
// points to the tag header at a specific file offset.
|
||||
tagEntries := readCompoundTagList(idx, rootHdr)
|
||||
|
||||
// Harbour orders tags by file offset (TagBlock) ascending, which
|
||||
// corresponds to creation order. The compound B-tree stores entries
|
||||
// alphabetically, so we must re-sort by offset to match Harbour.
|
||||
// See: hb_cdxIndexLoadAvailTags in dbfcdx1.c
|
||||
sort.Slice(tagEntries, func(i, j int) bool {
|
||||
return tagEntries[i].offset < tagEntries[j].offset
|
||||
})
|
||||
|
||||
for _, entry := range tagEntries {
|
||||
tagHdr, err := ReadTagHeader(f, entry.offset)
|
||||
if err != nil {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
package hbrdd
|
||||
|
||||
import (
|
||||
"five/hbrt"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
@@ -213,11 +214,18 @@ func (wm *WorkAreaManager) CloseAll() {
|
||||
}
|
||||
|
||||
// GetAliasField returns a field value from a named alias.
|
||||
// Used by alias->field syntax.
|
||||
func (wm *WorkAreaManager) GetAliasField(alias, field string) interface{} {
|
||||
area := wm.ByAlias(alias)
|
||||
// Used by alias->field syntax (e.g., CUSTOMERS->NAME, FIELD->AGE).
|
||||
func (wm *WorkAreaManager) GetAliasField(alias, field string) hbrt.Value {
|
||||
var area Area
|
||||
|
||||
// FIELD-> is a special alias meaning "current workarea"
|
||||
if strings.EqualFold(alias, "FIELD") || strings.EqualFold(alias, "_FIELD") {
|
||||
area = wm.Current()
|
||||
} else {
|
||||
area = wm.ByAlias(alias)
|
||||
}
|
||||
if area == nil {
|
||||
return nil
|
||||
return hbrt.MakeNil()
|
||||
}
|
||||
// Find field by name
|
||||
for i := 0; i < area.FieldCount(); i++ {
|
||||
@@ -227,7 +235,28 @@ func (wm *WorkAreaManager) GetAliasField(alias, field string) interface{} {
|
||||
return val
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return hbrt.MakeNil()
|
||||
}
|
||||
|
||||
// SetAliasField sets a field value by alias->field syntax.
|
||||
func (wm *WorkAreaManager) SetAliasField(alias, field string, val hbrt.Value) {
|
||||
var area Area
|
||||
|
||||
if strings.EqualFold(alias, "FIELD") || strings.EqualFold(alias, "_FIELD") {
|
||||
area = wm.Current()
|
||||
} else {
|
||||
area = wm.ByAlias(alias)
|
||||
}
|
||||
if area == nil {
|
||||
return
|
||||
}
|
||||
for i := 0; i < area.FieldCount(); i++ {
|
||||
fi := area.GetFieldInfo(i)
|
||||
if strings.EqualFold(fi.Name, field) {
|
||||
area.PutValue(i, val)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Helpers ---
|
||||
|
||||
Reference in New Issue
Block a user