Files
five/docs/gap-analysis.md
CharlesKWON fc1dca9551 feat(rdd): real POSIX file/record locking + gap analysis doc
Replaces the FLOCK/DBRLOCK/DBRUNLOCK no-op stubs with actual
fcntl(F_SETLK) byte-range advisory locks, matching Harbour's
hb_fsLockLarge implementation.

Before: rtlDbRLock always returned .T. regardless of contention.
        Multi-process writers could silently corrupt records.

After:  Non-blocking POSIX byte-range locks per file descriptor.
        Cross-process exclusion verified by a subprocess-spawning
        Go test that witnesses BUSY vs OK transitions.

New files:
  hbrdd/dbf/locks_posix.go    fcntl F_WRLCK/F_UNLCK wrappers
  hbrdd/dbf/locks_windows.go  stub (TODO: LockFileEx)
  hbrdd/dbf/lock_multi_test.go   cross-process verification
  docs/gap-analysis.md        honest Harbour parity assessment

Modified:
  hbrdd/dbf/dbf.go
    - DBFArea gains fileLocked bool + lockedRecs map
    - Close() calls releaseAllLocks() before dropping the fd
  hbrtl/database.go
    - rtlDbRLock / rtlDbRUnlock now delegate to DBFArea.LockRecord /
      UnlockRecord instead of returning fixed .T./NIL
    - New rtlFLock / rtlDbUnlock for FLOCK() / DBUNLOCK()
  hbrtl/register.go
    - FLOCK and DBUNLOCK symbols registered (were missing entirely)
  compiler/analyzer/analyzer.go
    - FLOCK / DBUNLOCK added to RTL known-function set

Lock region layout (non-overlapping on purpose):
  FLOCK region       [0, HeaderLen+1)
  Record N region    [RecordOffset(N), RecordLen)

So a workarea can hold FLOCK and multiple DBRLOCK simultaneously
on the same fd without conflict.

Design rationale (captured in locks_posix.go header):
  * POSIX fcntl, not flock(2) — byte-range + NFS-safe
  * Non-blocking F_SETLK — matches Clipper FLOCK() → .F. semantics
  * Released explicitly on Close to avoid workarea-sharing races
  * Windows falls back to no-op (TODO: LockFileEx)

Verification:
  go test ./hbrdd/dbf/ -run TestFLockBlocksAcrossProcesses  PASS
  go test ./hbrdd/dbf/ -run TestRLockBlocksAcrossProcesses  PASS
  go test ./...                                             ALL PASS
  FiveSql2 43/43                                            100%
  compat_harbour 51/51                                      100%

The gap-analysis doc (docs/gap-analysis.md) is a running inventory
of what works vs what's still missing vs Harbour 3.2, written for
users evaluating Five for production — not a sales pitch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 17:58:03 +09:00

12 KiB

Five vs Harbour — Gap Analysis

Last updated: 2026-04-11

This document is an honest account of what works in Five today and what is still missing versus Harbour 3.2.0dev. It is written for users who are evaluating Five for production, not for marketing.

The scope is deliberately narrow: we compare language runtime, RDD, RTL, tooling, and ecosystem. FiveSql2 (SQL:1999 on DBF) is out of scope — it has no Harbour counterpart and is documented separately.


Summary

Area Status One-line takeaway
Core language (LOCAL, IF, FOR, DO WHILE, CLASS, BEGIN SEQUENCE, code blocks, @byref, mutable closures) Works
DBF engine (open/append/update/delete/pack/zap) Works
NTX single-tag indexes (read + write) Works
CDX compound indexes (read) Byte-compatible with Harbour
CDX compound indexes (write) Not implemented — INDEX ON ... TAG currently produces NTX, not CDX
Memo fields (FPT) Works
Memo fields (DBT) Not implemented
Shared mode + file/record locking NEW POSIX byte-range locks via fcntl (verified cross-process)
Harbour parity test (281-line RDD diff) 0 lines differ
FLOCK / DBRLOCK / DBRUNLOCK / DBUNLOCK NEW Real locks (not stubs)
Preprocessor (#include, #define, #ifdef, #command, #translate) Works for most Harbour headers
Class system basics (DATA, METHOD, INHERIT, ACCESS/ASSIGN, SELF/SUPER) Works
Class advanced (CLASSDATA, HIDDEN/PROTECTED/EXPORTED, PROPERTY, operator overload) Not implemented
Threading exposed to PRG (hb_threadStart, hb_mutexCreate, …) Not registered — goroutines available internally only
Networking (sockets, HTTP, TCP/UDP, SSL/TLS) No RTL functions registered
Serialization (hb_Serialize / hb_Deserialize) Not implemented
Compression (hb_ZCompress / hb_ZUncompress) Not implemented
JSON (hb_JSONEncode / hb_JSONDecode) Not registered
hbmk2 equivalent ⚠️ five build is a thin wrapper around go build
Contrib libraries (curl/cairo/gd/qt/ssl/mysql/pgsql/…) None
GUI frameworks (HWGUI/HMG/MiniGUI) None
GET / @...SAY / READ forms ⚠️ Minimal implementation
FRM report form system Not implemented
TBrowse ⚠️ Basic scrolling only
CLI debugger Line-level TUI debugger works
Remote/DAP debugging Not implemented

Legend: works · ⚠️ partial · missing


RDD — Shared Mode & Locking (Fixed 2026-04-11)

Before

FLOCK(), DBRLOCK(), DBRUNLOCK() were registered as no-op stubs that always returned .T.. The OpenParams.Shared flag was accepted but no byte-range locks were ever acquired. Multi-process writers could corrupt records silently.

// hbrtl/database.go (before)
func rtlDbRLock(t *hbrt.Thread) {
    ...
    t.RetBool(true) // always succeeds
}

After

Real POSIX byte-range advisory locks via fcntl(F_SETLK).

RTL function Harbour behavior Five status
FLOCK() Exclusive file lock fcntl F_WRLCK on header region
DBRLOCK([n]) Exclusive record lock fcntl F_WRLCK on RecordOffset(n) + RecordLen
DBRUNLOCK([n]) Release one / all record locks fcntl F_UNLCK
DBUNLOCK() Release all locks fcntl F_UNLCK both ranges

Design choices (aligned with Harbour's hb_fsLockLarge layout):

  1. POSIX byte-range locks, not flock(2) — byte-range is required for record-level semantics, and fcntl locks work across NFS while flock does not. Harbour/Clipper both use this exact mechanism.
  2. Non-blocking (F_SETLK, not F_SETLKW) — matches Clipper FLOCK() → .F. / DBRLOCK() → .F. semantics when another process holds a conflicting lock.
  3. Non-overlapping regionsFLOCK covers [0, HeaderLen+1), record locks cover [RecordOffset(n), RecordLen). A process can hold both simultaneously on the same fd.
  4. Released on Close() — explicit releaseAllLocks() before fd close avoids races when multiple workareas share the same file.
  5. Windows stub retained//go:build windows falls back to the old always-true behavior. LockFileEx port is listed below.

Verified cross-process

hbrdd/dbf/lock_multi_test.go spawns a separate OS process via go build + exec.Command and verifies:

  • Process A holds FLOCK() → process B's FLOCK() returns BUSY
  • A releases → B can acquire
  • A holds DBRLOCK(2) → B's DBRLOCK(2) returns BUSY
  • B's DBRLOCK(1) on a different record returns OK (non-overlap)
  • A releases record 2 → B can acquire it

These tests exercise real fcntl(F_SETLK) behavior in separate processes, not goroutines within the same process (POSIX locks are per-process, not per-fd, so same-process tests would be meaningless).

Limitations

  • Windows: still a no-op stub. LockFileEx wrapper needed (~1 day).
  • Advisory only: processes that don't call FLOCK/DBRLOCK bypass protection. Same as Harbour and Clipper — this is expected behavior.
  • No timeout: Harbour's HB_SET_LOCKRETRY is not honored. Callers must implement their own retry loop.
  • No deadlock detection: POSIX F_SETLK is non-blocking, so deadlocks are not possible, but starvation is.

What's Still Missing (Ranked by Impact)

🔴 Blocker for enterprise adoption

  1. CDX write support — Five reads Harbour CDX byte-for-byte but cannot create or modify them. INDEX ON field TAG name TO bag currently produces an NTX file. Estimated: 2 weeks (hbrdd/cdx/cdx.go needs CreateIndex, InsertKey, structural root rebuilder).

  2. SQL RDDs — No MySQL, PostgreSQL, ODBC, or Advantage. Makes Five unsuitable for apps that mix DBF with modern RDBMS. Estimated: 4-8 weeks per driver via database/sql.

  3. Threading exposed to PRGhb_threadStart, hb_mutexCreate, hb_condNew, hb_threadSelf are all missing. Goroutines exist inside the runtime but aren't callable from PRG. Harbour ports use hb_thread* heavily for background workers. Estimated: 1 week (wrap Go primitives).

  4. Windows file locking — no-op stub, see above. Any Windows deployment is currently single-writer only. Estimated: 1 day.

🟡 Blocker for specific domains

  1. Networking — no hb_socketConnect, hb_inetConnect, HTTP client, SSL. Harbour apps that talk to web APIs or use hbnetio for remote DBF won't run. Estimated: 2 weeks (net + net/http wrappers).

  2. JSONhb_JSONEncode/hb_JSONDecode not registered. Trivial to add via encoding/json. Estimated: 1 day.

  3. Compressionhb_ZCompress/hb_ZUncompress missing. Required for FPT BLOB compression, network protocols. Estimated: 1 day (compress/zlib).

  4. Serializationhb_Serialize/hb_Deserialize missing. Any code that persists Harbour objects or sends them over sockets breaks. Estimated: 1 week (needs VM type bridge).

  5. DBT memo — dBASE III memo format not supported. Old Clipper codebases that haven't migrated to FPT will fail on open. Estimated: 3 days.

🟢 Polish / ecosystem

  1. Class advanced featuresCLASSDATA, HIDDEN/PROTECTED/ EXPORTED, PROPERTY, operator overload. Framework-style code that uses these won't compile. Estimated: 2 weeks (analyzer + gengo + runtime).

  2. GET/SAY form system — Harbour's @ row, col GET var / READ system is barely implemented. Classic text-mode data entry apps don't work. Estimated: 2 weeks (GTCGI integration + validation).

  3. FRM report forms — no support for .frm report files. Clipper reports are dead. Estimated: 2 weeks.

  4. Advanced TBrowse — only basic scrolling. No custom headers, column blocks, keyboard handlers. Estimated: 1 week.

  5. hbmk2 equivalent — no .hbp project files, no library linking, no resource files, no parallel builds. five build is just go build with PRG → Go transpilation. Estimated: 3 weeks for basic .hbp parsing.

  6. Contrib libraries — Harbour has 100+ contrib libs (hbcurl, hbssl, hbmysql, hbpgsql, hbqt, hbfship, hbgd, hbcairo, …). Five has none. Estimated: each contrib is 1-4 weeks.


Language/Compiler Corner Cases

From CLAUDE.md:

Issue Workaround
STATIC inside FUNCTION triggers local index 0 error Use module-level STATIC
Semicolon-inline IF ... ENDIF on one line Split into multiple lines
LOCAL inside IF/FOR block Declare all LOCALs at function top
FIELD->NAME (fixed 2026-04-11) Now works — see commit e95afad
OrdSetFocus(n) numeric (fixed 2026-04-11) Now works — same commit
OutStd()/OutErr() (fixed 2026-04-11) Now registered
Date + (i % 365) panic (fixed 2026-04-11) Fixed in 6c53747
INDEX ON ... TAG name TO bag emits NTX, not CDX Open issue — gengo drops TAG parameter

Quantitative Comparison

Metric Five Harbour
RTL functions registered 483 ~700+
Core LOC ~47,000 Go ~500,000 C + PRG
RDD drivers 4 (DBF, NTX, CDX-read, MEM) 15+
Compiler platforms darwin/linux/windows/freebsd (any Go target) 30+ including AIX, HP-UX, OS/2, DOS, Win CE
Built-in SQL engine FiveSql2 (43/43 tests)
Contrib libraries 0 100+
GUI frameworks 0 4+ (HWGUI, HMG, MiniGUI, Qt)
Test suites in tree compat_harbour (51), FiveSql2 (43), Go units Thousands

Performance (50k records, Apple M-series, same DBFNTX workload)

Operation Harbour 3.2 Five 0.1 Winner
APPEND 50k 90.7 ms 86.6 ms Five 5% faster
INDEX 4 keys 38.0 ms 33.3 ms Five 12% faster
SEEK 10k 12.3 ms 6.8 ms Five 45% faster
SKIP forward scan 4.0 ms 3.4 ms Five 15% faster
SKIP reverse scan 4.0 ms 4.1 ms ≈ tie
GOTO 10k random 5.0 ms 1.5 ms Five 3.3x faster
Total 154.0 ms 132.7 ms Five 14% faster

Source: docs/benchmarks.md (if/when published). Reproducible via bench_core.prg in the session archive.


Honest Positioning

Five is a Harbour-compatible runtime optimized for modern cloud-native deployments of classic dBASE/Clipper business logic.

When to use Five

  • Migrating a CLI data-processing app from Harbour to a single cross-compiled binary (no CGo, no dynamic linker, no glibc matching)
  • Running legacy DBF-based ETL / reporting pipelines on Linux containers or macOS
  • Adding SQL:1999 query capabilities to an existing DBF codebase (FiveSql2 — not available in Harbour)
  • Performance-critical RDD operations where Five is measurably faster than Harbour
  • Multi-process shared DBF workloads on Unix (now that locks work)

When NOT to use Five (yet)

  • Harbour apps that use @...GET, READ, FRM reports, or TBrowse-based data entry — the UI layer is too minimal
  • Apps that need SQL RDDs (MySQL, PostgreSQL, ODBC, ADS)
  • Apps that use threading, sockets, HTTP, or SSL from PRG
  • Apps that require CDX index creation (CDX read works; write is missing)
  • Windows deployments needing multi-writer safety (Windows lock stub is still no-op)
  • Anything depending on contrib libraries (hbcurl, hbssl, hbqt, …)

Roadmap priorities

In order of impact on "can this run real Harbour code":

  1. Windows LockFileEx (1 day)
  2. JSON RTL (1 day)
  3. Compression RTL (1 day)
  4. Threading RTL (1 week)
  5. Networking RTL + SSL (2 weeks)
  6. CDX write support (2 weeks)
  7. Serialization (1 week)
  8. SQL RDD (database/sql bridge) (4-8 weeks)

The first 5 items would close the gap for most server-side data workloads within a month of focused work.


Maintained by: Charles KWON OhJun charleskwonohjun@gmail.com Honesty policy: this document MUST be updated when features are added or limitations are discovered. Do not remove items — mark them or move them to a "fixed" section so readers see the history.