diff --git a/hbrdd/dbf/dbf.go b/hbrdd/dbf/dbf.go index f9fc84b..0e7b1df 100644 --- a/hbrdd/dbf/dbf.go +++ b/hbrdd/dbf/dbf.go @@ -361,6 +361,17 @@ func (a *DBFArea) Close() error { // MemoFile returns the FPT memo file, or nil if no memo fields. 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. // 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 diff --git a/hbrtl/indexrtl.go b/hbrtl/indexrtl.go index 839a4de..83ee2eb 100644 --- a/hbrtl/indexrtl.go +++ b/hbrtl/indexrtl.go @@ -218,20 +218,206 @@ func OrdScope(t *hbrt.Thread) { t.RetValue() } -// DBORDERINFO(nInfoType [, cBagName [, nOrder [, xNewSetting]]]) → xInfo -func DbOrderInfo(t *hbrt.Thread) { - nParams := t.ParamCount() - t.Frame(nParams, 0) - defer t.EndProc() - // TODO: implement full DBORDERINFO - t.RetNil() -} +// DBI_* constants. Mirror include/dbinfo.ch. Only the ones we actually +// answer are listed — unknown codes return NIL. +const ( + dbiIsDBF = 1 + dbiCanPutRec = 2 + dbiGetHeaderSize = 3 + 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 +// +// 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) { 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 + } + + 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() }