Files
five/hbrdd/driver.go
CharlesKWON f4ed42556b checkpoint: season-wide bug fix campaign + infra
Cumulative season's silent-bug hunting (~62 fixes) across the FiveSql2
SQL engine, the Five compiler/runtime, and the hbrdd RDD layer. Saved
as a single checkpoint before refactoring the parser to delegate xBase
command translation to the preprocessor.

Highlights:

FiveSql2 engine (_FiveSql2/src/)
- prefix-glob index attach -> explicit convention (<table>_pk.ntx,
  <table>_uq.ntx, <table>.cdx) — fixes silent multi-row INSERT row-drop
- DROP/CREATE TABLE FErase chain extended (.cdx, .fsc, .fsv, .dbt, .fpt)
- COUNT(DISTINCT col) parsed + aggregated via hSeen hash
- UNION column-count mismatch returns SQL_ERR_GRAMMAR (was silent)
- DISTINCT + ORDER BY hidden-col leak fixed (trim before DISTINCT)
- Derived table FROM (SELECT...) + JOIN right-side derived
- Self-FK CASCADE depth 2+ via SqlGetSingleColPK pre-collect
- LAG/LEAD default arg uses SqlEvalRowExpr (handles -N const exprs)
- DATE literal round-trip validation (Feb 29 non-leap rejected)
- CREATE OR REPLACE VIEW; CREATE VIEW errors on already-exists
- AlterTable type dispatcher comma-wrapped (1-char type "A" no longer
  matches CHARACTER)

Compiler / runtime
- gengo: HB_ -> FV_ prefix on emitted Go function names (Five identity)
- gengo split: emit_block.go, emit_stmt.go, folding.go extracted
- parser/stmtreg.go nudges
- hbrt: debug TUI/CLI restructure (debugcmd, debugkey, termios_*),
  windows debug stubs collapsed
- thread/vm/value/class/pcinterp tightening from panic traces

RDD layer (hbrdd/)
- dbf: null bitmap support (null.go + null_test.go), mmap split
  (mmap_posix.go / mmap_windows.go), byte-level numeric parse
- ntx/cdx: windows mmap parity
- workarea + mem RDD: cross-area state-bleed fixes

RTL (hbrtl/)
- errorlog rewrite with platform-specific FD (errorlog_fd_unix /
  errorlog_fd_other)
- sqlscan, sqlhelpers, indexrtl, datetime extensions

Gates green at checkpoint:
- go test ./...        : PASS
- FiveSql2 SQL:1999    : 43/43
- Harbour compat       : 56/56

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 09:26:25 +09:00

234 lines
6.4 KiB
Go

// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
// All rights reserved.
// RDD (Replaceable Database Driver) interface definitions for Five.
//
// Design: Harbour's 101-method RDDFUNCS vtable → Go interface composition.
// Each interface is small and focused (Go philosophy).
// Drivers implement only what they need; BaseArea provides defaults.
//
// Inheritance via Go embedding:
// BaseArea (WAAREA) → DBFArea → NTXArea / CDXArea
//
// Reference:
// /mnt/d/harbour-core/include/hbapirdd.h (lines 640-816)
// docs/rdd-architecture-spec.md
package hbrdd
import (
"five/hbrt"
"fmt"
"strings"
"sync"
)
// --- Driver registry ---
var (
driversMu sync.RWMutex
drivers = map[string]Driver{}
)
// RegisterDriver registers an RDD driver by name.
func RegisterDriver(d Driver) {
driversMu.Lock()
drivers[strings.ToUpper(d.Name())] = d
driversMu.Unlock()
}
// GetDriver returns a registered driver by name.
func GetDriver(name string) (Driver, error) {
driversMu.RLock()
d, ok := drivers[strings.ToUpper(name)]
driversMu.RUnlock()
if !ok {
return nil, fmt.Errorf("unknown RDD driver: %s", name)
}
return d, nil
}
// --- Core interfaces ---
// Driver creates and opens work areas.
// Harbour: RDD node with RDDFUNCS table.
type Driver interface {
Name() string
Open(params OpenParams) (Area, error)
Create(params CreateParams) (Area, error)
}
// OpenParams for opening an existing table.
type OpenParams struct {
Path string // file path (without extension)
Alias string // workarea alias
Shared bool // shared access mode
ReadOnly bool // read-only mode
CodePage string // code page name
}
// CreateParams for creating a new table.
type CreateParams struct {
Path string
Alias string
Fields []FieldInfo
CodePage string
}
// FieldInfo describes a database field.
// Harbour: DBFFIELD (32 bytes in file, this is the runtime representation)
type FieldInfo struct {
Name string // up to 10 chars (Harbour limit)
Type byte // 'C', 'N', 'L', 'D', 'M', 'I', 'B', '@', '+', '=', '^', 'Y', etc.
Len int // field length
Dec int // decimal places
Flags byte // 0x01=system, 0x02=nullable, 0x04=binary
}
// --- Area interface (WAAREA + DBF core) ---
// Area is the primary interface for accessing a database table.
// Harbour: AREAP with SELF_* macro dispatch.
type Area interface {
// Identity
Driver() Driver
Alias() string
SetAlias(string)
// Lifecycle
Close() error
Flush() error
// Movement — Harbour: hb_waBof, hb_waEof, hb_waFound, hb_waGoTo, etc.
BOF() bool
EOF() bool
Found() bool
GoTo(recNo uint32) error
GoTop() error
GoBottom() error
Skip(count int64) error
// Record info
RecNo() uint32
RecCount() (uint32, error)
Deleted() bool
// Field access — Harbour: SELF_FIELDCOUNT, SELF_GETVALUE, SELF_PUTVALUE
FieldCount() int
GetFieldInfo(index int) FieldInfo
GetValue(fieldIndex int) (hbrt.Value, error)
PutValue(fieldIndex int, val hbrt.Value) error
// Record operations — Harbour: SELF_APPEND, SELF_DELETE, SELF_RECALL
Append() error
Delete() error
Recall() error
// Bulk operations
Pack() error
Zap() error
// State — Harbour: hb_waSetFound, locate support
SetFound(b bool)
SetLocate(expr string, block func(*hbrt.Thread) bool)
LocateBlock() func(*hbrt.Thread) bool
// Filter
SetFilter(expr string, block func(*hbrt.Thread) bool) error
ClearFilter() error
HasFilter() bool
}
// --- Optional interfaces (drivers implement as needed) ---
// Indexer provides index management and key-based seeking.
// Harbour: order* methods in RDDFUNCS (9 methods).
// Only DBFNTX and DBFCDX implement this.
type Indexer interface {
// Order management
OrderCreate(params OrderCreateParams) error
OrderListAdd(path string) error
OrderListClear() error
OrderListFocus(tagName string) error
OrderListRebuild() error
OrderDestroy(tagName string) error
OrderInfo(ordNo int) (*OrderInfo, error)
// Key-based seeking — Harbour: SELF_SEEK
Seek(key hbrt.Value, softSeek bool, findLast bool) (bool, error)
}
// OrderCreateParams for INDEX ON.
type OrderCreateParams struct {
TagName string // index tag name
KeyExpr string // key expression (e.g., "UPPER(lastname+firstname)")
ForExpr string // FOR condition (e.g., "active = .T.")
FilePath string // index file path
Unique bool
Descending bool
// KeyFunc is an optional compiled key evaluator. When non-nil, the
// indexer calls it directly instead of going through MacroEval on the
// KeyExpr string. gengo emits this as an inline Go closure that
// mirrors the AST of the key expression — zero string parsing at
// runtime, symbol lookups hoisted out of the loop.
//
// Contract: caller must position the workarea (GoTo) before calling.
// Returns the key value for the current record.
KeyFunc func() hbrt.Value
// ForFunc is the compiled counterpart of KeyFunc for the optional
// FOR expression. When non-nil the indexer calls it instead of
// parsing ForExpr as a string and running it through the macro
// evaluator — eliminates strings.Index/ToUpper/splitArgs per record
// in filtered-index builds and rebuilds. Returns true when the
// current record should be included.
//
// Contract: caller must position the workarea (GoTo) before calling.
ForFunc func() bool
}
// OrderInfo holds information about an index order.
type OrderInfo struct {
Name string
KeyExpr string
ForExpr string
Unique bool
Descending bool
KeyCount uint32
Custom bool
}
// Locker provides record and file locking.
// Harbour: SELF_LOCK, SELF_UNLOCK, SELF_RAWLOCK
type Locker interface {
LockRecord(recNo uint32) (bool, error)
UnlockRecord(recNo uint32) error
LockFile() (bool, error)
UnlockFile() error
IsLocked(recNo uint32) bool
}
// Relater provides SET RELATION support.
// Harbour: SELF_SETREL, SELF_CLEARREL, SELF_FORCEREL
type Relater interface {
SetRelation(child Area, keyExpr func(*hbrt.Thread) hbrt.Value, scoped bool) error
ClearRelation() error
ForceRel() error
SyncChildren() error
}
// MemoHandler provides memo field read/write.
// Harbour: SELF_OPENMEMFILE, SELF_CLOSEMEMFILE, SELF_GETVALUEFILE, SELF_PUTVALUEFILE
type MemoHandler interface {
OpenMemo(path string) error
CloseMemo() error
ReadMemo(blockNo uint32) ([]byte, error)
WriteMemo(data []byte) (uint32, error)
}
// Scoper provides SET SCOPE support for index-based range queries.
type Scoper interface {
SetScope(top, bottom hbrt.Value) error
ClearScope() error
}