- skipFilter: skip deleted records in GoTop/GoBottom/Skip when SET DELETED ON - hbrdd.IsSetDeleted callback: avoids circular import hbrdd→hbrtl - Parser: capture ON/OFF for boolean SET commands (DELETED, EXACT, SOFTSEEK, etc.) - Parser: capture TO expr for SET DATE/DECIMALS/EPOCH - Gengo: emit proper t.Do() calls for 11 SET toggles + 3 value SETs - stmtSet: was stub (skipToEOL), now calls parseSet() - RTL: register 11 SET toggle functions (SETDELETED, SETEXACT, etc.) - RTL: DBLOCATE/DBCONTINUE for sequential search - RTL: DBSETFILTER/DBCLEARFILTER/DBFILTER - PadL/PadR: support 3rd param fill character - Area interface: added SetFound, SetLocate, LocateBlock, filter methods - MemRDD: implements new Area interface methods - Comprehensive PRG test: test_search.prg (7 test suites all pass) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
213 lines
5.4 KiB
Go
213 lines
5.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
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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
|
|
}
|