feat: CDX support + ORDSCOPE + cross-read Harbour compatibility
CDX Integration:
- IndexEngine interface: common for NTX Index and CDX Tag
- OrderListAdd: auto-detects .cdx/.ntx extension, opens CDX tags
- decodeCompoundLeaf: proper bit-packed tag directory decoding
(was stub falling through to scanCompoundLeaves with wrong names)
- CDX Tag: added KeyLen(), KeyExpr(), ForExpr(), IsDescending(), Close()
- CDX compound recNo = direct byte offset (not page number)
ORDSCOPE:
- SetScope/ClearScope/SetScopeTop/SetScopeBottom on DBFArea
- GoTopIndexed: seeks to scopeTop, validates within scopeBottom
- GoBottomIndexed: seeks to scopeBottom boundary
- SkipIndexed: stops at scope boundaries (top and bottom)
- OrdScope RTL function registered (nScope: 0=TOP, 1=BOTTOM)
- scopeKeyFromValue: converts Value to padded key bytes
Index Order Management:
- OrderListFocus: handles numeric order ("2" → order 2)
- SET ORDER TO n: gengo emits hbrt.NtoS for int-to-string conversion
- IndexOrd/OrdCount/OrdName/OrdKey: real implementations (were stubs)
- OrderCount/CurrentOrder/OrderName/OrderKeyExpr accessors on DBFArea
- ClearScope on order switch (prevents stale scope)
Cross-read test: Harbour-created CDX → Five reads, 20/20 items match:
NAME/CITY/ID seek, ORDSCOPE count, GoTop/GoBottom all identical
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -555,7 +555,7 @@ func (g *Generator) emitStmt(stmt ast.Stmt, locals localMap) {
|
|||||||
g.writeln("if idx, ok := area.(hbrdd.Indexer); ok {")
|
g.writeln("if idx, ok := area.(hbrdd.Indexer); ok {")
|
||||||
g.indent++
|
g.indent++
|
||||||
g.emitExpr(s.Expr)
|
g.emitExpr(s.Expr)
|
||||||
g.writeln(`idx.OrderListFocus(t.Pop2().AsString())`)
|
g.writeln(`{ _ov := t.Pop2(); var _os string; if _ov.IsNumeric() { _os = hbrt.NtoS(_ov.AsNumInt()) } else { _os = _ov.AsString() }; idx.OrderListFocus(_os) }`)
|
||||||
g.indent--
|
g.indent--
|
||||||
g.writeln("}")
|
g.writeln("}")
|
||||||
}
|
}
|
||||||
|
|||||||
87
examples/cross_cdx_test.prg
Normal file
87
examples/cross_cdx_test.prg
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
// Five reads Harbour-created CDX — binary compatibility + ORDSCOPE test
|
||||||
|
PROCEDURE Main()
|
||||||
|
LOCAL nCount
|
||||||
|
|
||||||
|
// Open Harbour-created DBF + CDX
|
||||||
|
USE "/tmp/cdx_test" NEW
|
||||||
|
SET INDEX TO "/tmp/cdx_test.cdx"
|
||||||
|
|
||||||
|
? "RECORDS=" + LTrim(Str(RecCount()))
|
||||||
|
? "ORDCOUNT=" + LTrim(Str(OrdCount()))
|
||||||
|
|
||||||
|
// NAME tag
|
||||||
|
OrdSetFocus("BYNAME")
|
||||||
|
GO TOP
|
||||||
|
? "N_TOP=" + RTrim(FieldGet(2)) + " " + LTrim(Str(FieldGet(1)))
|
||||||
|
|
||||||
|
SEEK PadR("Name_001", 20)
|
||||||
|
? "N_S001=" + IIF(Found(),".T.",".F.") + " " + LTrim(Str(RecNo()))
|
||||||
|
SEEK PadR("Name_025", 20)
|
||||||
|
? "N_S025=" + IIF(Found(),".T.",".F.") + " " + LTrim(Str(RecNo()))
|
||||||
|
SEEK PadR("Name_050", 20)
|
||||||
|
? "N_S050=" + IIF(Found(),".T.",".F.") + " " + LTrim(Str(RecNo()))
|
||||||
|
SEEK "Name_01"
|
||||||
|
? "N_P01=" + IIF(Found(),".T.",".F.") + " " + LTrim(Str(RecNo()))
|
||||||
|
|
||||||
|
GO BOTTOM
|
||||||
|
? "N_BOTTOM=" + RTrim(FieldGet(2)) + " " + LTrim(Str(RecNo()))
|
||||||
|
|
||||||
|
// CITY tag
|
||||||
|
OrdSetFocus("BYCITY")
|
||||||
|
GO TOP
|
||||||
|
? "C_TOP=" + RTrim(FieldGet(3)) + " " + LTrim(Str(FieldGet(1)))
|
||||||
|
SEEK PadR("Seoul", 15)
|
||||||
|
? "C_SEOUL=" + IIF(Found(),".T.",".F.") + " " + LTrim(Str(RecNo()))
|
||||||
|
SEEK PadR("Tokyo", 15)
|
||||||
|
? "C_TOKYO=" + IIF(Found(),".T.",".F.") + " " + LTrim(Str(RecNo()))
|
||||||
|
|
||||||
|
// ORDSCOPE on CITY: London..Seoul
|
||||||
|
OrdScope(0, PadR("London", 15))
|
||||||
|
OrdScope(1, PadR("Seoul", 15))
|
||||||
|
|
||||||
|
GO TOP
|
||||||
|
? "SCOPE_TOP=" + RTrim(FieldGet(3)) + " " + LTrim(Str(RecNo()))
|
||||||
|
GO BOTTOM
|
||||||
|
? "SCOPE_BOT=" + RTrim(FieldGet(3)) + " " + LTrim(Str(RecNo()))
|
||||||
|
|
||||||
|
GO TOP
|
||||||
|
nCount := 0
|
||||||
|
DO WHILE !EOF()
|
||||||
|
nCount++
|
||||||
|
SKIP
|
||||||
|
ENDDO
|
||||||
|
? "SCOPE_CNT=" + LTrim(Str(nCount))
|
||||||
|
|
||||||
|
// Clear scope
|
||||||
|
OrdScope(0, NIL)
|
||||||
|
OrdScope(1, NIL)
|
||||||
|
GO TOP
|
||||||
|
nCount := 0
|
||||||
|
DO WHILE !EOF()
|
||||||
|
nCount++
|
||||||
|
SKIP
|
||||||
|
ENDDO
|
||||||
|
? "NOSCOPE_CNT=" + LTrim(Str(nCount))
|
||||||
|
|
||||||
|
// ID tag
|
||||||
|
OrdSetFocus("BYID")
|
||||||
|
SEEK Str(10, 6)
|
||||||
|
? "I_S10=" + IIF(Found(),".T.",".F.") + " " + LTrim(Str(RecNo()))
|
||||||
|
SEEK Str(40, 6)
|
||||||
|
? "I_S40=" + IIF(Found(),".T.",".F.") + " " + LTrim(Str(RecNo()))
|
||||||
|
|
||||||
|
// ORDSCOPE on ID: 20..30
|
||||||
|
OrdScope(0, Str(20, 6))
|
||||||
|
OrdScope(1, Str(30, 6))
|
||||||
|
GO TOP
|
||||||
|
nCount := 0
|
||||||
|
DO WHILE !EOF()
|
||||||
|
nCount++
|
||||||
|
SKIP
|
||||||
|
ENDDO
|
||||||
|
? "ID_SCOPE_CNT=" + LTrim(Str(nCount))
|
||||||
|
OrdScope(0, NIL)
|
||||||
|
OrdScope(1, NIL)
|
||||||
|
|
||||||
|
CLOSE ALL
|
||||||
|
RETURN
|
||||||
@@ -379,6 +379,9 @@ func (idx *Index) GetTag(i int) *Tag {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tags returns all tags in the CDX.
|
||||||
|
func (idx *Index) Tags() []*Tag { return idx.tags }
|
||||||
|
|
||||||
// FindTag returns a tag by name.
|
// FindTag returns a tag by name.
|
||||||
func (idx *Index) FindTag(name string) *Tag {
|
func (idx *Index) FindTag(name string) *Tag {
|
||||||
upper := strings.ToUpper(name)
|
upper := strings.ToUpper(name)
|
||||||
@@ -498,12 +501,27 @@ func scanCompoundLeaves(f *os.File, rootHdr *TagHeader) []tagDirEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// decodeCompoundLeaf decodes tag entries from a compound leaf page.
|
// decodeCompoundLeaf decodes tag entries from a compound leaf page.
|
||||||
|
// Compound index uses the same bit-packed format as data leaves,
|
||||||
|
// with keyLen=10 (tag name) and recNo = page offset / PageLen.
|
||||||
func decodeCompoundLeaf(data []byte, nKeys int) []tagDirEntry {
|
func decodeCompoundLeaf(data []byte, nKeys int) []tagDirEntry {
|
||||||
|
if nKeys <= 0 || len(data) < ExtHeadSize {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the standard leaf key decoder with keyLen=10 (compound tag name size)
|
||||||
|
hdr := DecodeLeafHeader(data)
|
||||||
|
keys := DecodeLeafKeys(data, hdr, 10)
|
||||||
|
|
||||||
var entries []tagDirEntry
|
var entries []tagDirEntry
|
||||||
// Compound index leaf format is simpler than data index
|
for _, dk := range keys {
|
||||||
// Each entry: offset varies by CDX implementation
|
name := trimNull(dk.Key)
|
||||||
// For now return empty — scanCompoundLeaves handles it
|
name = strings.TrimSpace(name)
|
||||||
_ = nKeys
|
if name == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// RecNo in compound index = direct byte offset to tag header
|
||||||
|
entries = append(entries, tagDirEntry{name: name, offset: int64(dk.RecNo)})
|
||||||
|
}
|
||||||
return entries
|
return entries
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -795,6 +813,21 @@ func (t *Tag) IsEOF() bool { return t.tagEOF }
|
|||||||
// IsBOF returns true if before start.
|
// IsBOF returns true if before start.
|
||||||
func (t *Tag) IsBOF() bool { return t.tagBOF }
|
func (t *Tag) IsBOF() bool { return t.tagBOF }
|
||||||
|
|
||||||
|
// KeyLen returns the key length.
|
||||||
|
func (t *Tag) KeyLen() int { return t.keyLen }
|
||||||
|
|
||||||
|
// KeyExpr returns the key expression string stored in the CDX header.
|
||||||
|
func (t *Tag) KeyExpr() string { return t.header.KeyExpr }
|
||||||
|
|
||||||
|
// ForExpr returns the FOR condition expression.
|
||||||
|
func (t *Tag) ForExpr() string { return t.header.ForExpr }
|
||||||
|
|
||||||
|
// IsDescending returns true if the tag sorts in descending order.
|
||||||
|
func (t *Tag) IsDescending() bool { return t.header.Descending }
|
||||||
|
|
||||||
|
// Close is a no-op for tags (the parent Index owns the file).
|
||||||
|
func (t *Tag) Close() error { return nil }
|
||||||
|
|
||||||
// --- Helpers ---
|
// --- Helpers ---
|
||||||
|
|
||||||
func trimNull(b []byte) string {
|
func trimNull(b []byte) string {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"five/hbrt"
|
"five/hbrt"
|
||||||
"five/hbrdd"
|
"five/hbrdd"
|
||||||
|
"five/hbrdd/cdx"
|
||||||
"five/hbrdd/ntx"
|
"five/hbrdd/ntx"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
@@ -18,13 +19,31 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// IndexEngine is the common interface for NTX Index and CDX Tag.
|
||||||
|
type IndexEngine interface {
|
||||||
|
Seek(searchKey []byte) (uint32, bool)
|
||||||
|
GoTop() bool
|
||||||
|
GoBottom() bool
|
||||||
|
SkipNext() bool
|
||||||
|
SkipPrev() bool
|
||||||
|
CurRecNo() uint32
|
||||||
|
CurKey() []byte
|
||||||
|
IsEOF() bool
|
||||||
|
IsBOF() bool
|
||||||
|
KeyLen() int
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
// indexState holds active index state for a DBFArea.
|
// indexState holds active index state for a DBFArea.
|
||||||
type indexState struct {
|
type indexState struct {
|
||||||
indexes []*ntx.Index // open NTX index files
|
indexes []IndexEngine // open NTX/CDX index engines
|
||||||
names []string // index file paths
|
names []string // index file paths
|
||||||
tags []string // tag names (for display)
|
tags []string // tag names (for display)
|
||||||
current int // active index (-1 = natural order)
|
current int // active index (-1 = natural order)
|
||||||
keyExprs []string // key expressions for each index
|
keyExprs []string // key expressions for each index
|
||||||
|
// Scope support
|
||||||
|
scopeTop []byte // top scope key (nil = no scope)
|
||||||
|
scopeBottom []byte // bottom scope key (nil = no scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensureIndexState initializes the index state if nil.
|
// ensureIndexState initializes the index state if nil.
|
||||||
@@ -117,14 +136,40 @@ func (a *DBFArea) OrderCreate(params hbrdd.OrderCreateParams) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// OrderListAdd opens an existing index file.
|
// OrderListAdd opens an existing index file (NTX single-order or CDX compound).
|
||||||
func (a *DBFArea) OrderListAdd(path string) error {
|
func (a *DBFArea) OrderListAdd(path string) error {
|
||||||
a.ensureIndexState()
|
a.ensureIndexState()
|
||||||
|
|
||||||
|
// Auto-detect extension: try .cdx first, then .ntx
|
||||||
if !strings.Contains(filepath.Base(path), ".") {
|
if !strings.Contains(filepath.Base(path), ".") {
|
||||||
path += ".ntx"
|
if _, err := os.Stat(path + ".cdx"); err == nil {
|
||||||
|
path += ".cdx"
|
||||||
|
} else {
|
||||||
|
path += ".ntx"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ext := strings.ToLower(filepath.Ext(path))
|
||||||
|
|
||||||
|
if ext == ".cdx" {
|
||||||
|
// CDX compound index — opens all tags
|
||||||
|
ci, err := cdx.OpenIndex(path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("open CDX failed: %w", err)
|
||||||
|
}
|
||||||
|
for _, tag := range ci.Tags() {
|
||||||
|
a.idxState.indexes = append(a.idxState.indexes, tag)
|
||||||
|
a.idxState.names = append(a.idxState.names, path)
|
||||||
|
a.idxState.tags = append(a.idxState.tags, tag.Name)
|
||||||
|
a.idxState.keyExprs = append(a.idxState.keyExprs, tag.KeyExpr())
|
||||||
|
}
|
||||||
|
if len(ci.Tags()) > 0 {
|
||||||
|
a.idxState.current = len(a.idxState.indexes) - len(ci.Tags()) // first tag
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NTX single index
|
||||||
idx, err := ntx.OpenIndex(path)
|
idx, err := ntx.OpenIndex(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("open index failed: %w", err)
|
return fmt.Errorf("open index failed: %w", err)
|
||||||
@@ -151,31 +196,68 @@ func (a *DBFArea) OrderListClear() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// OrderListFocus sets the active index by tag name or number.
|
// OrderListFocus sets the active index by tag name, number, or file name.
|
||||||
|
// Harbour: OrdSetFocus(nOrder) or OrdSetFocus("tagName")
|
||||||
func (a *DBFArea) OrderListFocus(tagName string) error {
|
func (a *DBFArea) OrderListFocus(tagName string) error {
|
||||||
a.ensureIndexState()
|
a.ensureIndexState()
|
||||||
if tagName == "" || tagName == "0" {
|
if tagName == "" || tagName == "0" {
|
||||||
a.idxState.current = -1 // natural order
|
a.idxState.current = -1 // natural order
|
||||||
|
a.ClearScope()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
upper := strings.ToUpper(tagName)
|
|
||||||
for i, name := range a.idxState.tags {
|
// Try as numeric order (1-based)
|
||||||
if strings.ToUpper(name) == upper {
|
if n, err := parseOrderNum(tagName); err == nil {
|
||||||
a.idxState.current = i
|
if n == 0 {
|
||||||
|
a.idxState.current = -1
|
||||||
|
a.ClearScope()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if n >= 1 && n <= len(a.idxState.indexes) {
|
||||||
|
a.idxState.current = n - 1
|
||||||
|
a.ClearScope()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Try by file name
|
|
||||||
|
upper := strings.ToUpper(tagName)
|
||||||
|
// Match by tag name
|
||||||
|
for i, name := range a.idxState.tags {
|
||||||
|
if strings.ToUpper(name) == upper {
|
||||||
|
a.idxState.current = i
|
||||||
|
a.ClearScope()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Match by file name
|
||||||
for i, name := range a.idxState.names {
|
for i, name := range a.idxState.names {
|
||||||
base := strings.ToUpper(filepath.Base(name))
|
base := strings.ToUpper(filepath.Base(name))
|
||||||
if base == upper || strings.TrimSuffix(base, ".NTX") == upper {
|
ext := strings.ToUpper(filepath.Ext(name))
|
||||||
|
if base == upper || strings.TrimSuffix(base, ext) == upper {
|
||||||
a.idxState.current = i
|
a.idxState.current = i
|
||||||
|
a.ClearScope()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return fmt.Errorf("index not found: %s", tagName)
|
return fmt.Errorf("index not found: %s", tagName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseOrderNum tries to parse a string as a positive integer (order number).
|
||||||
|
func parseOrderNum(s string) (int, error) {
|
||||||
|
s = strings.TrimSpace(s)
|
||||||
|
if len(s) == 0 {
|
||||||
|
return 0, fmt.Errorf("empty")
|
||||||
|
}
|
||||||
|
n := 0
|
||||||
|
for _, c := range s {
|
||||||
|
if c < '0' || c > '9' {
|
||||||
|
return 0, fmt.Errorf("not a number")
|
||||||
|
}
|
||||||
|
n = n*10 + int(c-'0')
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
// OrderListRebuild rebuilds all indexes.
|
// OrderListRebuild rebuilds all indexes.
|
||||||
// Harbour: ORDLISTREBUILD / REINDEX — recreates all open indexes from current data.
|
// Harbour: ORDLISTREBUILD / REINDEX — recreates all open indexes from current data.
|
||||||
func (a *DBFArea) OrderListRebuild() error {
|
func (a *DBFArea) OrderListRebuild() error {
|
||||||
@@ -344,25 +426,83 @@ func (a *DBFArea) Seek(key hbrt.Value, softSeek bool, findLast bool) (bool, erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GoTopIndexed positions at the first key in the active index.
|
// GoTopIndexed positions at the first key in the active index.
|
||||||
|
// Harbour: if SCOPE is set, positions at the first key >= scopeTop.
|
||||||
func (a *DBFArea) GoTopIndexed() error {
|
func (a *DBFArea) GoTopIndexed() error {
|
||||||
if a.idxState == nil || a.idxState.current < 0 {
|
if a.idxState == nil || a.idxState.current < 0 {
|
||||||
return a.GoTop()
|
return a.GoTop()
|
||||||
}
|
}
|
||||||
idx := a.idxState.indexes[a.idxState.current]
|
idx := a.idxState.indexes[a.idxState.current]
|
||||||
|
|
||||||
|
if a.idxState.scopeTop != nil {
|
||||||
|
// Seek to scope top boundary
|
||||||
|
recNo, _ := idx.Seek(a.idxState.scopeTop)
|
||||||
|
if recNo == 0 || idx.IsEOF() {
|
||||||
|
rc, _ := a.RecCount()
|
||||||
|
a.FEof = true
|
||||||
|
return a.GoTo(rc + 1)
|
||||||
|
}
|
||||||
|
// Check if within bottom scope
|
||||||
|
if a.idxState.scopeBottom != nil {
|
||||||
|
if bytes.Compare(idx.CurKey(), a.idxState.scopeBottom) > 0 {
|
||||||
|
rc, _ := a.RecCount()
|
||||||
|
a.FEof = true
|
||||||
|
return a.GoTo(rc + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return a.GoTo(idx.CurRecNo())
|
||||||
|
}
|
||||||
|
|
||||||
idx.GoTop()
|
idx.GoTop()
|
||||||
if idx.IsEOF() {
|
if idx.IsEOF() {
|
||||||
rc, _ := a.RecCount()
|
rc, _ := a.RecCount()
|
||||||
|
a.FEof = true
|
||||||
return a.GoTo(rc + 1)
|
return a.GoTo(rc + 1)
|
||||||
}
|
}
|
||||||
return a.GoTo(idx.CurRecNo())
|
return a.GoTo(idx.CurRecNo())
|
||||||
}
|
}
|
||||||
|
|
||||||
// GoBottomIndexed positions at the last key in the active index.
|
// GoBottomIndexed positions at the last key in the active index.
|
||||||
|
// Harbour: if SCOPE is set, positions at the last key <= scopeBottom.
|
||||||
func (a *DBFArea) GoBottomIndexed() error {
|
func (a *DBFArea) GoBottomIndexed() error {
|
||||||
if a.idxState == nil || a.idxState.current < 0 {
|
if a.idxState == nil || a.idxState.current < 0 {
|
||||||
return a.GoBottom()
|
return a.GoBottom()
|
||||||
}
|
}
|
||||||
idx := a.idxState.indexes[a.idxState.current]
|
idx := a.idxState.indexes[a.idxState.current]
|
||||||
|
|
||||||
|
if a.idxState.scopeBottom != nil {
|
||||||
|
// Seek to scope bottom boundary
|
||||||
|
_, exact := idx.Seek(a.idxState.scopeBottom)
|
||||||
|
if idx.IsEOF() {
|
||||||
|
// All keys less than bottom scope — go to physical bottom
|
||||||
|
idx.GoBottom()
|
||||||
|
} else if !exact {
|
||||||
|
// Positioned past bottom — go back one
|
||||||
|
idx.SkipPrev()
|
||||||
|
} else {
|
||||||
|
// Exact match — skip forward to last matching key, then position there
|
||||||
|
for {
|
||||||
|
idx.SkipNext()
|
||||||
|
if idx.IsEOF() || bytes.Compare(idx.CurKey(), a.idxState.scopeBottom) > 0 {
|
||||||
|
idx.SkipPrev()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if idx.IsBOF() || idx.IsEOF() {
|
||||||
|
a.FBof = true
|
||||||
|
return a.GoTo(1)
|
||||||
|
}
|
||||||
|
// Verify within top scope
|
||||||
|
if a.idxState.scopeTop != nil {
|
||||||
|
if bytes.Compare(idx.CurKey(), a.idxState.scopeTop) < 0 {
|
||||||
|
a.FEof = true
|
||||||
|
rc, _ := a.RecCount()
|
||||||
|
return a.GoTo(rc + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return a.GoTo(idx.CurRecNo())
|
||||||
|
}
|
||||||
|
|
||||||
idx.GoBottom()
|
idx.GoBottom()
|
||||||
if idx.IsBOF() {
|
if idx.IsBOF() {
|
||||||
return a.GoTo(1)
|
return a.GoTo(1)
|
||||||
@@ -371,11 +511,13 @@ func (a *DBFArea) GoBottomIndexed() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SkipIndexed skips using the active index order.
|
// SkipIndexed skips using the active index order.
|
||||||
|
// Harbour: respects SCOPE boundaries — stops at scope edges.
|
||||||
func (a *DBFArea) SkipIndexed(count int64) error {
|
func (a *DBFArea) SkipIndexed(count int64) error {
|
||||||
if a.idxState == nil || a.idxState.current < 0 {
|
if a.idxState == nil || a.idxState.current < 0 {
|
||||||
return a.Skip(count)
|
return a.Skip(count)
|
||||||
}
|
}
|
||||||
idx := a.idxState.indexes[a.idxState.current]
|
idx := a.idxState.indexes[a.idxState.current]
|
||||||
|
hasScope := a.idxState.scopeTop != nil || a.idxState.scopeBottom != nil
|
||||||
|
|
||||||
if count > 0 {
|
if count > 0 {
|
||||||
for i := int64(0); i < count; i++ {
|
for i := int64(0); i < count; i++ {
|
||||||
@@ -386,18 +528,188 @@ func (a *DBFArea) SkipIndexed(count int64) error {
|
|||||||
a.FEof = true
|
a.FEof = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
// Check bottom scope
|
||||||
|
if hasScope && a.idxState.scopeBottom != nil {
|
||||||
|
if bytes.Compare(idx.CurKey(), a.idxState.scopeBottom) > 0 {
|
||||||
|
rc, _ := a.RecCount()
|
||||||
|
a.GoTo(rc + 1)
|
||||||
|
a.FEof = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if count < 0 {
|
} else if count < 0 {
|
||||||
for i := int64(0); i > count; i-- {
|
for i := int64(0); i > count; i-- {
|
||||||
idx.SkipPrev()
|
idx.SkipPrev()
|
||||||
if idx.IsBOF() {
|
if idx.IsBOF() {
|
||||||
|
a.FBof = true
|
||||||
|
// Stay at first record in scope
|
||||||
|
if a.idxState.scopeTop != nil {
|
||||||
|
idx.Seek(a.idxState.scopeTop)
|
||||||
|
} else {
|
||||||
|
idx.GoTop()
|
||||||
|
}
|
||||||
|
if !idx.IsEOF() {
|
||||||
|
return a.GoTo(idx.CurRecNo())
|
||||||
|
}
|
||||||
return a.GoTo(1)
|
return a.GoTo(1)
|
||||||
}
|
}
|
||||||
|
// Check top scope
|
||||||
|
if hasScope && a.idxState.scopeTop != nil {
|
||||||
|
if bytes.Compare(idx.CurKey(), a.idxState.scopeTop) < 0 {
|
||||||
|
a.FBof = true
|
||||||
|
idx.Seek(a.idxState.scopeTop)
|
||||||
|
if !idx.IsEOF() {
|
||||||
|
return a.GoTo(idx.CurRecNo())
|
||||||
|
}
|
||||||
|
return a.GoTo(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return a.GoTo(idx.CurRecNo())
|
return a.GoTo(idx.CurRecNo())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Scope support (ORDSCOPE) ---
|
||||||
|
|
||||||
|
// SetScope sets top and/or bottom scope boundaries for the active index.
|
||||||
|
// Harbour: OrdScope(TOPSCOPE, val) / OrdScope(BOTTOMSCOPE, val)
|
||||||
|
// Pass zero-value hbrt.Value{} (not MakeNil) to skip setting that boundary.
|
||||||
|
func (a *DBFArea) SetScope(top, bottom hbrt.Value) error {
|
||||||
|
a.ensureIndexState()
|
||||||
|
if a.idxState.current < 0 {
|
||||||
|
return fmt.Errorf("no active index")
|
||||||
|
}
|
||||||
|
idx := a.idxState.indexes[a.idxState.current]
|
||||||
|
keyLen := idx.KeyLen()
|
||||||
|
|
||||||
|
if !top.IsNil() && top.Type() != 0 {
|
||||||
|
a.idxState.scopeTop = scopeKeyFromValue(top, keyLen)
|
||||||
|
}
|
||||||
|
if !bottom.IsNil() && bottom.Type() != 0 {
|
||||||
|
a.idxState.scopeBottom = scopeKeyFromValue(bottom, keyLen)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetScopeTop sets only the top scope.
|
||||||
|
func (a *DBFArea) SetScopeTop(val hbrt.Value) {
|
||||||
|
a.ensureIndexState()
|
||||||
|
if a.idxState.current < 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
keyLen := a.idxState.indexes[a.idxState.current].KeyLen()
|
||||||
|
a.idxState.scopeTop = scopeKeyFromValue(val, keyLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetScopeBottom sets only the bottom scope.
|
||||||
|
func (a *DBFArea) SetScopeBottom(val hbrt.Value) {
|
||||||
|
a.ensureIndexState()
|
||||||
|
if a.idxState.current < 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
keyLen := a.idxState.indexes[a.idxState.current].KeyLen()
|
||||||
|
a.idxState.scopeBottom = scopeKeyFromValue(val, keyLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearScope removes all scope boundaries.
|
||||||
|
func (a *DBFArea) ClearScope() error {
|
||||||
|
if a.idxState != nil {
|
||||||
|
a.idxState.scopeTop = nil
|
||||||
|
a.idxState.scopeBottom = nil
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearScopeTop removes only the top scope boundary.
|
||||||
|
func (a *DBFArea) ClearScopeTop() {
|
||||||
|
if a.idxState != nil {
|
||||||
|
a.idxState.scopeTop = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearScopeBottom removes only the bottom scope boundary.
|
||||||
|
func (a *DBFArea) ClearScopeBottom() {
|
||||||
|
if a.idxState != nil {
|
||||||
|
a.idxState.scopeBottom = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetScopeTop returns the current top scope key (nil if none).
|
||||||
|
func (a *DBFArea) GetScopeTop() []byte {
|
||||||
|
if a.idxState != nil {
|
||||||
|
return a.idxState.scopeTop
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetScopeBottom returns the current bottom scope key (nil if none).
|
||||||
|
func (a *DBFArea) GetScopeBottom() []byte {
|
||||||
|
if a.idxState != nil {
|
||||||
|
return a.idxState.scopeBottom
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// scopeKeyFromValue converts a Harbour Value to a scope key byte slice.
|
||||||
|
func scopeKeyFromValue(v hbrt.Value, keyLen int) []byte {
|
||||||
|
var key []byte
|
||||||
|
if v.IsString() {
|
||||||
|
key = []byte(v.AsString())
|
||||||
|
} else if v.IsNumeric() {
|
||||||
|
key = []byte(fmt.Sprintf("%*d", keyLen, v.AsNumInt()))
|
||||||
|
} else {
|
||||||
|
key = []byte(v.AsString())
|
||||||
|
}
|
||||||
|
// Pad to keyLen
|
||||||
|
if len(key) < keyLen {
|
||||||
|
padded := make([]byte, keyLen)
|
||||||
|
copy(padded, key)
|
||||||
|
for i := len(key); i < keyLen; i++ {
|
||||||
|
padded[i] = ' '
|
||||||
|
}
|
||||||
|
return padded
|
||||||
|
}
|
||||||
|
if len(key) > keyLen {
|
||||||
|
return key[:keyLen]
|
||||||
|
}
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Index info accessors ---
|
||||||
|
|
||||||
|
// IndexCount returns the number of open indexes.
|
||||||
|
func (a *DBFArea) IndexCount() int {
|
||||||
|
if a.idxState == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return len(a.idxState.indexes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CurrentOrder returns the 1-based current order number (0 = natural).
|
||||||
|
func (a *DBFArea) CurrentOrder() int {
|
||||||
|
if a.idxState == nil || a.idxState.current < 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return a.idxState.current + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrderName returns the tag name for order n (1-based).
|
||||||
|
func (a *DBFArea) OrderName(n int) string {
|
||||||
|
if a.idxState == nil || n < 1 || n > len(a.idxState.tags) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return a.idxState.tags[n-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrderKeyExpr returns the key expression for order n (1-based).
|
||||||
|
func (a *DBFArea) OrderKeyExpr(n int) string {
|
||||||
|
if a.idxState == nil || n < 1 || n > len(a.idxState.keyExprs) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return a.idxState.keyExprs[n-1]
|
||||||
|
}
|
||||||
|
|
||||||
// evalKeyExpr evaluates an index key expression for a given record.
|
// evalKeyExpr evaluates an index key expression for a given record.
|
||||||
// Supports: field names, UPPER(), LOWER(), LTRIM(), RTRIM(), ALLTRIM(),
|
// Supports: field names, UPPER(), LOWER(), LTRIM(), RTRIM(), ALLTRIM(),
|
||||||
// STR(), DTOS(), SUBSTR(), LEFT(), RIGHT(), PADL(), PADR(),
|
// STR(), DTOS(), SUBSTR(), LEFT(), RIGHT(), PADL(), PADR(),
|
||||||
|
|||||||
@@ -22,9 +22,13 @@ package hbrt
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"strconv"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// NtoS converts int64 to string. Used by generated code for SET ORDER TO.
|
||||||
|
func NtoS(n int64) string { return strconv.FormatInt(n, 10) }
|
||||||
|
|
||||||
// Value is the fundamental value type in Five (24 bytes).
|
// Value is the fundamental value type in Five (24 bytes).
|
||||||
// Scalar types use scalar+info fields (ptr is nil).
|
// Scalar types use scalar+info fields (ptr is nil).
|
||||||
// Pointer types use ptr field (GC-traced) + info for metadata.
|
// Pointer types use ptr field (GC-traced) + info for metadata.
|
||||||
|
|||||||
@@ -2,19 +2,30 @@
|
|||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
|
|
||||||
// Index and database introspection RTL functions.
|
// Index and database introspection RTL functions.
|
||||||
|
// Harbour: INDEXORD, INDEXKEY, ORDSETFOCUS, ORDCOUNT, ORDNAME, ORDKEY,
|
||||||
|
// ORDFOR, ORDSCOPE, DBORDERINFO, DBINFO, DBCREATE, RDDSETDEFAULT
|
||||||
|
|
||||||
package hbrtl
|
package hbrtl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"five/hbrt"
|
"five/hbrt"
|
||||||
"five/hbrdd"
|
"five/hbrdd"
|
||||||
|
"five/hbrdd/dbf"
|
||||||
)
|
)
|
||||||
|
|
||||||
// INDEXORD() → nCurrentOrder
|
// INDEXORD() → nCurrentOrder (1-based, 0 = natural)
|
||||||
func IndexOrd(t *hbrt.Thread) {
|
func IndexOrd(t *hbrt.Thread) {
|
||||||
t.Frame(0, 0)
|
t.Frame(0, 0)
|
||||||
defer t.EndProc()
|
defer t.EndProc()
|
||||||
// Simplified: return 0 (no active order)
|
wam := getWA(t)
|
||||||
|
if wam != nil {
|
||||||
|
if area := wam.Current(); area != nil {
|
||||||
|
if da, ok := area.(*dbf.DBFArea); ok {
|
||||||
|
t.RetInt(int64(da.CurrentOrder()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
t.RetInt(0)
|
t.RetInt(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,10 +34,23 @@ func IndexKey(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 {
|
||||||
|
if area := wam.Current(); area != nil {
|
||||||
|
if da, ok := area.(*dbf.DBFArea); ok {
|
||||||
|
n := da.CurrentOrder()
|
||||||
|
if nParams >= 1 && !t.Local(1).IsNil() {
|
||||||
|
n = t.Local(1).AsInt()
|
||||||
|
}
|
||||||
|
t.RetString(da.OrderKeyExpr(n))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
t.RetString("")
|
t.RetString("")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ORDSETFOCUS([nOrder|cTag]) → nOldOrder
|
// ORDSETFOCUS([nOrder|cTag [, cBagName]]) → nOldOrder
|
||||||
func OrdSetFocus(t *hbrt.Thread) {
|
func OrdSetFocus(t *hbrt.Thread) {
|
||||||
nParams := t.ParamCount()
|
nParams := t.ParamCount()
|
||||||
t.Frame(nParams, 0)
|
t.Frame(nParams, 0)
|
||||||
@@ -41,46 +65,167 @@ func OrdSetFocus(t *hbrt.Thread) {
|
|||||||
t.RetInt(0)
|
t.RetInt(0)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
da, isDa := area.(*dbf.DBFArea)
|
||||||
|
oldOrd := 0
|
||||||
|
if isDa {
|
||||||
|
oldOrd = da.CurrentOrder()
|
||||||
|
}
|
||||||
if nParams >= 1 && !t.Local(1).IsNil() {
|
if nParams >= 1 && !t.Local(1).IsNil() {
|
||||||
if idx, ok := area.(hbrdd.Indexer); ok {
|
if idx, ok := area.(hbrdd.Indexer); ok {
|
||||||
tag := t.Local(1).AsString()
|
v := t.Local(1)
|
||||||
idx.OrderListFocus(tag)
|
if v.IsNumeric() {
|
||||||
|
// SET ORDER TO n
|
||||||
|
idx.OrderListFocus(v.AsString())
|
||||||
|
} else {
|
||||||
|
idx.OrderListFocus(v.AsString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.RetInt(int64(oldOrd))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ORDCOUNT([cBagName]) → nOrders
|
||||||
|
func OrdCount(t *hbrt.Thread) {
|
||||||
|
nParams := t.ParamCount()
|
||||||
|
t.Frame(nParams, 0)
|
||||||
|
defer t.EndProc()
|
||||||
|
wam := getWA(t)
|
||||||
|
if wam != nil {
|
||||||
|
if area := wam.Current(); area != nil {
|
||||||
|
if da, ok := area.(*dbf.DBFArea); ok {
|
||||||
|
t.RetInt(int64(da.IndexCount()))
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.RetInt(0)
|
t.RetInt(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ORDCOUNT() → nOrders
|
// ORDNAME([nOrder [, cBagName]]) → cTagName
|
||||||
func OrdCount(t *hbrt.Thread) {
|
|
||||||
t.Frame(0, 0)
|
|
||||||
defer t.EndProc()
|
|
||||||
t.RetInt(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ORDNAME([nOrder]) → cTagName
|
|
||||||
func OrdName(t *hbrt.Thread) {
|
func OrdName(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 {
|
||||||
|
if area := wam.Current(); area != nil {
|
||||||
|
if da, ok := area.(*dbf.DBFArea); ok {
|
||||||
|
n := da.CurrentOrder()
|
||||||
|
if nParams >= 1 && !t.Local(1).IsNil() {
|
||||||
|
n = t.Local(1).AsInt()
|
||||||
|
}
|
||||||
|
t.RetString(da.OrderName(n))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
t.RetString("")
|
t.RetString("")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ORDKEY([nOrder]) → cKeyExpression
|
// ORDKEY([nOrder [, cBagName]]) → cKeyExpression
|
||||||
func OrdKey(t *hbrt.Thread) {
|
func OrdKey(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 {
|
||||||
|
if area := wam.Current(); area != nil {
|
||||||
|
if da, ok := area.(*dbf.DBFArea); ok {
|
||||||
|
n := da.CurrentOrder()
|
||||||
|
if nParams >= 1 && !t.Local(1).IsNil() {
|
||||||
|
n = t.Local(1).AsInt()
|
||||||
|
}
|
||||||
|
t.RetString(da.OrderKeyExpr(n))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
t.RetString("")
|
t.RetString("")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ORDFOR([nOrder]) → cForExpression
|
// ORDFOR([nOrder [, cBagName]]) → cForExpression
|
||||||
func OrdFor(t *hbrt.Thread) {
|
func OrdFor(t *hbrt.Thread) {
|
||||||
nParams := t.ParamCount()
|
nParams := t.ParamCount()
|
||||||
t.Frame(nParams, 0)
|
t.Frame(nParams, 0)
|
||||||
defer t.EndProc()
|
defer t.EndProc()
|
||||||
|
// TODO: return FOR expression from index
|
||||||
t.RetString("")
|
t.RetString("")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ORDSCOPE(nScope [, xValue]) → xOldValue
|
||||||
|
// nScope: 0 = TOPSCOPE, 1 = BOTTOMSCOPE
|
||||||
|
// If xValue omitted, returns current scope. If xValue given, sets scope and returns old.
|
||||||
|
// Harbour: TOPSCOPE = 0, BOTTOMSCOPE = 1
|
||||||
|
func OrdScope(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
|
||||||
|
}
|
||||||
|
|
||||||
|
nScope := 0
|
||||||
|
if nParams >= 1 {
|
||||||
|
nScope = t.Local(1).AsInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get old scope value
|
||||||
|
var oldScope []byte
|
||||||
|
if nScope == 0 {
|
||||||
|
oldScope = da.GetScopeTop()
|
||||||
|
} else {
|
||||||
|
oldScope = da.GetScopeBottom()
|
||||||
|
}
|
||||||
|
|
||||||
|
if oldScope != nil {
|
||||||
|
t.PushString(string(oldScope))
|
||||||
|
} else {
|
||||||
|
t.PushNil()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set new scope if value provided
|
||||||
|
if nParams >= 2 {
|
||||||
|
val := t.Local(2)
|
||||||
|
if val.IsNil() {
|
||||||
|
if nScope == 0 {
|
||||||
|
da.ClearScopeTop()
|
||||||
|
} else {
|
||||||
|
da.ClearScopeBottom()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if nScope == 0 {
|
||||||
|
da.SetScopeTop(val)
|
||||||
|
} else {
|
||||||
|
da.SetScopeBottom(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
// DBINFO(nInfoType [, xNewSetting]) → xInfo
|
// DBINFO(nInfoType [, xNewSetting]) → xInfo
|
||||||
func DbInfo(t *hbrt.Thread) {
|
func DbInfo(t *hbrt.Thread) {
|
||||||
nParams := t.ParamCount()
|
nParams := t.ParamCount()
|
||||||
|
|||||||
@@ -411,6 +411,8 @@ func RegisterRTL(vm *hbrt.VM) {
|
|||||||
hbrt.Sym("ORDFOR", hbrt.FsPublic, OrdFor),
|
hbrt.Sym("ORDFOR", hbrt.FsPublic, OrdFor),
|
||||||
hbrt.Sym("DBINFO", hbrt.FsPublic, DbInfo),
|
hbrt.Sym("DBINFO", hbrt.FsPublic, DbInfo),
|
||||||
hbrt.Sym("ORDINFO", hbrt.FsPublic, OrdInfo),
|
hbrt.Sym("ORDINFO", hbrt.FsPublic, OrdInfo),
|
||||||
|
hbrt.Sym("ORDSCOPE", hbrt.FsPublic, OrdScope),
|
||||||
|
hbrt.Sym("DBORDERINFO", hbrt.FsPublic, DbOrderInfo),
|
||||||
hbrt.Sym("RDDSETDEFAULT", hbrt.FsPublic, RddSetDefault),
|
hbrt.Sym("RDDSETDEFAULT", hbrt.FsPublic, RddSetDefault),
|
||||||
hbrt.Sym("DBCREATE", hbrt.FsPublic, DbCreate),
|
hbrt.Sym("DBCREATE", hbrt.FsPublic, DbCreate),
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user