Skip to content

Commit 6abd2c9

Browse files
authored
feat(oracle): Support for WITH READ ONLY / CHECK OPTION (#3639)
* feat(oracle): Support for WITH READ ONLY / CHECK OPTION * PR Feedback 1 * PR Feedback 2 * PR Feedback 3
1 parent 6df5757 commit 6abd2c9

File tree

4 files changed

+48
-7
lines changed

4 files changed

+48
-7
lines changed

sqlglot/dialects/oracle.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
trim_sql,
1414
)
1515
from sqlglot.helper import seq_get
16+
from sqlglot.parser import OPTIONS_TYPE
1617
from sqlglot.tokens import TokenType
1718

1819
if t.TYPE_CHECKING:
@@ -36,8 +37,8 @@ def eliminate_join_marks(ast: exp.Expression) -> exp.Expression:
3637
from sqlglot.optimizer.scope import traverse_scope
3738

3839
"""Remove join marks from an expression
39-
40-
SELECT * FROM a, b WHERE a.id = b.id(+)
40+
41+
SELECT * FROM a, b WHERE a.id = b.id(+)
4142
becomes:
4243
SELECT * FROM a LEFT JOIN b ON a.id = b.id
4344
@@ -299,6 +300,7 @@ class Parser(parser.Parser):
299300
QUERY_MODIFIER_PARSERS = {
300301
**parser.Parser.QUERY_MODIFIER_PARSERS,
301302
TokenType.ORDER_SIBLINGS_BY: lambda self: ("order", self._parse_order()),
303+
TokenType.WITH: lambda self: ("options", self._parse_query_restrictions()),
302304
}
303305

304306
TYPE_LITERAL_PARSERS = {
@@ -311,6 +313,13 @@ class Parser(parser.Parser):
311313
# Reference: https://stackoverflow.com/a/336455
312314
DISTINCT_TOKENS = {TokenType.DISTINCT, TokenType.UNIQUE}
313315

316+
QUERY_RESTRICTIONS: OPTIONS_TYPE = {
317+
"WITH": (
318+
("READ", "ONLY"),
319+
("CHECK", "OPTION"),
320+
),
321+
}
322+
314323
def _parse_xml_table(self) -> exp.XMLTable:
315324
this = self._parse_string()
316325

@@ -354,6 +363,18 @@ def _parse_hint(self) -> t.Optional[exp.Hint]:
354363

355364
return None
356365

366+
def _parse_query_restrictions(self) -> t.Optional[exp.Expression]:
367+
kind = self._parse_var_from_options(self.QUERY_RESTRICTIONS, raise_unmatched=False)
368+
369+
if not kind:
370+
return None
371+
372+
return self.expression(
373+
exp.QueryOption,
374+
this=kind,
375+
expression=self._match(TokenType.CONSTRAINT) and self._parse_field(),
376+
)
377+
357378
class Generator(generator.Generator):
358379
LOCKING_READS_SUPPORTED = True
359380
JOIN_HINTS = False
@@ -442,3 +463,10 @@ def add_column_sql(self, expression: exp.AlterTable) -> str:
442463
if len(expression.args.get("actions", [])) > 1:
443464
return f"ADD ({actions})"
444465
return f"ADD {actions}"
466+
467+
def queryoption_sql(self, expression: exp.QueryOption) -> str:
468+
option = self.sql(expression, "this")
469+
value = self.sql(expression, "expression")
470+
value = f" CONSTRAINT {value}" if value else ""
471+
472+
return f"{option}{value}"

sqlglot/dialects/tsql.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1118,3 +1118,7 @@ def declareitem_sql(self, expression: exp.DeclareItem) -> str:
11181118
kind = f"TABLE {kind}"
11191119

11201120
return f"{variable} AS {kind}{default}"
1121+
1122+
def options_modifier(self, expression: exp.Expression) -> str:
1123+
options = self.expressions(expression, key="options")
1124+
return f" OPTION{self.wrap(options)}" if options else ""

sqlglot/generator.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2244,10 +2244,6 @@ def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str:
22442244
elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit):
22452245
limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression))
22462246

2247-
options = self.expressions(expression, key="options")
2248-
if options:
2249-
options = f" OPTION{self.wrap(options)}"
2250-
22512247
return csv(
22522248
*sqls,
22532249
*[self.sql(join) for join in expression.args.get("joins") or []],
@@ -2262,10 +2258,14 @@ def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str:
22622258
self.sql(expression, "order"),
22632259
*self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit),
22642260
*self.after_limit_modifiers(expression),
2265-
options,
2261+
self.options_modifier(expression),
22662262
sep="",
22672263
)
22682264

2265+
def options_modifier(self, expression: exp.Expression) -> str:
2266+
options = self.sql(expression, "options")
2267+
return f" {options}" if options else ""
2268+
22692269
def queryoption_sql(self, expression: exp.QueryOption) -> str:
22702270
return ""
22712271

tests/dialects/test_oracle.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,3 +468,12 @@ def test_eliminate_join_marks(self):
468468
eliminate_join_marks(self.parse_one(original)).sql(dialect=self.dialect),
469469
expected,
470470
)
471+
472+
def test_query_restrictions(self):
473+
for restriction in ("READ ONLY", "CHECK OPTION"):
474+
for constraint_name in (" CONSTRAINT name", ""):
475+
with self.subTest(f"Restriction: {restriction}"):
476+
self.validate_identity(f"SELECT * FROM tbl WITH {restriction}{constraint_name}")
477+
self.validate_identity(
478+
f"CREATE VIEW view AS SELECT * FROM tbl WITH {restriction}{constraint_name}"
479+
)

0 commit comments

Comments
 (0)