feat(pp,rtl): pre-release accuracy round (Wave 3)

Four audit findings around correctness/consistency in std.ch and the
SORT/UPDATE/TOTAL handlers:

  * #13: TOTAL/UPDATE key idiom inconsistency documented as inherent.
    TOTAL evaluates `<key>` only in the source workarea so verbatim
    `<{key}>` (alias-qualified or `_FIELD->`-prefixed by the user)
    works. UPDATE evaluates the same block in BOTH master and detail
    context, so it must wrap as `_FIELD-><key>` to dispatch to
    whichever WA is selected at eval time. The two rules look alike
    but their evaluation contexts differ — also documented in
    std.ch alongside both rules so the asymmetry isn't a surprise.
    Plus: TOTAL TO and ON are now mandatory (matching the COUNT/
    UPDATE pattern from Wave 1) — bare TOTAL would have produced
    broken syntax via the unconditional `<(f)>`/`<{key}>` template
    references.

  * #15/#16: SDF / DELIMITED variants of COPY and TO PRINTER /
    TO FILE variants of LIST / DISPLAY are now matched by stub
    rules (placed *before* the regular rules so they win) that
    expand to a new `__dbNotImpl(reason)` RTL primitive raising a
    clear `&hbrt.HbError`. BEGIN SEQUENCE / RECOVER catches the
    panic, so callers get a real error instead of the previous
    silent dispatch-to-regular-DBF-copy.

  * #19: SORT /C (case-insensitive) now actually folds case before
    the string compare, instead of being silently treated as
    ascending. Suffix parser also rebuilt as a multi-letter scanner
    so `name/CD`, `name/DC`, `name/C/D`, `name/D/C` all parse the
    same way — combine /C and /D freely. Unknown suffix letters
    (e.g., `name/X`) leave the suffix attached to the field name
    so a stray slash in user input doesn't get silently mangled
    into a broken field reference.

  * #27 SET DELETED: verified with a regression test that
    `SET DELETED ON` causes COUNT/COPY (and by extension
    SORT/TOTAL/JOIN/UPDATE — all of which iterate via Area.Skip)
    to skip rows marked deleted. The filtering is implemented at
    the workarea level (skipFilter in dbf.go honors hbrdd.IsSetDeleted)
    so no RTL changes were needed; this commit just adds the
    coverage so the behavior doesn't silently regress.

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:
2026-05-01 08:01:42 +09:00
parent f30704a854
commit 5b1d3fb32f
3 changed files with 103 additions and 20 deletions

View File

@@ -74,8 +74,15 @@
/* --- bulk record export ---
COPY TO copies visible records of the current workarea into a fresh
DBF. FIELDS/FOR/WHILE/NEXT/RECORD/REST work as in Harbour. SDF and
DELIMITED variants stay as silent no-ops in the parser until their
backends land. */
DELIMITED variants are not implemented; the matching rules below
raise a clear runtime error so callers don't quietly get a regular
DBF copy when they asked for an SDF dump. Order matters: the SDF /
DELIMITED rules must come before the regular COPY rule. */
#command COPY [TO <(f)>] [FIELDS <fields,...>] SDF [<*tail*>] => ;
__dbNotImpl("COPY TO ... SDF")
#command COPY [TO <(f)>] [FIELDS <fields,...>] DELIMITED [<*tail*>] => ;
__dbNotImpl("COPY TO ... DELIMITED")
#command COPY [TO <(f)>] [FIELDS <fields,...>] ;
[FOR <for>] [WHILE <while>] [NEXT <next>] ;
[RECORD <rec>] [<rest:REST>] [ALL] => ;
@@ -94,8 +101,19 @@
/* --- console output ---
LIST emits every record matching the filter; DISPLAY without ALL
shows just the current record. Both share __dbList — lAll
distinguishes them. TO PRINTER / TO FILE accepted but unused;
stdout is the only sink for now. */
distinguishes them. TO PRINTER / TO FILE redirection is not yet
implemented; the stub rules below surface a clear error rather
than silently sending output to stdout when a printer/file was
requested. Order matters: more specific rules first. */
#command LIST [<v,...>] TO PRINTER [<*tail*>] => ;
__dbNotImpl("LIST ... TO PRINTER")
#command LIST [<v,...>] TO FILE <(f)> [<*tail*>] => ;
__dbNotImpl("LIST ... TO FILE")
#command DISPLAY [<v,...>] TO PRINTER [<*tail*>] => ;
__dbNotImpl("DISPLAY ... TO PRINTER")
#command DISPLAY [<v,...>] TO FILE <(f)> [<*tail*>] => ;
__dbNotImpl("DISPLAY ... TO FILE")
#command LIST [<v,...>] [<off:OFF>] ;
[FOR <for>] [WHILE <while>] [NEXT <next>] ;
[RECORD <rec>] [<rest:REST>] [ALL] => ;
@@ -114,10 +132,14 @@
must already be sorted/indexed on the key for the grouping to
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,...>] ;
Note on key syntax — TOTAL evaluates `<key>` only in the source
workarea, so `<{key}>` (verbatim blockify) is enough; user can
write `ON src->dept` (alias-qualified) or `ON _FIELD->dept`
(current-area). UPDATE FROM evaluates the key block in BOTH
master and detail context and therefore needs `_FIELD->`-wrapped
bare keys instead — the two rules look superficially similar but
their evaluation contexts differ. */
#command TOTAL TO <(f)> ON <key> [FIELDS <fields,...>] ;
[FOR <for>] [WHILE <while>] [NEXT <next>] ;
[RECORD <rec>] [<rest:REST>] [ALL] => ;
__dbTotal( <(f)>, <{key}>, { <(fields)> }, ;