Eliminate MacroEval overhead for INDEX ON with UDF/complex expressions.
Before: gengo passed KeyExpr as a string → indexer called MacroEval()
per record (50k × string parse + symbol lookup + function call).
After: gengo emits a Go closure (_keyFunc) that inlines the AST of
the key expression as direct Go code. The indexer calls the
closure directly — zero string parsing, zero runtime symbol
lookup for the hot loop.
Three code paths in the closure, depending on expression type:
1. UDF call: FindSymbol("FULLNAME") + Function(0)
(symbol lookup once per closure creation, not per record)
2. Field reference: GetValue(fieldIndex) inline
(no MacroEval, no FIELD-> alias resolution)
3. UPPER/LOWER(expr): strings.ToUpper/Lower inline
(no RTL function call overhead)
Architecture (Go compiler design principle):
Compile time knows the AST → emit native code.
Don't serialize to string → re-parse at runtime 50k times.
Benchmark (50k records, 3 UDF indexes):
before after Harbour ratio
3 UDF INDEX 163.0ms 60.0ms 55.0ms Five/HB = 1.09x
SEEK 10k 7.6ms 7.6ms 14.0ms Five 1.8x faster
SCAN 50k 3.4ms 3.4ms 4.0ms Five 15% faster
TOTAL 233.0ms 130.0ms 147.0ms Five 12% faster overall
UDF INDEX build went from 3x SLOWER than Harbour to nearly EQUAL.
SEEK/SCAN remain faster than Harbour (mmap + NTX optimizations).
Changes:
hbrdd/driver.go KeyFunc field in OrderCreateParams
hbrdd/dbf/indexer.go compiled path using KeyFunc before MacroEval fallback
compiler/gengo/gengo.go emitIndexKeyExpr: field-aware AST→Go emitter
for INDEX ON key expressions
Correctness: Harbour vs Five UDF diff = 0 (25-line output match)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 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>