10001000from functools import lru_cache
10011001from functools import wraps
10021002import re
1003+ from typing import Any
1004+ from typing import Callable
1005+ from typing import TYPE_CHECKING
10031006
10041007from . import dictionary
1008+ from .json import JSON
1009+ from .json import JSONIndexType
1010+ from .json import JSONPathType
10051011from .types import _OracleBoolean
10061012from .types import _OracleDate
10071013from .types import BFILE
10361042from ...sql import and_
10371043from ...sql import bindparam
10381044from ...sql import compiler
1045+ from ...sql import elements
10391046from ...sql import expression
10401047from ...sql import func
10411048from ...sql import null
10451052from ...sql import sqltypes
10461053from ...sql import util as sql_util
10471054from ...sql import visitors
1055+ from ...sql .base import NO_ARG
10481056from ...sql .compiler import AggregateOrderByStyle
10491057from ...sql .visitors import InternalTraversal
10501058from ...types import BLOB
10571065from ...types import REAL
10581066from ...types import VARCHAR
10591067
1068+ if TYPE_CHECKING :
1069+ from ...sql .sqltypes import _JSON_VALUE
1070+
10601071RESERVED_WORDS = set (
10611072 "SHARE RAW DROP BETWEEN FROM DESC OPTION PRIOR LONG THEN "
10621073 "DEFAULT ALTER IS INTO MINUS INTEGER NUMBER GRANT IDENTIFIED "
10791090 sqltypes .Interval : INTERVAL ,
10801091 sqltypes .DateTime : DATE ,
10811092 sqltypes .Date : _OracleDate ,
1093+ sqltypes .JSON : JSON ,
1094+ sqltypes .JSON .JSONIndexType : JSONIndexType ,
1095+ sqltypes .JSON .JSONPathType : JSONPathType ,
10821096}
10831097
10841098ischema_names = {
11061120 "ROWID" : ROWID ,
11071121 "BOOLEAN" : BOOLEAN ,
11081122 "VECTOR" : VECTOR ,
1123+ "JSON" : JSON ,
11091124}
11101125
11111126
@@ -1278,6 +1293,18 @@ def visit_VECTOR(self, type_, **kw):
12781293 )
12791294 return f"VECTOR({ dim } ,{ storage_format } ,{ storage_type } )"
12801295
1296+ def visit_JSON (self , type_ : JSON , ** kw : Any ) -> str :
1297+ use_blob = (
1298+ not self .dialect ._supports_oracle_json
1299+ if getattr (type_ , "use_blob" , NO_ARG ) is NO_ARG
1300+ else type_ .use_blob
1301+ )
1302+
1303+ if use_blob :
1304+ return "BLOB"
1305+ else :
1306+ return "JSON"
1307+
12811308
12821309class OracleCompiler (compiler .SQLCompiler ):
12831310 """Oracle compiler modifies the lexical structure of Select
@@ -1321,6 +1348,23 @@ def visit_true(self, expr, **kw):
13211348 def visit_false (self , expr , ** kw ):
13221349 return "0"
13231350
1351+ def visit_cast (self , cast , ** kwargs ):
1352+ # Oracle requires VARCHAR2 to have a length in CAST expressions
1353+ # Adapt String types to VARCHAR2 with appropriate length
1354+ type_ = cast .typeclause .type
1355+ if isinstance (type_ , sqltypes .String ) and not isinstance (
1356+ type_ , (sqltypes .Text , sqltypes .CLOB )
1357+ ):
1358+ adapted = VARCHAR2 ._adapt_string_for_cast (type_ )
1359+ type_clause = self .dialect .type_compiler_instance .process (adapted )
1360+ else :
1361+ type_clause = cast .typeclause ._compiler_dispatch (self , ** kwargs )
1362+
1363+ return "CAST(%s AS %s)" % (
1364+ cast .clause ._compiler_dispatch (self , ** kwargs ),
1365+ type_clause ,
1366+ )
1367+
13241368 def get_cte_preamble (self , recursive ):
13251369 return "WITH"
13261370
@@ -1790,6 +1834,57 @@ def visit_bitwise_lshift_op_binary(self, binary, operator, **kw):
17901834 def visit_bitwise_not_op_unary_operator (self , element , operator , ** kw ):
17911835 raise exc .CompileError ("Cannot compile bitwise_not in oracle" )
17921836
1837+ def _render_json_extract_from_binary (self , binary , operator , ** kw ):
1838+ literal_kw = kw .copy ()
1839+ literal_kw ["literal_binds" ] = True
1840+
1841+ left = self .process (binary .left , ** kw )
1842+ right = self .process (binary .right , ** literal_kw )
1843+
1844+ if binary .type ._type_affinity is sqltypes .Boolean :
1845+ # RETURNING clause doesn't handle true/false to 1/0
1846+ # mapping, so use CASE expression for boolean
1847+ return (
1848+ f"CASE JSON_VALUE({ left } , { right } )"
1849+ f" WHEN 'true' THEN 1"
1850+ f" WHEN 'false' THEN 0"
1851+ f" ELSE CAST(JSON_VALUE({ left } , { right } )"
1852+ f" AS NUMBER(1)) END"
1853+ )
1854+ elif binary .type ._type_affinity is sqltypes .Integer :
1855+ json_value_returning = "INTEGER"
1856+ elif binary .type ._type_affinity in (
1857+ sqltypes .Numeric ,
1858+ sqltypes .Float ,
1859+ ):
1860+ if isinstance (binary .type , sqltypes .Float ):
1861+ json_value_returning = "FLOAT"
1862+ else :
1863+ json_value_returning = (
1864+ f"NUMBER({ binary .type .precision } , { binary .type .scale } )"
1865+ )
1866+ elif binary .type ._type_affinity is sqltypes .String :
1867+ json_value_returning = "VARCHAR2(4000)"
1868+ else :
1869+ # binary.type._type_affinity is sqltypes.JSON
1870+ # or other
1871+ return f"JSON_QUERY({ left } , { right } )"
1872+
1873+ return (
1874+ f"JSON_VALUE({ left } , { right } "
1875+ f" RETURNING { json_value_returning } ERROR ON ERROR)"
1876+ )
1877+
1878+ def visit_json_getitem_op_binary (
1879+ self , binary : elements .BinaryExpression [Any ], operator : Any , ** kw : Any
1880+ ) -> str :
1881+ return self ._render_json_extract_from_binary (binary , operator , ** kw )
1882+
1883+ def visit_json_path_getitem_op_binary (
1884+ self , binary : elements .BinaryExpression [Any ], operator : Any , ** kw : Any
1885+ ) -> str :
1886+ return self ._render_json_extract_from_binary (binary , operator , ** kw )
1887+
17931888
17941889class OracleDDLCompiler (compiler .DDLCompiler ):
17951890
@@ -2023,6 +2118,8 @@ class OracleDialect(default.DefaultDialect):
20232118 supports_empty_insert = False
20242119 supports_identity_columns = True
20252120
2121+ _supports_oracle_json = True
2122+
20262123 aggregate_order_by_style = AggregateOrderByStyle .WITHIN_GROUP
20272124
20282125 statement_compiler = OracleCompiler
@@ -2077,6 +2174,8 @@ def __init__(
20772174 use_nchar_for_unicode = False ,
20782175 exclude_tablespaces = ("SYSTEM" , "SYSAUX" ),
20792176 enable_offset_fetch = True ,
2177+ json_serializer : Callable [[_JSON_VALUE ], str ] | None = None ,
2178+ json_deserializer : Callable [[str ], _JSON_VALUE ] | None = None ,
20802179 ** kwargs ,
20812180 ):
20822181 default .DefaultDialect .__init__ (self , ** kwargs )
@@ -2087,6 +2186,8 @@ def __init__(
20872186 self .enable_offset_fetch = self ._supports_offset_fetch = (
20882187 enable_offset_fetch
20892188 )
2189+ self ._json_serializer = json_serializer
2190+ self ._json_deserializer = json_deserializer
20902191
20912192 def initialize (self , connection ):
20922193 super ().initialize (connection )
@@ -2102,6 +2203,7 @@ def initialize(self, connection):
21022203 self .colspecs .pop (sqltypes .Interval )
21032204 self .use_ansi = False
21042205
2206+ self ._supports_oracle_json = self .server_version_info >= (21 ,)
21052207 self .supports_native_boolean = self .server_version_info >= (23 ,)
21062208 self .supports_identity_columns = self .server_version_info >= (12 ,)
21072209 self ._supports_offset_fetch = (
0 commit comments