feat(pp): UPDATE FROM via std.ch + nested-bracket fix in matchSegment
`UPDATE [FROM <alias>] [ON <key>] [RANDOM] REPLACE <f1> WITH <x1>
[, <fN> WITH <xN>]` becomes a preprocessor rewrite to a new RTL
primitive __dbUpdate. For each detail record, find the master
record with matching key (forward-walk if both sorted, full scan
when RANDOM) and apply the REPLACE clauses in master's context.
Same shape as harbour-core/src/rdd/dbupdat.prg. The REPLACE clauses
expand to comma-separated assignments inside one block —
`{|| _FIELD->total := del->amt, _FIELD->status := "OK" }` — using
the multi-pair `[, <fN> WITH <xN>]` optional-repeat that std.ch
already establishes for SUM and DEFAULT.
Five-specific tweak: ON <key> wraps as `{|| _FIELD-><key> }` rather
than Harbour's bare `<{key}>`. Five doesn't auto-resolve a bare
identifier in a code block to the current workarea's field, and the
UPDATE block must evaluate against both detail and master so an
explicit alias prefix won't do — _FIELD-> dispatches to whichever
area is selected at eval time, which is what's needed.
Wiring up UPDATE surfaced one further matchSegment gap that fell
out of the multi-pair `[REPLACE ... [, ...]]` shape:
* matchSegment didn't handle nested `[...]` inside its body.
`[REPLACE <f1> WITH <x1> [, <fN> WITH <xN>]]` gave the inner
`[` as a literal token to match against the line, so even the
single-pair `REPLACE total WITH del->amt` form failed and f1/x1
came back empty. Now matchSegment runs the same repeat-loop on
inner `[...]` blocks that the top-level matcher uses, with its
own outer-tail computed from the segment tail past the inner
`]`.
Parser cleanup: UPDATE removed from the IDENT-statement no-op switch.
Gates green:
go test ./... : PASS
FiveSql2 SQL:1999 : 43/43
Harbour compat : 56/56
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1650,6 +1650,144 @@ func rtlDbJoin(t *hbrt.Thread) {
|
||||
t.RetBool(true)
|
||||
}
|
||||
|
||||
// rtlDbUpdate implements __dbUpdate(cAlias, bKey, lRandom, bAssign) —
|
||||
// for each record in the detail workarea, find the matching master
|
||||
// record (by key equality) and apply bAssign in master's context.
|
||||
// Same shape as harbour-core/src/rdd/dbupdat.prg:
|
||||
//
|
||||
// * bKey runs in either context — typically a bare field name that
|
||||
// exists in both areas.
|
||||
// * bAssign runs in *master* context — Harbour's std.ch wraps each
|
||||
// `<f> WITH <x>` clause as `_FIELD-><f> := <x>` so the assignment
|
||||
// targets the master row while `<x>` is free to read detail->...
|
||||
// * lRandom .T.: scan master from top for each detail key. Otherwise
|
||||
// both areas must be sorted on the key; the master pointer just
|
||||
// walks forward through equal-or-greater keys (matches Harbour's
|
||||
// forward-walk semantics for the non-random branch).
|
||||
//
|
||||
// Used by `UPDATE FROM <alias> [ON <key>] [RANDOM] REPLACE ...`
|
||||
// in std.ch.
|
||||
func rtlDbUpdate(t *hbrt.Thread) {
|
||||
nParams := t.ParamCount()
|
||||
t.Frame(nParams, 0)
|
||||
defer t.EndProcFast()
|
||||
|
||||
wam := getWA(t)
|
||||
if wam == nil {
|
||||
t.RetBool(false)
|
||||
return
|
||||
}
|
||||
master := wam.Current()
|
||||
if master == nil {
|
||||
t.RetBool(false)
|
||||
return
|
||||
}
|
||||
masterSel := wam.CurrentNum()
|
||||
|
||||
if nParams < 1 || t.Local(1).IsNil() {
|
||||
t.RetBool(false)
|
||||
return
|
||||
}
|
||||
cAlias := strings.TrimSpace(t.Local(1).AsString())
|
||||
if cAlias == "" {
|
||||
t.RetBool(false)
|
||||
return
|
||||
}
|
||||
detailSel := wam.FindByAlias(cAlias)
|
||||
if detailSel == 0 {
|
||||
t.RetBool(false)
|
||||
return
|
||||
}
|
||||
detail := wam.AreaAt(detailSel)
|
||||
if detail == nil {
|
||||
t.RetBool(false)
|
||||
return
|
||||
}
|
||||
|
||||
bKey := hbrt.Value{}
|
||||
if nParams >= 2 {
|
||||
bKey = t.Local(2)
|
||||
}
|
||||
if !bKey.IsBlock() {
|
||||
// No key block — nothing to drive the update by.
|
||||
t.RetBool(false)
|
||||
return
|
||||
}
|
||||
|
||||
lRandom := false
|
||||
if nParams >= 3 && !t.Local(3).IsNil() {
|
||||
lRandom = t.Local(3).AsBool()
|
||||
}
|
||||
|
||||
bAssign := hbrt.Value{}
|
||||
if nParams >= 4 {
|
||||
bAssign = t.Local(4)
|
||||
}
|
||||
if !bAssign.IsBlock() {
|
||||
t.RetBool(false)
|
||||
return
|
||||
}
|
||||
|
||||
// Position both areas at top.
|
||||
master.GoTop()
|
||||
wam.SelectByNum(detailSel)
|
||||
detail.GoTop()
|
||||
wam.SelectByNum(masterSel)
|
||||
|
||||
for {
|
||||
wam.SelectByNum(detailSel)
|
||||
if detail.EOF() {
|
||||
break
|
||||
}
|
||||
// Key from the detail row.
|
||||
t.PendingParams2(0)
|
||||
bKey.AsBlock().Fn(t)
|
||||
detailKey := t.GetRetValue()
|
||||
wam.SelectByNum(masterSel)
|
||||
|
||||
if lRandom {
|
||||
// Linear scan from top — index-aware seek would be
|
||||
// faster but Five's seek API isn't part of the Area
|
||||
// interface yet.
|
||||
master.GoTop()
|
||||
for !master.EOF() {
|
||||
t.PendingParams2(0)
|
||||
bKey.AsBlock().Fn(t)
|
||||
if compareValues(t.GetRetValue(), detailKey) == 0 {
|
||||
break
|
||||
}
|
||||
master.Skip(1)
|
||||
}
|
||||
} else {
|
||||
// Walk forward while master's key < detail's key.
|
||||
for !master.EOF() {
|
||||
t.PendingParams2(0)
|
||||
bKey.AsBlock().Fn(t)
|
||||
if compareValues(t.GetRetValue(), detailKey) >= 0 {
|
||||
break
|
||||
}
|
||||
master.Skip(1)
|
||||
}
|
||||
}
|
||||
if !master.EOF() {
|
||||
// Match check (also covers the random branch's
|
||||
// "found something" outcome).
|
||||
t.PendingParams2(0)
|
||||
bKey.AsBlock().Fn(t)
|
||||
if compareValues(t.GetRetValue(), detailKey) == 0 {
|
||||
t.PendingParams2(0)
|
||||
bAssign.AsBlock().Fn(t)
|
||||
}
|
||||
}
|
||||
|
||||
wam.SelectByNum(detailSel)
|
||||
detail.Skip(1)
|
||||
}
|
||||
|
||||
wam.SelectByNum(masterSel)
|
||||
t.RetBool(true)
|
||||
}
|
||||
|
||||
// stableSort is a tiny insertion sort for small N (typical DBF SORT
|
||||
// targets are interactive datasets). Avoids a sort import dependency.
|
||||
func stableSort(rows [][]hbrt.Value, less func(i, j int) bool) {
|
||||
|
||||
@@ -204,6 +204,7 @@ func RegisterRTL(vm *hbrt.VM) {
|
||||
hbrt.Sym("__DBLIST", hbrt.FsPublic, rtlDbList),
|
||||
hbrt.Sym("__DBTOTAL", hbrt.FsPublic, rtlDbTotal),
|
||||
hbrt.Sym("__DBJOIN", hbrt.FsPublic, rtlDbJoin),
|
||||
hbrt.Sym("__DBUPDATE", hbrt.FsPublic, rtlDbUpdate),
|
||||
hbrt.Sym("DBSETFILTER", hbrt.FsPublic, rtlDbSetFilter),
|
||||
hbrt.Sym("DBCLEARFILTER", hbrt.FsPublic, rtlDbClearFilter),
|
||||
hbrt.Sym("DBFILTER", hbrt.FsPublic, rtlDbFilter),
|
||||
|
||||
Reference in New Issue
Block a user