Skip to content

Commit b59fe3b

Browse files
Added support for new JSON data type available in Oracle Client and Database 21
and higher.
1 parent 9d4973c commit b59fe3b

File tree

13 files changed

+1061
-39
lines changed

13 files changed

+1061
-39
lines changed

doc/src/api_manual/module.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1176,6 +1176,14 @@ when binding data.
11761176
by cx_Oracle.
11771177

11781178

1179+
.. data:: DB_TYPE_JSON
1180+
1181+
Describes columns in a database that are of type JSON (with Oracle Database
1182+
21 or later).
1183+
1184+
.. versionadded:: 8.1
1185+
1186+
11791187
.. data:: DB_TYPE_LONG
11801188

11811189
Describes columns, attributes or array elements in a database that are of

doc/src/release_notes.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ Version 8.1 (TBD)
1111
#) Updated embedded ODPI-C to `version 4.1.0
1212
<https://oracle.github.io/odpi/doc/releasenotes.html#
1313
version-4-1-0-tbd>`__.
14+
#) Added support for new JSON data type available in Oracle Client and
15+
Database 21 and higher.
1416
#) Dropped support for Python 3.5. Added support for Python 3.9.
1517
#) Added internal methods for getting/setting OCI attributes that are
1618
otherwise not supported by cx_Oracle. These methods should only be used as

doc/src/user_guide/json_data_type.rst

Lines changed: 273 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,36 @@
44
Working with the JSON Data Type
55
*******************************
66

7-
Native support for JSON data was introduced in Oracle database 12c. You can use
8-
the relational database to store and query JSON data and benefit from the easy
9-
extensibility of JSON data while retaining the performance and structure of the
10-
relational database. JSON data is stored in the database in BLOB, CLOB or
11-
VARCHAR2 columns. For performance reasons, it is always a good idea to store
12-
JSON data in BLOB columns. To ensure that only JSON data is stored in that
13-
column, use a check constraint with the clause ``is JSON`` as shown in the
14-
following SQL to create a table containing JSON data:
7+
Native support for JSON data was introduced in Oracle Database 12c. You can
8+
use JSON with relational database features, including transactions, indexing,
9+
declarative querying, and views. You can project JSON data relationally,
10+
making it available for relational processes and tools. Also see
11+
:ref:`Simple Oracle Document Access (SODA) <sodausermanual>`, which allows
12+
access to JSON documents through a set of NoSQL-style APIs.
13+
14+
Prior to Oracle Database 21, JSON in relational tables is stored as BLOB, CLOB
15+
or VARCHAR2 data, allowing easy access with cx_Oracle. Oracle Database 21
16+
introduced a dedicated JSON data type with a new `binary storage format
17+
<https://blogs.oracle.com/jsondb/osonformat>`__ that improves performance and
18+
functionality. To use the new dedicated JSON type, the Oracle Database and
19+
Oracle Client libraries must be version 21, or later. Also cx_Oracle must be
20+
8.1, or later.
21+
22+
For more information about using JSON in Oracle Database see the
23+
`Database JSON Developer's Guide
24+
<https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=ADJSN>`__.
25+
26+
In Oracle Database 21, to create a table with a column called ``JSON_DATA`` for
27+
JSON data:
28+
29+
.. code-block:: sql
30+
31+
create table customers (
32+
id integer not null primary key,
33+
json_data json
34+
);
35+
36+
For older Oracle Database versions the syntax is:
1537

1638
.. code-block:: sql
1739
@@ -20,57 +42,269 @@ following SQL to create a table containing JSON data:
2042
json_data blob check (json_data is json)
2143
);
2244
23-
The following Python code can then be used to insert some data into the
24-
database:
45+
The check constraint with the clause ``IS JSON`` ensures only JSON data is
46+
stored in that column.
47+
48+
The older syntax can still be used in Oracle Database 21, however the
49+
recommendation is to move to the new JSON type. With the old syntax, the
50+
storage can be BLOB, CLOB or VARCHAR2. Of these, BLOB is preferred to avoid
51+
character set conversion overheads.
52+
53+
Using Oracle Database 21 and Oracle Client 21 with cx_Oracle 8.1 (or later),
54+
you can insert by binding as shown below:
2555

2656
.. code-block:: python
2757
28-
import json
58+
import datetime
2959
30-
customerData = dict(name="Rod", dept="Sales", location="Germany")
31-
cursor.execute("insert into customers (id, json_data) values (:1, :2)",
32-
[1, json.dumps(customerData)])
60+
json_data = [
61+
2.78,
62+
True,
63+
'Ocean Beach',
64+
b'Some bytes',
65+
{'keyA': 1, 'KeyB': 'Melbourne'},
66+
datetime.date.today()
67+
]
68+
69+
var = cursor.var(cx_Oracle.DB_TYPE_JSON)
70+
var.setvalue(0, json_data)
71+
cursor.execute("insert into customers values (:1, :2)", [123, var])
3372
34-
The data can be retrieved in its entirety using the following code:
73+
# or these two lines can replace the three previous lines
74+
cursor.setinputsizes(None, cx_Oracle.DB_TYPE_JSON)
75+
cursor.execute("insert into customers values (:1, :2)", [123, json_data])
76+
77+
Fetching with:
3578

3679
.. code-block:: python
3780
38-
import json
81+
for row in cursor.execute("SELECT c.json_data FROM customers c"):
82+
print(row)
3983
40-
for blob, in cursor.execute("select json_data from customers"):
41-
data = json.loads(blob.read())
42-
print(data["name"]) # will print Rod
84+
gives output like::
85+
86+
([Decimal('2.78'), True, 'Ocean Beach',
87+
b'Some bytes',
88+
{'keyA': Decimal('1'), 'KeyB': 'Melbourne'},
89+
datetime.datetime(2020, 12, 2, 0, 0)],)
4390

44-
If only the department needs to be read, the following code can be used
45-
instead:
91+
With the older BLOB storage, or to insert JSON strings, use:
4692

4793
.. code-block:: python
4894
49-
for deptName, in cursor.execute("select c.json_data.dept from customers c"):
50-
print(deptName) # will print Sales
95+
import json
96+
97+
customer_data = dict(name="Rod", dept="Sales", location="Germany")
98+
cursor.execute("insert into customers (id, json_data) values (:1, :2)",
99+
[1, json.dumps(customer_data)])
100+
101+
102+
IN Bind Type Mapping
103+
====================
104+
105+
When binding to a JSON value, the type parameter for the variable must be
106+
specified as :data:`cx_Oracle.DB_TYPE_JSON`. Python values are converted to
107+
JSON values as shown in the following table. The 'SQL Equivalent' syntax can
108+
be used in SQL INSERT and UPDATE statements if specific attribute types are
109+
needed but there is no direct mapping from Python.
110+
111+
.. list-table::
112+
:header-rows: 1
113+
:widths: 1 1 1
114+
:align: left
51115

52-
You can convert the data stored in relational tables into JSON data by using
53-
the JSON_OBJECT SQL operator. For example:
116+
* - Python Type or Value
117+
- JSON Attribute Type or Value
118+
- SQL Equivalent Example
119+
* - None
120+
- null
121+
- NULL
122+
* - True
123+
- true
124+
- n/a
125+
* - False
126+
- false
127+
- n/a
128+
* - int
129+
- NUMBER
130+
- json_scalar(1)
131+
* - float
132+
- NUMBER
133+
- json_scalar(1)
134+
* - decimal.Decimal
135+
- NUMBER
136+
- json_scalar(1)
137+
* - str
138+
- VARCHAR2
139+
- json_scalar('String')
140+
* - datetime.date
141+
- TIMESTAMP
142+
- json_scalar(to_timestamp('2020-03-10', 'YYYY-MM-DD'))
143+
* - datetime.datetime
144+
- TIMESTAMP
145+
- json_scalar(to_timestamp('2020-03-10', 'YYYY-MM-DD'))
146+
* - bytes
147+
- RAW
148+
- json_scalar(utl_raw.cast_to_raw('A raw value'))
149+
* - list
150+
- Array
151+
- json_array(1, 2, 3 returning json)
152+
* - dict
153+
- Object
154+
- json_object(key 'Fred' value json_scalar(5), key 'George' value json_scalar('A string') returning json)
155+
* - n/a
156+
- CLOB
157+
- json_scalar(to_clob('A short CLOB'))
158+
* - n/a
159+
- BLOB
160+
- json_scalar(to_blob(utl_raw.cast_to_raw('A short BLOB')))
161+
* - n/a
162+
- DATE
163+
- json_scalar(to_date('2020-03-10', 'YYYY-MM-DD'))
164+
* - n/a
165+
- INTERVAL YEAR TO MONTH
166+
- json_scalar(to_yminterval('+5-9'))
167+
* - n/a
168+
- INTERVAL DAY TO SECOND
169+
- json_scalar(to_dsinterval('P25DT8H25M'))
170+
* - n/a
171+
- BINARY_DOUBLE
172+
- json_scalar(to_binary_double(25))
173+
* - n/a
174+
- BINARY_FLOAT
175+
- json_scalar(to_binary_float(15.5))
176+
177+
An example of creating a CLOB attribute with key ``mydocument`` in a JSON column
178+
using SQL is:
54179

55180
.. code-block:: python
56181
57-
import json
58182
cursor.execute("""
59-
select json_object(
60-
'id' value employee_id,
61-
'name' value (first_name || ' ' || last_name))
62-
from employees where rownum <= 3""")
63-
for value, in cursor:
64-
print(json.loads(value,))
183+
insert into mytab (myjsoncol) values
184+
(json_object(key 'mydocument' value json_scalar(to_clob(:b))
185+
returning json))""",
186+
['A short CLOB'])
187+
188+
When `mytab` is queried in cx_Oracle, the CLOB data will be returned as a
189+
Python string, as shown by the following table. Output might be like::
65190

66-
The result is::
191+
{mydocument: 'A short CLOB'}
67192

68-
{'id': 100, 'name': 'Steven King'}
69-
{'id': 101, 'name': 'Neena Kochhar'}
70-
{'id': 102, 'name': 'Lex De Haan'}
71193

194+
Query and OUT Bind Type Mapping
195+
===============================
72196

73-
See `JSON Developer's Guide
197+
When getting Oracle Database 21 JSON values from the database, the following
198+
attribute mapping occurs:
199+
200+
.. list-table::
201+
:header-rows: 1
202+
:widths: 1 1
203+
:align: left
204+
205+
* - Database JSON Attribute Type or Value
206+
- Python Type or Value
207+
* - null
208+
- None
209+
* - false
210+
- False
211+
* - true
212+
- True
213+
* - NUMBER
214+
- decimal.Decimal
215+
* - VARCHAR2
216+
- str
217+
* - RAW
218+
- bytes
219+
* - CLOB
220+
- str
221+
* - BLOB
222+
- bytes
223+
* - DATE
224+
- datetime.datetime
225+
* - TIMESTAMP
226+
- datetime.datetime
227+
* - INTERVAL YEAR TO MONTH
228+
- not supported
229+
* - INTERVAL DAY TO SECOND
230+
- datetime.timedelta
231+
* - BINARY_DOUBLE
232+
- float
233+
* - BINARY_FLOAT
234+
- float
235+
* - Arrays
236+
- list
237+
* - Objects
238+
- dict
239+
240+
SQL/JSON Path Expressions
241+
=========================
242+
243+
Oracle Database provides SQL access to JSON data using SQL/JSON path
244+
expressions. A path expression selects zero or more JSON values that match, or
245+
satisfy, it. Path expressions can use wildcards and array ranges. A simple
246+
path expression is ``$.friends`` which is the value of the JSON field
247+
``friends``.
248+
249+
For example, the previously created ``customers`` table with JSON column
250+
``json_data`` can be queried like:
251+
252+
.. code-block:: sql
253+
254+
select c.json_data.location FROM customers c
255+
256+
With the JSON ``'{"name":"Rod","dept":"Sales","location":"Germany"}'`` stored
257+
in the table, the queried value would be ``Germany``.
258+
259+
The JSON_EXISTS functions tests for the existence of a particular value within
260+
some JSON data. To look for JSON entries that have a ``location`` field:
261+
262+
.. code-block:: python
263+
264+
for blob, in cursor.execute("""
265+
select json_data
266+
from customers
267+
where json_exists(json_data, '$.location')"""):
268+
data = json.loads(blob.read())
269+
print(data)
270+
271+
This query might display::
272+
273+
{'name': 'Rod', 'dept': 'Sales', 'location': 'Germany'}
274+
275+
The SQL/JSON functions ``JSON_VALUE`` and ``JSON_QUERY`` can also be used.
276+
277+
Note that the default error-handling behavior for these functions is
278+
``NULL ON ERROR``, which means that no value is returned if an error occurs.
279+
To ensure that an error is raised, use ``ERROR ON ERROR``.
280+
281+
For more information, see `SQL/JSON Path Expressions
74282
<https://www.oracle.com/pls/topic/lookup?ctx=dblatest&
75-
id=GUID-17642E43-7D87-4590-8870-06E9FDE9A6E9>`__ for more information about
76-
using JSON in Oracle Database.
283+
id=GUID-2DC05D71-3D62-4A14-855F-76E054032494>`__
284+
in the Oracle JSON Developer's Guide.
285+
286+
287+
Accessing Relational Data as JSON
288+
=================================
289+
290+
In Oracle Database 12.2, or later, the `JSON_OBJECT
291+
<https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-1EF347AE-7FDA-4B41-AFE0-DD5A49E8B370>`__
292+
function is a great way to convert relational table data to JSON:
293+
294+
.. code-block:: python
295+
296+
cursor.execute("""
297+
select json_object('deptId' is d.department_id, 'name' is d.department_name) department
298+
from departments d
299+
where department_id < :did
300+
order by d.department_id""",
301+
[50]);
302+
for row in cursor:
303+
print(row)
304+
305+
This produces::
306+
307+
('{"deptId":10,"name":"Administration"}',)
308+
('{"deptId":20,"name":"Marketing"}',)
309+
('{"deptId":30,"name":"Purchasing"}',)
310+
('{"deptId":40,"name":"Human Resources"}',)

doc/src/user_guide/sql_execution.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,9 @@ Python object that is returned by default. Python types can be changed with
197197
* - INTERVAL DAY TO SECOND
198198
- :attr:`cx_Oracle.DB_TYPE_INTERVAL_DS`
199199
- datetime.timedelta
200+
* - JSON
201+
- :attr:`cx_Oracle.DB_TYPE_JSON`
202+
- dict, list or a scalar value [4]_
200203
* - LONG
201204
- :attr:`cx_Oracle.DB_TYPE_LONG`
202205
- str
@@ -250,6 +253,10 @@ Python object that is returned by default. Python types can be changed with
250253
information present.
251254
.. [3] These include all user-defined types such as VARRAY, NESTED TABLE, etc.
252255
256+
.. [4] If the JSON is an object, then a dict is returned. If it is an array,
257+
then a list is returned. If it is a scalar value, then that particular
258+
scalar value is returned.
259+
253260
254261
.. _outputtypehandlers:
255262

0 commit comments

Comments
 (0)