🚨 Oracle not porting Rdb to x86 and EOL on Itanium 12/2027 🚨
Technical Capabilities
Having trouble finding what you need?
Get in touch with us, so we can answer your specific questions directly.
Get In Touch

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 &amp; scope</h2><p> RDML is the verb-keyed record-at-a-time data manipulation language that DEC&nbsp;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&nbsp;SQL</code> wrapper differ per&nbsp;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> &mdash; 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&nbsp;7</td></tr><tr><td>COBOL (free)</td><td><code>*&gt;</code> to end of line</td></tr><tr><td>Pascal</td><td><code>{&hellip;}</code> and <code>(*&hellip;*)</code></td></tr><tr><td>FORTRAN (fixed)</td><td><code>C</code> or <code>*</code> in column&nbsp;1</td></tr><tr><td>FORTRAN (free) / BASIC</td><td><code>!</code> to end of line</td></tr><tr><td>C</td><td><code>/* &hellip; */</code></td></tr></tbody></table></div><h6>Operators &amp; 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>&lt;</code><code>&lt;=</code><code>&gt;</code><code>&gt;=</code><code>&lt;&gt;</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> &hellip;)</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> &gt; <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&nbsp;1-1 of the <em>VAX Rdb/VMS RDML Reference Manual</em> (V4.0). VX/RDML also recognises extended forms used in later releases &mdash; 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>) &mdash; 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>[&nbsp;]</code> mean optional. <code>|</code> means alternative. <code>&hellip;</code> means repetition. Words shown in <span class="kw">UPPER&nbsp;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">&sect;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">&sect;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 &lt;TODO type&gt;.</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">&sect;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 &hellip;</code>.</p></div><!-- READY --><div class="verb" id="s-ready"><h6>READY <span class="anchor">&sect;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>]&hellip;] [<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">&sect;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>]&hellip;] [<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 [, &hellip;] ] [ <span class="kw">RESERVING</span> reserving-clause [, &hellip;] ] on-clause ::= <span class="kw">ON</span> <em>db-handle</em> [, <em>db-handle</em>]&hellip; <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> [, &hellip;] <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>&nbsp;&rarr;&nbsp;<code>SET TRANSACTION READ ONLY</code></li><li><code>READ_WRITE</code>&nbsp;&rarr;&nbsp;<code>SET TRANSACTION READ WRITE</code></li><li><code>CONSISTENCY</code>&nbsp;&rarr;&nbsp;<code>ISOLATION LEVEL SERIALIZABLE</code></li><li><code>RESERVING <em>rel</em> FOR EXCLUSIVE WRITE</code>&nbsp;&rarr;&nbsp;<code>LOCK TABLE rel IN ACCESS EXCLUSIVE MODE</code></li><li><code>EVALUATING <em>c</em> AT COMMIT_TIME</code>&nbsp;&rarr;&nbsp;<code>SET CONSTRAINTS c DEFERRED</code></li><li><code>BATCH_UPDATE</code>&nbsp;&rarr;&nbsp;no exact equivalent; warns.</li></ul></div><!-- COMMIT / ROLLBACK --><div class="verb" id="s-commit"><h6>COMMIT &nbsp;/ &nbsp;ROLLBACK <span class="anchor">&sect;6.2 / &sect;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">&sect;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>]&hellip;] [<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">&sect;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">&sect;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]&hellip; <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 &hellip; FROM rse END-EXEC. EXEC SQL OPEN c<sub>n</sub> END-EXEC. EXEC SQL FETCH c<sub>n</sub> INTO :hv1, :hv2, &hellip; END-EXEC. &lt;loop body&gt; 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">&sect;6.14</span></h6><p class="one-liner">Iterates the segments of a SEGMENTED&nbsp;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&hellip; <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">&sect;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 ;]&hellip; <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(&hellip;) INTO :hv FROM rel</code>.</li><li><strong>Trailing inside a STORE</strong>: capture metadata about the record just stored (commonly <code>= &hellip;.RDB$DB_KEY</code>). Maps to <code>INSERT &hellip; 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">&sect;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 ;]&hellip; [<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, &hellip;) VALUES (e1, e2, &hellip;)</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">&sect;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 ;]&hellip; <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, &hellip; 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">&sect;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">&sect;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&hellip; <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, &hellip;</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">&sect;6.22 (declared) / &sect;6.23 (undeclared)</span></h6><p class="one-liner">Opens an explicit record stream &mdash; 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 &hellip;</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">&sect;6.8 / &sect;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">&sect;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 ;]&hellip; <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>, &hellip;]</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 &amp; conditional expressions</h2><h6>Value expressions (Chapter 2)</h6><p>The parser implements full operator precedence with a recursive descent over five layers: concat&nbsp;&lt;&nbsp;additive&nbsp;&lt;&nbsp;multiplicative&nbsp;&lt;&nbsp;unary&nbsp;&lt;&nbsp;primary.</p><div class="table-wrapper"><table class="fs-toc-ignore"><thead><tr><th>RDML form</th><th>Maps to&nbsp;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> &gt; <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> &hellip; <code>COUNT_OF</code> &hellip; <code>MAX_OF</code> &hellip; <code>MIN_OF</code> &hellip; <code>TOTAL_OF</code> &hellip;</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>(&nbsp;)</code>&nbsp;&gt;&nbsp;<code>NOT</code>&nbsp;&gt;&nbsp;<code>AND</code>&nbsp;&gt;&nbsp;<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&nbsp;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>&lt;&gt;</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 &lt;&gt; 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>&lt;</code><code>&lt;=</code><code>&gt;</code><code>&gt;=</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) &lt;= 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&nbsp;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>]]&hellip; [<span class="kw">WITH</span> <em>cond-expr</em>] [<span class="kw">SORTED</span> <span class="kw">BY</span> sort-key [, sort-key]&hellip;] [<span class="kw">REDUCED</span> <span class="kw">TO</span> <em>field</em> [, <em>field</em>]&hellip;] 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] [, &hellip;]</code></td></tr><tr><td><code>REDUCED TO</code></td><td><code>SELECT DISTINCT &hellip;</code> on the listed columns (preserved as a comment for review)</td></tr></tbody></table></div><h2 id="mapping">RDML &rarr; 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 &hellip; 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 &mdash; 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> &mdash; <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> &mdash; explicit <code>REQUEST_HANDLE</code> / <code>TRANSACTION_HANDLE</code> reuse across compilation units, asynchronous fetches.</li><li><strong>Dynamic relations</strong> &mdash; relation name held in a host variable.</li><li><strong>INVOKE</strong> &mdash; calls to external Rdb modules.</li><li><strong>BATCH_UPDATE transactions</strong> &mdash; 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> &mdash; mapped to <code>SET CONSTRAINTS &hellip; IMMEDIATE/DEFERRED</code> with a warning that timing semantics may differ.</li><li><strong>WAIT/NOWAIT</strong> deadlock semantics &mdash; mapped to dialect-specific <code>SET LOCK_TIMEOUT</code> / <code>SET TRANSACTION ... NOWAIT</code>; warns.</li></ul></div>

Frequently Asked Questions

Curious about how Sector7 can facilitate your application migration? Explore our FAQs for expert insights.

What is RDML and what does VX/RDML convert?

RDML (Rdb Data Manipulation Language) is the verb-keyed, record-at-a-time language Oracle/DEC Rdb embeds in host programs. VX/RDML is Sector7’s source-to-source converter that parses RDML in COBOL, BASIC, FORTRAN, C, and Pascal and rewrites it into standard EXEC SQL for PostgreSQL or Oracle.

See the VX/RDML technical page for the full verb grammar and SQL mapping reference.

Which host languages does VX/RDML support?

VX/RDML uses a shared RDML parser across five host languages. A small slicer per language finds RDML regions in the source; the same lexer, AST, expression engine, and SQL emitter run for all of them.

Supported hosts include COBOL (.rco, .cob, .cbl), Pascal (.rpa, .pas), FORTRAN, DEC/VAX BASIC, and C. Only host-variable declarations and the EXEC SQL wrapper differ per language.

Does VX/RDML require rewriting non-RDML host code?

No. VX/RDML converts RDML regions only. Surrounding COBOL, Pascal, FORTRAN, BASIC, or C logic is passed through unchanged. RDML statements such as FOR, STORE, GET, and START_TRANSACTION are translated to equivalent EXEC SQL (cursors, DML, transactions) while the host program structure stays intact.

For broader migration planning, see OpenVMS to Linux and UNIX migration services.

Can VX/RDML target PostgreSQL and Oracle?

Yes. VX/RDML emits dialect-appropriate SQL — for example CONNECT / LIMIT / RETURNING ctid for PostgreSQL, and Oracle-specific forms such as ROWID and FETCH FIRST n ROWS ONLY where required.

Organisations leaving Oracle Rdb can also review the Rdb to Oracle/PostgreSQL toolset bundle and related tools such as VX/SQL-COBOL for embedded SQL in COBOL.

How does VX/RDML translate FOR loops and DML verbs?

An RDML FOR … END_FOR over a record selection expression (RSE) becomes a synthesised cursor: DECLARE CURSOR, OPEN, repeated FETCH, and CLOSE. GET items inside the loop are folded into the cursor’s FETCH INTO list.

STORE maps to INSERT, MODIFY to UPDATE … WHERE CURRENT OF, and ERASE to DELETE … WHERE CURRENT OF. Transaction verbs (START_TRANSACTION, COMMIT, ROLLBACK) map to standard SQL transaction statements.

Which RDML constructs have no direct SQL equivalent?

Some RDML features are recognised but preserved as comments with warnings — for example segmented-string FOR loops, INVOKE of external Rdb modules, BATCH_UPDATE transactions, and some WAIT/NOWAIT semantics. VX/RDML writes details to a .rdml.warnings.txt sidecar file.

With --strict, the converter exits non-zero when unsupported constructs are encountered. Contact Sector7 to review a representative source sample before production migration.

Transform Your Legacy Software Today!

Get In Touch
Unlock the potential of your legacy software with our expert migration services.