diff --git a/samples/tutorial/Python-and-Oracle-Database-12c-Scripting-for-the-Future.html b/samples/tutorial/Python-and-Oracle-Database-Scripting-for-the-Future.html similarity index 92% rename from samples/tutorial/Python-and-Oracle-Database-12c-Scripting-for-the-Future.html rename to samples/tutorial/Python-and-Oracle-Database-Scripting-for-the-Future.html index bbb7ae5..3ce2d3b 100644 --- a/samples/tutorial/Python-and-Oracle-Database-12c-Scripting-for-the-Future.html +++ b/samples/tutorial/Python-and-Oracle-Database-Scripting-for-the-Future.html @@ -1,7 +1,7 @@
-
@@ -19,11 +19,11 @@
This tutorial was originally run as a 'Hands on Lab' at Oracle - OpenWorld 2017. Attendees used a presupplied virtual machine - preconfigured with Oracle Database, Python 3.6, cx_Oracle 6.0, a - text editor, and access to a terminal console.
- -To set up a similar environment yourself, install the following required software:
+If you are running this tutorial in your own environment, install the following required software:
To create the schema run:
@@ -152,16 +147,17 @@ sqlplus sys/yoursyspassword@localhost/orclpdb as sysdba @sql/SetupSamples be done in any order. Choose the content that interests you and your skill level. -Follow the steps in this document. The home directory has
- scripts to run and modify. The solutions directory has
- scripts with the suggested code changes.
Follow the steps in this document. The tutorial
+ directory has scripts to run and modify. The
+ tutorial/solutions directory has scripts with the
+ suggested code changes.
Use the Desktop icons to start editors and terminal windows.
If you are new to Python review the Appendix: Python Primer to gain an understanding of the language.
-Python is a popular general purpose dynamic scripting language. The cx_Oracle interface provides Python API to access Oracle @@ -172,8 +168,8 @@ sqlplus sys/yoursyspassword@localhost/orclpdb as sysdba @sql/SetupSamples
Review the code contained in db_config.py and db_config.sql:
Review db_config.py and db_config.sql. These are included in other Python and SQL files in this tutorial:
db_config.py
user = "pythonhol" @@ -185,8 +181,9 @@ dsn = "localhost/orclpdb" def user = "pythonhol" def pw = "welcome" def connect_string = "localhost/orclpdb" --
Modify the values in both files to match the connection information for your environment.
+ + +By default they connect to the 'orclpdb' database service on the same machine as Python. You can modify the values in both files to match the connection information for your environment.
localhost, and the database service name
orclpdb.
- Open a command terminal and run:
+Open a command terminal and change to the tutorial directory:
cd tutorial+ +
Run the Python script:
python connect.py@@ -223,8 +224,8 @@ print("Database version:", con.version)
cx_Oracle also supports "external authentication", which allows connections without needing usernames and passwords - to be embedded in the code. Authentication will instead be - performed by, for example, LDAP.
+ to be embedded in the code. Authentication would then + instead be performed by, for example, LDAP.Open connect.py in an editor. Indent the
- print statement with some spaces and save the file:
import cx_Oracle
@@ -245,7 +246,7 @@ con = cx_Oracle.connect(db_config.user, db_config.pw, db_config.dsn)
print("Database version:", con.version)
- Run the script again:
+Save the script and run it again:
python connect.py@@ -289,7 +290,7 @@ for row in res: print(row) -
Make sure the print(row) line is indented.
Make sure the print(row) line is indented. This lab uses spaces, not tabs.
The code executes a query and fetches all data.
@@ -307,39 +308,17 @@ for row in res:Connections and other resources used by cx_Oracle will - automatically be closed at the end of scope. This is a common - programming style.
+ automatically be closed at the end of scope. This is a + common programming style that takes care of the correct + order of resource closure.Resources can also be explicitly closed to free up - database resources if they are no longer needed.
+ database resources if they are no longer needed. This may + be useful in blocks of code that remain active for some + time. -Open query.py in an editor and add a call to
- close() like:
-import cx_Oracle
-import db_config
-
-con = cx_Oracle.connect(db_config.user, db_config.pw, db_config.dsn)
-
-cur = con.cursor()
-cur.execute("select * from dept order by deptno")
-res = cur.fetchall()
-for row in res:
- print(row)
-
-con.close()
-
-
- Run the script:
- -python query.py- -
If you are using cx_Oracle 6.2+ the connection will close and you can skip ahead to section 1.6.
- -If you are using an older version of cx_Oracle, this gives the error "cx_Oracle.DatabaseError: DPI-1054: connection cannot be closed when open statements or LOBs exist".
- -To fix this, edit the file and close the cursor:
+Open query.py in an editor and add calls to
+ close the cursor and connection like:
import cx_Oracle
@@ -354,15 +333,14 @@ for row in res:
print(row)
cur.close()
-con.close()
+con.close()
- If you run the script again, it will succeed without - error.
+Running the script completes without error:
-In your own applications, you may prefer to let cx_Oracle - automatically close resources at end of scope.
+python query.py+
If you swap the order of the two close() calls you will see an error.
When the script is run, it will display:
-6.0.2 -Database version: 12.2.0.1.0 -Client version: (12, 2, 0, 1, 0) +7.0.0 +Database version: 18.3.0.0.0 +Client version: (18, 3, 0, 0, 0)
Note the client version is a tuple.
-An application can support multiple Oracle versions. By - checking the Oracle Database and client versions numbers, - the application can make use of the best Oracle features - available.
+Any cx_Oracle installation can connect to older and newer + Oracle Database versions. By checking the Oracle Database + and client versions numbers, the application can make use of + the best Oracle features available.
The seqval, = cur.fetchone() line fetches a
row and puts the single value contained in the result tuple
into the variable seqval. Without the comma,
- the value in seqval would be a tuple like
+ the value in seqval would be a tuple like
"(1,)".
Two threads are created, each invoking the @@ -494,7 +472,7 @@ run.
Review connect_pool2.py, which has a loop for the number
-of threads:
Query() method:
import cx_Oracle @@ -541,6 +519,11 @@ cx_Oracle.SPOOL_ATTRVAL_WAIT to the from taking place, but will cause the thread to wait until a session is available. +Pool configurations where
+minis the same as +max(andincrement = 0) are often +recommended as a way to avoid connection storms on the database +server.
DRCP works well with session pooling.
-Edit connect_pool2.py and modify it to use DRCP:
Edit connect_pool2.py, reset any changed pool options, and modify it to use DRCP:
import cx_Oracle
import threading
@@ -664,6 +647,13 @@ print("All done!")
python connect_pool2.py
+ If you get the error "ORA-24418: Cannot open further
+ sessions", it is because connection requests are being made
+ while the pool is starting or growing. Add the argument
+ getmode = cx_Oracle.SPOOL_ATTRVAL_WAIT to the
+ cx_Oracle.SessionPool() call so connection
+ requests wait for pooled sessions to be available.
+
Open a new a terminal window and invoke SQL*Plus:
sqlplus /nolog @drcp_query.sql
@@ -681,19 +671,20 @@ print("All done!")
2.5 More DRCP investigation
To explore the behaviors of session and DRCP pooling futher,
- you could include the time module at the file
+ you could try changing the purity to
+ cx_Oracle.ATTR_PURITY_NEW to see the effect on the
+ DRCP NUM_MISSES statistic.
+
+ Another experiement is to include the time module at the file
top:
import time
and add calls to time.sleep(1) in the code, for
- example in the query loop. Then use
- drcp_query.sql to monitor pooled behavior.
+ example in the query loop. Then look at the way the threads execute. Use
+ drcp_query.sql to monitor the pool's behavior.
- Also try changing the purity to
- cx_Oracle.ATTR_PURITY_NEW to see the effect on the
- DRCP NUM_MISSES statistic.
The fetchmany() method returns a list of
- tuples. By default the number of rows returned is specified by the
- cursor attribute arraysize. Here the numRows
- parameter specifies that three rows should be returned.
The fetchmany() method returns a list of tuples. By
+ default the number of rows returned is specified by the cursor
+ attribute arraysize (which defaults to 100). Here the
+ numRows parameter specifies that three rows should be
+ returned.
Run the script in a terminal window:
@@ -933,13 +925,13 @@ print(elapsed, "seconds") to use different arraysizes than those given here to see a meaningful time difference. -The default arraysize used by cx_Oracle 6 is 100. There is a +
The default arraysize used by cx_Oracle 7 is 100. There is a time/space tradeoff for increasing the arraysize. Larger arraysizes will require more memory in Python for buffering the records.
-If you know a query only returns a few records, consider - decreasing the arraysize from the default to reduce memory +
If you know a query only returns a few records, + decrease the arraysize from the default to reduce memory usage.
@@ -1112,8 +1104,9 @@ print(res)python bind_insert.py
The new code shows the offending duplicate row: "ORA-00001: - unique constraint (PYTHONHOL.MY_PK) violated at row offset - 6"
+ unique constraint (PYTHONHOL.MY_PK) violated at row offset 6". + This indicates the 6th data value (counting from 0) had a + problem.The other data gets inserted and is queried back.
@@ -1132,8 +1125,7 @@ print(res)cx_Oracle can fetch and bind named object types such as Oracle's Spatial Data Objects (SDO).
-In a terminal window, start SQL*Plus:
-(Modify the following command to use your connection values.)
+In a terminal window, start SQL*Plus using the lab credentials and connection string, such as:
sqlplus pythonhol/welcome@localhost/orclpdb @@ -1216,11 +1208,14 @@ SDO_ELEM_INFO_ARRAY are set withextend().(1, <cx_Oracle.Object MDSYS.SDO_GEOMETRY at 0x104a76230>)-To show the attribute values, edit the file so the query code -(below the existing comment "
+# Query the row") is -like:To show the attribute values, edit the the query code section at +the end of the file. Add a new method that traverses the object. The +file below the existing comment "
# (Change below here)") +should look like:+# (Change below here) + # Define a function to dump the contents of an Oracle object def dumpobject(obj, prefix = " "): if obj.type.iscollection: @@ -1259,21 +1254,21 @@ for id, obj in cur: Querying row just inserted... Id: 1 { - SDO_GTYPE : 2003.0 + SDO_GTYPE : 2003 SDO_SRID : None SDO_POINT : None SDO_ELEM_INFO : [ - 1.0 - 1003.0 - 3.0 + 1 + 1003 + 3 ] SDO_ORDINATES : [ - 1.0 - 1.0 - 5.0 - 7.0 + 1 + 1 + 5 + 7 ] }@@ -1338,7 +1333,7 @@ print(res) The second parameter is the type of the returned value. It should be one of the types supported by cx_Oracle or one of the type constants defined by cx_Oracle (such as cx_Oracle.NUMBER). The two PL/SQL function - parameters are passed as a tuple and bound to the function parameter + parameters are passed as a tuple, binding them to the function parameter arguments.From a terminal window, run:
@@ -1437,7 +1432,7 @@ for row in cur.execute("select * from dept"):This shows the department number represented as digits like
-10.Add an output type handler to the bottom of file:
+Add an output type handler to the bottom of the file:
def ReturnNumbersAsStrings(cursor, name, defaultType, size, precision, scale): @@ -1521,7 +1516,7 @@ for value, in cur.execute("select 0.1 from dual"): fromdecimal.Decimalis returned in the output tuple. -Run the file:
+Run the file again:
python type_converter.py@@ -1719,7 +1714,8 @@ print("CLOB data:", clobdata)For CLOBs small enough to fit in the application memory, it is much faster to fetch them directly as strings.
-Review the code contained in
+clob_string.py:Review the code contained in
clob_string.py. + The differences fromclob.pyare shown in bold:import cx_Oracle @@ -1809,7 +1805,7 @@ for c1, c2 in rows:Both access methods gives the same results.
To use a rowfactory function, edit
+ add this code at the bottom:rowfactory.pyand - add:print('Rowfactory:') @@ -2084,6 +2080,11 @@ dequeueoptions.waitvalue to +When you run scripts, Python automatically creates bytecode + versions of them in a folder called
+__pycache__. + These improve performance of scripts that are run multiple times. + They are automatically recreated if the source file changes.Indentation
Whitespace indentation is significant in Python. When copying @@ -2146,7 +2147,8 @@ print('Value:', count)
Note the
+ 2. Examples in this lab usefrom __future__ import print_function +so that they run with Python 2 and Python 3.Data Structures
diff --git a/samples/tutorial/bind_sdo.py b/samples/tutorial/bind_sdo.py index ca9e767..edb358f 100644 --- a/samples/tutorial/bind_sdo.py +++ b/samples/tutorial/bind_sdo.py @@ -43,6 +43,8 @@ print("Adding row to table...") cur.execute("insert into testgeometry values (1, :obj)", obj = obj) print("Row added!") +# (Change below here) + # Query the row print("Querying row just inserted...") cur.execute("select id, geometry from testgeometry"); diff --git a/samples/tutorial/connect.py b/samples/tutorial/connect.py index 70a9346..627e643 100644 --- a/samples/tutorial/connect.py +++ b/samples/tutorial/connect.py @@ -1,5 +1,5 @@ #------------------------------------------------------------------------------ -# connect.py (Section 1.1 and 1.2) +# connect.py (Section 1.2 and 1.3) #------------------------------------------------------------------------------ #------------------------------------------------------------------------------ diff --git a/samples/tutorial/drcp_query.sql b/samples/tutorial/drcp_query.sql index 7a642a0..1e497e4 100644 --- a/samples/tutorial/drcp_query.sql +++ b/samples/tutorial/drcp_query.sql @@ -1,5 +1,5 @@ ------------------------------------------------------------------------------- --- drcp_query.sql (Section 2.5) +-- drcp_query.sql (Section 2.4 and 2.5) ------------------------------------------------------------------------------- /*----------------------------------------------------------------------------- diff --git a/samples/tutorial/query.py b/samples/tutorial/query.py index 195b32f..fd16d5c 100644 --- a/samples/tutorial/query.py +++ b/samples/tutorial/query.py @@ -1,5 +1,5 @@ #------------------------------------------------------------------------------ -# query.py (Section 1.3) +# query.py (Section 1.4 and 1.5) #------------------------------------------------------------------------------ #------------------------------------------------------------------------------ diff --git a/samples/tutorial/solutions/bind_sdo.py b/samples/tutorial/solutions/bind_sdo.py index ff65053..007fad7 100644 --- a/samples/tutorial/solutions/bind_sdo.py +++ b/samples/tutorial/solutions/bind_sdo.py @@ -50,6 +50,8 @@ print("Adding row to table...") cur.execute("insert into testgeometry values (1, :objbv)", objbv = obj) print("Row added!") +# (Change below here) + # Define a function to dump the contents of an Oracle object def dumpobject(obj, prefix = " "): if obj.type.iscollection: diff --git a/samples/tutorial/solutions/connect_pool2.py b/samples/tutorial/solutions/connect_pool2.py index a238ed3..cd977e5 100644 --- a/samples/tutorial/solutions/connect_pool2.py +++ b/samples/tutorial/solutions/connect_pool2.py @@ -14,7 +14,8 @@ import time import db_config pool = cx_Oracle.SessionPool(db_config.user, db_config.pw, db_config.dsn + ":pooled", - min = 2, max = 5, increment = 1, threaded = True) + min = 2, max = 5, increment = 1, threaded = True, + getmode = cx_Oracle.SPOOL_ATTRVAL_WAIT) def Query(): con = pool.acquire(cclass="PYTHONHOL", purity=cx_Oracle.ATTR_PURITY_SELF) diff --git a/samples/tutorial/solutions/query-2.py b/samples/tutorial/solutions/query-2.py index efa72d5..c7de475 100644 --- a/samples/tutorial/solutions/query-2.py +++ b/samples/tutorial/solutions/query-2.py @@ -1,5 +1,5 @@ #------------------------------------------------------------------------------ -# query.py (Section 1.4) +# query.py (Section 1.5) #------------------------------------------------------------------------------ #------------------------------------------------------------------------------ diff --git a/samples/tutorial/solutions/query.py b/samples/tutorial/solutions/query.py index 0e4bde5..ca548bb 100644 --- a/samples/tutorial/solutions/query.py +++ b/samples/tutorial/solutions/query.py @@ -1,5 +1,5 @@ #------------------------------------------------------------------------------ -# query.py (Section 1.3) +# query.py (Section 1.4 and 1.5) #------------------------------------------------------------------------------ #------------------------------------------------------------------------------ diff --git a/samples/tutorial/solutions/query_scroll.py b/samples/tutorial/solutions/query_scroll.py new file mode 100644 index 0000000..4c1a668 --- /dev/null +++ b/samples/tutorial/solutions/query_scroll.py @@ -0,0 +1,29 @@ +#------------------------------------------------------------------------------ +# query_scroll.py (Section 3.4) +#------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------ +# Copyright 2017, 2018, Oracle and/or its affiliates. All rights reserved. +#------------------------------------------------------------------------------ + +from __future__ import print_function + +import cx_Oracle +import db_config + +con = cx_Oracle.connect(db_config.user, db_config.pw, db_config.dsn) +cur = con.cursor(scrollable = True) + +cur.execute("select * from dept order by deptno") + +cur.scroll(2, mode = "absolute") # go to second row +print(cur.fetchone()) + +cur.scroll(-1) # go back one row +print(cur.fetchone()) + +cur.scroll(1) # go to next row +print(cur.fetchone()) + +cur.scroll(mode = "first") # go to first row +print(cur.fetchone()) diff --git a/samples/tutorial/solutions/versions.py b/samples/tutorial/solutions/versions.py index efa90c3..cc683d4 100644 --- a/samples/tutorial/solutions/versions.py +++ b/samples/tutorial/solutions/versions.py @@ -1,5 +1,5 @@ #------------------------------------------------------------------------------ -# versions.py (Section 1.5) +# versions.py (Section 1.6) #------------------------------------------------------------------------------ #------------------------------------------------------------------------------ diff --git a/samples/tutorial/versions.py b/samples/tutorial/versions.py index b2e776a..7bbafc8 100644 --- a/samples/tutorial/versions.py +++ b/samples/tutorial/versions.py @@ -1,5 +1,5 @@ #------------------------------------------------------------------------------ -# versions.py (Section 1.5) +# versions.py (Section 1.6) #------------------------------------------------------------------------------ #------------------------------------------------------------------------------