Skip to content

Commit 57b6ef7

Browse files
authored
feat(plsql): fill CREATE TABLE grammar gaps against Oracle 19c spec (#66)
* feat(plsql): fill CREATE TABLE grammar gaps against Oracle 19c spec Systematic pass comparing the PL/SQL grammar against the Oracle 19c CREATE TABLE reference. Directly unblocks the reported failure on CREATE TABLE ... PARTITION BY RANGE ... INTERVAL (NUMTODSINTERVAL(...)) (... VALUES LESS THAN (DATE '...')) Tier 1 — partition value clauses accept full expressions: - range_values_clause: literal-only -> (expression | MAXVALUE) - list_values_clause: broadened to (expression | NULL) and tuple form for multi-column LIST partitioning; kept DEFAULT Tier 2 — structural keywords and blockchain flexibility: - CREATE TABLE IF NOT EXISTS - list_partitions / composite_list_partitions / subpartition_by_list: accept multi-column partition keys and optional AUTOMATIC - blockchain_table_clauses: NO DROP / NO DELETE clauses now optional - blockchain_row_retention_clause: accept 'DAYS IDLE' form - blockchain_hash_and_data_format_clause: accept arbitrary CHAR_STRING or DELIMITED_ID for hash algorithm and version (removed the hardcoded SHA2_512_Q / V1_Q lexer tokens) Tier 3 — missing top-level partition strategies (12.2+/19c): - PARTITION BY CONSISTENT HASH (... ) PARTITIONS AUTO [SUBPARTITION ...] - PARTITION BY PARTITIONSET {RANGE|LIST} with range_partitionset_desc and list_partitionset_desc Tier 4 — completeness: - range_subpartition_desc / list_subpartition_desc / individual_hash_subparts: accept optional read_only_clause and indexing_clause - column_definition: accept optional annotations_clause (Oracle 23c) New regression fixture plsql/examples/create_table_grammar_gaps.sql covers every scenario above. Full test suite (go test ./...) is green. * refactor(plsql): drop redundant list_partition_value_item tuple rule Address review feedback: the parenthesized-tuple alternative in list_partition_value was unreachable because expression -> atom already matches LEFT_PAREN expressions RIGHT_PAREN with identical semantics. Remove the dedicated tuple layer and rely on expression for tuple form in multi-column LIST partitioning — same parse coverage with simpler AST shape. Comments on range_partition_value and list_partition_value explain why their inner alternatives stay (clarity of intended terminals).
1 parent 1d52e54 commit 57b6ef7

9 files changed

Lines changed: 49738 additions & 45877 deletions

plsql/PlSqlLexer.g4

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1299,6 +1299,7 @@ PARTITION_LIST: 'PARTITION_LIST';
12991299
PARTITION: 'PARTITION';
13001300
PARTITION_RANGE: 'PARTITION_RANGE';
13011301
PARTITIONS: 'PARTITIONS';
1302+
PARTITIONSET: 'PARTITIONSET';
13021303
PARTNUMINST: 'PART$NUM$INST';
13031304
PASSING: 'PASSING';
13041305
PASSWORD_GRACE_TIME: 'PASSWORD_GRACE_TIME';
@@ -2303,9 +2304,7 @@ ORACLE_DATAPUMP: 'ORACLE_DATAPUMP';
23032304
ORACLE_HDFS: 'ORACLE_HDFS';
23042305
ORACLE_HIVE: 'ORACLE_HIVE';
23052306
ORACLE_LOADER: 'ORACLE_LOADER';
2306-
SHA2_512_Q: '"SHA2_512"';
23072307
SHARDED: 'SHARDED';
2308-
V1_Q: '"V1"';
23092308

23102309
ISOLATE: 'ISOLATE';
23112310
ROOT: 'ROOT';

plsql/PlSqlParser.g4

Lines changed: 81 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3295,7 +3295,7 @@ create_table
32953295
| IMMUTABLE? BLOCKCHAIN
32963296
| IMMUTABLE
32973297
)?
3298-
TABLE (schema_name PERIOD)? table_name
3298+
TABLE (IF NOT EXISTS)? (schema_name PERIOD)? table_name
32993299
(SHARING EQUALS_OP (METADATA | EXTENDED? DATA | NONE))?
33003300
(relational_table | object_table | xmltype_table)
33013301
(MEMOPTIMIZE FOR READ)?
@@ -3395,19 +3395,19 @@ immutable_table_no_delete_clause
33953395
;
33963396

33973397
blockchain_table_clauses
3398-
: blockchain_drop_table_clause blockchain_row_retention_clause blockchain_hash_and_data_format_clause
3398+
: blockchain_drop_table_clause? blockchain_row_retention_clause? blockchain_hash_and_data_format_clause
33993399
;
34003400

34013401
blockchain_drop_table_clause
34023402
: NO DROP (UNTIL numeric DAYS IDLE)?
34033403
;
34043404

34053405
blockchain_row_retention_clause
3406-
: NO DELETE (LOCKED? | UNTIL numeric DAYS AFTER INSERT LOCKED?)
3406+
: NO DELETE (LOCKED? | UNTIL numeric DAYS (IDLE | AFTER INSERT) LOCKED?)
34073407
;
34083408

34093409
blockchain_hash_and_data_format_clause
3410-
: HASHING USING SHA2_512_Q VERSION V1_Q
3410+
: HASHING USING (CHAR_STRING | DELIMITED_ID) VERSION (CHAR_STRING | DELIMITED_ID)
34113411
;
34123412

34133413
collation_name
@@ -3512,6 +3512,9 @@ table_partitioning_clauses
35123512
| composite_hash_partitions
35133513
| reference_partitioning
35143514
| system_partitioning
3515+
| consistent_hash_partitions
3516+
| consistent_hash_with_subpartitions
3517+
| partitionset_clauses
35153518
;
35163519

35173520
range_partitions
@@ -3521,7 +3524,8 @@ range_partitions
35213524
;
35223525

35233526
list_partitions
3524-
: PARTITION BY LIST LEFT_PAREN column_name RIGHT_PAREN
3527+
: PARTITION BY LIST LEFT_PAREN column_name (COMMA column_name)* RIGHT_PAREN
3528+
AUTOMATIC?
35253529
LEFT_PAREN PARTITION partition_name? list_values_clause table_partition_description (COMMA PARTITION partition_name? list_values_clause table_partition_description )* RIGHT_PAREN
35263530
;
35273531

@@ -3553,7 +3557,8 @@ composite_range_partitions
35533557
;
35543558

35553559
composite_list_partitions
3556-
: PARTITION BY LIST LEFT_PAREN column_name RIGHT_PAREN
3560+
: PARTITION BY LIST LEFT_PAREN column_name (COMMA column_name)* RIGHT_PAREN
3561+
AUTOMATIC?
35573562
(subpartition_by_range | subpartition_by_list | subpartition_by_hash)
35583563
LEFT_PAREN list_partition_desc (COMMA list_partition_desc)* RIGHT_PAREN
35593564
;
@@ -3578,6 +3583,51 @@ system_partitioning
35783583
(PARTITIONS UNSIGNED_INTEGER | reference_partition_desc (COMMA reference_partition_desc)*)?
35793584
;
35803585

3586+
consistent_hash_partitions
3587+
: PARTITION BY CONSISTENT HASH LEFT_PAREN column_name (COMMA column_name)* RIGHT_PAREN
3588+
PARTITIONS AUTO
3589+
;
3590+
3591+
consistent_hash_with_subpartitions
3592+
: PARTITION BY CONSISTENT HASH LEFT_PAREN column_name (COMMA column_name)* RIGHT_PAREN
3593+
PARTITIONS AUTO
3594+
SUBPARTITION BY
3595+
( RANGE LEFT_PAREN column_name (COMMA column_name)* RIGHT_PAREN
3596+
| LIST LEFT_PAREN column_name (COMMA column_name)* RIGHT_PAREN
3597+
)
3598+
SUBPARTITIONS AUTO
3599+
;
3600+
3601+
partitionset_clauses
3602+
: PARTITION BY PARTITIONSET (range_partitionset_clause | list_partitionset_clause)
3603+
;
3604+
3605+
range_partitionset_clause
3606+
: RANGE LEFT_PAREN column_name (COMMA column_name)* RIGHT_PAREN
3607+
LEFT_PAREN range_partitionset_desc (COMMA range_partitionset_desc)* RIGHT_PAREN
3608+
;
3609+
3610+
range_partitionset_desc
3611+
: PARTITIONSET partitionset_name
3612+
VALUES LESS THAN LEFT_PAREN range_partition_value (COMMA range_partition_value)* RIGHT_PAREN
3613+
LEFT_PAREN range_partition_desc (COMMA range_partition_desc)* RIGHT_PAREN
3614+
;
3615+
3616+
list_partitionset_clause
3617+
: LIST LEFT_PAREN column_name (COMMA column_name)* RIGHT_PAREN
3618+
LEFT_PAREN list_partitionset_desc (COMMA list_partitionset_desc)* RIGHT_PAREN
3619+
;
3620+
3621+
list_partitionset_desc
3622+
: PARTITIONSET partitionset_name
3623+
VALUES LEFT_PAREN (list_partition_value (COMMA list_partition_value)* | DEFAULT) RIGHT_PAREN
3624+
LEFT_PAREN list_partition_desc (COMMA list_partition_desc)* RIGHT_PAREN
3625+
;
3626+
3627+
partitionset_name
3628+
: identifier
3629+
;
3630+
35813631
range_partition_desc
35823632
: PARTITION partition_name? range_values_clause table_partition_description
35833633
( ( LEFT_PAREN ( range_subpartition_desc (COMMA range_subpartition_desc)*
@@ -3623,7 +3673,7 @@ subpartition_by_range
36233673
;
36243674

36253675
subpartition_by_list
3626-
: SUBPARTITION BY LIST LEFT_PAREN column_name RIGHT_PAREN subpartition_template?
3676+
: SUBPARTITION BY LIST LEFT_PAREN column_name (COMMA column_name)* RIGHT_PAREN subpartition_template?
36273677
;
36283678

36293679
subpartition_by_hash
@@ -3638,27 +3688,44 @@ subpartition_name
36383688
;
36393689

36403690
range_subpartition_desc
3641-
: SUBPARTITION subpartition_name? range_values_clause partitioning_storage_clause?
3691+
: SUBPARTITION subpartition_name? range_values_clause read_only_clause? indexing_clause? partitioning_storage_clause?
36423692
;
36433693

36443694
list_subpartition_desc
3645-
: SUBPARTITION subpartition_name? list_values_clause partitioning_storage_clause?
3695+
: SUBPARTITION subpartition_name? list_values_clause read_only_clause? indexing_clause? partitioning_storage_clause?
36463696
;
36473697

36483698
individual_hash_subparts
3649-
: SUBPARTITION subpartition_name? partitioning_storage_clause?
3699+
: SUBPARTITION subpartition_name? read_only_clause? indexing_clause? partitioning_storage_clause?
36503700
;
36513701

36523702
hash_subparts_by_quantity
36533703
: SUBPARTITIONS UNSIGNED_INTEGER (STORE IN LEFT_PAREN tablespace (COMMA tablespace)* RIGHT_PAREN )?
36543704
;
36553705

36563706
range_values_clause
3657-
: VALUES LESS THAN LEFT_PAREN literal (COMMA literal)* RIGHT_PAREN
3707+
: VALUES LESS THAN LEFT_PAREN range_partition_value (COMMA range_partition_value)* RIGHT_PAREN
3708+
;
3709+
3710+
// expression already covers MAXVALUE via constant_without_variable; kept as an explicit
3711+
// alternative purely for clarity — the intended terminals in this context are expression or MAXVALUE.
3712+
range_partition_value
3713+
: expression
3714+
| MAXVALUE
36583715
;
36593716

36603717
list_values_clause
3661-
: VALUES LEFT_PAREN (literal (COMMA literal)* | TIMESTAMP literal (COMMA TIMESTAMP literal)* | DEFAULT) RIGHT_PAREN
3718+
: VALUES LEFT_PAREN (list_partition_value (COMMA list_partition_value)* | DEFAULT) RIGHT_PAREN
3719+
;
3720+
3721+
// expression already covers NULL (and, pragmatically, DEFAULT) via constant/regular_id;
3722+
// semantic validity of `DEFAULT` mixed with other values is checked by the server,
3723+
// matching how other SQL grammars in this repo over-accept at the parser level.
3724+
// Tuple form for multi-column LIST partitioning is handled by expression -> atom
3725+
// (`LEFT_PAREN expressions RIGHT_PAREN`), so no dedicated tuple rule is needed.
3726+
list_partition_value
3727+
: expression
3728+
| NULL_
36623729
;
36633730

36643731
table_partition_description
@@ -5255,6 +5322,7 @@ column_definition
52555322
(DEFAULT (ON NULL_)? expression | identity_clause)?
52565323
(ENCRYPT encryption_spec)?
52575324
(inline_constraint+ | inline_ref_constraint)?
5325+
annotations_clause?
52585326
;
52595327

52605328
column_collation_name
@@ -9023,6 +9091,7 @@ non_reserved_keywords_pre12c
90239091
| PARTITION
90249092
| PARTITION_RANGE
90259093
| PARTITIONS
9094+
| PARTITIONSET
90269095
| PARTNUMINST
90279096
| PASSING
90289097
| PASSWORD_GRACE_TIME
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
-- Regression fixtures for CREATE TABLE grammar gaps closed against Oracle 19c docs.
2+
-- Each block below previously failed to parse; see PR description for details.
3+
4+
--------------------------------------------------------------------------------
5+
-- Tier 1: partition value clauses accept full expressions (fixes BYT-9302)
6+
--------------------------------------------------------------------------------
7+
8+
-- DATE typed literal in RANGE bound with INTERVAL partitioning
9+
CREATE TABLE gcp.lead_drop_mc_native_data (
10+
txn_date DATE,
11+
userid VARCHAR2(100),
12+
custid VARCHAR2(100),
13+
screenid VARCHAR2(500),
14+
eventtime DATE,
15+
status NUMBER
16+
)
17+
PARTITION BY RANGE (txn_date)
18+
INTERVAL (NUMTODSINTERVAL(1,'DAY'))
19+
(PARTITION p0 VALUES LESS THAN (DATE '2026-01-01'));
20+
21+
-- TIMESTAMP typed literal in RANGE bound
22+
CREATE TABLE range_ts (d TIMESTAMP)
23+
PARTITION BY RANGE (d)
24+
(PARTITION p0 VALUES LESS THAN (TIMESTAMP '2026-01-01 00:00:00'));
25+
26+
-- Arithmetic expression in RANGE bound
27+
CREATE TABLE range_expr (c NUMBER)
28+
PARTITION BY RANGE (c)
29+
(PARTITION p0 VALUES LESS THAN (10*10 + 5));
30+
31+
-- NULL in LIST values
32+
CREATE TABLE list_null (c VARCHAR2(10))
33+
PARTITION BY LIST (c)
34+
(PARTITION p0 VALUES (NULL, 'a', 'b'));
35+
36+
-- Expression in LIST values
37+
CREATE TABLE list_expr (c NUMBER)
38+
PARTITION BY LIST (c)
39+
(PARTITION p0 VALUES (1+2, 3*3));
40+
41+
-- Multi-column LIST partitioning with tuple form
42+
CREATE TABLE list_multi_col (a NUMBER, b NUMBER)
43+
PARTITION BY LIST (a, b)
44+
(
45+
PARTITION p0 VALUES ((1,2),(3,4)),
46+
PARTITION p1 VALUES ((5,6))
47+
);
48+
49+
--------------------------------------------------------------------------------
50+
-- Tier 2: IF NOT EXISTS, LIST AUTOMATIC, blockchain flexibility
51+
--------------------------------------------------------------------------------
52+
53+
-- IF NOT EXISTS on CREATE TABLE
54+
CREATE TABLE IF NOT EXISTS t_ine (a NUMBER, b VARCHAR2(10));
55+
56+
-- AUTOMATIC list partitioning
57+
CREATE TABLE list_auto (c NUMBER)
58+
PARTITION BY LIST (c) AUTOMATIC
59+
(PARTITION p0 VALUES (1));
60+
61+
-- Blockchain: new DAYS IDLE form on retention clause, with single-quoted algorithm/version
62+
CREATE BLOCKCHAIN TABLE bc_idle (a NUMBER)
63+
NO DROP UNTIL 0 DAYS IDLE
64+
NO DELETE UNTIL 16 DAYS IDLE
65+
HASHING USING 'SHA2_512' VERSION 'v1';
66+
67+
-- Blockchain: drop/retention clauses both omitted (only the hash clause is required)
68+
CREATE BLOCKCHAIN TABLE bc_hash_only (a NUMBER)
69+
HASHING USING "SHA2_512" VERSION "v1";
70+
71+
--------------------------------------------------------------------------------
72+
-- Tier 3: CONSISTENT HASH and PARTITIONSET
73+
--------------------------------------------------------------------------------
74+
75+
-- Consistent hash partitioning
76+
CREATE TABLE ch_simple (a NUMBER)
77+
PARTITION BY CONSISTENT HASH (a)
78+
PARTITIONS AUTO;
79+
80+
-- Consistent hash with range sub-partitioning
81+
CREATE TABLE ch_sub (a NUMBER, d DATE)
82+
PARTITION BY CONSISTENT HASH (a)
83+
PARTITIONS AUTO
84+
SUBPARTITION BY RANGE (d)
85+
SUBPARTITIONS AUTO;
86+
87+
-- Range partitionset
88+
CREATE TABLE range_ps (a NUMBER, b NUMBER)
89+
PARTITION BY PARTITIONSET RANGE (a)
90+
(
91+
PARTITIONSET ps1 VALUES LESS THAN (100)
92+
(PARTITION p1 VALUES LESS THAN (10))
93+
);
94+
95+
-- List partitionset
96+
CREATE TABLE list_ps (a NUMBER, b NUMBER)
97+
PARTITION BY PARTITIONSET LIST (a)
98+
(
99+
PARTITIONSET ps1 VALUES (1,2,3)
100+
(PARTITION p1 VALUES (1))
101+
);
102+
103+
--------------------------------------------------------------------------------
104+
-- Tier 4: subpartition read_only / indexing clauses + column annotations
105+
--------------------------------------------------------------------------------
106+
107+
-- read_only_clause on range_subpartition_desc (inside composite range partitioning)
108+
CREATE TABLE comp_range_ro (a NUMBER, d DATE)
109+
PARTITION BY RANGE (d)
110+
SUBPARTITION BY RANGE (a)
111+
(PARTITION p0 VALUES LESS THAN (DATE '2026-01-01')
112+
(SUBPARTITION sp0 VALUES LESS THAN (100) READ ONLY,
113+
SUBPARTITION sp1 VALUES LESS THAN (MAXVALUE) READ WRITE INDEXING OFF));
114+
115+
-- indexing_clause on individual_hash_subparts
116+
CREATE TABLE comp_hash_idx (a NUMBER, b NUMBER)
117+
PARTITION BY RANGE (a)
118+
SUBPARTITION BY HASH (b)
119+
(PARTITION p0 VALUES LESS THAN (100)
120+
(SUBPARTITION sp0 INDEXING ON,
121+
SUBPARTITION sp1 INDEXING OFF));
122+
123+
-- annotations_clause on column definition (Oracle 23c)
124+
CREATE TABLE annotated_cols (
125+
id NUMBER ANNOTATIONS (Display 'Identifier'),
126+
name VARCHAR2(100) ANNOTATIONS (ADD Display 'Name', MaxLength '100')
127+
);

0 commit comments

Comments
 (0)