Tutorial
Getting a Connection
import Oracle
username = "my_username"
password = "my_password"
connect_string = "//IP_ADDRESS/XE" # a valid Oracle connect string
conn = Oracle.Connection(username, password, connect_string)
Currently, this driver only supports connections using ASCII or UTF-8 encodings. All connections are created using UTF-8 encoding by default, for both CHAR
and NCHAR
data types.
To connect as SYSDBA
, use the appropriate auth_mode
parameter.
conn = Oracle.Connection(username, password, connect_string, auth_mode=Oracle.ORA_MODE_AUTH_SYSDBA)
You should always close connections using Oracle.close
method.
Oracle.close(conn)
Executing a Statement
Oracle.execute(conn, "CREATE TABLE TB_TEST ( ID INT NULL )")
Oracle.execute(conn, "INSERT INTO TB_TEST ( ID ) VALUES ( 1 )")
Oracle.execute(conn, "INSERT INTO TB_TEST ( ID ) VALUES ( null )")
Oracle.commit(conn) # will commit 2 lines
Oracle.execute(conn, "INSERT INTO TB_TEST ( ID ) VALUES ( 3 )")
Oracle.rollback(conn) # abort insertion of the third line
Binding values to a Statement
Oracle.execute(conn, "CREATE TABLE TB_BIND ( ID NUMBER(15,0) NULL, FLT NUMBER(15,4) NULL, STR VARCHAR(255) NULL, DT DATE NULL)")
# get an explicit reference to a statement
stmt = Oracle.Stmt(conn, "INSERT INTO TB_BIND ( ID, FLT, STR, DT ) VALUES ( :id, :flt, :str, :dt )")
# will add a single line to TB_BIND
stmt[:id] = 1
stmt[:flt] = 10.23
stmt[:str] = "a string"
stmt[:dt] = Date(2018,12,31)
Oracle.execute(stmt)
Oracle.commit(conn)
Oracle.close(stmt)
Whenever you get an explicit reference to a statement, you should always use Oracle.close
method when you're done with it.
The next example constructs a Statement using the do-syntax, that automatically closes the statement at the end. It also shows how to bind values by position.
Oracle.stmt(conn, "INSERT INTO TB_BIND ( ID, FLT, STR, DT ) VALUES ( :id, :flt, :str, :dt )") do stmt
stmt[1] = 1
stmt[2] = 10.1234
stmt[3] = "this is a string"
stmt[4, Date] = missing # we must inform the column type when setting value as missing
Oracle.execute(stmt)
Oracle.commit(conn)
end
Executing a Query
Use Oracle.query
to execute a query. It returns a ResultSet
, which is a table-like struct. All data is fetched from the statement before returning the ResultSet
.
rs = Oracle.query(conn, "SELECT ID, FLT, STR, DT FROM TB_BIND")
println(names(rs)) # print column names
println(rs[2, 1]) # will print the element at row 2, column 1.
println(rs[2, "ID"]) # will print element at row 2, column ID (same as column 1).
println(rs[:, 1]) # will print all the elements in column 1.
ResultSet
implements Tables.jl interface. That means that you can transform it into a DataFrame.
using DataFrames
rs = Oracle.query(conn, "SELECT ID, FLT, STR, DT FROM TB_BIND")
println(DataFrame(rs))
The last example was easy to use, but maybe your memory can't hold all the data in the ResultSet
.
Use Oracle.query
method with do-syntax to get a reference to a cursor, which will fetch one row at a time.
Oracle.query(conn, "SELECT * FROM TB_BIND") do cursor
# prints column names
println(names(cursor))
for row in cursor
# row values can be accessed using column name or position
println( row["ID"] ) # same as row[1]
println( row["FLT"] )
println( row["STR"] )
println( row["DT"] ) # same as row[4]
end
end
You can also use a prepared statement to execute a query.
Oracle.stmt(conn, "SELECT FLT FROM TB_BIND WHERE ID = :id") do stmt
stmt[:id] = 1
Oracle.query(stmt) do cursor
for row in cursor
println(row["FLT"])
end
end
end
There is also the possibility to fetch one row at a time manually, with a small overhead when compared to previous methods.
Oracle.stmt(conn, "SELECT FLT FROM TB_BIND") do stmt
Oracle.execute(stmt)
row = Oracle.fetchrow(stmt)
while row != nothing
println(row[1])
row = Oracle.fetchrow(stmt)
end
end
Nullable columns
A nullable column is marked as NULL
in SQL.
From the Julia perspective, a NULL
value will be translated as missing
.
As an example, given the following SQL column definition:
STR VARCHAR(255) NULL
When reading from the column STR
, we get values of type Union{Missing, String}
.
When setting values on a statement, we must inform the column type for non-null values.
stmt[:str, String] = missing
When reading from a row with a NULL
value, we get missing
values:
row[:str] :: Missing
Batch statement execution
If you need to execute the same statement many times but binding different values each time, pass a vector of columns to Oracle.execute
method.
This will use the ODPI-C executeMany feature.
NUM_ROWS = 1_000
column_1 = [ i for i in 1:NUM_ROWS ]
column_2 = .5 * column_1
sql = "INSERT INTO TB_BENCH_EXECUTE_MANY ( ID, FLT ) VALUES ( :1, :2 )"
Oracle.execute_many(conn, sql, [ column_1, column_2 ])
It is also possible to bind input and output variables in one go. The following example shows how to insert data to a table that generates keys with a sequence, returning the keys that were created.
Oracle.execute(conn, "CREATE TABLE TB_EXEC_MANY_INOUT ( ID NUMBER(15, 0) NOT NULL, STR VARCHAR2(4000), LB BLOB NOT NULL )")
Oracle.execute(conn, "ALTER TABLE TB_EXEC_MANY_INOUT ADD CONSTRAINT XPK_TB_EXEC_MANY_INOUT PRIMARY KEY (ID)")
Oracle.execute(conn, "CREATE SEQUENCE SQ_TB_EXEC_MANY_INOUT INCREMENT BY 1 START WITH 1001")
input_data = ["input1", "input2", "input3"]
num_iters = length(input_data)
var_input = Oracle.Variable(conn, input_data)
# example of BLOB variables
blob_data = [ rand(UInt8, 5000) for i in 1:num_iters ]
blobs = [ Oracle.Lob(conn, Oracle.ORA_ORACLE_TYPE_BLOB) for i in 1:num_iters ]
for i in 1:num_iters
write(blobs[i], blob_data[i])
end
var_blob = Oracle.Variable(conn, blobs)
var_output = Oracle.Variable(conn, Int, buffer_capacity=num_iters)
vars = Dict(:var_input => var_input, :var_blob => var_blob, :var_output => var_output)
sql_insert = "INSERT INTO TB_EXEC_MANY_INOUT ( ID, STR, LB ) VALUES ( SQ_TB_EXEC_MANY_INOUT.nextval, :var_input, :var_blob ) RETURNING ID INTO :var_output"
Oracle.stmt(conn, sql_insert) do stmt
Oracle.execute_many(stmt, num_iters, vars)
end
Oracle.commit(conn)
try
Oracle.query(conn, "SELECT ID, STR, LB FROM TB_EXEC_MANY_INOUT ORDER BY ID") do cursor
i = 1
for row in cursor
@test parse(Int, row["STR"][end]) + 1000 == row["ID"]
@test row["ID"] == Oracle.get_returned_data(var_output, i)[1]
@test read(row["LB"]) == blob_data[i]
i += 1
end
end
for i in 1:num_iters
@test Oracle.get_returned_data(var_output, i)[1] == 1000 + i
end
finally
Oracle.execute(conn, "DROP SEQUENCE SQ_TB_EXEC_MANY_INOUT")
Oracle.execute(conn, "DROP TABLE TB_EXEC_MANY_INOUT")
end
Session Pools
A Pool represents a pool of connections, and provides a faster way to acquire connections to the database.
# creates a pool for a maximum of 2 sessions
pool = Oracle.Pool(username, password, connect_string, max_sessions=2, session_increment=1)
conn_1 = Oracle.Connection(pool)
conn_2 = Oracle.Connection(pool) # at this point, we can't acquire more connections
# release a connection so that we can acquire another one.
Oracle.close(conn_1)
# by now, acquiring a new connection should be pretty fast
# since the new connection will be taken from the pool
conn_3 = Oracle.Connection(pool)
# release all connections that are still open
Oracle.close(conn_2)
Oracle.close(conn_3)
Oracle.close(pool)
You should always close Pools using Oracle.close
method.
LOB
Oracle LOB fields can hold up to 4GB of data.
They come in two flavors:
Binary LOBs: BLOB or BFILE.
Character LOBs: CLOB or NCLOB.
LOB values are represented as a value of type Oracle.Lob
in this package.
From a LOB value, you can use read
and write
methods to manipulate whole contents of the LOB value. For incremental reading/writing, you can use open
with do-syntax do get an IO stream out of a Oracle.Lob
.
IO Streams created on Character LOBs use the character index as its position, and only support reading/writing for Char
and String
data types.
You should always close a LOB using Oracle.close
method.
Currently, BFILE is not supported.
Reading from a BLOB
lyric = "hey you. 🎵 🎶 Out there in the cold. getting lonely, getting old. Can you feel me? 📼📼📼📼"
Oracle.execute(conn, "CREATE TABLE TB_BLOB ( b BLOB )")
Oracle.execute(conn, "INSERT INTO TB_BLOB ( B ) VALUES ( utl_raw.cast_to_raw('$lyric'))")
Oracle.query(conn, "SELECT B FROM TB_BLOB") do cursor
for row in cursor
blob = row["B"]
bytes_vector = read(blob) # Vector{UInt8}
println(String(bytes_vector))
end
end
Writing to a BLOB
Follow these steps to write to a BLOB field in the database.
Create a temporary Lob associated with the connection using
Oracle.Lob(connection, oracle_type)
.Write data to the Lob.
Wrap the Lob into a Variable.
Bind the variable to the statement.
Execute the statement.
Oracle.execute(conn, "CREATE TABLE TB_BLOB_VARIABLE ( B BLOB )")
test_data = rand(UInt8, 5000)
# creates a temporary Lob bounded to the Connection
blob = Oracle.Lob(conn, Oracle.ORA_ORACLE_TYPE_BLOB)
# replaces all Lob data with the contents of the array test_data
write(blob, test_data)
# wraps the blob in a Variable
ora_var = Oracle.Variable(conn, blob)
Oracle.stmt(conn, "INSERT INTO TB_BLOB_VARIABLE ( B ) VALUES ( :1 )") do stmt
# binds the variable to the statement
stmt[1] = ora_var
Oracle.execute(stmt)
end
Transactions
The way Oracle Database works, “a transaction in Oracle begins when the first executable SQL statement is encountered”.
Use Oracle.commit
to commit and Oracle.rollback
to abort a transaction.
The following example is a valid transaction.
Oracle.execute(conn, "INSERT INTO TB_TEST ( ID ) VALUES ( 1 )") # will start a transaction
Oracle.execute(conn, "INSERT INTO TB_TEST ( ID ) VALUES ( null )")
Oracle.commit(conn) # will commit 2 lines
Oracle.execute(conn, "INSERT INTO TB_TEST ( ID ) VALUES ( 3 )") # will start a new transaction
Oracle.rollback(conn) # abort insertion of the third line