feat(rdd): dbInfo / dbOrderInfo — implement the stubs
Replaces the `return NIL` stubs with real implementations that read from the current workarea. Covers the info codes actually used by downstream code (FiveSql2 TSqlIndex, standalone callers): DBINFO: DBI_ISDBF, DBI_CANPUTREC, DBI_FULLPATH, DBI_TABLEEXT, DBI_MEMOEXT, DBI_SHARED, DBI_ISREADONLY, DBI_GETRECSIZE, DBI_DBVERSION, DBI_RDDVERSION, DBI_BOF, DBI_EOF, DBI_FOUND, DBI_FCOUNT, DBI_ALIAS, DBI_POSITIONED DBORDERINFO: DBOI_EXPRESSION, DBOI_NAME, DBOI_NUMBER, DBOI_POSITION, DBOI_ORDERCOUNT, DBOI_KEYCOUNT, DBOI_KEYCOUNTRAW Unknown info codes still return NIL (Harbour's forgiving fallback). New accessors on DBFArea (FullPath, IsShared, IsReadOnly) expose the private filePath/shared/readOnly fields to the hbrtl layer without plumbing them through the generic Area interface. Unblocks TSqlIndex:FindExclusive's original DBI_FULLPATH/DBI_SHARED scan — though the short-circuit there stays in place for now since it's a correctness workaround that no longer masks a crash thanks to the recent gengo PushMemvar fallback. Validation: - FiveSql2 43/43 (0 warnings) - Harbour compat 51/51 - go test ./... ALL PASS Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -361,6 +361,17 @@ func (a *DBFArea) Close() error {
|
|||||||
// MemoFile returns the FPT memo file, or nil if no memo fields.
|
// MemoFile returns the FPT memo file, or nil if no memo fields.
|
||||||
func (a *DBFArea) MemoFile() *FPTFile { return a.memoFile }
|
func (a *DBFArea) MemoFile() *FPTFile { return a.memoFile }
|
||||||
|
|
||||||
|
// FullPath returns the on-disk path of the DBF. For dbInfo(DBI_FULLPATH).
|
||||||
|
func (a *DBFArea) FullPath() string { return a.filePath }
|
||||||
|
|
||||||
|
// IsShared returns true if the area was opened shared.
|
||||||
|
// For dbInfo(DBI_SHARED).
|
||||||
|
func (a *DBFArea) IsShared() bool { return a.shared }
|
||||||
|
|
||||||
|
// IsReadOnly returns true if the area was opened read-only.
|
||||||
|
// For dbInfo(DBI_ISREADONLY).
|
||||||
|
func (a *DBFArea) IsReadOnly() bool { return a.readOnly }
|
||||||
|
|
||||||
// FieldPosCache returns the 1-based field position for a field name.
|
// FieldPosCache returns the 1-based field position for a field name.
|
||||||
// Uses a lazily-built hash map for O(1) lookup instead of O(n) linear scan.
|
// Uses a lazily-built hash map for O(1) lookup instead of O(n) linear scan.
|
||||||
// SQLite: "column affinity binding" — critical for SQL engines that call
|
// SQLite: "column affinity binding" — critical for SQL engines that call
|
||||||
|
|||||||
@@ -218,20 +218,206 @@ func OrdScope(t *hbrt.Thread) {
|
|||||||
t.RetValue()
|
t.RetValue()
|
||||||
}
|
}
|
||||||
|
|
||||||
// DBORDERINFO(nInfoType [, cBagName [, nOrder [, xNewSetting]]]) → xInfo
|
// DBI_* constants. Mirror include/dbinfo.ch. Only the ones we actually
|
||||||
func DbOrderInfo(t *hbrt.Thread) {
|
// answer are listed — unknown codes return NIL.
|
||||||
nParams := t.ParamCount()
|
const (
|
||||||
t.Frame(nParams, 0)
|
dbiIsDBF = 1
|
||||||
defer t.EndProc()
|
dbiCanPutRec = 2
|
||||||
// TODO: implement full DBORDERINFO
|
dbiGetHeaderSize = 3
|
||||||
t.RetNil()
|
dbiLastUpdate = 4
|
||||||
}
|
dbiGetRecSize = 7
|
||||||
|
dbiTableExt = 9
|
||||||
|
dbiFullPath = 10
|
||||||
|
dbiMemoExt = 11
|
||||||
|
dbiDBVersion = 12
|
||||||
|
dbiRDDVersion = 13
|
||||||
|
dbiShared = 42
|
||||||
|
dbiIsReadOnly = 43
|
||||||
|
dbiPositioned = 45
|
||||||
|
dbiLockCount = 49
|
||||||
|
dbiBOF = 51
|
||||||
|
dbiEOF = 52
|
||||||
|
dbiFound = 54
|
||||||
|
dbiFCount = 55
|
||||||
|
dbiAlias = 56
|
||||||
|
)
|
||||||
|
|
||||||
// DBINFO(nInfoType [, xNewSetting]) → xInfo
|
// DBINFO(nInfoType [, xNewSetting]) → xInfo
|
||||||
|
//
|
||||||
|
// Queries workarea metadata. Only the setters that change observable
|
||||||
|
// state are implemented; unknown info codes return NIL (Harbour's
|
||||||
|
// forgiving behavior). xNewSetting is accepted but only honored for
|
||||||
|
// fields where it makes sense.
|
||||||
func DbInfo(t *hbrt.Thread) {
|
func DbInfo(t *hbrt.Thread) {
|
||||||
nParams := t.ParamCount()
|
nParams := t.ParamCount()
|
||||||
t.Frame(nParams, 0)
|
t.Frame(nParams, 0)
|
||||||
defer t.EndProc()
|
defer t.EndProc()
|
||||||
|
|
||||||
|
wam := getWA(t)
|
||||||
|
if wam == nil {
|
||||||
|
t.RetNil()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
area := wam.Current()
|
||||||
|
if area == nil {
|
||||||
|
t.RetNil()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if nParams < 1 {
|
||||||
|
t.RetNil()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nInfo := int(t.Local(1).AsNumInt())
|
||||||
|
|
||||||
|
// DBF-specific queries
|
||||||
|
if da, ok := area.(*dbf.DBFArea); ok {
|
||||||
|
switch nInfo {
|
||||||
|
case dbiIsDBF:
|
||||||
|
t.RetBool(true)
|
||||||
|
return
|
||||||
|
case dbiCanPutRec:
|
||||||
|
t.RetBool(!da.IsReadOnly())
|
||||||
|
return
|
||||||
|
case dbiFullPath:
|
||||||
|
t.RetString(da.FullPath())
|
||||||
|
return
|
||||||
|
case dbiTableExt:
|
||||||
|
t.RetString(".dbf")
|
||||||
|
return
|
||||||
|
case dbiMemoExt:
|
||||||
|
if da.MemoFile() != nil {
|
||||||
|
t.RetString(".fpt")
|
||||||
|
} else {
|
||||||
|
t.RetString("")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
case dbiShared:
|
||||||
|
t.RetBool(da.IsShared())
|
||||||
|
return
|
||||||
|
case dbiIsReadOnly:
|
||||||
|
t.RetBool(da.IsReadOnly())
|
||||||
|
return
|
||||||
|
case dbiGetRecSize:
|
||||||
|
nCount, _ := da.RecCount()
|
||||||
|
_ = nCount
|
||||||
|
// Header + records length — approximation from FieldInfo
|
||||||
|
total := 0
|
||||||
|
for i := 0; i < da.FieldCount(); i++ {
|
||||||
|
total += da.GetFieldInfo(i).Len
|
||||||
|
}
|
||||||
|
t.RetInt(int64(total + 1)) // +1 for delete flag
|
||||||
|
return
|
||||||
|
case dbiDBVersion:
|
||||||
|
t.RetString("Five DBF 1.0")
|
||||||
|
return
|
||||||
|
case dbiRDDVersion:
|
||||||
|
t.RetString("Five 1.0")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generic (any Area) queries
|
||||||
|
switch nInfo {
|
||||||
|
case dbiBOF:
|
||||||
|
t.RetBool(area.BOF())
|
||||||
|
return
|
||||||
|
case dbiEOF:
|
||||||
|
t.RetBool(area.EOF())
|
||||||
|
return
|
||||||
|
case dbiFound:
|
||||||
|
t.RetBool(area.Found())
|
||||||
|
return
|
||||||
|
case dbiFCount:
|
||||||
|
t.RetInt(int64(area.FieldCount()))
|
||||||
|
return
|
||||||
|
case dbiAlias:
|
||||||
|
t.RetString(area.Alias())
|
||||||
|
return
|
||||||
|
case dbiPositioned:
|
||||||
|
t.RetBool(!area.BOF() && !area.EOF())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.RetNil()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DBOI_* constants. Mirror include/dbinfo.ch.
|
||||||
|
const (
|
||||||
|
dboiCondition = 1
|
||||||
|
dboiExpression = 2
|
||||||
|
dboiPosition = 3
|
||||||
|
dboiName = 4
|
||||||
|
dboiNumber = 5
|
||||||
|
dboiBagName = 6
|
||||||
|
dboiBagExt = 7
|
||||||
|
dboiIndexName = 8
|
||||||
|
dboiOrderCount = 9
|
||||||
|
dboiIsCond = 11
|
||||||
|
dboiIsDesc = 12
|
||||||
|
dboiUnique = 13
|
||||||
|
dboiKeyType = 14
|
||||||
|
dboiKeySize = 15
|
||||||
|
dboiKeyCount = 22
|
||||||
|
dboiKeyCountRaw = 34
|
||||||
|
)
|
||||||
|
|
||||||
|
// DBORDERINFO(nInfoType [, cBagName [, nOrder [, xNewSetting]]]) → xInfo
|
||||||
|
//
|
||||||
|
// Queries metadata about an active order (index). The order is identified
|
||||||
|
// by nOrder (1-based) or defaults to the current focus.
|
||||||
|
func DbOrderInfo(t *hbrt.Thread) {
|
||||||
|
nParams := t.ParamCount()
|
||||||
|
t.Frame(nParams, 0)
|
||||||
|
defer t.EndProc()
|
||||||
|
|
||||||
|
wam := getWA(t)
|
||||||
|
if wam == nil {
|
||||||
|
t.RetNil()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
area := wam.Current()
|
||||||
|
if area == nil {
|
||||||
|
t.RetNil()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
da, ok := area.(*dbf.DBFArea)
|
||||||
|
if !ok {
|
||||||
|
t.RetNil()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if nParams < 1 {
|
||||||
|
t.RetNil()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nInfo := int(t.Local(1).AsNumInt())
|
||||||
|
|
||||||
|
// Resolve which order we're asking about.
|
||||||
|
ord := da.CurrentOrder()
|
||||||
|
if nParams >= 3 && !t.Local(3).IsNil() {
|
||||||
|
ord = int(t.Local(3).AsNumInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
switch nInfo {
|
||||||
|
case dboiExpression:
|
||||||
|
t.RetString(da.OrderKeyExpr(ord))
|
||||||
|
return
|
||||||
|
case dboiName:
|
||||||
|
t.RetString(da.OrderName(ord))
|
||||||
|
return
|
||||||
|
case dboiNumber, dboiPosition:
|
||||||
|
t.RetInt(int64(ord))
|
||||||
|
return
|
||||||
|
case dboiOrderCount:
|
||||||
|
t.RetInt(int64(da.IndexCount()))
|
||||||
|
return
|
||||||
|
case dboiKeyCount, dboiKeyCountRaw:
|
||||||
|
n, _ := da.RecCount()
|
||||||
|
t.RetInt(int64(n))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
t.RetNil()
|
t.RetNil()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user