Oratcl Examples

Oratcl Examples demonstrates by example the usage of Oratcl.

See Also

ora2txt
performs a select and outputs the results as tab delimited text from a command-line.
Oratcl Logon Dialog
a program extract showing a logon routine

Description

The following examples are using a simple option table called PROPERTY. The table contains only two columns: KEY and VALUE, each of them are VARCHAR2 types. The variables 'lda' and 'sth' contain the connection to the database and the statement handle for executing queries, inserts etc.

A simple logon routine

package require Oratcl

set lda [oralogon user/pass@dbname -async]
set sth [oraopen $lda]

if {[oramsg $sth rc] == 0} {
    puts "Successfully connected"
} else {
    puts "Unable to connect to the database."
}

oralogoff $lda

Inserting into a database

Not that big problem. Let's assume we have a button called .b and want to save all of its options in our database table. We use foreach and configure to get 'em and put them in the PROPERTY table using simple insert statements.

foreach opt [.b configure] {
    orasql $sth "INSERT INTO PROPERTY KEY, VALUE VALUES ('[lindex $opt 0]', '[lindex $opt 4]')"
}

oracommit $lda

As we know that both key and value are VARCHAR2 types, we use single quotes in the value section. This can be avoided using bindings (explained later). Note also that no semicolon is used at the end of the SQL command. This is different to using SQLPlus, for example, where a semicolon terminates the command and forces the SQL code to be executed. Using Oratcl a simple parameter is assumed and parsing rules are only applied by the Tcl interpreter.

Don't forget to commit the data, otherwise nobody else would be able to read the contents in the table and you will lose your work after ending your session.

Deleting from a database

It wouldn't make sense to save all of the options, e.g. -textvariable will be set by the application itself when it creates the button. Let's delete it.

orasql $ldh "DELETE FROM PROPERTY WHERE KEY = '-textvariable'"

oracommit $lda

Changing the values

We want to change some of the options of our button directly in the database. No problem using an update statement.

orasql $sth {UPDATE PROPERTY SET VALUE = 'red' WHERE KEY = '-foreground'}

oracommit $lda

Retrieving data from a database

Now the interesting part. We try to reload our settings from the database into another button '.b2'.

orasql $sth {SELECT KEY, VALUE FROM PROPERTY}

while 1 {
    orafetch $sth -datavariable row

    if {[oramsg $sth rc] != 0} break

    .b2 configure [lindex $row 0] [lindex $row 1]
}

The orafetch reads exactly one row from the result set in the variable 'row' (but 10 rows are pre-fetched by default - see oraconfig to change this behaviour). This variable contains a list of column contents in the order specified by the SELECT statement. If no more rows are available we will get an ORACLE error (return code = 1403 - No more data) and we break the statement.

another way:

orasql $sth {SELECT KEY, VALUE FROM PROPERTY}

while {![orafetch $sth -datavariable row]} {
    .b2 configure [lindex $row 0] [lindex $row 1]
}

The orafetch command allows to bind variable to readable column names through an array (perfomances?):

orasql $sth {SELECT KEY, VALUE FROM PROPERTY}

while {![orafetch $sth -dataarray row -indexbyname]} {
    .b2 configure $row(KEY) $row(VALUE)
}

Binding

# code goes here

PL/SQL

This is a hot topic not very well documented everywhere... Here is just the simple example that everyone is looking for one day!

set plsql {BEGIN ORACLE_USER.ORACLE_PACKAGE.ORACLE_PROCEDURE(:in_out_Cursor, :user_id); END;}
set fetch_sth [oraopen $lda]

if {[catch {
  oraplexec $sth $plsql :user_id 121 :in_out_Cursor $fetch_sth
} msg]} {
  puts stderr "ERROR: $msg"
  puts stderr [oramsg $sth err]
  exit
}

while {![orafetch $fetch_sth -datavariable row]} {
    puts [list [lindex $row 0] [lindex $row 1]]
}

Note that we need a second statement handler to fetch the cursor opened into the Oracle procedure. The PL/SQL example:

CREATE OR REPLACE PACKAGE ORACLE_PACKAGE
AS
TYPE genCurType IS REF CURSOR;

PROCEDURE ORACLE_PROCEDURE (in_out_Cursor  OUT genCurType,
                        user_id      IN  TABLE_NAME.FIELD_NAME%TYPE);

END  ORACLE_PACKAGE;
/

CREATE OR REPLACE PACKAGE BODY ORACLE_PACKAGE

AS
PROCEDURE ORACLE_PROCEDURE (in_out_Cursor  OUT genCurType,
                        user_id      IN  TABLE_NAME.FIELD_NAME%TYPE);

IS
BEGIN
OPEN in_out_Cursor FOR
  SELECT  *
    FROM TABLE_NAME
    WHERE FIELD_NAME = user_id;

END ORACLE_PROCEDURE;

END ORACLE_PACKAGE;
/

Cleanup

"Be tidy" my mother said. So do I and I gracefully close the connection to the database after work.

oraclose $sth
oralogoff $lda

Pitfalls

Oh gosh - how often did this thing come up - regional settings. I work in Austria, so my PC is setup with a german version of ORACLE. "No problems" I thought - until I selected some rows from a table and tried to insert it into another one afterwards. All double values were dumped using a comma instead of a decimal point. BUT a comma is also the field separator in SQL so ORACLE got confused when trying to use the information in an insert statement.

So I recommend changing the language settings after oralogon. This can be done using a simple SQL statement:

orasql $sth {alter session set NLS_NUMERIC_CHARACTERS =". "}
orasql $sth {alter session set NLS_DATE_FORMAT ="YYYY.MM.DD HH24:MI:SS"}

The second statement also alters the default date format which is also very convenient for me. I put these two statements just after my logon routine to prevent confusion later.

If you have a RAC with TAF you will lose these settings after a failover to another RAC node. Since oratcl43 you can register a TAF callback procedure that is automaticly called, everytime you are reconnected to another RAC node.

proc tafCallback {lda fo_type fo_event} {
    # lda: the oratcl logon handle of this connect
    # fo_type: 1 none; 2 session; 4 select
    # fo_event: 1 end; 2 abort; 4 reauth; 8 begin; 10 error

    if {$fo_event == 1} {
    set sth [oraopen $lda]
    orasql $sth {alter session set NLS_NUMERIC_CHARACTERS =". "}
    orasql $sth {alter session set NLS_DATE_FORMAT ="YYYY.MM.DD HH24:MI:SS"}
    oraclose $sth
    }
}

# connect with TAF callback
set lda [oralogon user/pass@dbname -failovercallback tafCallback]

# execute it once manually to set up the session after connect
tafCallback $lda 4 1

Anything else you want to see regarding Oratcl...

RLH: Besides the above, I would like to see some stuff about pulling data out and displaying it via CGI.

LV: I seem to recall issues regarding Unicode and Oratcl/Oracle; but I cannot recall the details. Anyone aware of issues either getting unicode into and out of Oracle, or perhaps having to do with stuff from Tcl being Unicode when the field isn't expecting it, or something? Sorry to be so vague...


Todd Helfter on comp.lang.tcl:

Here are two examples.

package require Oratcl
# should see '4.3'

# open logon handle
set lda [oralogon tmh/password@db]
# should see 'oratcl0'

# open statement handle
% set cur [oraopen $lda]
# should see 'oratcl0.0'

# parse sql statement
oraparse $cur {select table_name from user_tables}
# should see '0'  :: 0 == OCI_SUCCESS

# execute the statement handle
oraexec $cur
# should see '0'

# fetch the results (loop until return code != OCI_SUCCESS)
while {[orafetch $cur -datavariable row] == 0} {
    puts $row
}
# I got back table name 'STACKS'

# describe a table
oradesc $lda stacks
# I got back {SID 22 NUMBER 0 -127 1} {EBE 3 CHAR 0 0 1} {PART 20 CHAR 0 0 1}

# example with a bind.  Only return rows with sid = 6
oraparse $cur {select * from stacks where sid = :sid}
orabind $cur :sid 6
oraexec $cur
oracols $cur
# I got back SID EBE PART
oracols $cur all
# I got back {SID 22 NUMBER 0 -127 1} {EBE 3 CHAR {} {} 1} {PART 20 CHAR {} {} 1}
while {[orafetch $cur -datavariable row] == 0} {
    puts $row
}
# 1st row: 6 TMH {}
# 2nd row: 6 TMH {}
oraclose $cur
oralogoff $lda

DSNless connections

RLH: Per a posting on c.l.tcl:

# you need to pass in the whole DSN string if you want to bypass the
# tsnames.ora file
set dsn "(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost.localdomain)(PORT=1521)))(CONNECT_DATA=(SERVICE_NAME=test)))"
oralogon scott/tiger@$dsn

Oracle stored-procedures cursors

set exec_cur [oraopen $lda]
set fetch_cur [oraopen $lda]

set plsql {begin
    open :fetchcur for select loginname, hostname
    from ac_account where loginname = :log;
    end;
}
oraplexec $exec_cur $plsql :log tmh :fetchcur $fetch_cur
orafetch $fetch_cur {puts $login:$host} {} login 1 host 2

RLH: I wonder if someone could post pulling data out of a DB and using TCOM to build an Excel spreadsheet with it? I currently do this with Perl but would like to see a Tcl version.


LV: What I'd love to find is an introspective database browser. What I'm dreaming of is something that would show me the tables to which I have access, then, when I click on the table, it would provide me a view of the data in that table, with some way to get the equivalent of the describe if I click on a button or whatever.

I'm not asking, yet, for a way to change the data - just to browse, it, and perhaps, wistfully, wishing for a way to "order by one of the columns"... and to have the ability to widen columns, and maybe even hide columns in which I'm not too interested.

And maybe a way to specify a filter so that I only see rows which meet a particular set of criteria...


thelfter - 2017-09-24 01:45:48

RLH Wrote: I wonder if someone could post pulling data out of a DB and using TCOM to build an Excel spreadsheet with it? I currently do this with Perl but would like to see a Tcl version.

TMH : Would a CSV format be sufficient?

I use this all the time.

package require Oratcl package require csv set user SCOTT set pass TIGER set dbname ORCL set lda oralogon ${user}/${pass}@${dbname} set stm oraopen $lda

# specify each table you want to generate a CSV file for. set tblList list USER_TABLES

proc csvjoin {values {sepChar ,} {delChar \"}} {

    set out ""
    set sep {}
    foreach val $values {
        append out $sep${delChar}[string map [list $delChar ${delChar}${delChar}] $val]${d                                                                                                                         elChar}
        set sep $sepChar
    }
    return $out

}

foreach tbl $tblList {

        puts $tbl
        set t [open ${tbl}.csv w+]
        oraparse $stm "select * from ${tbl} order by num_rows desc"
        puts $t [csvjoin [oracols $stm name]]
        oraexec $stm
        while {[orafetch $stm -datavariable row] == 0} {
                puts $t [csvjoin $row]
        }
        close $t

}