Files
five/hbrtl/database.go
Charles KWON OhJun 05ccef05e2 perf: EndProcFast — eliminate defer recover() from RTL hot paths
Problem: every RTL function calls defer t.EndProc() which does recover().
50K SEEK loop = 250K recover() calls = ~12ms wasted.

Solution: EndProcFast() skips recover (only needs endFrame restore).
Applied to ALL RTL functions in strings.go, rdd.go, missing.go, database.go.
EndProc() with recover kept for generated PRG code (needs BEGIN SEQUENCE).

Analysis (50K sequential SEEK breakdown):
  Go NTX Seek direct: 7ms (faster than Harbour 27ms!)
  PRG VM overhead:    38ms (Frame + RTL calls + key generation)
  Key generation:     25ms (Str+LTrim+PadL+PadR = 5 RTL Frame/EndProc per iter)

With EndProcFast: RTL overhead reduced ~30%.

CDX SCOPE: 2ms (Harbour 4ms — 2x FASTER!)
82/82 stress PASS. 14 packages ALL PASS.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 21:43:39 +09:00

603 lines
10 KiB
Go

// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
// All rights reserved.
// Database callable functions: FIELDPUT, ALIAS, DBEVAL, DBUSEAREA, DBCLOSEAREA,
// DBGOTO, DBSKIP, DBAPPEND, DBDELETE, DBRECALL, DBCOMMIT, DBSEEK,
// DBGOTOP, DBGOBOTTOM, DBRLOCKLIST, DBSETFILTER, DBCLEARFILTER
package hbrtl
import (
"five/hbrt"
"five/hbrdd"
)
// FIELDPUT(nField, xValue) → xValue
func rtlFieldPut(t *hbrt.Thread) {
t.Frame(2, 0)
defer t.EndProcFast()
wam := getWA(t)
if wam == nil {
t.RetNil()
return
}
area := wam.Current()
if area == nil {
t.RetNil()
return
}
nField := t.Local(1).AsInt()
val := t.Local(2)
area.PutValue(nField-1, val) // 1-based to 0-based
t.RetVal(val)
}
// ALIAS([nWorkArea]) → cAlias
func rtlAlias(t *hbrt.Thread) {
nParams := t.ParamCount()
t.Frame(nParams, 0)
defer t.EndProcFast()
wam := getWA(t)
if wam == nil {
t.RetString("")
return
}
area := wam.Current()
if area != nil {
t.RetString(area.Alias())
} else {
t.RetString("")
}
}
// DBEVAL(bBlock [, bFor [, bWhile [, nCount [, nRecord [, lRest]]]]]) → NIL
func rtlDbEval(t *hbrt.Thread) {
nParams := t.ParamCount()
t.Frame(nParams, 0)
defer t.EndProcFast()
wam := getWA(t)
if wam == nil {
t.RetNil()
return
}
area := wam.Current()
if area == nil {
t.RetNil()
return
}
block := t.Local(1)
if !block.IsBlock() {
t.RetNil()
return
}
var bFor, bWhile hbrt.Value
nCount := -1
lRest := false
if nParams >= 2 {
bFor = t.Local(2)
}
if nParams >= 3 {
bWhile = t.Local(3)
}
if nParams >= 4 && !t.Local(4).IsNil() {
nCount = t.Local(4).AsInt()
}
if nParams >= 5 && !t.Local(5).IsNil() {
nRec := t.Local(5).AsInt()
area.GoTo(uint32(nRec))
}
if nParams >= 6 && !t.Local(6).IsNil() {
lRest = t.Local(6).AsBool()
}
// If not lRest and no record specified, go top
if !lRest && (nParams < 5 || t.Local(5).IsNil()) {
area.GoTop()
}
count := 0
for !area.EOF() {
if nCount >= 0 && count >= nCount {
break
}
// While condition
if !bWhile.IsNil() && bWhile.IsBlock() {
blk := bWhile.AsBlock()
t.PendingParams2(0)
blk.Fn(t)
if !t.GetRetValue().AsBool() {
break
}
}
// For condition
doBlock := true
if !bFor.IsNil() && bFor.IsBlock() {
blk := bFor.AsBlock()
t.PendingParams2(0)
blk.Fn(t)
doBlock = t.GetRetValue().AsBool()
}
if doBlock {
blk := block.AsBlock()
t.PendingParams2(0)
blk.Fn(t)
}
area.Skip(1)
count++
}
t.RetNil()
}
// DBUSEAREA([lNewArea], [cDriver], cName, [cAlias], [lShared], [lReadOnly]) → NIL
func rtlDbUseArea(t *hbrt.Thread) {
nParams := t.ParamCount()
t.Frame(nParams, 0)
defer t.EndProcFast()
wam := getWA(t)
if wam == nil {
t.RetNil()
return
}
cName := ""
cAlias := ""
cDriver := "DBFNTX"
if nParams >= 3 && !t.Local(3).IsNil() {
cName = t.Local(3).AsString()
}
if nParams >= 4 && !t.Local(4).IsNil() {
cAlias = t.Local(4).AsString()
}
if nParams >= 2 && !t.Local(2).IsNil() {
cDriver = t.Local(2).AsString()
}
shared := false
readOnly := false
if nParams >= 5 && !t.Local(5).IsNil() {
shared = t.Local(5).AsBool()
}
if nParams >= 6 && !t.Local(6).IsNil() {
readOnly = t.Local(6).AsBool()
}
wam.Open(cDriver, cName, cAlias, shared, readOnly)
t.RetNil()
}
// DBCLOSEAREA() → NIL
func rtlDbCloseArea(t *hbrt.Thread) {
t.Frame(0, 0)
defer t.EndProcFast()
wam := getWA(t)
if wam != nil {
wam.Close()
}
t.RetNil()
}
// DBGOTO(nRecNo) → NIL
func rtlDbGoTo(t *hbrt.Thread) {
t.Frame(1, 0)
defer t.EndProcFast()
wam := getWA(t)
if wam == nil {
t.RetNil()
return
}
area := wam.Current()
if area != nil {
area.GoTo(uint32(t.Local(1).AsLong()))
}
t.RetNil()
}
// DBSKIP([nRecords]) → NIL
func rtlDbSkip(t *hbrt.Thread) {
nParams := t.ParamCount()
t.Frame(nParams, 0)
defer t.EndProcFast()
wam := getWA(t)
if wam == nil {
t.RetNil()
return
}
area := wam.Current()
if area == nil {
t.RetNil()
return
}
n := int64(1)
if nParams >= 1 && !t.Local(1).IsNil() {
n = t.Local(1).AsLong()
}
area.Skip(n)
t.RetNil()
}
// DBGOTOP() → NIL
func rtlDbGoTop(t *hbrt.Thread) {
t.Frame(0, 0)
defer t.EndProcFast()
wam := getWA(t)
if wam == nil {
t.RetNil()
return
}
area := wam.Current()
if area != nil {
area.GoTop()
}
t.RetNil()
}
// DBGOBOTTOM() → NIL
func rtlDbGoBottom(t *hbrt.Thread) {
t.Frame(0, 0)
defer t.EndProcFast()
wam := getWA(t)
if wam == nil {
t.RetNil()
return
}
area := wam.Current()
if area != nil {
area.GoBottom()
}
t.RetNil()
}
// DBAPPEND([lUnlock]) → NIL
func rtlDbAppend(t *hbrt.Thread) {
nParams := t.ParamCount()
t.Frame(nParams, 0)
defer t.EndProcFast()
wam := getWA(t)
if wam == nil {
t.RetNil()
return
}
area := wam.Current()
if area != nil {
area.Append()
}
t.RetNil()
}
// DBDELETE() → NIL
func rtlDbDelete(t *hbrt.Thread) {
t.Frame(0, 0)
defer t.EndProcFast()
wam := getWA(t)
if wam == nil {
t.RetNil()
return
}
area := wam.Current()
if area != nil {
area.Delete()
}
t.RetNil()
}
// DBRECALL() → NIL
func rtlDbRecall(t *hbrt.Thread) {
t.Frame(0, 0)
defer t.EndProcFast()
wam := getWA(t)
if wam == nil {
t.RetNil()
return
}
area := wam.Current()
if area != nil {
area.Recall()
}
t.RetNil()
}
// DBCOMMIT() → NIL
func rtlDbCommit(t *hbrt.Thread) {
t.Frame(0, 0)
defer t.EndProcFast()
wam := getWA(t)
if wam == nil {
t.RetNil()
return
}
area := wam.Current()
if area != nil {
area.Flush()
}
t.RetNil()
}
// DBSEEK(xValue [, lSoftSeek [, lLast]]) → lFound
func rtlDbSeek(t *hbrt.Thread) {
nParams := t.ParamCount()
t.Frame(nParams, 0)
defer t.EndProcFast()
wam := getWA(t)
if wam == nil {
t.RetBool(false)
return
}
area := wam.Current()
if area == nil {
t.RetBool(false)
return
}
val := t.Local(1)
softSeek := GetSetSoftSeek() // default: check SET SOFTSEEK
findLast := false
if nParams >= 2 && !t.Local(2).IsNil() {
softSeek = t.Local(2).AsBool()
}
if nParams >= 3 && !t.Local(3).IsNil() {
findLast = t.Local(3).AsBool()
}
// Check if area implements Indexer
if idx, ok := area.(hbrdd.Indexer); ok {
found, _ := idx.Seek(val, softSeek, findLast)
t.RetBool(found)
} else {
t.RetBool(false)
}
}
// DBSELECTAREA(nArea | cAlias) → NIL
func rtlDbSelectArea(t *hbrt.Thread) {
t.Frame(1, 0)
defer t.EndProcFast()
wam := getWA(t)
if wam == nil {
t.RetNil()
return
}
v := t.Local(1)
if v.IsString() {
wam.Select(v.AsString())
} else {
wam.Select(uint16(v.AsInt()))
}
t.RetNil()
}
// DBPACK() → NIL
func rtlDbPack(t *hbrt.Thread) {
t.Frame(0, 0)
defer t.EndProcFast()
wam := getWA(t)
if wam == nil {
t.RetNil()
return
}
area := wam.Current()
if area != nil {
area.Pack()
}
t.RetNil()
}
// DBZAP() → NIL
func rtlDbZap(t *hbrt.Thread) {
t.Frame(0, 0)
defer t.EndProcFast()
wam := getWA(t)
if wam == nil {
t.RetNil()
return
}
area := wam.Current()
if area != nil {
area.Zap()
}
t.RetNil()
}
// --- LOCATE / CONTINUE ---
// Harbour: DBLOCATE(bFor, bWhile, nNext, nRec, lRest) → .T./.F.
// Searches from current position for record matching condition.
func rtlDbLocate(t *hbrt.Thread) {
nParams := t.ParamCount()
t.Frame(nParams, 0)
defer t.EndProcFast()
wam := getWA(t)
if wam == nil {
t.RetBool(false)
return
}
area := wam.Current()
if area == nil {
t.RetBool(false)
return
}
// param 1: bFor condition block
var bFor hbrt.Value
if nParams >= 1 && !t.Local(1).IsNil() {
bFor = t.Local(1)
}
// param 2: bWhile condition block
var bWhile hbrt.Value
if nParams >= 2 && !t.Local(2).IsNil() {
bWhile = t.Local(2)
}
// param 3: nNext — max records to scan
nNext := int64(0) // 0 = all
if nParams >= 3 && !t.Local(3).IsNil() {
nNext = t.Local(3).AsNumInt()
}
// param 4: nRec — specific record number
if nParams >= 4 && !t.Local(4).IsNil() {
nRec := uint32(t.Local(4).AsNumInt())
area.GoTo(nRec)
if bFor.IsBlock() {
t.PendingParams2(0)
bFor.AsBlock().Fn(t)
result := t.Pop2()
area.SetFound(result.AsBool())
} else {
area.SetFound(true)
}
t.RetBool(area.Found())
return
}
// param 5: lRest — .T. = continue from current, .F. = from top
lRest := false
if nParams >= 5 && !t.Local(5).IsNil() {
lRest = t.Local(5).AsBool()
}
if !lRest && nNext == 0 {
area.GoTop()
}
// Store locate block for __dbContinue
if bFor.IsBlock() {
area.SetLocate("", func(lt *hbrt.Thread) bool {
lt.PendingParams2(0)
bFor.AsBlock().Fn(lt)
return lt.Pop2().AsBool()
})
}
found := false
count := int64(0)
for !area.EOF() {
if nNext > 0 && count >= nNext {
break
}
// Check WHILE condition
if bWhile.IsBlock() {
t.PendingParams2(0)
bWhile.AsBlock().Fn(t)
if !t.Pop2().AsBool() {
break
}
}
// Check FOR condition
if bFor.IsBlock() {
t.PendingParams2(0)
bFor.AsBlock().Fn(t)
if t.Pop2().AsBool() {
found = true
break
}
} else {
found = true
break
}
area.Skip(1)
count++
}
area.SetFound(found)
t.RetBool(found)
}
// __DBCONTINUE — continue previous LOCATE search.
// Harbour: __dbContinue()
func rtlDbContinue(t *hbrt.Thread) {
t.Frame(0, 0)
defer t.EndProcFast()
wam := getWA(t)
if wam == nil {
t.RetBool(false)
return
}
area := wam.Current()
if area == nil {
t.RetBool(false)
return
}
blk := area.LocateBlock()
if blk == nil {
area.SetFound(false)
t.RetBool(false)
return
}
// Skip past current record
area.Skip(1)
found := false
for !area.EOF() {
if blk(t) {
found = true
break
}
area.Skip(1)
}
area.SetFound(found)
t.RetBool(found)
}
// --- DBSETFILTER / DBCLEARFILTER / DBFILTER ---
// DBSETFILTER(bCondition [, cCondition])
func rtlDbSetFilter(t *hbrt.Thread) {
nParams := t.ParamCount()
t.Frame(nParams, 0)
defer t.EndProcFast()
wam := getWA(t)
if wam == nil {
t.RetNil()
return
}
area := wam.Current()
if area == nil {
t.RetNil()
return
}
if nParams >= 1 && t.Local(1).IsBlock() {
bFilter := t.Local(1)
expr := ""
if nParams >= 2 && t.Local(2).IsString() {
expr = t.Local(2).AsString()
}
area.SetFilter(expr, func(lt *hbrt.Thread) bool {
lt.PendingParams2(0)
bFilter.AsBlock().Fn(lt)
return lt.Pop2().AsBool()
})
}
t.RetNil()
}
// DBCLEARFILTER()
func rtlDbClearFilter(t *hbrt.Thread) {
t.Frame(0, 0)
defer t.EndProcFast()
wam := getWA(t)
if wam == nil {
t.RetNil()
return
}
if area := wam.Current(); area != nil {
area.ClearFilter()
}
t.RetNil()
}
// DBFILTER() → cFilterExpression
func rtlDbFilter(t *hbrt.Thread) {
t.Frame(0, 0)
defer t.EndProcFast()
// TODO: return stored filter expression from area
t.RetString("")
}