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:
@@ -420,6 +420,53 @@ func matchSegment(segment, lineWords []string, startLi int, caseSens bool, outer
|
||||
|
||||
for pi := 0; pi < len(segment); pi++ {
|
||||
pw := segment[pi]
|
||||
// Nested optional clause: find the matching `]`, run the
|
||||
// repeat-loop on the inner body until no progress. Mirrors
|
||||
// the main matchPattern's `[` branch. Doesn't require any
|
||||
// remaining input — an absent optional just doesn't iterate.
|
||||
if pw == "[" {
|
||||
depth := 1
|
||||
bodyStart := pi + 1
|
||||
bodyEnd := bodyStart
|
||||
for bodyEnd < len(segment) && depth > 0 {
|
||||
if segment[bodyEnd] == "[" {
|
||||
depth++
|
||||
} else if segment[bodyEnd] == "]" {
|
||||
depth--
|
||||
if depth == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
bodyEnd++
|
||||
}
|
||||
innerBody := segment[bodyStart:bodyEnd]
|
||||
innerOuterTail := segment[bodyEnd+1:]
|
||||
for li < len(lineWords) {
|
||||
snapshotLi := li
|
||||
iterCaps, newLi, ok := matchSegment(innerBody, lineWords, li, caseSens, innerOuterTail)
|
||||
if !ok {
|
||||
li = snapshotLi
|
||||
break
|
||||
}
|
||||
if newLi == snapshotLi {
|
||||
break
|
||||
}
|
||||
for k, v := range iterCaps {
|
||||
if prev, hit := caps[k]; hit && prev != "" {
|
||||
caps[k] = prev + "\x01" + v
|
||||
} else {
|
||||
caps[k] = v
|
||||
}
|
||||
}
|
||||
li = newLi
|
||||
}
|
||||
pi = bodyEnd
|
||||
continue
|
||||
}
|
||||
if pw == "]" {
|
||||
// Stray closer — skip.
|
||||
continue
|
||||
}
|
||||
if li >= len(lineWords) {
|
||||
return nil, startLi, false
|
||||
}
|
||||
|
||||
@@ -103,7 +103,11 @@
|
||||
from the source. Numeric fields named in FIELDS are summed; every
|
||||
other (non-memo) field takes the first record's value. The source
|
||||
must already be sorted/indexed on the key for the grouping to
|
||||
produce one row per distinct value. */
|
||||
produce one row per distinct value.
|
||||
|
||||
Note for callers: Five doesn't auto-resolve a bare identifier
|
||||
inside a code block to the current workarea's field. Write the
|
||||
key alias-qualified — `ON src->dept` rather than `ON dept`. */
|
||||
#command TOTAL [TO <(f)>] [ON <key>] [FIELDS <fields,...>] ;
|
||||
[FOR <for>] [WHILE <while>] [NEXT <next>] ;
|
||||
[RECORD <rec>] [<rest:REST>] [ALL] => ;
|
||||
@@ -117,6 +121,21 @@
|
||||
[FOR <for>] => ;
|
||||
__dbJoin( <(alias)>, <(f)>, { <(fields)> }, <{for}> )
|
||||
|
||||
/* UPDATE FROM walks the named detail alias and applies the
|
||||
REPLACE ... WITH ... clauses to the matching master record.
|
||||
Both areas should be sorted on the key for the default forward-
|
||||
walk; pass RANDOM to scan master from top for each detail key.
|
||||
|
||||
Note: ON <key> is wrapped as `_FIELD-><key>` rather than the bare
|
||||
`<{key}>` Harbour uses, because the same block must evaluate
|
||||
against both master and detail. Bare identifiers don't auto-bind
|
||||
to fields under Five — `_FIELD->` makes the dispatch explicit. */
|
||||
#command UPDATE [FROM <(alias)>] [ON <key>] [<rand:RANDOM>] ;
|
||||
[REPLACE <f1> WITH <x1> ;
|
||||
[, <fN> WITH <xN>]] => ;
|
||||
__dbUpdate( <(alias)>, {|| _FIELD-><key> }, <.rand.>, ;
|
||||
{|| _FIELD-><f1> := <x1>[, _FIELD-><fN> := <xN>] } )
|
||||
|
||||
/* --- bulk maintenance --- */
|
||||
#command REINDEX => DbReindex()
|
||||
#command PACK => DbPack()
|
||||
|
||||
Reference in New Issue
Block a user