Completes the per-STATIC migration started in 5bba0c2. The
remaining three TSqlExecutor module STATICs (s_nSchemaVer,
s_nRCJSeq, s_hAutoInc) genuinely needed cross-connection
visibility — a CREATE TABLE on connection A MUST invalidate B's
plan cache, an RCJ alias MUST be unique across all live queries,
and an IDENTITY column MUST hand out monotonic values across all
writers. Moving them to TSqlSession (per-instance) would have
broken those semantics.
Solution: back them with Go-side primitives exposed via HB_FUNCs:
s_nSchemaVer → atomic.Uint64 (SqlSchemaVer / SqlBumpSchemaVer)
s_nRCJSeq → atomic.Uint64 (SqlNextRCJSeq, returns mod-100000)
s_hAutoInc → sync.RWMutex + map[string][]string
(SqlSetAutoInc / SqlGetAutoIncFields)
Lives in `hbrtl/sqlglobals.go`. The PRG-side `FUNCTION
SqlSchemaVer() / SqlBumpSchemaVer() / SqlSetAutoInc() /
SqlGetAutoIncFields()` definitions in TSqlExecutor.prg are
deleted; the HB_FUNC dispatch takes their place. The single PRG
caller of `s_nRCJSeq` (in the RCJ helper around line 5600)
becomes `SqlNextRCJSeq()` and reads cleaner — the old
`s_nRCJSeq := (s_nRCJSeq + 1) % 100000` was both racy and a
non-atomic two-write update under multi-conn load.
The other module STATIC, `s_hAutoInc`, used to lazy-init on
first use (`IF s_hAutoInc == NIL ... := { => }`); two concurrent
first-CREATE TABLE calls hit "concurrent map writes" on that
branch. The Go RWMutex eliminates the race; reads still scale
(RLock) so the IDENTITY-lookup at INSERT time isn't a contention
hot-spot.
All six release gates green:
go test ./... ✓
FiveSql2 SQL:1999 43/43 ✓
Harbour compat 56/56 ✓
std.ch 17/17 ✓
FRB 7/7 ✓
pgserver integration 6/6 ✓
Concurrency stress (3-worker × 20):
pre-Layer-1: ~60% pass + occasional Go panic
+Layer 1+2: 80% pass, no panics
+3a: 80% pass
+per-session 3 STATIC move: 90% pass
+this commit: ~75% pass (variability — Go map atomic + mutex
serialise the writers but the underlying
hbrdd multi-area mmap path still has its own
race, deferred to follow-up)
The next bottleneck is at the hbrdd workarea layer (multi-Area
instances per file each holding their own mmap snapshot), not at
the FiveSql2 STATIC level. That fix is its own commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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
Copyright (c) 2025-2026 Charles KWON (Charles KWON OhJun) Email: charleskwonohjun@gmail.com All rights reserved.