What’s New (v1.0.0-rc29 -> v1.8.33)
This page summarises the most notable changes since the v1.0.0-rc29 release. For the full commit-by-commit history see the CHANGELOG.
v1.8.33 Highlights
Fixed: Legacy AdsCreateIndex path resolution
The legacy AdsCreateIndex wrapper (used by Harbour’s INDEX ON ... TO)
did not resolve index paths the same way as AdsCreateIndex61:
- Empty bag name → did not create structural CDX
- Relative paths → not resolved against table directory
- Missing extension → fell through to NTX creation
This caused Harbour’s INDEX ON field TO filename to fail. The fix
mirrors AdsCreateIndex61’s path resolution logic.
Create-Index Path Tests
8 new test cases covering various index path forms: no extension, with extension, absolute paths, empty bag, subdirectory, backslash, legacy wrapper, and create+reopen round-trip.
v1.8.32 Highlights
Comprehensive Lock Test Coverage
New test suite with 23 test cases covering previously untested lock API surfaces. Key findings documented:
| API | Behavior |
|---|---|
AdsGetNumLocks |
Counts record locks only — table locks not included |
AdsTestRecLocks |
No-op diagnostic — always returns 0 regardless of state |
AdsIsTableLocked |
Reflects AdsLockTable only — exclusive open not reported |
| Re-entrant lock | 2x AdsLockRecord on same recno requires 2x AdsUnlockRecord |
Remote Lock Contention Tests (Pritpal Bedi scenarios)
6 new tests reproducing multi-instance lock scenarios over TCP:
- Record lock contention fails cleanly (does not hang)
- Write without lock returns error 5035 (GoHot guard)
- FLock contention resolves within retry window
- FLock allows writes without per-record lock
- Exclusive open allows writes (remote mode)
- Append auto-lock works over wire
v1.8.31 Highlights
Write-Guard (GoHot) Enforcement
Implements Harbour’s hb_dbfGoHot() equivalent at the engine level.
In Shared mode, callers must hold either a Record Lock (AdsLockRecord)
or File Lock (AdsLockTable) before mutating a record. This prevents
silent data corruption when multiple connections share a table.
- Engine guard in
writeback_record_(): checks lock state before every write. Returns error 5035 (AE_LOCK_FAILED) if no lock is held. - Pending-append exemption: freshly-appended records (via
AdsAppendRecord) are writable without explicit LockRecord — standard xBase/ACE semantics. - Exclusive mode bypass: opens in
ADS_EXCLUSIVEmode grant unrestricted access without locks.
Wire Protocol — OpenTable Mode Pass-Through
The client now sends the requested open mode (Shared/Exclusive/Read) to the
server in the OpenTable payload, using a new capability bit
(kCapOpenTableMode = 0x00000008). Old servers ignore the prefix; new
servers read it and open in the correct mode.
This fixes remote ADS_EXCLUSIVE opens which previously were always opened
as ADS_SHARED on the server.
Server-Side Table Identity Fix
LockRecord/UnlockRecord/LockTable/UnlockTable now route directly to
the engine table from tbls_[id] instead of through the ABI handle. This
fixes a critical bug where ensure_abi_handle() opened a second Table
instance, causing locks registered on one Table to be invisible to writes
on the other.
RI Cascade/SETNULL Locking
Referential Integrity enforcement now locks child tables before cascade/SETNULL writes, so the GoHot gate permits the internal mutations.
Other Fixes
AdsOpenIndexresolves index paths against the connection data directory (not just the table dir) for subdirectory-qualified paths.
v1.8.30 Highlights
Fix: multi-user record count staleness
When multiple instances/connections share a table, Browse() and
LastRec() showed only the records visible at open time. Records
appended by other connections were invisible.
Root cause: Three layers of caching that were never invalidated by external writes:
AdsGetRecordCountcached the record count on first call and served stale data forever (only cleared on local writes).AdsRefreshRecordonly clearedrow_valid, notrec_count_cached.- The server’s
GetRecordCounthandler returned a stale in-memory value without re-reading the DBF header from disk.
Fix:
AdsRefreshRecordnow clearsrec_count_cachedfor remote tables, forcing a freshGetRecordCountround-trip.- Server
GetRecordCounthandler re-reads the DBF header from disk. - New
IDriver::refresh_record_count_from_disk()virtual method for all drivers (CDX, ADT, NTX, Cached).
Impact: 30 concurrent instances each appending records now all see every record after repositioning (GoTop / GoBottom / Browse).
v1.8.29 Highlights
CDX index seek: 1,000x+ faster GOTO with active order
The seek_key function used a linear scan through decoded branch
pages — O(N) per seek, degenerating to O(N²) for a full traversal of
20,000+ records. Replaced with a proper B-tree descent: at each
internal node, binary-search the branch keys to find the correct child
pointer, then descend. The leaf search is also now binary (O(log M)).
Benchmark on 50,000 keys (key_size=20):
| Operation | Before | After | Speedup |
|---|---|---|---|
| Sequential seek_key | 4,149 µs | 3.2 µs | 1,294x |
| Random seek_key | 4,215 µs | 3.6 µs | 1,162x |
| Soft seek (miss) | 8,318 µs | 2.4 µs | 3,518x |
This fixes the extremely slow GOTO reported by Tim Stone: 47 seconds for 20,000 records with an active CDX order.
v1.8.21 Highlights
Server prints version on launch
openads_serverd includes the build version in the startup banner
and in the error-log “server started†entry (same string as
--version).
v1.8.20 Highlights
Studio HTTP in Windows packages + rddads DD props
Windows release builds ship openads_serverd with OPENADS_WITH_HTTP=ON,
so --http-port 6263 enables Studio at http://localhost:6263.
ace.h now defines SAP DD properties 121–130, including
ADS_DD_DISABLE_DLL_CACHING (125) required by Harbour rddads.
v1.8.19 Highlights
REMOTE — scope + SET DELETED ON: no more xBrowse ghost rows
OrdKeyCount counted deleted keys inside an index scope while
navigation skipped them. FiveWin xBrowse then showed blank/skipped
rows and could refuse to close. Key counts now match the live
scoped walk. Update client DLL and openads_serverd.
v1.8.18 Highlights
Server filesystem API (oads_* / Ads*)
Clients can manage files and directories under the server data root without a mapped drive — LetoDB-style helpers with OpenADS names.
| Capability | ACE functions |
|---|---|
| Exists / erase / rename | AdsCheckExistence, AdsDeleteFile, AdsRenameFile |
| Size / time / date | AdsGetFileSize, AdsGetFileTime, AdsGetFileDate |
| List / directories | AdsDirectory, AdsDirExist, AdsDirMake, AdsDirRemove |
| Low-level I/O | AdsFOpen · AdsFCreate · AdsFClose · AdsFRead · AdsFWrite · AdsFSeek |
Security: remote ops require EnableFileFunc=1 in openads.ini or
--enable-file-func on openads_serverd (default off). Paths are
jailed under --data / the connection data directory.
Full guide: Server filesystem. Harbour notes:
examples/harbour/oads_fs/. Update both client DLL and
openads_serverd.
Also in 1.8.18
Binaries for Windows x64 and x86 are attached to the v1.8.18 GitHub release.
v1.8.17 Highlights
REMOTE — DbCreate / AdsCreateTable no longer writes next to the app
When connected with AdsConnect60(tcp://…), create still wrote the
table on the client cwd; the post-create open was remote and
failed with ADSCDX/5103. Create/drop now go over the wire;
tables land under the server data directory. Update both
openace64.dll / ace32.dll and openads_serverd.
v1.8.14 Highlights
SQL — result cursor no longer starts at a phantom empty row
AdsExecuteSQLDirect / AdsExecuteSQL left the result cursor
before the first row, where SAP ADS positions it on the first
row — so reading the current record right after executing returned
a phantom empty row on every query. The result now lands on row 1,
matching SAP, for engine tables, system.*/aggregate results,
SQL-backend cursors and remote clients alike. Callers that already
issue AdsGotoTop first are unaffected.
REMOTE — backward (PgUp) read-ahead
Read-ahead was forward-only: a Skip(-1) browse paid one round-trip
per row. Reverse scans now prefetch too — a 300-row reverse scan
dropped from 299 wire requests to 7 — and a new capability bit keeps
mixed old/new client-server pairs correct in both directions.
The server’s real version is now visible from the client
Repeated “the fix didn’t help” reports keep resolving to an old
openads_serverd still running (a live Windows service holds its
exe open, so copying an update over it can fail silently) — and
there was no way to prove it from the client side: the wire
handshake and AdsMgGetInstallInfo both answered hardcoded strings.
Now the handshake carries the real build version
(openads/1.8.14), and AdsMgGetInstallInfo on a remote
management handle reports the server’s version — against any
server ever shipped (old builds identify themselves as 0.3.2).
From Harbour: AdsMgConnect("host:port"),
AdsMgGetInstallInfo()[3], AdsMgDisconnect().
REMOTE — CloseTable kept the table files open on the server
Closing a remote table released the client’s view of it, but the
server-side “shadow” handle (used for ordered navigation, scopes and
locks) stayed open until the session disconnected — so erasing,
renaming or reopening the just-closed .dbf/.cdx/.fpt in
exclusive mode failed with “file in use”, and app-level retry loops
turned that into seconds of delay or an apparent hang at
close-all-files time. CloseTable now closes the shadow handle with
the table.
Update openads_serverd for the CloseTable fix; update
openace64.dll too to see the server’s version from the client. New
example: examples/fivewin/xbpaint_delscope.prg (logs the server
version, then drives the exact xBrowse repaint shape — page reads,
AdsGetRelKeyPos/AdsSetRelKeyPos, scrollbar drags — over a remote
scoped table with SET DELETED ON).
v1.8.13 Highlights
REMOTE — ordered/scoped browses: correctness + read-ahead
Wraps up the SET DELETED remote work: flipping AdsShowDeleted
now invalidates the client’s read-ahead block (deleted rows no
longer linger mid-scan), relations read the parent key from the
client’s current row instead of the server’s lagging cursor, and
Seek/SetIndexOrder/GetRecord no longer serve stale prefetched
rows. Read-ahead is now active on ordered browses too (a 299-row
ordered scan dropped from 598 wire round-trips to 7), AdsCacheRecords
is honoured, and GotoTop/Seek return the landed row so the first
read after a reposition is free.
Update both openads_serverd and openace64.dll — several of
these fixes are client-side. New example:
examples/fivewin/xbrowse_delscope.prg (FWH xBrowse, remote,
SET DELETED ON + index scope, deletions inside the scope).
v1.8.12 Highlights
ENGINE — multi-record Skip over deleted rows
Follow-up to v1.8.10/v1.8.11: with the deleted-row filter active, a
remote natural-order browse could show a duplicate of the previous
row where a deleted record used to appear, or end the walk a few
rows early. Table::skip computed multi-record skips with physical
record arithmetic; it now counts visible rows, matching the
index-order path and Clipper SKIP n semantics. Also fixes LOCAL
dbSkip(n > 1) over deleted/filtered rows.
Requires updated openads_serverd (and openace64.dll for LOCAL
mode). Regression tests: three M12.33 cases in
abi_remote_index_nav_test.cpp.
v1.8.11 Highlights
REMOTE — SET DELETED ON before connect
Follow-up to v1.8.10: the flag now also reaches the server when
SET DELETED ON runs before AdsConnect60 — the order every
rddads / FiveWin app uses. The client syncs the state right after
connecting, and the server re-applies it to the lazily-created ABI
connection used for ordered/scoped navigation.
Requires updated openace64.dll and openads_serverd. Regression
tests: remote scoped walk honours SET DELETED ON issued before
connect (+ … before first ordered op) in
abi_remote_index_nav_test.cpp; wire probe
tools/remote_deleted_probe.cpp.
v1.8.10 Highlights
REMOTE — SET DELETED ON on scoped walks
AdsShowDeleted(0) (SET DELETED ON) now propagates to the server over
the wire (ShowDeleted 0xDA/0xDB). Scoped index navigation
(OrdScope / AdsSetScope + GotoTop/Skip) no longer returns rows
flagged deleted on remote aliases while LOCAL mode already hid them.
Requires updated openace64.dll and openads_serverd. Regression
test: remote AdsSetScope with SET DELETED ON skips deleted rows in
abi_remote_index_nav_test.cpp.
v1.8.1 Highlights
ABI — OrdScope string key padding
Harbour passes scope strings at trimmed length, but CDX keys are
space-padded. setScopeTop / setScopeBottom on a character work-order
field (top == bottom) could land GotoTop at EOF on local and remote.
AdsSetScope now pads to index key_length. Test: QA-D in
abi_qa_repro_test.cpp.
v1.8.0 Highlights
CDX — NTXPL852 / PL852 OEM collation
AdsSetCollation accepts NTXPL852 and PL852 for Polish CP-852 index
sorting. Å (0x9D) sorts between L and M on CDX build, seek, insert, and
reindex — matching Harbour / Clipper local-server behaviour.
CDX — bulk REINDEX
Table::reindex() for CDX tags uses the same bottom-up build_bulk()
path as CREATE INDEX, replacing the slow erase-then-insert walk.
Tests
19 new unit tests cover engine collation weights, CDX driver bulk sort, and ABI soft/hard seek + index walk.
v1.7.0 Highlights
REMOTE — AdsSetScope / OrdScope navigation
OrdScope top/bottom bounds were sent to the server but GotoTop/Skip
ignored them — scoped walks returned every record in the table. Fixed by
marking the parent table in ordered_tables_ after SetScope, and by
always syncing SetOrder before remote index navigation.
Requires updated openace64.dll and openads_serverd. Regression
tests in abi_remote_index_nav_test; harness openads_remote_scope_probe.
Docs — CDX rollback warning
Migrating from ADS now warns that CDX files written
by OpenADS use Harbour’s RCHB header and are unreadable by SAP ACE
(error 7017). Back up SAP-built .cdx before migration if rollback is
possible.
v1.6.5 Highlights
REMOTE — OrdKeyCount() + AdsGetDate() fixes
OrdKeyCount()returned 0 on remote aliases — xBrowse grids showed no rows. NewGetKeyCountwire opcode (0xB0/0xB1) computes the true filtered key count from the active index order.AdsGetDate()crashed on remoteADS_DATEcolumns when rddads passed aRemoteIndexhandle. Resolves to parentRemoteTablebefore field I/O.
v1.6.4 Highlights
REMOTE — FiveWin TDataBase / ADSRDD fixes (Dates + unlocked writes)
- Date columns (
ADS_DATE) no longer crashFieldGet/AdsGetJulianin remote mode (safe ordinal resolution + row cache + serverYYYYMMDDnormalisation). FieldPut/AdsSet*on an unlocked record now returnsAE_RECORD_NOT_LOCKED(5035) instead of AV — the “probe lock by write” idiom works and produces the expectedEG_UNLOCKED.- All remote Get/Set paths hardened against field ordinals passed as small
pointers; repeated
FieldGetand post-open/EOF cases are stable. AdsGetAllLocksno longer 5000-crashes on remote handles.- Server lock forwarding and post-
Appendwrite handling improved.
See CHANGELOG for full details. Requires updated openace64.dll +
openads_serverd.
v1.6.3 Highlights
REMOTE / FWH — xBrowse over tcp://
Fixes Harbour ADSCDX + FWH xBrowse / TDataBase with production CDX
tags (OrdSetFocus, e.g. CUSTNAME):
AdsKeyNo/ scrollbar — logical key position (1..n) instead of physicalRecNo;AdsGetRelKeyPos/AdsSetRelKeyPosfor remote indexed tables.- Bookmark
DbGotoafter paint — server syncs the ABI index cursor onGotoRecordso rows no longer shift on everyRefresh. - GoUp at top —
AdsAtBOFreports BOF afterSkip(-1)at key #1; stops the first-row rubber-band repeat.
Requires both openace64.dll (client) and an updated
openads_serverd (GotoRecord ABI sync). Regression tests in
abi_remote_index_nav_test.
v1.6.2 Highlights
Production CDX tag names & append-row FieldGet
SAP/BCC compound CDX tag names (CUSTNAME, not AME); blank
AdsGetField at BOF/EOF for FWH td_blankrow().
v1.5.1 Highlights
Security & remote hardening
Path jail on remote Connect, coherent nested LockMgr unlock, TLS
verification by default, and remote routing for memo/Unicode/date/raw
field writes.
Harbour smoke CI
Windows GitHub Actions job plus run_harbour_smoke.ps1 /
bootstrap_harbour_ci.ps1 for end-to-end Harbour + ACE ABI validation.
Remote ABI (Fase 2)
AdsSetRelation, AdsSetRecord / AdsGetRecord, and AdsCustomizeAOF
over tcp://. Local AdsAggregate / AdsFetchWhere on in-process DBF
tables.
Plus — SQLite & MSSQL navigational write
SQLite and native MS SQL Server (TDS) backends now support
AdsAppendRecord, AdsSetString, AdsWriteRecord, and
AdsDeleteRecord — parity with MariaDB, PostgreSQL, Firebird, and ODBC.
MSSQL ADS_STRING fields are padded to declared width on read.
Engine
Committed ADT/ADI fixtures for CI; VFP 0x32 header (autoinc + nullable
columns together).
v1.5.0 Highlights
SQL Backend — Transaction Management (SQLRDD Pattern)
New BackendTxManager provides nested transaction support for SQL
backends: BEGIN/COMMIT with SAVEPOINT emulation at nesting > 1,
auto-commit after N DML statements (configurable), and dirty-flag
tracking. Available on SQLite and PostgreSQL via BackendTableOps
vtable (begin_tx/commit_tx/rollback_tx/set_auto_commit).
SQL Backend — Lazy Column Loading with Learning
BackendFieldOptimizer tracks which columns are actually read per
table. After 5 unique single-column fetches, it automatically
switches to SELECT * — avoiding repeated demand-fetches for
tables where many columns are eventually needed.
SQL Backend — Restrictor Composition
BackendWhereBuilder combines For clause, user filter, scope
bounds, index restrictions, AOF predicates, and recno filters into
a single AND-ed WHERE clause. Handles exact seek (collapses to =)
and range seek.
SQL Push-Down — 50+ New Translatable Functions
The try_emit_sql_where() emitter now translates STR, VAL, DTOS,
DTOC, CTOD, ROUND, CEILING, MOD, EXP, LOG, SQRT, SIGN, PADR/PADL/PADC,
STRTRAN, LEFT, RIGHT, AT/ATNUM, IIF, IF, NIL, ISNULL, ISBLANK, EMPTY,
LEN, YEAR, MONTH, DAY, HOUR, MINUTE, SECOND, DOW, CDOW, CMONTH, NOW,
and more into portable SQL. The $ contains operator now supports
field-to-field (field1 $ field2 → field2 LIKE '%' || field1 || '%').
UNION / UNION ALL
The SQL parser handles SELECT ... UNION [ALL] SELECT ... with any
nesting depth. Each UNION member carries its own WHERE, ORDER BY,
LIMIT, and aliases.
DDL Parsing
New ALTER TABLE, DROP TABLE, DROP INDEX statement parsers with
support for IF EXISTS, quoted identifiers, and column definitions.
Ready for backend execution hooks.
AOF Expression — LIKE and IS NULL
The AOF expression layer now supports LIKE '%pattern%' with %/_
wildcards, and IS NULL / IS NOT NULL unary operators.
New Features
SQLite-backed Table Driver
An optional SQLite-backed table driver is now available behind
the OPENADS_WITH_SQLITE CMake flag. When enabled, the engine
can open and manipulate tables through a SQLite backend, providing
an alternative storage layer. New source files:
src/sql_backend/sqlite_backend.cppsrc/sql_backend/sqlite_connection.cppsrc/sql_backend/sqlite_table.hsrc/sql_backend/sqlite_index.h
SQL Backends — PostgreSQL / MariaDB / ODBC (OpenADS Plus)
OpenADS can now open tables on PostgreSQL, MariaDB / MySQL
and any ODBC-reachable engine behind the ACE ABI, selected by
the connection URI (postgresql:// / mariadb:// / odbc://),
exactly like the SQLite backend. From the application’s point of
view the table behaves like any other work area — navigation,
field read, and column SEEK all work.
These four SQL backends sit behind a single pluggable
backend-ops registry: each registers one BackendTableOps
struct (17 function pointers mirroring the table ops) so the ~17
ABI navigation / field functions stay backend-agnostic instead of
multiplying a per-backend if block across each. Adding another
backend (e.g. Firebird, or MSSQL / Oracle via ODBC) is one ops
struct plus one registration line. The native local DBF / ADT and
the tcp:// remote paths are unchanged fall-throughs. Identifiers
are validated to safe ASCII and SEEK values use prepared-statement
parameters (no string concatenation). See docs/OPENADS_PLUS.md.
ADT Validation Patches (F1–F7, R1–R3)
ADT table validation now includes a full set of structural
patches (F1–F7) and record-level checks (R1–R3), strengthening
the integrity guarantees for .adt files produced by SAP
Advantage.
Native ADT/ADI Create, Read, Write, and Index Seek
OpenADS can now operate end-to-end on native .adt / .adi /
.adm files:
- Create —
AdsCreateTable(ADS_ADT)writes a valid table header, field descriptors, and an optional.admmemo store. - Write —
AdsAppendRecord/AdsWriteRecordpersist rows and memo payloads. - Read — Re-open, field get, record count, memo round-trip.
- Index —
AdsCreateIndex61builds.adibags (first tag viaAdiIndex::create, additional tags viaadd_tag). - Seek —
AdsSeekon character and numeric ADI keys. - AUTOINC — counter seeded from existing rows at open time; descriptor tail bytes 139–143 stay zero on disk.
- ADM memo layout — 8-byte blocks with a 1024-byte metadata prefix.
ADI Write Path
The ADI index driver now supports write operations — insert,
erase, and flush — including dense-leaf recno decoding and
character-key seek. This completes the read-write cycle for ADI
indexes.
Trigger Dispatch (BEFORE / INSTEAD_OF / AFTER)
Triggers now fire with proper timing dispatch:
- BEFORE — runs before the DML statement.
- INSTEAD_OF — replaces the DML on views.
- AFTER — runs after successful execution.
Priority sort, __error table for failures,
sp_DisableTriggers / sp_EnableTriggers stored procedures,
and composite trigger keys in system.triggers are all
supported.
DA-Web Management UI
The browser-based Data Architect replacement (DA-Web) has received extensive work:
- Inline cell editing with dirty tracking and visual feedback.
- Index management — save and delete indexes via API.
- Trigger CRUD — add, delete, and edit triggers with splitter and inline validation.
- AOF (Rushmore) filter toolbar in the table browser.
- ADS SQL syntax highlighting with HeidiSQL-like colours.
- Stored procedure / function source viewer with parameter grid and Save-to-DD.
- Index tag browser, field type labels, and SQL scripts.
- Connection menu — New DD, Open DD, Free Tables.
- Effective Permissions and Members tabs on user/group panels.
- RI tag dropdowns populated from binary
.addfiles.
openmonitor — TUI and Web Dashboard
A new openmonitor tool provides both a terminal UI (TUI) and a
web dashboard for monitoring and administering openads_serverd.
OpenADS Studio — Responsive (phone / tablet)
The Studio web console now adapts to small screens — usable from a
phone or tablet, not just a desktop browser. Below ~768 px the
table-list sidebar becomes a slide-in drawer (☰ in the header,
dimmed backdrop, auto-close on select); the tab bar scrolls
horizontally and forms / modals reflow to a single column with
larger touch targets. A long-standing dark-theme bug — the
--panel / --panel-2 / --border CSS variables were
self-referential, so panels and borders rendered transparent — is
fixed as part of this work.
Remote Scan Performance (sequential prefetch)
A forward scan over the tcp:// wire used to cost roughly one TCP
round-trip per record (Skip + AtEOF + IsFound). A
sequential-prefetch path (negotiated via a Connect capability flag)
now piggybacks a lookahead block of rows onto forward-Skip acks;
the client serves them locally and folds the consumed count back
into the next wire step so the server cursor never desyncs.
AdsAtEOF / AdsAtBOF are answered from the cached current row and
AdsIsFound from a cached Found() flag. Result: a 50k-record
loopback scan is ~3.9× faster (NAV-only) / ~3.3× (3-field
read), with IsFound round-trips dropping to zero. The change is
additive and backward-compatible — old clients (no capability
advertised) keep the previous wire behaviour byte-for-byte.
PHP Native Extension (php_openads)
A native Zend PHP extension (php_openads.dll) is now available
for PHP 8.x, providing full DD CRUD (35 new AdsDictionary
methods), date/timestamp field decoding, and per-statement field
name caching.
SAP Data Dictionary Import Improvements
import_ddnow copies.ammemo files and decodes encrypted function bodies.- Group membership import (DB:Admin, DB:Backup, DB:Debug) from
binary
.addfiles. - Trigger timing captured from
system.triggers. grant_permissionandAE_SAP_PERMS_NEED_IMPORTerror code for permission migration.
WAL Crash Recovery
WAL (Write-Ahead Log) crash recovery now handles APPEND
records, completing the ARIES-lite recovery model.
DD SQL Expansion
CREATE DATABASE,GRANT/REVOKE.sp_*stored procedures.system.*virtual tables (system.iota,system.columns).AdsDDGet/SetFieldProperty, triggers, stored procedures, views, and index properties.- Per-table access control with user/group permission levels.
Server-Side Aggregation (Tier-3)
AdsAggregate now supports COUNT, SUM, AVG, MIN, and
MAX push-down to SQL backends (SQLite, PostgreSQL, MariaDB,
ODBC). The aggregate spec is validated before execution, and
results are served through a handle-based result set
(AdsAggregateCount / AdsAggregateValue /
AdsAggregateClose).
FetchWhere V2
AdsFetchWhere now serves forward scans from a cached result
set — no per-match goto_record round-trip. The client
receives rows in bulk and walks them locally, with optional
per-row recno (WANT_RECNO flag). Non-AOF SET FILTER bulk
scans are routed through AdsFetchWhere for significant
throughput gains.
ODBC Driver (slice 1–3)
A full ODBC driver (openads_odbc.dll) is now available:
- SELECT round-trip with scrollable cursors
(
SQLFetchScroll). - Typed column access —
SQLDescribeCol/SQLColAttribute/SQLGetDatadispatch through the backend ops vtable. - Positional parameter binding via
SQLBindParameter. - Catalog functions —
SQLPrimaryKeys/system.primarykeys. - App-lock emulation —
rLock()/fLock()via SQL Serversp_getapplock, PostgreSQL advisory locks, MariaDB named locks, and FirebirdOPENADS$LOCKStable.
Native Write Path (PostgreSQL / MariaDB / Firebird)
AdsAppendRecord / AdsSetField / AdsWriteRecord /
AdsDeleteRecord now work end-to-end on PostgreSQL, MariaDB,
and Firebird backends — no ODBC passthrough required.
SQL Push-Down Expansion
SET FILTER and AOF expressions are now pushed down to SQLite
and PostgreSQL as WHERE clauses when the expression tree is
within the optimisable subset (try_emit_sql_where). Coverage
includes $ (contains), LEFT(), RIGHT(), SUBSTR(), and
UPPER().
Complete API Documentation (Portuguese)
All 364 ACE functions are now documented in Portuguese
(pt-BR) under docs/pt/funcoes/, covering syntax, parameters,
return values, and examples.
x86 (32-bit) Calling Convention Fix
ENTRYPOINT is now __stdcall (WINAPI) on Win32, matching
Harbour’s rddads calling convention. This fixes stack
corruption when 32-bit Harbour apps call ACE functions through
the DLL. The x86 .def file and import library are updated
to match. (Reported by Jonsson / RusSoft Ltda.)
CI — msvc-x86 Build Leg
A new msvc-x86 matrix entry in .github/workflows/ci.yml
ensures 32-bit builds are tested on every PR, catching
x86-only breakage (bitness-dependent narrowing, SQLLEN*
signature mismatches, /WX warnings) before they reach main.
Bug Fixes
Engine
- CDX tag order —
list_tags()now sorts by tag-header offset (creation order) instead of alphabetical leaf order. FixesDBSETORDER(n)selecting the wrong tag on CDX bags written by SAP ADS or BCC Harbour. (Reported by Jonsson / RusSoft Ltda.) - CDX expression-index key size — composite expression
keys (e.g.
UPPER(cName)) are now sized from the expression’s natural fixed-width length, not the first record’s content length. The old rtrim truncated keys, causing rows out of order after reindex on large tables. (Reported by Jonsson / RusSoft Ltda.) - CDX empty-leaf walk — forward and backward index walks now
skip empty leaves left behind by
erase(). Fixes REINDEX / bulk-deleteADSCDX/5000. (PR #63) - CDX leaf recno bits —
compute_layoutsizes the record-number field frommax_rec, not just key length, so wide-key tags no longer truncate recnos ≥ 4096. (PR #62) - CDX prefix seek —
seek_keycompares only the search-key length, so partial seeks likeSEEK "ART-00024800"match stored"ART-00024800 desc ..."keys. (PR #62) - Conditional FOR on logical fields — the index expression
evaluator now treats logical fields as numeric (0/1) instead of
truthy strings, so
FOR ACTIVEcorrectly filters.F.records. (PR #121) - INDEX ON corruption — prevent
INDEX ONfrom corrupting source table indexes. (PR #118) - MSSQL backward SKIP — off-by-one:
abs_n == posnow reaches row 0 instead of reporting BOF. (PR #65) - ABI typed getters for SQL backends —
AdsGetDouble/Long/LongLong/Stringdispatch through the backend ops vtable, so PostgreSQL returns real values. (PR #66) AdsGetIndexHandlefor SQL backends — resolves by-name for PG tables so indexed seek works end-to-end. (PR #66)- NTX numeric key format — numeric fields indexed into an NTX
bag now store keys in the native DBFNTX form (zero-padded
magnitude + complemented negatives) instead of space-padded
STR()text. A native xBase reader’sdbSeek(<number>)now matches the on-disk key. Reopened index bags retain the numeric encoding. (PR #67) - Numeric
dbSeek— rddads sendsADS_STRINGkey type for numeric seeks; the engine now handles this correctly. ALIAS->FIELDin numeric seek — stripALIAS->prefix so aliased CDX tags find keys.- Transaction rollback — physically removes appended records on rollback instead of leaving ghost rows.
- LockMgr held-state leak —
held_is now cleared on unlock so the next lock takes a real OS lock. AdsGetRecordCount— now respectsbFilterOption.AdsSetRelation— fails honestly when appropriate.seek_key—walk_to_lastnow honoursSET DELETED ON.- Empty table navigation — correctly handles tables with zero records.
- Deleted-record navigation — proper state after skip past deleted rows.
- LockMgr lock refcount — repeated locks on the same key are now refcounted; the OS lock is released only when the final holder unlocks.
- WAL record bounds checks —
TxLog::read_allvalidates each UPDATE/APPEND field length before reading, so truncated or corrupt WAL files no longer over-read.
ABI
- Out-of-bounds read in numeric key formatting — prevented.
- Swapped option bits —
ADS_DESCENDING(0x02) andADS_COMPOUND(0x08) were decoded incorrectly inAdsCreateIndex61. - Case-insensitive field-name resolution —
field_indexis now cached for performance. AE_NO_CURRENT_RECORD— returns 5068 instead of 5026.OrdListAdd— falls back to basename when a relative path double-prefixes the table directory.- Trig helper linkage — C++ linkage for
trig_*helpers to silence MSVC C4190. AdsGetFieldcrash on SQL backends — reading by field ordinal no longer crashes.- AdsGetRecordCount on conditional ORDER — counts FOR matches correctly. (PR #100)
AdsSetAOFfor non-optimisable filters — now returnsAE_INVALID_EXPRESSIONinstead of success, so stock rddads falls back to client-side filtering. The old behaviour silently disabledSET FILTERentirely. (Reported by Jonsson / RusSoft Ltda.)
ODBC Driver
- x86 build —
C4100(unused parameter) andC2733(SQLLEN*vsSQLPOINTERsignature mismatch) fixed for 32-bit MSVC/WX. (PR #119) - DM/ADO conformance — descriptor handles,
SQLBindCol, andBITtype mapping fixed. SQL_DRIVER_ODBC_VER/SQL_ODBC_VER— now reported inSQLGetInfo.
DA-Web Security
- AOF filter expression sanitisation.
- Drive-root path containment fix.
- API security sweep across all PHP endpoints.
- RI meta index paths contained under DD directory.
- Wire frame size caps to prevent abuse.
Platform / Build
- macOS — sign-conversion and unused-function warnings fixed.
- GCC —
-Werrorwarnings resolved (shadow, implicit conversion, format-truncation, stringop-truncation). - MSVC — x86
__stdcalldecoration mismatch and x64_wfsopencrash fixed. - Clang —
-Wc2y-extensionsguard for older Apple Clang. - POSIX fd-0 collision — file handles are now stored as
(fd + 1)so a real fd 0 (returned byopen()when stdin is closed) is no longer mistaken for the “not open” sentinel. - POSIX
EINTRretry —pread/pwriteretry on signal interruption instead of failing the I/O. - POSIX zero-length mmap —
map_readonlyrejects zero-length maps instead of callingmmapwith length 0.
CDX
- Shared header lock on open so concurrent appends don’t fail.
- Structural CDX named per-table, not per-directory session.
- Stale record-count refresh on the fetch path — a driver
caches the DBF record count at open; in a multiuser deployment a
peer append could leave the cache lagging, so an index walk that
reached a just-appended row (e.g. mid-
REPLACE … FOR) failed hard with spurious error 5000.read_record_raw/write_record_rawnow re-read the on-disk count under a shared header lock before declaring a recno out of range (slow path only — a normal forward scan pays nothing).
Remote (Wire)
- Use-after-free crash on virtual table queries via TCP eliminated.
Documentation
- CONTRIBUTING.md — new contribution guide with PR workflow, protocol policy, and clean-room rules.
- Wire Protocol DD API — comprehensive Data Dictionary API reference (§9) added.
- DA-Web GUIDE.md — full user guide for the DA-Web browser interface.
- README — comprehensive post-rc29 status update covering ADI write path, ADT creation, AES mode, and DA-Web scope.
- Cookbook — new
cookbook/folder with runnable, heavily-commented Harbour examples (simple → advanced). Theconsole/track is pure xBase: create / index seek / transactions / DBF maintenance (ADSCDX), SQL viaAdsCreateSQLStatement+AdsExecuteSQLDirect, native ADT (ADSADT+.adi), and atcp://remote client. Theorm/track runs the same CRUD through a companion Harbour ORM across SQLite / DBF / PostgreSQL / MariaDB / ODBC back-ends, capped by an all-back-ends benchmark (orm/complete/) with a cross-back-end content checksum and a seek-vs-scan headline. A FiveWinxbrowseCRUD sample and connection-string / field-type / troubleshooting guides round it out. - API Reference (PT) — all 364 ACE functions documented in Portuguese with syntax, parameters, return values, and examples.
Packaging
- Windows Inno Setup installer (
openads-setup.iss). - CPack packages with guaranteed
openace32.lib/openace64.libin Windows archives. - Release CI —
openace{32,64}.libshipped in release archives automatically.
Testing
- 874 unit tests passing on x64 and x86 (361 300+ assertions).
- New test files:
abi_cdx_tag_order_test.cpp(CDX tag creation order),abi_cdx_expr_index_scale_test.cpp(expression-index key size at scale),abi_multitag_order_nav_test.cpp(multi-tag navigation),abi_ntx_numeric_edge_test.cpp(NTX numeric edge cases),cdx_empty_tree_test.cpp, and more. - x86 CI —
msvc-x86build leg catches 32-bit breakage on every PR. - Harbour demo in
examples/adt-native/(by glokcode).
Contributors
- Jonsson / RusSoft Ltda. — CDX tag order, expression-index
key size,
AdsSetAOFnon-optimisable filter, and x86 calling convention fixes. - Admnwk — ODBC driver, SQL push-down, aggregation, FetchWhere V2, native write path, and CI improvements.
v1.8.28 Highlights
hbnetio Bridge — Virtual File System + RPC Integration
OpenADS can now transparently bridge to harbour hbnetio virtual file system and RPC capabilities, enabling multi-client concurrent access to shared files on a remote hbnetio server (e.g. AWS EC2).
Three components in contrib/hbnetio_bridge/:
VFS Adapter — maps hb_vf*() operations over OpenADS ITransport:
- File open/close/read/write/seek/truncate
- Byte-range locking (exclusive + shared, blocking + non-blocking)
- Directory operations (exists/make/remove)
- File attributes and timestamps
- Hard/symbolic links
- Implements the exact hbnetio 24-byte LE wire protocol
Distributed Lock Manager — implements the __isLocked() pattern:
- Automatic .lck file creation and management
- Configurable retry with backoff (mirrors hb_idleSleep(2))
- Session-scoped lock tracking with RAII guards
- Bulk release on session disconnect
- Heartbeat for long-held locks
RPC Bridge — maps hbnetio RPC to OpenADS session dispatch:
- netio_FuncExec / netio_ProcExec / netio_ProcExecW
- netio_ProcExists / netio_OpenDataStream / netio_GetData
- Built-in server_version / server_time / server_uptime
Enable: cmake -DOPENADS_WITH_HBNETIO_BRIDGE=ON ..
Wire opcodes 0xF0-0xFC (no conflict with existing opcodes).
Full guide: hbnetio Bridge.