VX/RDML - Convert RDML to embedded SQL
Overview
VX/RDML is a source-to-source converter that parses Oracle/DEC Rdb RDML embedded in COBOL, BASIC, FORTRAN, C, and Pascal programs and rewrites it into standard EXEC SQL blocks targeting PostgreSQL or Oracle.
Oracle Rdb is end-of-life — migrate record-oriented RDML in legacy programs to PostgreSQL or Oracle SQL with the VX/RDML converter.
<!-- Webflow embed (min). Source: vx-rdml-page/rdml-page-body-embed-dark.html → python3 website/embeds/minify_webflow_embed.py … -->
<div class="s7-tech-embed" data-vxrdml-body-embed="1" data-technical-mega-embed="1" lang="en"><h2 id="scope">Host languages & scope</h2><p>
RDML is the verb-keyed record-at-a-time data manipulation language that
DEC Rdb embeds in host programs. The <em>same</em> RDML keywords
and the <em>same</em> verb grammar appear inside COBOL, BASIC, FORTRAN,
C, and Pascal source. VX/RDML's parser is host-language-independent: a
small slicer per language identifies the RDML regions in the host
source, then the shared lexer, AST, expression engine, and SQL emitter
do the rest.
</p><div class="table-wrapper"><table class="fs-toc-ignore"><thead><tr><th>Host language</th><th>Source extensions</th></tr></thead><tbody><tr><td>COBOL (with RDO via RDBPRE)</td><td><code>.rco</code>, <code>.cob</code>, <code>.cbl</code></td></tr><tr><td>Pascal</td><td><code>.rpa</code>, <code>.pas</code></td></tr><tr><td>FORTRAN</td><td><code>.for</code>, <code>.f</code>, <code>.f77</code>, <code>.f90</code></td></tr><tr><td>BASIC (DEC BASIC / VAX BASIC)</td><td><code>.bas</code>, <code>.rba</code></td></tr><tr><td>C</td><td><code>.rc</code>, <code>.c</code></td></tr></tbody></table></div><p class="callout"><strong>Same verb grammar, five host wrappers.</strong> The RDML
preprocessor recognises RDML statements by their reserved keywords;
the surrounding host-language code is passed through unchanged. So a
<code>FOR</code> loop, a <code>STORE</code> statement, or a
<code>GET</code> assignment looks the same whether the surrounding
program is COBOL, Pascal, FORTRAN, BASIC, or C. Only the
host-variable declarations and the <code>EXEC SQL</code> wrapper
differ per language.
</p><h2 id="lexical">Lexical structure</h2><h6>Identifiers</h6><ul><li>Start with a letter, <code>_</code>, or <code>$</code>.</li><li>Continue with letters, digits, <code>_</code>, <code>$</code>, or <code>-</code> (hyphen, to support COBOL data names like <code>WS-EMP-ID</code>).</li><li>Case-insensitive in Pascal/FORTRAN/BASIC; case-insensitive RDML elements in C (the host source itself stays case-sensitive).</li><li><code>$</code> participates in identifiers so <code>RDB$LENGTH</code>, <code>RDB$VALUE</code>, <code>RDB$MISSING</code>, and <code>RDB$DB_KEY</code> lex as single tokens.</li><li>Qualified references use <code>.</code> — e.g. <code>EMP.LAST_NAME</code> or <code>PERS.EMPLOYEES.SALARY</code>.</li></ul><h6>Literals</h6><ul><li><strong>Numeric:</strong> integer (<code>123</code>), fixed-point (<code>1.05</code>), scientific (<code>1.5e-3</code>).</li><li><strong>String:</strong> single-quoted (<code>'foo'</code>) or double-quoted (<code>"foo"</code>); doubled quote is the escape (<code>'O''Brien'</code>).</li><li><strong>Date/time literals</strong> like <code>'23-APR-1990'</code> are recognised as strings; conversion is host-language driven.</li></ul><h6>Comments</h6><p>
RDML has no comment syntax of its own. Comments inside an RDML region
follow the host language's conventions:
</p><div class="table-wrapper"><table class="fs-toc-ignore"><thead><tr><th>Host</th><th>Comment forms recognised</th></tr></thead><tbody><tr><td>COBOL (fixed)</td><td><code>*</code> in column 7</td></tr><tr><td>COBOL (free)</td><td><code>*></code> to end of line</td></tr><tr><td>Pascal</td><td><code>{…}</code> and <code>(*…*)</code></td></tr><tr><td>FORTRAN (fixed)</td><td><code>C</code> or <code>*</code> in column 1</td></tr><tr><td>FORTRAN (free) / BASIC</td><td><code>!</code> to end of line</td></tr><tr><td>C</td><td><code>/* … */</code></td></tr></tbody></table></div><h6>Operators & punctuation</h6><div class="table-wrapper"><table class="fs-toc-ignore"><thead><tr><th>Symbol</th><th>Meaning</th></tr></thead><tbody><tr><td><code>=</code><code><</code><code><=</code><code>></code><code>>=</code><code><></code><code>!=</code><code>^=</code></td><td>relational operators (also accepted as keywords <code>EQ</code><code>NE</code><code>LT</code><code>LE</code><code>GT</code><code>GE</code><code>NOT_EQUAL</code><code>GREATER_THAN</code> …)</td></tr><tr><td><code>+</code><code>-</code><code>*</code><code>/</code><code>DIV</code></td><td>arithmetic (precedence: <code>*</code><code>/</code><code>DIV</code> > <code>+</code><code>-</code>)</td></tr><tr><td><code>|</code><code>||</code></td><td>string concatenation</td></tr><tr><td><code>(</code><code>)</code><code>,</code><code>.</code><code>;</code></td><td>punctuation; <code>;</code> separates GET items and assignment list entries</td></tr><tr><td><code>*</code> (suffix)</td><td><code>ctx.*</code> wildcard whole-record reference</td></tr></tbody></table></div><h2 id="keywords">Reserved keywords</h2><p>
The 110 reserved RDML keywords from Table 1-1 of the <em>VAX
Rdb/VMS RDML Reference Manual</em> (V4.0). VX/RDML also recognises
extended forms used in later releases — underscore-joined
variants (<code>BASED_ON</code>, <code>STARTING_WITH</code>,
<code>SAME_AS</code>, <code>INVOKE_DATABASE</code>) and the
single-keyword aggregate forms (<code>AVERAGE_OF</code>,
<code>COUNT_OF</code>, <code>MAX_OF</code>, <code>MIN_OF</code>,
<code>TOTAL_OF</code>) — plus the modern spelling
<code>START_TRANSACTION</code> for V4.0's <code>START_TRANS</code>.
</p><div class="kw-grid"><div>ALPHABETIZED</div><div>AND</div><div>ANY</div><div>AS</div><div>ASC</div><div>ASCENDING</div><div>AT</div><div>AVERAGE</div><div>BASED</div><div>BATCH_UPDATE</div><div>BETWEEN</div><div>BY</div><div>COMMIT</div><div>COMMIT_TIME</div><div>COMPILETIME</div><div>CONCURRENCY</div><div>CONSISTENCY</div><div>CONTAINING</div><div>COUNT</div><div>CROSS</div><div>DATABASE</div><div>DBKEY</div><div>DECLARE_STREAM</div><div>DECLARE_VARIABLE</div><div>DEFAULT</div><div>DEFAULTS</div><div>DESC</div><div>DESCENDING</div><div>DIV</div><div>END</div><div>END_ERROR</div><div>END_FETCH</div><div>END_FOR</div><div>END_GET</div><div>END_MODIFY</div><div>END_STORE</div><div>END_STREAM</div><div>EQ</div><div>ERASE</div><div>ERROR</div><div>EVALUATING</div><div>EXCLUSIVE</div><div>EXTERN</div><div>EXTERNAL</div><div>FETCH</div><div>FILENAME</div><div>FINISH</div><div>FIRST</div><div>FOR</div><div>FROM</div><div>GE</div><div>GET</div><div>GLOBAL</div><div>GREATER_EQUAL</div><div>GREATER_THAN</div><div>GT</div><div>IN</div><div>INVOKE</div><div>IS</div><div>LE</div><div>LENGTH</div><div>LESS_EQUAL</div><div>LESS_THAN</div><div>LOCAL</div><div>LT</div><div>MATCHING</div><div>MAX</div><div>MIN</div><div>MISSING</div><div>MODIFY</div><div>NE</div><div>NOT</div><div>NOT_EQUAL</div><div>NOWAIT</div><div>OF</div><div>ON</div><div>ON_ERROR</div><div>OR</div><div>OVER</div><div>PATHNAME</div><div>PREPARE</div><div>PROTECTED</div><div>RDB$LENGTH</div><div>RDB$MISSING</div><div>RDB$VALUE</div><div>READ</div><div>READ_ONLY</div><div>READ_WRITE</div><div>READY</div><div>REDUCED</div><div>REQUEST_HANDLE</div><div>RESERVING</div><div>ROLLBACK</div><div>RUNTIME</div><div>SAME</div><div>SCOPE</div><div>SHARED</div><div>SORTED</div><div>STARTING</div><div>START_STREAM</div><div>START_TRANS</div><div>STORE</div><div>TO</div><div>TOTAL</div><div>TRANSACTION_HANDLE</div><div>UNIQUE</div><div>USING</div><div>VALUE</div><div>VERB_TIME</div><div>WAIT</div><div>WITH</div><div>WRITE</div></div><p class="callout"><strong>Reading the syntax diagrams below.</strong> Brackets
<code>[ ]</code> mean optional. <code>|</code> means alternative.
<code>…</code> means repetition. Words shown in <span class="kw">UPPER CASE</span>
are RDML reserved keywords; lower-case words are user-supplied
identifiers, expressions, or sub-clauses defined elsewhere on this
page.
</p><h2 id="statements">Statements</h2><!-- DATABASE --><div class="verb" id="s-database"><h6>DATABASE <span class="anchor">§6.3</span></h6><p class="one-liner">Names a database, optionally declares its handle and scope.</p>
<pre><code>[<span class="kw">INVOKE</span>] <span class="kw req">DATABASE</span>
[db-handle <span class="kw">=</span> [handle-scope]]
{<span class="kw">FILENAME</span> | <span class="kw">PATHNAME</span>} <em>file-or-path-spec</em>
[<span class="kw">COMPILETIME</span> {<span class="kw">FILENAME</span> | <span class="kw">PATHNAME</span>} <em>spec</em>]
[<span class="kw">RUNTIME</span> <span class="kw">FILENAME</span> {<em>file-spec</em> | <em>host-variable</em>}]
[<span class="kw">DBKEY</span> <span class="kw">SCOPE</span> <span class="kw">IS</span> {<span class="kw">COMMIT</span> | <span class="kw">FINISH</span>}]
[<span class="kw">REQUEST_HANDLE</span> <span class="kw">SCOPE</span> <span class="kw">IS</span> {<span class="kw">DEFAULT</span> | <span class="kw">FINISH</span>}]
handle-scope ::= [ <span class="kw">GLOBAL</span> | <span class="kw">EXTERNAL</span> | <span class="kw">LOCAL</span> ] (square brackets are literal)</code></pre>
<p><strong>Maps to:</strong><code>EXEC SQL CONNECT TO 'spec' AS handle</code> (PostgreSQL) or <code>EXEC SQL DECLARE handle ALIAS FOR FILENAME 'spec'</code> (Oracle).</p><p><strong>Note:</strong><code>DECLARE_DATABASE</code> is <em>not</em> in the canonical RDML grammar; the keyword is plain <code>DATABASE</code>. VX/RDML accepts <code>DECLARE_DATABASE</code> as a tolerant alias because some legacy programs use it.</p></div><!-- DECLARE_VARIABLE --><div class="verb" id="s-declare-variable"><h6>DECLARE_VARIABLE <span class="anchor">§6.6</span></h6><p class="one-liner">Declares a host-language variable typed by a database field.</p>
<pre><code><span class="kw req">DECLARE_VARIABLE</span> <em>name</em> {<span class="kw">SAME</span> <span class="kw">AS</span> | <span class="kw">BASED</span> <span class="kw">ON</span>} [<em>db-handle</em>.]<em>relation</em>.<em>field</em></code></pre>
<p><strong>Maps to:</strong> a host-language declaration in the converter's output (<code>01 NAME PIC <TODO type>.</code> in COBOL, <code>VAR name : type;</code> in Pascal, etc.). The exact type is filled from the database schema if available; otherwise a placeholder is emitted with a warning so the user can complete it.</p></div><!-- DECLARE_STREAM --><div class="verb" id="s-declare-stream"><h6>DECLARE_STREAM <span class="anchor">§6.5</span></h6><p class="one-liner">Names a stream and binds it to a record selection expression for later <code>START_STREAM</code> / <code>FETCH</code> / <code>END_STREAM</code>.</p>
<pre><code><span class="kw req">DECLARE_STREAM</span> [(handle-options)] <em>declared-stream-name</em>
<span class="kw req">USING</span> <em>rse</em></code></pre>
<p><strong>Maps to:</strong><code>EXEC SQL DECLARE cursor CURSOR FOR SELECT …</code>.</p></div><!-- READY --><div class="verb" id="s-ready"><h6>READY <span class="anchor">§6.19</span></h6><p class="one-liner">Attaches one or more declared databases.</p>
<pre><code><span class="kw req">READY</span> [<em>db-handle</em> [, <em>db-handle</em>]…] [<em>on-error</em>]</code></pre>
<p><strong>Maps to:</strong> the <code>CONNECT</code> already emitted from <code>DATABASE</code> (PostgreSQL) or implicit attach (Oracle). VX/RDML emits a marker comment confirming the attach and any <code>ON ERROR</code> handler scope.</p><p><strong>Important:</strong><code>READY</code> takes <em>only</em> a database-handle list. Transaction options (<code>READ_ONLY</code>, <code>READ_WRITE</code>, isolation modes) belong on <code>START_TRANSACTION</code>, not on <code>READY</code>.</p></div><!-- START_TRANSACTION --><div class="verb" id="s-start-transaction"><h6>START_TRANSACTION <span class="anchor">§6.24</span></h6><p class="one-liner">Initiates a transaction with a full options grammar.</p>
<pre><code><span class="kw req">START_TRANSACTION</span>
[(<span class="kw">TRANSACTION_HANDLE</span> <em>var</em>)]
[<em>distributed-flag</em>]
[<em>tx-options</em>]
[<em>on-clause</em> [<span class="kw">AND</span> <em>on-clause</em>]…]
[<em>on-error</em>]
distributed-flag ::= <span class="kw">DISTRIBUTED_TRANSACTION</span>
| <span class="kw">DISTRIBUTED_TID</span> <em>distributed-tid</em>
tx-options ::= [ <span class="kw">BATCH_UPDATE</span>
| { <span class="kw">READ_ONLY</span> | <span class="kw">READ_WRITE</span> }
[ <span class="kw">WAIT</span> | <span class="kw">NOWAIT</span> ]
[ <span class="kw">CONCURRENCY</span> | <span class="kw">CONSISTENCY</span> ] (Rdb/ELN)
]
[ <span class="kw">EVALUATING</span> evaluating-clause [, …] ]
[ <span class="kw">RESERVING</span> reserving-clause [, …] ]
on-clause ::= <span class="kw">ON</span> <em>db-handle</em> [, <em>db-handle</em>]…
<span class="kw">USING</span> ( { tx-options | <span class="kw">DEFAULTS</span> } )
evaluating-clause ::= [<em>db-handle</em>.]<em>constraint</em>
<span class="kw">AT</span> { <span class="kw">VERB_TIME</span> | <span class="kw">COMMIT_TIME</span> }
reserving-clause ::= [<em>db-handle</em>.]<em>relation</em> [, …]
<span class="kw">FOR</span> { <span class="kw">EXCLUSIVE</span> | <span class="kw">PROTECTED</span> | <span class="kw">SHARED</span> }
{ <span class="kw">READ</span> | <span class="kw">WRITE</span> }
[ <span class="kw">WITH</span> AUTO_LOCKING | <span class="kw">WITH</span> NOAUTO_LOCKING ]</code></pre>
<p><strong>Maps to:</strong></p><ul><li><code>READ_ONLY</code> → <code>SET TRANSACTION READ ONLY</code></li><li><code>READ_WRITE</code> → <code>SET TRANSACTION READ WRITE</code></li><li><code>CONSISTENCY</code> → <code>ISOLATION LEVEL SERIALIZABLE</code></li><li><code>RESERVING <em>rel</em> FOR EXCLUSIVE WRITE</code> → <code>LOCK TABLE rel IN ACCESS EXCLUSIVE MODE</code></li><li><code>EVALUATING <em>c</em> AT COMMIT_TIME</code> → <code>SET CONSTRAINTS c DEFERRED</code></li><li><code>BATCH_UPDATE</code> → no exact equivalent; warns.</li></ul></div><!-- COMMIT / ROLLBACK --><div class="verb" id="s-commit"><h6>COMMIT / ROLLBACK <span class="anchor">§6.2 / §6.21</span></h6><p class="one-liner">End the current transaction.</p>
<pre><code><span class="kw req">COMMIT</span> [(<span class="kw">TRANSACTION_HANDLE</span> <em>var</em>)] [<em>on-error</em>]
<span class="kw req">ROLLBACK</span> [(<span class="kw">TRANSACTION_HANDLE</span> <em>var</em>)] [<em>on-error</em>]</code></pre>
<p><strong>Maps to:</strong><code>EXEC SQL COMMIT</code> / <code>EXEC SQL ROLLBACK</code>.</p></div><!-- FINISH --><div class="verb" id="s-finish"><h6>FINISH <span class="anchor">§6.12</span></h6><p class="one-liner">Detaches one or more databases.</p>
<pre><code><span class="kw req">FINISH</span> [<em>db-handle</em> [, <em>db-handle</em>]…] [<em>on-error</em>]</code></pre>
<p><strong>Maps to:</strong><code>EXEC SQL DISCONNECT alias</code> or <code>EXEC SQL DISCONNECT ALL</code>.</p></div><!-- PREPARE --><div class="verb" id="s-prepare"><h6>PREPARE <span class="anchor">§6.18</span></h6><p class="one-liner">Rdb/ELN-only commit pre-check (no-op in Rdb/VMS).</p>
<pre><code><span class="kw req">PREPARE</span> [(<span class="kw">TRANSACTION_HANDLE</span> <em>var</em>)] [<em>on-error</em>]</code></pre>
<p><strong>Maps to:</strong> a comment noting it has no SQL equivalent.</p></div><!-- FOR --><div class="verb" id="s-for"><h6>FOR <span class="anchor">§6.13</span></h6><p class="one-liner">Iterates a record stream; auto-advances. The body intermixes RDML and host-language statements.</p>
<pre><code><span class="kw req">FOR</span> [(handle-options)]
<em>rse</em>
[<em>on-error</em>]
statement
[statement]…
<span class="kw req">END_FOR</span>
handle-options ::= <span class="kw">REQUEST_HANDLE</span> <em>var</em>
| <span class="kw">TRANSACTION_HANDLE</span> <em>var</em>
| <span class="kw">REQUEST_HANDLE</span> <em>var</em>, <span class="kw">TRANSACTION_HANDLE</span> <em>var</em></code></pre>
<p><strong>Maps to:</strong> a synthesised cursor:</p>
<pre><code>EXEC SQL DECLARE c<sub>n</sub> CURSOR FOR SELECT … FROM rse END-EXEC.
EXEC SQL OPEN c<sub>n</sub> END-EXEC.
EXEC SQL FETCH c<sub>n</sub> INTO :hv1, :hv2, … END-EXEC.
<loop body>
EXEC SQL CLOSE c<sub>n</sub> END-EXEC.</code></pre>
<p>The <code>SELECT</code> projection and the <code>FETCH INTO</code> host-variable list are populated by walking the FOR body to find the <code>GET</code> items that bind <code>ctx.field</code> values to host variables; those <code>GET</code>s are then realised by the <code>FETCH</code> and dropped as redundant. <code>MODIFY</code> and <code>ERASE</code> inside the body inherit the cursor name and target relation from the enclosing <code>FOR</code> for <code>WHERE CURRENT OF</code>.</p></div><!-- FOR Segmented String --><div class="verb" id="s-for-segmented"><h6>FOR (segmented string) <span class="anchor">§6.14</span></h6><p class="one-liner">Iterates the segments of a SEGMENTED STRING field; only legal nested inside another FOR.</p>
<pre><code><span class="kw req">FOR</span> <em>ss-handle</em> <span class="kw req">IN</span> <em>context-var</em>.<em>ss-field</em>
[<em>on-error</em>]
statement…
<span class="kw req">END_FOR</span></code></pre>
<p>VX/RDML detects this form when the IN-target's qualifier matches a context variable in scope (rather than a database handle). It has no SQL equivalent (segmented strings predate LOBs and are fundamentally tied to Rdb's storage model), so the iteration is preserved as a <code>TODO[rdml]</code> comment plus a warning.</p></div><!-- GET --><div class="verb" id="s-get"><h6>GET <span class="anchor">§6.15</span></h6><p class="one-liner">Assignment statement: copies a database value (or aggregate, or whole record) into a host variable. <strong>Not</strong> a SELECT-INTO.</p>
<pre><code><span class="kw req">GET</span> [<em>on-error</em>]
get-item ;
[get-item ;]…
<span class="kw req">END_GET</span>
get-item ::= <em>host-var</em> <span class="kw">=</span> <em>value-expr</em>
| <em>host-var</em> <span class="kw">=</span> <em>statistical-expr</em>
| <em>record-descr</em> <span class="kw">=</span> <em>context-var</em> . <span class="kw">*</span></code></pre>
<p>Used in three ways:</p><ol><li><strong>Inside a FOR / START_STREAM</strong>: read fields of the current record into host vars. VX/RDML folds these into the cursor's <code>FETCH INTO</code> list.</li><li><strong>Standalone with a statistical or FIRST FROM expression</strong>: compute an aggregate value. Maps to <code>SELECT AGG(…) INTO :hv FROM rel</code>.</li><li><strong>Trailing inside a STORE</strong>: capture metadata about the record just stored (commonly <code>= ….RDB$DB_KEY</code>). Maps to <code>INSERT … RETURNING ctid INTO :hv</code> (PostgreSQL) or <code>RETURNING ROWID</code> (Oracle).</li></ol><p>The <code>GET *</code> form (<code>record-descr = ctx.*</code>) maps to <code>FETCH cursor INTO :record_var</code> where the host record's structure matches the relation's columns.</p></div><!-- STORE --><div class="verb" id="s-store"><h6>STORE <span class="anchor">§6.25</span></h6><p class="one-liner">Inserts a new record into a relation.</p>
<pre><code><span class="kw req">STORE</span> [(handle-options)]
<em>context-var</em> <span class="kw req">IN</span> [<em>db-handle</em>.]<em>relation</em>
<span class="kw req">USING</span>
[<em>on-error</em>]
statement ;
[statement ;]…
[<em>get-statement</em>]
<span class="kw req">END_STORE</span>
statement ::= <em>context-var</em>.<em>field</em> <span class="kw">=</span> <em>value-expr</em>
| <em>context-var</em> . <span class="kw">*</span> <span class="kw">=</span> <em>record-descr</em>
| host-language-statement</code></pre>
<p><strong>Maps to:</strong><code>EXEC SQL INSERT INTO rel (col1, col2, …) VALUES (e1, e2, …)</code>. A trailing <code>GET</code> capturing <code>RDB$DB_KEY</code> becomes a <code>RETURNING</code> clause. The <code>STORE *</code> wildcard form emits a structured TODO with a warning so the user can supply the column list from the schema.</p><p><strong>Note:</strong> the first identifier after <code>STORE</code> is a <em>fresh</em> context variable for the new record; the relation name follows <code>IN</code>.</p></div><!-- MODIFY --><div class="verb" id="s-modify"><h6>MODIFY <span class="anchor">§6.16</span></h6><p class="one-liner">Updates the record currently positioned in a stream.</p>
<pre><code><span class="kw req">MODIFY</span> <em>context-var</em> <span class="kw req">USING</span>
[<em>on-error</em>]
statement ;
[statement ;]…
<span class="kw req">END_MODIFY</span>
statement ::= <em>context-var</em>.<em>field</em> <span class="kw">=</span> <em>value-expr</em>
| <em>context-var</em> . <span class="kw">*</span> <span class="kw">=</span> <em>record-descr</em>
| host-language-statement</code></pre>
<p><strong>Maps to:</strong><code>EXEC SQL UPDATE rel SET col1=e1, col2=e2, … WHERE CURRENT OF cursor</code>. The cursor name and target relation are inherited from the enclosing <code>FOR</code> or <code>START_STREAM</code>.</p><p><strong>Note:</strong><code>USING</code> is required (not optional). The <code>context-var</code> must be defined by an enclosing FOR or START_STREAM.</p></div><!-- ERASE --><div class="verb" id="s-erase"><h6>ERASE <span class="anchor">§6.10</span></h6><p class="one-liner">Deletes the current record from the relation.</p>
<pre><code><span class="kw req">ERASE</span> <em>context-var</em> [<em>on-error</em>]</code></pre>
<p>No <code>END_ERASE</code>. <strong>Maps to:</strong><code>EXEC SQL DELETE FROM rel WHERE CURRENT OF cursor</code>.</p></div><!-- FETCH --><div class="verb" id="s-fetch"><h6>FETCH <span class="anchor">§6.11</span></h6><p class="one-liner">Advances an explicit (declared or undeclared) stream by one record.</p>
<pre><code><span class="kw req">FETCH</span> <em>target</em>
[<span class="kw">AT</span> <span class="kw">END</span>
statement…
<span class="kw req">END_FETCH</span>]
[<em>on-error</em>]</code></pre>
<p><strong>Maps to:</strong><code>EXEC SQL FETCH cursor INTO :hv1, :hv2, …</code>. VX/RDML peeks one statement ahead and, if a <code>GET</code> follows, harvests its <code>host-var = ctx.field</code> assignments to populate the FETCH's INTO list automatically.</p></div><!-- START_STREAM --><div class="verb" id="s-start-stream"><h6>START_STREAM <span class="anchor">§6.22 (declared) / §6.23 (undeclared)</span></h6><p class="one-liner">Opens an explicit record stream — the alternative to <code>FOR</code> when iteration order is interleaved with control flow.</p>
<pre><code><span class="kw req">START_STREAM</span> [(handle-options)] <em>declared-stream-name</em> [<em>on-error</em>] <em>(declared form)</em>
<span class="kw req">START_STREAM</span> [(handle-options)] <em>context-var</em>
<span class="kw req">USING</span> <em>rse</em> [<em>on-error</em>] <em>(undeclared form)</em></code></pre>
<p><strong>Maps to:</strong><code>EXEC SQL DECLARE cursor CURSOR FOR …</code> then <code>EXEC SQL OPEN cursor</code>.</p></div><!-- END_STREAM --><div class="verb" id="s-end-stream"><h6>END_STREAM <span class="anchor">§6.8 / §6.9</span></h6><p class="one-liner">Closes a stream opened by <code>START_STREAM</code>.</p>
<pre><code><span class="kw req">END_STREAM</span> <em>target</em> [<em>on-error</em>]</code></pre>
<p><strong>Maps to:</strong><code>EXEC SQL CLOSE cursor</code>.</p></div><!-- ON ERROR --><div class="verb" id="s-on-error"><h6>ON ERROR <span class="anchor">§6.17</span></h6><p class="one-liner">Handler clause attached to almost any RDML statement.</p>
<pre><code><span class="kw req">ON</span> <span class="kw req">ERROR</span>
statement ;
[statement ;]…
<span class="kw req">END_ERROR</span></code></pre>
<p>An <code>ON ERROR</code> clause may appear inside any RDML statement except <code>DATABASE</code> and <code>DECLARE_STREAM</code>. Inside the handler, <code>RDB$STATUS</code> (longword condition value) and <code>RDB$MESSAGE_VECTOR</code> are populated.</p><p><strong>Maps to:</strong><code>EXEC SQL WHENEVER SQLERROR GOTO label</code> bracketing the protected statement, plus a synthesised handler paragraph (<code>RDML-ERR-N</code> in COBOL). VX/RDML emits a warning whenever it generates this so the user can review WHENEVER's compilation-unit scope.</p></div><!-- INVOKE --><div class="verb" id="s-invoke"><h6>INVOKE <span class="anchor">(rare)</span></h6><p class="one-liner">Calls an external Rdb-side module. No SQL equivalent.</p>
<pre><code><span class="kw req">INVOKE</span> <em>module</em> [<em>arg</em>, …]</code></pre>
<p>VX/RDML preserves the original RDML as a comment and emits a warning. With <code>--strict</code>, the converter exits with a non-zero status.</p></div><h2 id="expressions">Value & conditional expressions</h2><h6>Value expressions (Chapter 2)</h6><p>The parser implements full operator precedence with a recursive descent over five layers: concat < additive < multiplicative < unary < primary.</p><div class="table-wrapper"><table class="fs-toc-ignore"><thead><tr><th>RDML form</th><th>Maps to SQL</th></tr></thead><tbody><tr><td><code>123</code><code>1.05</code><code>1.5e-3</code></td><td>numeric literal</td></tr><tr><td><code>'string'</code></td><td><code>'string'</code> (single-quoted, with <code>''</code> escape)</td></tr><tr><td><em>host-variable</em></td><td><code>:host_variable</code></td></tr><tr><td><em>context-var</em>.<em>field</em></td><td><code>context_var.field</code></td></tr><tr><td><em>context-var</em>.<code>RDB$DB_KEY</code></td><td><code>ctx.CTID</code> (PostgreSQL) / <code>ctx.ROWID</code> (Oracle)</td></tr><tr><td><code>RDB$LENGTH(</code><em>x</em><code>)</code></td><td><code>LENGTH(x)</code></td></tr><tr><td><code>RDB$VALUE(</code><em>x</em><code>)</code></td><td><code>(x)</code> (pass-through)</td></tr><tr><td><code>RDB$MISSING(</code><em>x</em><code>)</code></td><td><code>(x IS NULL)</code></td></tr><tr><td><em>a</em><code>+</code><em>b</em><em>a</em><code>-</code><em>b</em><em>a</em><code>*</code><em>b</em><em>a</em><code>/</code><em>b</em><em>a</em><code>DIV</code><em>b</em></td><td>arithmetic with precedence (<code>*</code><code>/</code><code>DIV</code> > <code>+</code><code>-</code>)</td></tr><tr><td><em>a</em><code>|</code><em>b</em></td><td><code>(a || b)</code> (concatenation)</td></tr><tr><td><code>(</code><em>expr</em><code>)</code></td><td>parenthesised</td></tr><tr><td><code>FIRST</code> [<em>n</em>] <em>value-expr</em><code>FROM</code><em>rse</em></td><td><code>(SELECT value-expr FROM rse LIMIT n)</code></td></tr><tr><td><code>{AVERAGE | COUNT | MAX | MIN | TOTAL}</code> [<code>OF</code>] [<em>value-expr</em>] <code>OF</code><em>context-var</em><code>IN</code><em>relation</em> [<code>WITH</code><em>cond</em>]</td><td><code>(SELECT AVG/COUNT/MAX/MIN/SUM(value-expr) FROM relation [WHERE cond])</code></td></tr><tr><td><code>AVERAGE_OF</code> … <code>COUNT_OF</code> … <code>MAX_OF</code> … <code>MIN_OF</code> … <code>TOTAL_OF</code> …</td><td>same as the spaced forms (single-keyword variants accepted)</td></tr></tbody></table></div><h6>Conditional expressions (Chapter 3)</h6><p>Boolean precedence (highest to lowest): <code>( )</code> > <code>NOT</code> > <code>AND</code> > <code>OR</code>. The parser recognises both <code>NOT BETWEEN</code> / <code>NOT MISSING</code>-style postfix negation and a free-standing <code>NOT</code> prefix.</p><div class="table-wrapper"><table class="fs-toc-ignore"><thead><tr><th>RDML form</th><th>Maps to SQL</th></tr></thead><tbody><tr><td><em>a</em><code>EQ</code><em>b</em><em>a</em><code>=</code><em>b</em></td><td><code>a = b</code></td></tr><tr><td><em>a</em><code>NE</code><em>b</em><em>a</em><code><></code><em>b</em><em>a</em><code>!=</code><em>b</em><em>a</em><code>NOT_EQUAL</code><em>b</em></td><td><code>a <> b</code></td></tr><tr><td><code>LT / LE / GT / GE</code><code>LESS_THAN / LESS_EQUAL / GREATER_THAN / GREATER_EQUAL</code></td><td><code><</code><code><=</code><code>></code><code>>=</code></td></tr><tr><td><em>x</em> [<code>NOT</code>] <code>BETWEEN</code><em>lo</em><code>AND</code><em>hi</em></td><td><code>x [NOT] BETWEEN lo AND hi</code></td></tr><tr><td><em>x</em> [<code>NOT</code>] <code>CONTAINING</code><em>'s'</em></td><td><code>x [NOT] LIKE '%s%'</code></td></tr><tr><td><em>x</em> [<code>NOT</code>] <code>MATCHING</code><em>'pat'</em></td><td><code>x [NOT] LIKE 'pat'</code></td></tr><tr><td><em>x</em> [<code>NOT</code>] <code>STARTING</code> [<code>WITH</code>] <em>'p'</em></td><td><code>x [NOT] LIKE 'p%'</code></td></tr><tr><td><em>x</em> [<code>NOT</code>] <code>MISSING</code></td><td><code>x IS [NOT] NULL</code></td></tr><tr><td>[<code>NOT</code>] <code>ANY</code><em>rse</em></td><td><code>[NOT] EXISTS (SELECT 1 FROM rse)</code></td></tr><tr><td>[<code>NOT</code>] <code>UNIQUE</code><em>rse</em></td><td><code>(SELECT COUNT(*) FROM rse) <= 1</code> (approximated; warns)</td></tr><tr><td><em>p</em><code>AND</code><em>q</em><em>p</em><code>OR</code><em>q</em><code>NOT</code><em>p</em><code>(</code><em>p</em><code>)</code></td><td><code>(p AND q)</code><code>(p OR q)</code><code>(NOT p)</code><code>(p)</code></td></tr></tbody></table></div><h2 id="rse">Record Selection Expression (RSE)</h2><p>An RSE is the cursor specification used by <code>FOR</code>, <code>GET</code> with statistical aggregates, <code>FIRST FROM</code>, <code>DECLARE_STREAM</code>, and <code>START_STREAM</code> (undeclared form).</p>
<pre><code>rse ::= [<span class="kw">FIRST</span> {<em>n</em> | <em>host-var</em>}]
<em>context-var</em> <span class="kw">IN</span> [<em>db-handle</em>.]<em>relation</em>
[<span class="kw">CROSS</span> <em>context-var</em> <span class="kw">IN</span> [<em>db-handle</em>.]<em>relation</em> [<span class="kw">WITH</span> <em>cond</em>]]…
[<span class="kw">WITH</span> <em>cond-expr</em>]
[<span class="kw">SORTED</span> <span class="kw">BY</span> sort-key [, sort-key]…]
[<span class="kw">REDUCED</span> <span class="kw">TO</span> <em>field</em> [, <em>field</em>]…]
sort-key ::= <em>value-expr</em> [<span class="kw">ASC</span> | <span class="kw">ASCENDING</span> | <span class="kw">DESC</span> | <span class="kw">DESCENDING</span>] [<span class="kw">ALPHABETIZED</span>]</code></pre>
<h6>How RSEs are emitted as SQL</h6><div class="table-wrapper"><table class="fs-toc-ignore"><thead><tr><th>RSE clause</th><th>SQL emission</th></tr></thead><tbody><tr><td><code>FIRST <em>n</em></code></td><td>appended <code>LIMIT n</code> (PostgreSQL) or <code>FETCH FIRST n ROWS ONLY</code> (Oracle)</td></tr><tr><td>first relation: <em>ctx</em><code>IN</code><em>rel</em></td><td><code>FROM rel AS ctx</code></td></tr><tr><td>additional <code>CROSS</code> with no WITH</td><td><code>CROSS JOIN rel AS ctx</code> (Cartesian product)</td></tr><tr><td>additional <code>CROSS</code> with WITH</td><td><code>INNER JOIN rel AS ctx ON cond</code></td></tr><tr><td>top-level <code>WITH</code><em>cond</em></td><td><code>WHERE cond</code></td></tr><tr><td><code>SORTED BY</code></td><td><code>ORDER BY value [ASC|DESC] [, …]</code></td></tr><tr><td><code>REDUCED TO</code></td><td><code>SELECT DISTINCT …</code> on the listed columns (preserved as a comment for review)</td></tr></tbody></table></div><h2 id="mapping">RDML → SQL mapping reference</h2><div class="table-wrapper"><table class="fs-toc-ignore"><thead><tr><th>RDML construct</th><th>SQL emission (PostgreSQL default)</th></tr></thead><tbody><tr><td><code>DATABASE pers = FILENAME 'PERSONNEL'</code></td><td><code>EXEC SQL CONNECT TO 'PERSONNEL' AS pers</code></td></tr><tr><td><code>READY pers</code></td><td>marker comment (attach already done at <code>CONNECT</code>)</td></tr><tr><td><code>START_TRANSACTION READ_WRITE</code></td><td><code>EXEC SQL SET TRANSACTION READ WRITE</code></td></tr><tr><td><code>START_TRANSACTION ... RESERVING rel FOR EXCLUSIVE WRITE</code></td><td><code>SET TRANSACTION ...; LOCK TABLE rel IN ACCESS EXCLUSIVE MODE</code></td></tr><tr><td><code>FOR e IN EMPLOYEES WITH e.id = :hv ... GET ws-name = e.last_name; END_GET END_FOR</code></td><td><code>DECLARE c CURSOR FOR SELECT e.last_name FROM EMPLOYEES AS e WHERE (e.id = :hv); OPEN c; FETCH c INTO :ws-name; PERFORM UNTIL SQLCODE NOT = 0 ... END-PERFORM; CLOSE c</code></td></tr><tr><td><code>STORE c IN COLLEGES USING c.code = 'X'; c.name = :n; END_STORE</code></td><td><code>EXEC SQL INSERT INTO COLLEGES (code, name) VALUES ('X', :n)</code></td></tr><tr><td><code>STORE c IN T USING ... GET :h = c.RDB$DB_KEY; END_GET END_STORE</code></td><td><code>INSERT ... RETURNING ctid INTO :h</code> (Postgres) / <code>RETURNING ROWID INTO :h</code> (Oracle)</td></tr><tr><td><code>FOR e IN T MODIFY e USING e.salary = e.salary * 1.05; END_MODIFY END_FOR</code></td><td><code>UPDATE T SET salary = (e.salary * 1.05) WHERE CURRENT OF c</code></td></tr><tr><td><code>FOR e IN T WITH e.id = :h ERASE e END_FOR</code></td><td><code>DELETE FROM T WHERE CURRENT OF c</code></td></tr><tr><td><code>GET :max = MAX e.salary OF e IN EMPLOYEES; END_GET</code></td><td><code>SELECT MAX(e.salary) INTO :max FROM EMPLOYEES AS e</code></td></tr><tr><td><code>GET :first = FIRST e.last_name FROM e IN T SORTED BY e.id; END_GET</code></td><td><code>SELECT (SELECT e.last_name FROM T AS e ORDER BY e.id ASC LIMIT 1) INTO :first</code></td></tr><tr><td><code>FOR e IN A CROSS d IN B WITH d.id = e.id END_FOR</code></td><td><code>SELECT … FROM A AS e INNER JOIN B AS d ON (d.id = e.id)</code></td></tr><tr><td><code>WITH NOT ANY d IN DEGREES WITH d.id = e.id</code></td><td><code>WHERE NOT EXISTS (SELECT 1 FROM DEGREES AS d WHERE (d.id = e.id))</code></td></tr><tr><td><code>WITH e.last_name STARTING WITH 'S'</code></td><td><code>WHERE e.last_name LIKE 'S%'</code></td></tr><tr><td><code>WITH e.salary BETWEEN :lo AND :hi</code></td><td><code>WHERE e.salary BETWEEN :lo AND :hi</code></td></tr><tr><td><code>WITH e.bonus MISSING</code></td><td><code>WHERE e.bonus IS NULL</code></td></tr><tr><td><code>RDB$LENGTH(name)</code><code>RDB$MISSING(x)</code></td><td><code>LENGTH(name)</code><code>(x IS NULL)</code></td></tr><tr><td><code>RDB$DB_KEY</code></td><td><code>CTID</code> (PostgreSQL) / <code>ROWID</code> (Oracle)</td></tr><tr><td><code>ON ERROR ... END_ERROR</code></td><td><code>WHENEVER SQLERROR GOTO label</code> + synthesised handler paragraph</td></tr><tr><td><code>COMMIT</code><code>ROLLBACK</code><code>FINISH</code></td><td><code>COMMIT</code><code>ROLLBACK</code><code>DISCONNECT [alias | ALL]</code></td></tr></tbody></table></div><h2 id="unsupported">Out of scope — preserved as comments with warnings</h2><p>
The constructs below are recognised by the parser as RDML but have no
direct SQL equivalent. VX/RDML preserves the original RDML as a
comment in the output and writes a warning to the sidecar
<code>.rdml.warnings.txt</code> file. With the <code>--strict</code>
flag, the converter exits non-zero whenever any of these are
encountered.
</p><ul><li><strong>Segmented strings</strong> — <code>FOR</code> over a <code>SEGMENTED STRING</code> field, <code>RDB$VALUE</code>/<code>RDB$LENGTH</code> as field names within a segment loop. (Predates SQL LOBs; tied to Rdb storage internals.)</li><li><strong>Stream / request handles beyond cursor scope</strong> — explicit <code>REQUEST_HANDLE</code> / <code>TRANSACTION_HANDLE</code> reuse across compilation units, asynchronous fetches.</li><li><strong>Dynamic relations</strong> — relation name held in a host variable.</li><li><strong>INVOKE</strong> — calls to external Rdb modules.</li><li><strong>BATCH_UPDATE transactions</strong> — no SQL standard equivalent; converted with a warning.</li><li><strong>EVALUATING</strong> with custom timing on <code>VERB_TIME</code>/<code>COMMIT_TIME</code> — mapped to <code>SET CONSTRAINTS … IMMEDIATE/DEFERRED</code> with a warning that timing semantics may differ.</li><li><strong>WAIT/NOWAIT</strong> deadlock semantics — mapped to dialect-specific <code>SET LOCK_TIMEOUT</code> / <code>SET TRANSACTION ... NOWAIT</code>; warns.</li></ul></div>