Files
five/_FiveSql2
CharlesKWON 67cd8f2306 fix(pgserver,dbf): partial fix for multi-session concurrency race
Addresses two of the three layers behind the audit's "WorkArea
collision under multi-session" risk surfaced in Phase 3:

1. Shared DATA-INIT hash literals (PRG side).

   TSqlSession.prg declared `DATA hPlanCache INIT { => }` (plus
   hSavepoints + hRolePerms etc.). On the gengo path that
   compiles class-DATA INITs, the {=>} literal is sometimes
   evaluated ONCE at class-definition time, with every
   subsequent New() reusing the same hash pointer. Two pgserver
   connections then read/wrote a single shared HbHash from
   different goroutines, eventually hitting `concurrent map
   writes` inside HbHash.ensureIndex (the lazy O(1)-lookup
   index map).

   The pre-existing gotcha is already documented in
   TSqlExecutor.prg's hSubCache comment ("DATA INIT on hash/
   array literals can end up sharing the same instance across
   New() calls depending on the compile path") — TSqlSession had
   missed the same workaround. Moving the explicit
   `::hPlanCache := { => }` etc. into the constructor body
   guarantees a fresh hash per instance.

2. Stale cross-session recCount cache (Go side).

   `*DBFArea.RecCount()` in shared mode caches its result for
   the duration of `recCountCacheGen`. Append() bumped the count
   on disk + refreshed THIS area's count under the append-intent
   lock (Phase 1 of pre-1.0 audit) but never invalidated the
   cache on peer DBFArea instances — so a second pgserver
   connection's RecCount() kept returning its pre-Append cached
   value. The peer's SELECT then iterated 1..old_count and
   missed the newly inserted row.

   Append() now calls `InvalidateRecCountCache()` after
   committing the bumped header. The generation counter went
   to atomic.AddUint64 / atomic.LoadUint64 so the bump is
   safe to fire from any goroutine without a lock around the
   variable.

Measured impact
---------------

Same 3-worker concurrent-INSERT-then-SELECT stress test that was
~3/5 passing pre-fix:
  before: 3 / 5  (40% — plus occasional Go-level panic)
  after:  8 / 10 (80% — no panics, just intermittent missed rows)

The remaining 20% flake is on the third layer — peer mmap shows a
pre-Append snapshot when Append's `unmap()` only invalidates this
area's own mmap, not the other workareas that opened the same DBF
file independently via dbUseArea. Fixing that requires either a
cross-area registry of mmap views to invalidate, or skipping
mmap entirely when SHARED && cache-gen has bumped. Tracked as a
proper follow-up; tests/pgserver/run.sh's "Known limitation"
header now points at the narrower problem.

Standalone six-gate verification:
  go test ./...               ✓
  FiveSql2 SQL:1999 43/43     ✓
  Harbour compat 56/56        ✓
  std.ch 17/17                ✓
  FRB 7/7                     ✓
  pgserver integration 6/6    ✓

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 16:20:25 +09:00
..

FiveSql2 — SQL Engine for Harbour DBF/NTX/CDX

Pratt parser + SQL:1992-2023 full standard support Supports both NTX (Clipper) and CDX (FoxPro/ADS) indexes

Architecture

five_SQL("SELECT ...")
   │
   ├── TSqlLexer        Tokenizer
   ├── TSqlParser2      Pratt parser (data-driven operators)
   ├── TSqlExecutor     Query executor (Volcano model)
   │     ├── TSqlAlias  Central alias manager (no collisions)
   │     ├── TSqlIndex  NTX/CDX index optimization (auto-detect)
   │     ├── TSqlAgg    GROUP BY / aggregation
   │     ├── TSqlSort   ORDER BY / DISTINCT
   │     ├── TSqlDDL    CREATE/DROP/ALTER TABLE/INDEX
   │     └── TSqlTxn    BEGIN/COMMIT/ROLLBACK
   ├── TSqlExpr         AST nodes + expression evaluation
   └── TSqlFunc         60+ scalar functions

Build & Test

export PATH="/path/to/harbour-core/bin/linux/gcc:$PATH"
export HB_INSTALL_PREFIX="/path/to/harbour-core"

make          # Build all tests
make test     # Run all 157 tests
make bench    # Parser benchmark
make clean    # Clean

SQL Standard Coverage

Standard Features Tests
SQL:1992 SELECT, JOIN, GROUP BY, HAVING, Subquery, CASE, CAST 43
SQL:1999 CTE, Recursive CTE, Window Functions, MERGE 10
SQL:2003 SIMILAR TO, GROUPING SETS, LATERAL, Window frames 64
SQL:2008 FETCH/OFFSET, FOR UPDATE, Extended MERGE (incl.)
SQL:2016 JSON functions, LISTAGG (incl.)
SQL:2023 ANY_VALUE, GREATEST/LEAST, BOOL_AND/OR (incl.)
Challenge LeetCode-level complex queries 15
Extreme Production analytics stress tests 15

Adding New Operators

Edit TSqlParser2.prg, method InitInfixTables():

::hInfixTT[ TK_MYOP ] := { "<=>", 40, 41, ND_BIN }

One line. No structural changes needed.

Copyright (c) 2025-2026 Charles KWON (Charles KWON OhJun) Email: charleskwonohjun@gmail.com All rights reserved.