Same command line, same build, different database
SQL$PRE V7.3 compatibility is the contract: qualifiers, output names, $STATUS severity and the shape of the
generated code. Install the x86 precompiler under the name SQL$PRE and the symbols your build
procedures already define keep working; or point the one symbol at the Sector7 image.
Rdb on Alpha or Itanium
$ SCOB :== $SQL$PRE/COBOL
$ SCOB /COBOL /NOWARN /DEBUG -
/OBJECT=AVERTZ_OBJ:VR_FIND_CU_PROC.OBJ -
AVERTZ_SOURCE:VR_FIND_CU_PROC.SCOSQL$PRE on OpenVMS x86
$ SCOB :== $VXSQL_ROOT:[OPENVMS.BIN]VXSQL.EXE
$ SCOB /COBOL /NOWARN /DEBUG /XA_COMMIT -
/OBJECT=AVERTZ_OBJ:VR_FIND_CU_PROC.OBJ -
AVERTZ_SOURCE:VR_FIND_CU_PROC.SCO
- The SQL$PRE V7.3 qualifiers are accepted, including abbreviations:
/COBOL, /[NO]ANSI_FORMAT, /LIST, /OBJECT, /[NO]MACHINE_CODE, /[NO]G_FLOAT, /FLOAT=, and the /SQLOPTIONS= vocabulary. Qualifiers meant for the COBOL compiler are passed through to it.
- The same outputs:
NAME.COB, NAME.OBJ, /LIST NAME.LIS, and SQL$KEEP_PREP_FILES.
- The same success status,
%X10000001, and the same fatal severity on failure, so existing .COM procedures do not change.
- On OpenVMS x86 one qualifier adds function:
/XA_COMMIT (short form /XA), for ACMS applications that commit through DECdtm.
One symbol changes in the build. SCOB now points at the
Sector7 precompiler, and ACMS servers that commit through DECdtm add /XA_COMMIT. Nothing changes in the COBOL.
Built for Rdb COBOL
Generic SQL precompilers stop at C and C++. Rdb shops wrote COBOL. SQL$PRE precompiles VSI COBOL on x86,
in fixed or free format, with the SQL$PRE semantics Rdb programs depend on: the SQLCA, the Rdb message vector,
WHENEVER, cursors, dynamic SQL, and USING CONTEXT for ACMS. The same runtime
also serves the FORTRAN, Pascal and C precompilers.
What the generated COBOL looks like
Real output from the AVERTZ x86 build, from Rdb embedded SQL precompiled exactly as written:
EXEC SQL DECLARE RSLIST_CURSOR CURSOR FOR
SELECT R.RESERVATION_ID, R.CUSTOMER_ID,
C.LAST_NAME, C.FIRST_NAME, ...
FROM RESERVATIONS R, CUSTOMERS C, SITES S
WHERE R.CUSTOMER_ID = C.CUSTOMER_ID
AND ((R.RESERV_STATUS_FLAG = :C-ZERO) OR
(R.RESERV_STATUS_FLAG = :C-ONE))
END-EXEC.
EXEC SQL FETCH RSLIST_CURSOR INTO
:VR_RSLIST_ROW_WKSP.RESERVATION_ID:RL-IND-1,
...
:VR_RSLIST_ROW_WKSP.VEHICLE_CHECKOUT_DATE:RL-IND-9
END-EXEC.SQL$PRE turns it into ordinary COBOL that the VSI COBOL compiler builds:
01 SQ0002.
02 FILLER PIC X(59) VALUE "SELECT R.RESERVATION_ID , R.CUS".
02 FILLER PIC X(59) VALUE "... WHERE R.CUSTOMER_ID = C.CUS".
02 FILLER PIC X(48) VALUE "... = $1 OR ... = $2 ) ORDER BY".
CALL "S7RdbMsgVec" USING BY REFERENCE RDB$MESSAGE_VECTOR
CALL "S7StartSQL"
CALL "S7SetResultParams" USING
BY VALUE 23 BY VALUE 18 BY VALUE -7 BY VALUE 0
BY REFERENCE VEHICLE_CHECKOUT_DATE OF VR_RSLIST_ROW_WKSP
BY REFERENCE RL-IND-9
END-CALL
CALL "S7CursorFetch" USING BY REFERENCE SQLCA
BY REFERENCE S7-CSTR-0003 BY VALUE 1 BY VALUE 0
END-CALL
CALL "S7EndSQL"
EVALUATE TRUE
WHEN SQLCODE = 100 GO TO SQL-ARR-NOT-FOUND
WHEN SQLCODE < 0 GO TO SQL-ERROR-HANDLER
END-EVALUATE
- The SQL text travels as a string constant, with
:host variables rewritten to $n parameters.
- Each host variable is bound with its type code, digits, scale and indicator. The quadword date arrives as
PIC S9(11)V9(7) COMP (scale -7), and the runtime converts it whenever the column is a DATE, TIME or TIMESTAMP.
WHENEVER becomes an EVALUATE after each statement: no hidden control flow, nothing to maintain by hand.
Rdb extended SQL, not an ANSI subset
SQL$PRE was built from the Rdb grammar and the Rdb corpus, statement by statement, so it reads the
SQL your programs actually contain:
- Indicator variables, in both the juxtaposed and the
INDICATOR form.
DECLARE ALIAS, with each alias mapped to a database through the connection file.
- Cursors and
PREPARE / EXECUTE, with dynamic SQL through SQL descriptors (DESCRIBE OUTPUT, GET / SET DESCRIPTOR) and the SQLDA.
- Dictionary records:
INCLUDE FROM DICTIONARY and COPY ... FROM DICTIONARY in COBOL, and the DICTIONARY directive in FORTRAN.
- SQL module language procedures alongside the embedded SQL, through SQL$MOD (VX/SQL$MOD).
SET TRANSACTION READ ONLY / READ WRITE, RESERVING ... FOR SHARED | PROTECTED | EXCLUSIVE READ | WRITE, WAIT n, NOWAIT and isolation levels.
DECLARE TRANSACTION, CONNECT TO ... ALIAS and SET CONNECT.
EVALUATING ... AT VERB TIME clauses, accepted as written, with the target database's constraints doing the checking.
- Cursors declared
WITH HOLD.
Inside the precompiler
Every compile runs the same five stages, whatever the host language. Only the host compiler changes.
| Stage | What happens |
| 1. DCL front end | The SQL$PRE command line is read, and every qualifier and SQLOPTIONS value is parsed and validated against the V7.3 grammar. |
| 2. EXEC SQL parse | The source is consolidated and each EXEC SQL statement is parsed; host variables and indicators are resolved. |
| 3. Dictionary expansion | Dictionary references (INCLUDE FROM DICTIONARY and COPY FROM DICTIONARY in COBOL, DICTIONARY in FORTRAN) are walked through the dictionary; records become host declarations whose members bind as host variables. |
| 4. Translate and bind | Rdb SQL is rewritten for the target database; :host variables become $1..$n parameters and result columns are bound to typed targets. |
| 5. Generate and compile | Native source is emitted with calls into the SQL$PRE runtime and the SQLCA declared for the host language (COBOL also gets the Rdb message vector), the VSI compiler is invoked, and the OBJ and LIS are produced. |
The generated source is kept for audit, and a failed precompile leaves the source and the diagnostics
in the output file, exactly as Rdb's SQL$PRE does.
The dictionary is the source of truth
COBOL's INCLUDE FROM DICTIONARY and FORTRAN's DICTIONARY directive go through
the same dictionary protocol the VSI compilers use (CDD$SIGN_IN, CDD$FIND_NODE,
CDD$GET_ATTS, CDD$GET_ENTITY_CELL), so a record arrives as the compiler would
see it. In COBOL every field keeps its datatype, length, digits, scale, OCCURS and initial value, and a
scaled longword is bound as a scaled numeric, not an integer. In FORTRAN the record becomes a DEC
STRUCTURE, and the members of each RECORD of it bind as host variables. On OpenVMS x86 the dictionary is VX/CDD, Sector7's
CDD for x86 and Linux, and VSI's VDD works through the same protocol. DECforms and ACMS use the same
dictionary records, so the screen, the task workspace and the SQL host structure stay one definition.
One generated program, many databases
The generated code never knows which database it is talking to. It calls a small, stable runtime
interface; the runtime is one shareable image per target, chosen at link time.
| Layer | What it does |
| Generated program | Calls the runtime: connect, bind, execute, fetch, transaction and diagnostics calls. |
| SQL$PRE runtime (shareable image) | Statement scope, parameter binding, type conversion, the SQLCA and message vector, and transaction handling. |
| Linked driver | On OpenVMS x86: PostgreSQL (libpq), Microsoft SQL Server (FreeTDS), MySQL and MariaDB (Connector/C), SQLite, and SQL Relay, which also reaches Oracle. |
EXEC SQL DECLARE EXTERNAL ALIAS FILENAME
'AVERTZ_DATABASE:VEHICLE_RENTALS' END-EXEC.
! VXSQL_DATABASE_CONNECT.INI, kept outside the source tree
VEHICLE_RENTALS = postgresql://dbhost:5432/avertz_demo?...
VEHICLE_HISTORY = postgresql://dbhost:5432/avertz_demo?...
The Rdb alias in your source stays as it is. A connection file maps each alias to a database for each
environment, so credentials never enter the source or the build. Moving the same objects to
another database is a relink against that database's runtime image.
OpenVMS data types in, SQL types out
Conversions happen in the runtime, at bind and at fetch, keyed on the host type and the column type.
Nothing changes in the application.
| OpenVMS host declaration | SQL type | What the runtime does |
Longword (PIC S9(9) COMP, INTEGER*4) | INTEGER | Native x86 byte order, width preserved. |
Quadword integer (PIC S9(18) COMP, INTEGER*8) | BIGINT | 8-byte binary, no floating point anywhere. |
Scaled binary (PIC S9(7)V99 COMP) | NUMERIC(9,2) | Exact digit arithmetic places the decimal point: 3000 becomes 30.00. Money never touches floating point. Rdb INTEGER(2) columns migrate as NUMERIC(10,2). |
Packed decimal (COMP-3) | NUMERIC | Decoded digit by digit, sign nibble honoured, re-packed on fetch. |
Character (PIC X(n), CHARACTER*n) | CHAR / VARCHAR | Fixed width preserved; varying-length groups supported. |
| OpenVMS quadword date | TIMESTAMP, DATE, TIME | 100-nanosecond ticks since 17-NOV-1858 converted to a SQL timestamp and back, keyed on the column type. |
| Indicator variable | NULL | -1 is NULL in and out; truncation is reported through SQLSTATE 01004 and the SQLWARN flags. |
Your dates stay quadwords. The application keeps its
64-bit dates and SYS$GETTIM values; the database, and every report, JDBC client and BI tool
that reads it, sees ordinary SQL timestamps. The migrated data is immediately usable outside OpenVMS.
Transactions with Rdb semantics
The precompiler carries the Rdb SET TRANSACTION text unchanged, and the runtime lowers it
for the database you linked. On PostgreSQL, for example, RESERVING modes become table locks:
| Rdb RESERVING mode | PostgreSQL lock |
| SHARED READ | no lock (readers never block) |
| SHARED WRITE | ROW EXCLUSIVE |
| PROTECTED READ | SHARE |
| PROTECTED WRITE | SHARE ROW EXCLUSIVE |
| EXCLUSIVE READ | EXCLUSIVE |
| EXCLUSIVE WRITE | ACCESS EXCLUSIVE |
READ ONLY / READ WRITE and the isolation level are set on each transaction; WAIT n becomes that transaction's lock timeout, and NOWAIT is applied to the table locks that RESERVING takes.
DECLARE TRANSACTION is re-applied after every COMMIT and ROLLBACK, as Rdb does.
COMMIT and ROLLBACK close open cursors that are not WITH HOLD, as Rdb does.
The error handling you already wrote keeps working
Database errors are mapped to the Rdb SQLCODE values your handlers test, and to the
RDB$MESSAGE_VECTOR conditions. An error handler written in 1991 still selects the
right message.
| Condition (PostgreSQL SQLSTATE) | SQLCODE | RDB$MESSAGE_VECTOR status |
No data (02000) | 100 | RDB$_STREAM_EOF |
Unique violation (23505) | -803 | RDB$_NO_DUP |
Deadlock (40P01, 40001) | -913 | RDB$_DEADLOCK |
Lock not available (55P03) | -1003 | RDB$_LOCK_CONFLICT |
Integrity failure (23000, 23502, 23503, 23514) | -1001 | RDB$_INTEG_FAIL |
More than one row (21000) | -811 | error path taken |
Read-only transaction (25006) | error | RDB$_READ_ONLY_TRANS |
The SQLCA is filled on every call (SQLCODE, SQLSTATE, SQLERRMC). The
message vector is published after every statement, and SQL$SIGNAL raises the condition
through LIB$SIGNAL. The no-data code is configurable for shops that used a different
convention.
XA two-phase commit through DECdtm
Under ACMS, the transaction boundary belongs to DECdtm, and it still does. SQL$PRE adds
/XA_COMMIT: each ACMS procedure server binds its database connection to DECdtm as a resource
manager. The task
definition opens and ends the transaction as it always has, the COBOL joins it with
USING CONTEXT, and DECdtm prepares and commits every branch.
| ACMS / DECdtm | What reaches PostgreSQL |
BLOCK WITH TRANSACTION ($START_TRANS) | BEGIN on each server's branch |
Phase one of COMMIT TRANSACTION ($END_TRANS) | PREPARE TRANSACTION on every branch |
| Phase two | COMMIT PREPARED on every branch |
ROLLBACK TRANSACTION, CANCEL TASK ($ABORT_TRANS) | ROLLBACK or ROLLBACK PREPARED |
| Recovery after a failure | A dedicated recovery process resolves in-doubt branches from pg_prepared_xacts |
- The whole DECdtm transaction id is carried into the PostgreSQL transaction name, checksummed, so a DBA can see the DECdtm transaction in
pg_prepared_xacts and recovery can rebuild the exact XA id.
- Measured in the AVERTZ acceptance run: both branches prepared before either committed, 8 milliseconds from the first
PREPARE to the last COMMIT PREPARED across two server processes. PostgreSQL's log and DECdtm's own log agree.
- The XA runtime fails closed: it never falls back to autocommit, writes outside a transaction context are refused at precompile time and at run time, and any change to a session's settings rolls the branch back before prepare.
Proven on AVERTZ
AVERTZ is the vehicle-rental installation verification program HP shipped with ACMS: every OpenVMS
transaction-processing component in one application. Sector7 built it from scratch and ran it on
OpenVMS x86 against PostgreSQL, from HP's original 1991 source with four procedures adapted for DECdtm XA.
| Component | In AVERTZ |
| ACMS application | One EXC, five procedure servers, eight tasks, queued and composite tasks |
| DECforms | 24x80 panels for reserve, checkout, check-in, customer and vehicle lists |
| Dictionary | 23 workspace records shared by ACMS, DECforms and the COBOL |
| COBOL | 18 embedded-SQL procedures precompiled with SQL$PRE /XA_COMMIT, 25 USING CONTEXT statements |
| Databases | The two Rdb databases became two schemas in one PostgreSQL database, so one DECdtm transaction still spans two resource managers |
HP's task definitions, its VR_FORM screens and its business logic carry over. Sector7 added a
reservation-list task and form built the same way, and a DECforms menu agent. The build is one command of thirteen
gated steps, and the run reserves a car through DECforms with a two-phase commit across both
databases.
The same tool on Linux
The precompiler and the runtime also run natively on Linux, for off-platform builds or for a later move
off OpenVMS, where Oracle and ODBC data sources are reachable directly as well. The same source, the
same SQL and the same runtime calls are used on both, so the platform can be chosen, and changed,
without touching the embedded SQL.
VSI Boot Camp 2026 presentation
The full session from VSI's OpenVMS Boot Camp 2026: how SQL$PRE works, XA two-phase commit with DECdtm,
the AVERTZ build on OpenVMS x86, SQL$MOD, and the VX/SQL$PRE back end both compilers share, in 59 slides.
Download the presentation (PDF, 59 slides)