Skip to content

fix(oracle): require ';' after PL/SQL statements, declarations, and END (BYT-10301) - #447

Merged
rebelice merged 3 commits into
mainfrom
claude/oracle-plsql-statement-terminators
Oct 10, 2026
Merged

rebelice merged 3 commits into
mainfrom
claude/oracle-plsql-statement-terminators

Conversation

@rebelice

Copy link
Copy Markdown
Collaborator

Next step of the BYT-10301 plan, after #443 and #446. omni's Oracle parser accepted PL/SQL that Oracle rejects with PLS-00103, because three loops recovered from errors silently instead of reporting them. This PR makes them strict. It also implements the constructs that the silent recovery had been hiding.

What was lenient

Where Before Now
statement list after a statement that stopped early, the leftover words parsed as procedure calls (BEGIN NULL END; was accepted) every statement ends with ;
DECLARE section and subprogram declarations a declaration without ; ran into the next one every declaration ends with ;
package spec and body unrecognized items, and stray ; between items, were skipped an item that is not a declaration, or a stray ;, is an error
END of a block, package, type body, compound trigger ; taken only when present required
label after END / END LOOP any word, so in END LOOP END; the second END was taken as a label and the error appeared at end of input a PL/SQL reserved word is not a label, so the error is reported at the second END, where Oracle reports it. The reserved set was measured on the engine: 85 words. Oracle allows TYPE and LOOP as labels but rejects PRIOR

What the leniency hid, now parsed

Construct Before Now
COMMIT, ROLLBACK, SAVEPOINT, SET TRANSACTION, LOCK TABLE in a PL/SQL body parsed as procedure calls, or rejected (LOCK TABLE) parsed as the SQL statements they are
PRAGMA INLINE / COVERAGE / DEPRECATE / SUPPRESSES_WARNING_6009 as statements parsed as procedure calls PLSQLPragma. All other pragmas are rejected in a body, as Oracle rejects them (PLS-00710 etc.)
SUBTYPE name IS base_type [RANGE lo .. hi] [NOT NULL] skipped in packages, rejected in blocks new node PLSQLSubtypeDecl. 34 Oracle-shipped packages declare one
TABLE OF t NOT NULL, VARRAY(n) OF t NOT NULL rejected PLSQLTypeDecl.ElementNotNull
length, precision, or scale given as an expression in a PL/SQL declaration (VARCHAR2(ORA_MAX_NAME_LEN + 2), varchar2(pkg.c)) rejected, or skipped in packages parsed as an expression. That the expression is static is a compile-time check (PLS-00491), not a parse-time one. SQL still takes only integer literals, which Oracle also requires (CREATE TABLE t (c VARCHAR2(10 + 2)) is ORA-00907)

Engine evidence

  • Reference rows: ref_105 to ref_128 in reference_oracle.tsv are checked against the pinned Oracle image. Main disagreed with Oracle on 14 of the 24:
    • 4 valid statements were rejected: LOCK TABLE, SUBTYPE, expression lengths, collection NOT NULL.
    • 10 invalid ones were accepted: missing ; after a statement, after a declaration, after END IF, after a unit's END, and after a package's END; a stray ; in a package; a non-declaration package item; a body-only pragma; a reserved word used as an END label.
  • Unit tests: plsql_terminator_test.go pins the error position of 25 malformed inputs, plus the AST of the new forms.
  • Dictionary manifest (plsql_dictionary_known_failures.tsv, 875 Oracle-shipped units): no unit went from passing to failing.
    • DBMS_HADOOP body now parses.
    • DBMS_HADOOP_INTERNAL now fails later, at a parenthesized UNION ALL inside IN.
    • Four DBMS_FEATURE_* units now fail at the unsupported JSON_SERIALIZE(... RETURNING ...). Before, the parser silently skipped past that clause and failed after it.
    • STANDARD now stops at type BOOLEAN is (FALSE, TRUE), an enumeration type outside STANDARD (PLS-00505). It is filed under Oracle-internal syntax, and the category name now mentions enumeration types.
    • Total: 101 failing units (was 102).

Impact on Bytebase

A PL/SQL unit without its final ; is now a syntax error:

CREATE OR REPLACE PROCEDURE p AS BEGIN NULL; END
/

Oracle compiles this with PLS-00103, so omni now agrees with the engine. Bytebase up to 3.18, which parsed with ANTLR, appended a missing final ; to PL/SQL units. Whether to restore that is a Bytebase decision, handled in the omni bump PR.

Verification

  • make test-oracle passes, including TestOracleReference, TestOraclePLSQLDictionary, and the keyword-column audit, all against the pinned container.
  • TestOracleCompatibilityReferenceReport outcome counts are identical to main: match_accept=361, match_reject=182.
  • go vet ./oracle/ast/ ./oracle/parser/ has the same 13 findings as main.
  • go generate ./oracle/ast/... is in sync, gofmt is clean, and go build ./... passes.

Left for later

These are outside this PR:

  • Compound trigger bodies are still skipped by counting BEGIN/END.
  • Other lenient spots I saw but did not change:
    • IS and OF are optional in TYPE ... IS TABLE OF.
    • The cursor declaration's IS is optional.
    • SQL type modifiers accept a missing ).
  • Remaining ANTLR-era dictionary categories: CHARACTER SET ANY_CS (27), PIPELINED / AGGREGATE USING (24), ACCESSIBLE BY (19), and others.

🤖 Generated with Claude Code

…ND (BYT-10301)

omni accepted PL/SQL that Oracle rejects with PLS-00103 because three
parsers recovered silently instead of failing:
- the statement loop resumed after a statement that stopped early, so
  the words left over parsed as procedure calls (BEGIN NULL END;);
- the declaration loops and the package item loop skipped what they did
  not recognize, and stray ';' between items;
- END of a block, package, type body, and compound trigger took its ';'
  only when present.

Each statement, declaration, package item, and END now requires its ';',
and a stray ';' is an error, as on Oracle 23ai. Missing ';' is reported
at the token Oracle reports: a PL/SQL reserved word after END or
END LOOP is no longer taken as a label (END LOOP END;), using the
reserved set measured on the engine.

The leniency hid constructs omni did not parse, now implemented:
- COMMIT, ROLLBACK, SAVEPOINT, SET TRANSACTION, and LOCK TABLE inside a
  PL/SQL body, and the INLINE, COVERAGE, DEPRECATE, and
  SUPPRESSES_WARNING_6009 pragmas as statements (other pragmas fail
  there, as in Oracle);
- SUBTYPE name IS base_type [RANGE low .. high] [NOT NULL]
  (PLSQLSubtypeDecl);
- NOT NULL on the element type of TABLE OF and VARRAY OF;
- expressions as a length, precision, or scale in PL/SQL declarations
  (VARCHAR2(ORA_MAX_NAME_LEN + 2), varchar2(pkg.c)); SQL keeps integer
  literals.

Reference rows ref_105 to ref_128 record the engine's answers; main
disagreed with Oracle on 14 of them. In the dictionary manifest,
DBMS_HADOOP's body now parses; four JSON units fail at the unsupported
RETURNING clause instead of after it; STANDARD stops at its enumeration
type, Oracle-internal syntax that Oracle rejects elsewhere (PLS-00505).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-10T07:13:26.384721Z 1bcd5e1 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 19967dcecf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread oracle/parser/plsql_block.go
Comment thread oracle/parser/plsql_block.go
Comment thread oracle/parser/plsql_block.go
Comment thread oracle/parser/type.go
Comment thread oracle/parser/plsql_block.go
…ess (BYT-10301)

Review follow-up, each form checked on Oracle 23ai:
- CASE statement arms parse through the statement list, so every arm
  statement needs its ';' (THEN NULL x := 1 was accepted).
- END CASE and END IF are required (END; alone is PLS-00103), take an
  optional label as Oracle allows (END IF l;), and need their ';'.
- A block body, IF and CASE branch, loop body, and exception handler hold
  at least one statement (BEGIN END; is PLS-00103), a CASE statement at
  least one WHEN, and a handler requires THEN.
- SUBTYPE ... RANGE needs both bounds.
- LOCK requires TABLE (ORA-00966), in SQL and in PL/SQL.
- A datatype nested in a PL/SQL type-length expression, as in
  VARCHAR2(CAST(1 AS NUMBER(foo))), follows the SQL rules again.

Reference rows ref_129 to ref_139 record the engine's answers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f771826518

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread oracle/parser/plsql_block.go
…es (BYT-10301)

PL/SQL now dispatches LOCK TABLE to the shared parser, which took any
words before MODE, WAIT without an interval, and anything inside the
partition parentheses. Checked on Oracle 23ai:
- the lock mode is one of ROW SHARE, ROW EXCLUSIVE, SHARE UPDATE, SHARE,
  SHARE ROW EXCLUSIVE, EXCLUSIVE (IN FOO MODE is ORA-01737, IN SHARE
  EXCLUSIVE MODE ORA-01739);
- WAIT takes an unsigned integer literal (WAIT, WAIT -1: ORA-30005;
  WAIT 2.5: ORA-02017; WAIT 1 + 1 and a PL/SQL constant are rejected);
- PARTITION (name) takes one name (PARTITION () and PARTITION (p1, p2)
  are ORA-01738); PARTITION FOR takes key value expressions.

Reference rows ref_140 to ref_146.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rebelice
rebelice merged commit a72c077 into main Oct 10, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant