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>
234 lines
6.4 KiB
Go
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
|
|
}
|