Skip to content

Commit 744f756

Browse files
authored
Merge pull request IvorySQL#1008 from rophy/feat/1002
feat: support # character in Oracle identifiers
2 parents 6108b08 + 6ea3c01 commit 744f756

6 files changed

Lines changed: 188 additions & 8 deletions

File tree

contrib/ivorysql_ora/expected/ora_misc_functions.out

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2913,9 +2913,9 @@ LINE 1: select decode(5,%^*&,'south',2,'north',4,'changsha');
29132913
^
29142914
--3.result输入非法值:
29152915
select decode(5,1,'south',2,'north',3,&%¥#,4,'changsha');--报错
2916-
ERROR: syntax error at or near ","
2916+
ERROR: "¥#": invalid identifier
29172917
LINE 1: select decode(5,1,'south',2,'north',3,&%¥#,4,'changsha');
2918-
^
2918+
^
29192919
--参数expr为任意数据类型,且expr和search类型一致
29202920
--数值类型:
29212921
--1.smallint:

src/backend/oracle_parser/liboracle_parser.c

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1272,8 +1272,9 @@ oracle_quote_identifier(const char *ident)
12721272
{
12731273
/*
12741274
* Can avoid quoting if ident starts with a lowercase letter or underscore
1275-
* and contains only lowercase letters, digits, and underscores, *and* is
1276-
* not any SQL keyword. Otherwise, supply quotes.
1275+
* and contains only lowercase letters, digits, underscores, and the
1276+
* special characters # and $ (for Oracle compatibility), *and* is not any
1277+
* SQL keyword. Otherwise, supply quotes.
12771278
*/
12781279
int nquotes = 0;
12791280
bool safe;
@@ -1293,7 +1294,9 @@ oracle_quote_identifier(const char *ident)
12931294

12941295
if ((ch >= 'a' && ch <= 'z') ||
12951296
(ch >= '0' && ch <= '9') ||
1296-
(ch == '_'))
1297+
(ch == '_') ||
1298+
(ch == '#') ||
1299+
(ch == '$'))
12971300
{
12981301
/* okay */
12991302
}

src/backend/oracle_parser/ora_scan.l

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,6 @@ static void check_escape_warning(ora_core_yyscan_t yyscanner);
152152
%option 8bit
153153
%option never-interactive
154154
%option nodefault
155-
%option noinput
156-
%option nounput
157155
%option noyywrap
158156
%option noyyalloc
159157
%option noyyrealloc
@@ -350,8 +348,13 @@ xcstop \*+\/
350348
xcinside [^*/]+
351349

352350
ident_start [A-Za-z\200-\377_]
353-
ident_cont [A-Za-z\200-\377_0-9\$]
351+
ident_cont [A-Za-z\200-\377_0-9\$#]
354352

353+
/*
354+
* Oracle allows # in identifiers (e.g., STATISTIC#).
355+
* The identifier action code uses lookahead to distinguish between
356+
* identifiers ending with # and JSON operators (#>, #>>, #-).
357+
*/
355358
identifier {ident_start}{ident_cont}*
356359

357360
/* Assorted special-case operators and operator-like tokens */
@@ -1260,6 +1263,33 @@ other .
12601263
{identifier} {
12611264
int kwnum;
12621265
char *ident;
1266+
int len = yyleng;
1267+
int nextchar;
1268+
1269+
/*
1270+
* Oracle mode: Check if identifier ends with # and is followed
1271+
* by operator chars (>, -) which would make it a JSON path
1272+
* operator (#>, #>>, #-). If so, push back the # so the operator
1273+
* gets lexed as a whole.
1274+
* This allows both "STATISTIC#" (identifier) and "json#>path"
1275+
* to work without requiring spaces.
1276+
*/
1277+
if (len >= 2 && yytext[len - 1] == '#')
1278+
{
1279+
/* Identifier ends with #. Peek at next character */
1280+
nextchar = input(yyscanner);
1281+
if (nextchar == '>' || nextchar == '-')
1282+
{
1283+
/* Followed by > or -, so this # starts an operator */
1284+
unput(nextchar);
1285+
yyless(len - 1); /* Push back the # */
1286+
}
1287+
else if (nextchar != EOF)
1288+
{
1289+
/* Not an operator, keep the # as part of identifier */
1290+
unput(nextchar);
1291+
}
1292+
}
12631293

12641294
SET_YYLLOC();
12651295

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
--
2+
-- Test Oracle-compatible identifier syntax
3+
-- Issue #1002: Support # character in unquoted identifiers
4+
--
5+
-- Test # in column names
6+
CREATE TABLE test_hash_ident (
7+
statistic# INT,
8+
col#1 INT,
9+
my#column INT
10+
);
11+
INSERT INTO test_hash_ident VALUES (1, 2, 3);
12+
SELECT statistic#, col#1, my#column FROM test_hash_ident;
13+
statistic# | col#1 | my#column
14+
------------+-------+-----------
15+
1 | 2 | 3
16+
(1 row)
17+
18+
-- Test # in table names
19+
CREATE TABLE test#table (id INT);
20+
INSERT INTO test#table VALUES (100);
21+
SELECT * FROM test#table;
22+
id
23+
-----
24+
100
25+
(1 row)
26+
27+
DROP TABLE test#table;
28+
-- Test $ in identifiers (already supported, verify no regression)
29+
CREATE TABLE test$table (col$1 INT);
30+
INSERT INTO test$table VALUES (200);
31+
SELECT col$1 FROM test$table;
32+
col$1
33+
-------
34+
200
35+
(1 row)
36+
37+
DROP TABLE test$table;
38+
-- Test mixed special characters
39+
CREATE TABLE test_mixed (
40+
col_1 INT,
41+
col$2 INT,
42+
col#3 INT,
43+
col_$#4 INT
44+
);
45+
INSERT INTO test_mixed VALUES (1, 2, 3, 4);
46+
SELECT col_1, col$2, col#3, col_$#4 FROM test_mixed;
47+
col_1 | col$2 | col#3 | col_$#4
48+
-------+-------+-------+---------
49+
1 | 2 | 3 | 4
50+
(1 row)
51+
52+
DROP TABLE test_mixed;
53+
-- Verify # XOR operator still works (both with and without spaces)
54+
SELECT 5 # 3 AS xor_result;
55+
xor_result
56+
------------
57+
6
58+
(1 row)
59+
60+
SELECT 5#3 AS xor_no_spaces;
61+
xor_no_spaces
62+
---------------
63+
6
64+
(1 row)
65+
66+
SELECT B'1010' # B'1100' AS bit_xor;
67+
bit_xor
68+
---------
69+
0110
70+
(1 row)
71+
72+
-- Verify JSON path operators work without spaces (issue was potential conflict)
73+
SELECT '{"a":1}'::json#>'{a}' AS json_path_no_space;
74+
json_path_no_space
75+
--------------------
76+
1
77+
(1 row)
78+
79+
SELECT '{"b":2}'::jsonb#>>'{b}' AS jsonb_path_no_space;
80+
jsonb_path_no_space
81+
---------------------
82+
2
83+
(1 row)
84+
85+
SELECT '{"a":1,"b":2}'::jsonb#-'{a}' AS jsonb_delete_no_space;
86+
jsonb_delete_no_space
87+
-----------------------
88+
{"b": 2}
89+
(1 row)
90+
91+
-- Clean up
92+
DROP TABLE test_hash_ident;

src/oracle_test/regress/parallel_schedule

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,3 +160,8 @@ test: emptystring_to_null
160160
test: ora_package
161161

162162
test: ora_force_view
163+
164+
# ----------
165+
# Oracle identifier syntax (#1002)
166+
# ----------
167+
test: ora_identifiers
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
--
2+
-- Test Oracle-compatible identifier syntax
3+
-- Issue #1002: Support # character in unquoted identifiers
4+
--
5+
6+
-- Test # in column names
7+
CREATE TABLE test_hash_ident (
8+
statistic# INT,
9+
col#1 INT,
10+
my#column INT
11+
);
12+
13+
INSERT INTO test_hash_ident VALUES (1, 2, 3);
14+
SELECT statistic#, col#1, my#column FROM test_hash_ident;
15+
16+
-- Test # in table names
17+
CREATE TABLE test#table (id INT);
18+
INSERT INTO test#table VALUES (100);
19+
SELECT * FROM test#table;
20+
DROP TABLE test#table;
21+
22+
-- Test $ in identifiers (already supported, verify no regression)
23+
CREATE TABLE test$table (col$1 INT);
24+
INSERT INTO test$table VALUES (200);
25+
SELECT col$1 FROM test$table;
26+
DROP TABLE test$table;
27+
28+
-- Test mixed special characters
29+
CREATE TABLE test_mixed (
30+
col_1 INT,
31+
col$2 INT,
32+
col#3 INT,
33+
col_$#4 INT
34+
);
35+
INSERT INTO test_mixed VALUES (1, 2, 3, 4);
36+
SELECT col_1, col$2, col#3, col_$#4 FROM test_mixed;
37+
DROP TABLE test_mixed;
38+
39+
-- Verify # XOR operator still works (both with and without spaces)
40+
SELECT 5 # 3 AS xor_result;
41+
SELECT 5#3 AS xor_no_spaces;
42+
SELECT B'1010' # B'1100' AS bit_xor;
43+
44+
-- Verify JSON path operators work without spaces (issue was potential conflict)
45+
SELECT '{"a":1}'::json#>'{a}' AS json_path_no_space;
46+
SELECT '{"b":2}'::jsonb#>>'{b}' AS jsonb_path_no_space;
47+
SELECT '{"a":1,"b":2}'::jsonb#-'{a}' AS jsonb_delete_no_space;
48+
49+
-- Clean up
50+
DROP TABLE test_hash_ident;

0 commit comments

Comments
 (0)