Implemented #385 enhancement and updated documentation (#549) · SoniaComp/python-cx_Oracle@95baec2 · GitHub
Skip to content

Commit 95baec2

Browse files
authored
Implemented oracle#385 enhancement and updated documentation (oracle#549)
* Implemented oracle#385 enhancement and updated documentation Signed-off-by: Darko Djolovic <ddjolovic@outlook.com> * Created flag to Cursor.var() Signed-off-by: Darko Djolovic <ddjolovic@outlook.com> * Removed first commit changes, updated documetnation Signed-off-by: Darko Djolovic <ddjolovic@outlook.com> * Added testing sample 'QueringRawData.py' and renamed attribute 'bypassstringencoding' to 'bypassencoding' with updated documentation Signed-off-by: Darko Djolovic <ddjolovic@outlook.com>
1 parent ffa2086 commit 95baec2

4 files changed

Lines changed: 175 additions & 8 deletions

File tree

doc/src/api_manual/cursor.rst

Lines changed: 7 additions & 1 deletion

doc/src/user_guide/sql_execution.rst

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -287,8 +287,8 @@ going to be fetched. The function is expected to return a
287287
or the value ``None``. The value ``None`` indicates that the default type
288288
should be used.
289289

290-
Examples of output handlers are shown in :ref:`numberprecision` and
291-
:ref:`directlobs`. Also see samples such as `samples/TypeHandlers.py
290+
Examples of output handlers are shown in :ref:`numberprecision`,
291+
:ref:`directlobs` and :ref:`fetching-raw-data`. Also see samples such as `samples/TypeHandlers.py
292292
<https://github.com/oracle/python-cx_Oracle/blob/master/samples/TypeHandlers.py>`__
293293

294294
.. _numberprecision:
@@ -344,6 +344,87 @@ See `samples/ReturnNumbersAsDecimals.py
344344
<https://github.com/oracle/python-cx_Oracle/blob/master/samples/ReturnNumbersAsDecimals.py>`__
345345

346346

347+
.. _fetching-raw-data:
348+
349+
Fetching Raw Data
350+
---------------------
351+
352+
Sometimes cx_Oracle may have problems converting data to unicode and you may
353+
want to inspect the problem closer rather than auto-fix it using the
354+
encodingerrors parameter. This may be useful when a database contains
355+
records or fields that are in a wrong encoding altogether.
356+
357+
It is not recommended to use mixed encodings in databases.
358+
This functionality is aimed at troubleshooting databases
359+
that have inconsistent encodings for external reasons.
360+
361+
For these cases, you can pass in the in additional keyword argument
362+
``bypassencoding = True`` into :meth:`Cursor.var()`. This needs
363+
to be used in combination with :ref:`outputtypehandlers`
364+
365+
.. code-block:: python
366+
367+
#defining output type handlers method
368+
def ConvertStringToBytes(cursor, name, defaultType, size, precision, scale):
369+
if defaultType == cx_Oracle.STRING:
370+
return cursor.var(str, arraysize=cursor.arraysize, bypassencoding = True)
371+
372+
#set cursor outputtypehandler to the method above
373+
cursor = connection.cursor()
374+
ursor.outputtypehandler = ConvertStringToBytes
375+
376+
377+
This will allow you to receive data as raw bytes.
378+
379+
.. code-block:: python
380+
381+
statement = cursor.execute("select content, charset from SomeTable")
382+
data = statement.fetchall()
383+
384+
385+
This will produce output as:
386+
387+
.. code-block:: python
388+
389+
[(b'Fianc\xc3\xa9', b'UTF-8')]
390+
391+
392+
Note that last \xc3\xa9 is é in UTF-8. Then in you can do following:
393+
394+
395+
.. code-block:: python
396+
397+
import codecs
398+
# data = [(b'Fianc\xc3\xa9', b'UTF-8')]
399+
unicodecontent = data[0][0].decode(data[0][1].decode()) # Assuming your charset encoding is UTF-8
400+
401+
402+
This will revert it back to "Fiancé".
403+
404+
If you want to save ``b'Fianc\xc3\xa9'`` to database you will need to create
405+
:meth:`Cursor.var()` that will tell cx_Oracle that the value is indeed
406+
intended as a string:
407+
408+
409+
.. code-block:: python
410+
411+
connection = cx_Oracle.connect("hr", userpwd, "dbhost.example.com/orclpdb1")
412+
cursor = connection.cursor()
413+
cursorvariable = cursor.var(cx_Oracle.STRING)
414+
cursorvariable.setvalue(0, "Fiancé".encode("UTF-8")) # b'Fianc\xc4\x9b'
415+
cursor.execute("update SomeTable set SomeColumn = :param where id = 1", param=cursorvariable)
416+
417+
418+
At that point, the bytes will be assumed to be in the correct encoding and should insert as you expect.
419+
420+
.. warning::
421+
This functionality is "as-is": when saving strings like this,
422+
the bytes will be assumed to be in the correct encoding and will
423+
insert like that. Proper encoding is the responsibility of the user and
424+
no correctness of any data in the database can be assumed
425+
to exist by itself.
426+
427+
347428
.. _outconverters:
348429

349430
Changing Query Results with Outconverters

samples/QueringRawData.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# -*- coding: utf-8 -*-
2+
import cx_Oracle
3+
import sample_env
4+
5+
"The test below verifies that the option to work around saving and reading of inconsistent encodings works"
6+
7+
def ConvertStringToBytes(cursor, name, defaultType, size, precision, scale):
8+
if defaultType == cx_Oracle.STRING:
9+
return cursor.var(str, arraysize=cursor.arraysize, bypassencoding = True)
10+
11+
connection = cx_Oracle.connect(sample_env.get_main_connect_string())
12+
cursor = connection.cursor()
13+
14+
cursor.outputtypehandler = ConvertStringToBytes
15+
16+
sql = 'create table EncodingExperiment (content varchar2(100), encoding varchar2(15))'
17+
18+
print('Creating experiment table')
19+
try:
20+
cursor.execute(sql)
21+
print('Success, will attempt to add records')
22+
except Exception as err:
23+
# table already exists
24+
print('%s\n%s'%(err, 'EncodingExperiment table exists... Will attempt to add records'))
25+
26+
# variable that we will test encodings against
27+
unicode_string = 'I bought a cafetière on the Champs-Élysées'
28+
29+
# First test
30+
windows_1252_encoded = unicode_string.encode('windows-1252')
31+
# Second test
32+
utf8_encoded = unicode_string.encode('utf-8')
33+
34+
sqlparameters = [(windows_1252_encoded, 'windows-1252'), (utf8_encoded, 'utf-8')]
35+
36+
sql = 'insert into EncodingExperiment (content, encoding) values (:content, :encoding)'
37+
38+
# cx_Oracle string variable in which we will store byte value and insert it as such
39+
content_variable = cursor.var(cx_Oracle.STRING)
40+
41+
print('Adding records to the table: "EncodingExperiment"')
42+
for sqlparameter in sqlparameters:
43+
content, encoding = sqlparameter
44+
# setting content_variable value to a byte value and instert it as such
45+
content_variable.setvalue(0, content)
46+
cursor.execute(sql, content=content_variable, encoding=encoding)
47+
48+
sql = 'select * from EncodingExperiment'
49+
50+
print('Fetching records from table EncodingExperiment')
51+
result = cursor.execute(sql).fetchall()
52+
53+
for dataset in result:
54+
content, encoding = dataset[0], dataset[1].decode()
55+
decodedcontent = content.decode(encoding)
56+
print('Is "%s" == "%s" ?\nResult: %s, (decoded from: %s)'%(decodedcontent, unicode_string, decodedcontent == unicode_string, encoding))
57+
58+
print('Finished testing, will attempt to drop the table "EncodingExperiment"')
59+
# drop table after finished testing
60+
sql = 'drop table EncodingExperiment'
61+
try:
62+
cursor.execute(sql)
63+
print('Successfully droped table "EncodingExperiment" from database.')
64+
except Exception as err:
65+
print('Failed to drop table from the database, info: %s'%err)
66+
67+
68+
69+
70+
71+
72+
73+
74+
75+

src/cxoCursor.c

Lines changed: 10 additions & 5 deletions

0 commit comments

Comments
 (0)