Updated 2018-09-04 10:47:49 by JMeh

JMeh 2018-09-04: Protocol-Driver for TBDC-API using Oratcl 4.5

Example Call:
package require tdbc::oratcl
tdbc::oratcl connection create db $connectionString

Fossil repository: https://tcl.sowaswie.de/repos/fossil/tdbc_oratcl/

Driver:
package require Tcl 8.6
package require tdbc
package require Oratcl

package provide tdbc::oratcl 0.4


::namespace eval ::tdbc::oratcl {
  namespace export connection
}


##
# @brief Class representing a connection to a Oratcl database connector
# @class ::tdbc::oratcl::connection
#
::oo::class create ::tdbc::oratcl::connection {
  superclass ::tdbc::connection
  variable orahdl

  constructor {connectStr} {
    next
    set orahdl [oralogon $connectStr]
    set cursor [oraopen $orahdl]
    orasql $cursor {alter session set nls_date_format='YYYY-MM-DD HH24:MI:SS'}
    oraclose $cursor
    # puts "orahdl = $orahdl"
  }


  forward statementCreate ::tdbc::oratcl::statement create


  method close {} {
    # puts "connection close"
    oralogoff $orahdl
    return [next]
  }
  
  
  method splitOwnerAndTable {ownerAndTable refOwner refTable} {
    upvar $refOwner owner
    upvar $refTable table
    lassign [split [string toupper $ownerAndTable] .] part1 part2
    if {$part2 eq {}} {
      set owner %
      set table $part1
    } else {
      set owner $part1
      set table $part2
    }
  }


  method tables {{pattern %}} {
    my splitOwnerAndTable $pattern owner table
    set ownerClause {}
    if {$owner ne {}} {
      set ownerClause {and OWNER = :owner_name}
    }
    set sql "
      select OWNER,TABLE_NAME 
        from All_Tables
        where table_name like :table_name $ownerClause
        order by OWNER,TABLE_NAME
    "
    set cursor [oraopen $orahdl]
    orasql $cursor $sql -parseonly
    orabindexec $cursor :owner_name $owner :table_name $table
    set result {}
    array set data {}
    while {[orafetch $cursor -dataarray data] == 0} {
      lappend result $data(TABLE_NAME) [list owner $data(OWNER) table_name $data(TABLE_NAME)]
    }
    oraclose $cursor
    return $result
  }


  method columns {ownerAndTable {pattern %}} {
    my splitOwnerAndTable $ownerAndTable owner table
    set ownerClause {}
    if {$owner ne {}} {
      set ownerClause {and OWNER = :owner_name}
    }
    set sql "
      select COLUMN_ID,COLUMN_NAME,DATA_TYPE,DATA_LENGTH,DATA_PRECISION,DATA_SCALE,NULLABLE
        from ALL_TAB_COLS 
        where TABLE_NAME = :table_name and COLUMN_NAME like :column_name $ownerClause
        order by COLUMN_ID
    "
    set cursor [oraopen $orahdl]
    orasql $cursor $sql -parseonly
    orabindexec $cursor :owner_name $owner :table_name $table :column_name $pattern
    set result [dict create]
    array set data {}
    while {[orafetch $cursor -dataarray data] == 0} {
      set infos [dict create]
      dict set infos type [string tolower $data(DATA_TYPE)]
      switch $data(DATA_TYPE) {
        VARCHAR2 - NVARCHAR2 {
          dict set infos type varchar
        }
        LONG {
          dict set infos type integer
        }
        RAW {
          dict set infos type varbinary
        }
        {LONG RAW} - BLOB - BFILE - CFILE - CLOB - NCLOB {
          dict set infos type longvarbinary
        }
        ROWID {
          dict set infos type binary
        }
      }
      dict set infos precision $data(DATA_PRECISION)
      if {$data(DATA_PRECISION) eq {}} {
        dict set infos precision $data(DATA_LENGTH)
        if {$data(DATA_SCALE) > 0} {
          dict set infos precision [+ $data(DATA_PRECISION) $data(DATA_SCALE)]
        }
      }
      dict set infos scale $data(DATA_SCALE)
      dict set infos nullable [string equal $data(DATA_SCALE) Y]
      dict set infos oratype $data(DATA_TYPE)
      dict set infos oraid $data(COLUMN_ID)
      dict set infos column_name $data(COLUMN_NAME)
      dict set result [string tolower $data(COLUMN_NAME)] $infos
    }
    oraclose $cursor
    return $result
  }


  method primarykeys {table} {
    return -code error "Feature is not supported"
  }


  method foreignkeys {args} {
    return -code error "Feature is not supported"
  }


  method begintransaction {} {
  }


  method commit {} {
    oracommit $orahdl
  }


  method rollback {} {
    oraroll $orahdl
  }


  method prepare {sqlCode} {
    set result [next $sqlCode]
    return $result
  }


  method getDBhandle {} {
    return $orahdl
  }
}


##
# @brief Statement class for tdbc::oratcl connection
# @class ::tdbc::oratcl::statement
#
::oo::class create ::tdbc::oratcl::statement {
  superclass ::tdbc::statement
  variable cursor bindVars

  constructor {connection sqlcode} {
    # puts "statement constructor"
    next
    set bindVars {}
    foreach token [::tdbc::tokenize $sqlcode] {
      if {[string index $token 0] in {$ : @}} {
        # puts "token = $token"
        lappend bindVars [string range $token 1 end]
      }
    }
    set cursor [oraopen [$connection getDBhandle]]
    oraconfig $cursor nullvalue {}
    set rc [oraparse $cursor $sqlcode]
    if {$rc != 0} {
      return -code error [list ORACLE error $rc on oraparse $sqlcode]
    }
  }


  forward resultSetCreate ::tdbc::oratcl::resultset create


  method execute {{bindings {}}} {
    if {$bindVars ne {}} {
      set bindList {}
      foreach varName $bindVars {
        if {[dict exists $bindings $varName]} {
          lappend bindList :$varName [dict get $bindings $varName]
        } else {
          lappend bindList :$varName {}
        }
      }
      set rc [orabind $cursor {*}$bindList]
      if {$rc != 0} {
        return -code error [list ORACLE errro $rc in orabind $bindList]
      }
    }
    oraexec $cursor
    return [next]
  }


  method params {} {
    set result [dict create]
    foreach varName $bindVars {
      dict set result $varName [dict create direction in type {} precision {} scale {} nullable {}]
    }
    return $result
  }


  method configure {args} {
    # puts [list method configure {*}$args ([llength $args] args)]
    set options {
      -longsize -bindsize -nullvalue -fetchrows -lobpsize -longpsize -utfmode -numbsize -datesize
    }
    if {[llength $args] == 0} {
      return [concat $options]
    }
    if {[llength $args] == 1} {
      lassign $args option 
      if {$option in $options} {
        return [oraconfig $cursor [string range $option 1 end]]
      }
      return -code error "Option \"$option\" is not supported"
    }
    if {[llength $args] % 2 == 0} {
      foreach {option value} $args {
        if {$option in $options} {
          oraconfig $cursor [string range $option 1 end] $value
        } else {
          return -code error "Option \"$option\" is not supported"
        }
      }
      return
    }
    return -code error "wrong # args, should be \" configure ?-option value?...\""
  }


  method getDBhandle {} {
    return [$connection getDBhandle]
  }


  method getCursor {} {
    return $cursor
  }


  method close {} {
    # puts "statement close"
    oraclose $cursor
    return [next]
  }
}


##
# @brief Result-Set class for tdbc::oratcl connection
# @class ::tdbc::oratcl::resultset
#
::oo::class create ::tdbc::oratcl::resultset {
  superclass ::tdbc::resultset
  variable cursor columns nullvalue

  constructor {statement args} {
    # puts "resultset $args"
    set cursor [$statement getCursor]
    set nullvalue [oraconfig $cursor nullvalue]
    set columns [string tolower [oracols $cursor name]]
    next
  }


  method columns {} {
    return $columns
  }


  method rowcount {} {
    set cnt [oramsg $cursor rows]
    return $cnt
  }


  method nextlist {varname} {
    upvar $varname data
    if {[orafetch $cursor -datavariable data] == 0} {
      return 1
    }
    return 0
  }


  method nextdict {varname} {
    upvar $varname data
    if {[orafetch $cursor -datavariable data] == 0} {
      set result [dict create]
      foreach column $columns value $data {
        if {$value ne $nullvalue} {
          dict set result $column $value
        }
      }
      set data $result
      return 1
    }
    return 0
  }


  method nextresults {} {
    if {[oramsg $cursor rc] == 0} {
      return 1
    }
    return 0
  }


  method close {} {
    # puts "resultset close"
    return [next]
  }
}

Package Index:
package ifneeded tdbc::oratcl 0.4 [list source [file join $dir tdbc_oratcl.tcl]]