dbConnect

dbConnect is an Itcl object that allows working with different databases (DKF: by acting as a database interface). it was based on ns_database idea but it has better performance as it does not add a lot of wrapping around the sql. we use it in clearance gateway. It works on unix/windows using different databases.

Currently dbConnect supports mysql (mysqltcl or sql package), oracle (version 3.4), sqlServer with tclodbc,informix (Isql) and postgres (Pgtcl). Adding support for another database should not be a hard task.

dbConnect needs of course the database-specific package.

db supports transactions


The base interface is:

dbConnect ::db
db loadDriver $type

type is db type mysql oracle sqlserver odbc-mysql odbc-oracle

db connect $user $password $name $host

name is database name
host is where db is (localhost)

Simple query:

db sql $sql

the result will come as a list of lists.

db commit - for commiting changes (the default is autocommit)
db rollback - for rollback
db desc - for getting unified desc of database

Database specific function can be used by

db now
db getDate
db toDate
db year
db day
db month
db insertGetID $sql $idField ($sequenceName only in oracle)
db getNumerator table column {where ""} to get and increment a numerator.

insert to seuqence or auto_increment column and getting the new id

where sql is the sql statement and idField is the id field.

On create command dbConnect will format the create statement to database create types.

Supported fields are: varchar, char, tinyint, smallint, int, bigint, date and UNIQUE_ID for auto_increment integer (or oracle sequence). you can also use upgradeTable to check whether a table was changed and get suggestion for the needed changes. We use this object in production code (although it is beta) and it works fine.


package provide dbConnect 1.7
package require Itcl
itcl::class dbConnect {

     private variable driver
     private variable conn
     private variable selectCommand
     private variable execCommand
     private variable commitCommand
     private variable rollbackCommand
     private variable getTablesCommand
     private variable setAutoCommitCommand 
     private variable autoCommit 1
     private variable connectCommand
     private variable disconnectCommand
     private variable sequenceCommand
     private variable limitCommand
     private variable descCommand
     private variable versionCommand
     private variable formatCreate
     private variable insertGetIdCommand
     private variable lockCommand
     private variable functions
     private variable debugIn ""
     private variable debugOut ""
     private variable tableColumns 
     private variable currentSql 
     variable dbType
 
     constructor {{driverName ""} {environment ""}} {
         initFunctions
         if {$driverName!=""} {
             loadDriver $driverName $environment
         }
     }
     destructor {
         catch {eval $disconnectCommand} msg
     }
     private method initFunctions {} {
         array set functions [list \
         toDate '\$mytimefmt' \
         getDate \$column 
         ]
     }
     public method loadDriver {{driverName ""} {environment ""}} {
         if {$environment!=""} {
             array set ::env $environment
         }
         set driver $driverName
         set dbType $driverName
         switch -glob $driverName {
             oracle {
                 set pkg Oratcl
                 if {[catch { package require Oratcl 4 }]} {
                     package require Oratcl 3
                     set pkgVersion 3
                 } else {
                     set pkgVersion 4
                 }
             }
             mysql {
                 if {[catch {package require mysqltcl}]} {
                     package require sql
                     set pkg Sql
                 } else {
                     set pkg Mysqltcl
                 }
             }
             informix {
                 package require Isql
                 set pkg Isqltcl
 
             }
             postgres {
                 package require Pgtcl
                 set pkg Pgtcl
             }
             odbc* {
                 package require tclodbc
                 set pkg Tclodbc
                 set dbType [lindex [split $driverName "-"] 1]
                 set driverName "odbc"
             }
             default {
                 error "Unsupported driver $driverName. supported drivers are : oracle,mysql,informix,postgres,odbc,odbc-mysql,odbc-sqlserver,odbc-oracle"
             }
         }
         set connectCommand "${driverName}${pkg}Connect"
         set disconnectCommand "${driverName}${pkg}Disconnect"
         set selectCommand "${driverName}${pkg}Select"
         set getTablesCommand "${driverName}${pkg}GetTables"
         set execCommand "${driverName}${pkg}Exec"
         set descCommand "${dbType}Desc"
         set lockCommand "${dbType}LockTable"
         set insertGetIdCommand "${dbType}InsertGetId"
         set sequenceCommand "${dbType}Sequence"
         set limitCommand "${driverName}Limit"
         set formatCreate "${dbType}Format"
         set setAutoCommitCommand "${driverName}SetAutoCommitCommand"
         set commitCommand "${driverName}CommitCommand"
         set rollbackCommand "${driverName}RollbackCommand"
         set versionCommand "${driverName}VersionCommand"
 
         switch $dbType {
             mysql {
                 set setAutoCommitCommand "mysqlSetAutoCommitCommand"
                 set commitCommand "mysqlCommitCommand"
                 set rollbackCommand "mysqlRollbackCommand"
                 set functions(now) "now()"
                 set functions(dateTime) "\$column"
                 set functions(date) "DATE_FORMAT(\$column,'%Y-%m-%d')"
                 set functions(userDate) "DATE_FORMAT(\$column,'%d-%m-%Y')"
                 set functions(userMonth) "DATE_FORMAT(\$column,'%m-%Y')"
                 set functions(time) "DATE_FORMAT(\$column,'%T')"
                 set functions(day) "DATE_FORMAT(\$column,'%d')"
                 set functions(month) "DATE_FORMAT(\$column,'%m')"
                 set functions(year) "DATE_FORMAT(\$column,'%Y')"
                 set functions(mod) "\$column%\$arg1"
                 set functions(substr) "substring(\$column,\$arg1,\$arg2)"
             }
             "" -
             sqlserver {
                 set functions(now) "convert(varchar(19),getdate(),20)"
                 set functions(getDate) "convert(varchar(19),\$column,20)"
                 set functions(dateTime) "convert(varchar(19),\$column,20)"
                 set functions(date) "convert(varchar(10),\$column,20)"
                 set functions(userDate) "convert(varchar(10),\$column,105)"
                 set functions(time) "convert(varchar(8),\$column,114)"
                 set functions(day) "DATEPART(dd,\$column)"
                 set functions(month) "DATEPART(mm,\$column)"
                 set functions(year) "DATEPART(yyyy,\$column)"
                 set functions(mod) "\$column%\$arg1"
                 set functions(substr) "substring(\$column,\$arg1,\$arg2)"
             }
             oracle {
                 if {$driverName eq "oracle"} {
                     set selectCommand "${driverName}${pkg}${pkgVersion}Select"
                 }
                 set functions(now) "sysdate"
                 set functions(toDate) "to_date('\$mytimefmt','YYYY-MM-DD HH24:MI:SS')"
                 set functions(getDate) "to_char(\$column,'YYYY-MM-DD HH24:MI:SS')"
                 set functions(dateTime) "to_char(\$column,'YYYY-MM-DD HH24:MI:SS')"
                 set functions(time) "to_char(\$column,'HH24:MI:SS')"
                 set functions(date) "to_char(\$column,'YYYY-MM-DD')"
                 set functions(userDate) "to_char(\$column,'DD-MM-YYYY')"
                 set functions(userMonth) "to_char(\$column,'MM-YYYY')"
                 set functions(day) "to_char(\$column,'DD')"
                 set functions(month) "to_char(\$column,'MM')"
                 set functions(year) "to_char(\$column,'YYYY')"
                 set functions(mod) "mod(\$column,\$arg1)"
                 set functions(substr) "substr(\$column,\$arg1,\$arg2)"
 
             }
             informix {
                 set functions(now) "current year to second"
                 set functions(toDate) "'\$mytimefmt'"
                 set functions(dateTime) "\$column"
                 set functions(time) "extend(\$column,hour to second)"
                 set functions(date) "extend(\$column,year to day)"
                 set functions(userDate) "extend(\$column,year to day)"
                 set functions(day) "day(\$column)"
                 set functions(month) "month(\$column)"
                 set functions(year) "year(\$column)"
                 set functions(mod) "mod(\$column,\$arg1)"
             }
             postgres {
                 set functions(now) "now()"
                 set functions(toDate) "'\$mytimefmt'"
                 set functions(getDate) "to_char(\$column,'YYYY-MM-DD HH24:MI:SS')"
                 set functions(dateTime) "to_char(\$column,'YYYY-MM-DD HH24:MI:SS')"
                 set functions(time) "to_char(\$column,'HH24:MI:SS')"
                 set functions(date) "to_char(\$column,'YYYY-MM-DD')"
                 set functions(userDate) "to_char(\$column,'DD-MM-YYYY')"
                 set functions(userMonth) "to_char(\$column,'MM-YYYY')"
                 set functions(day) "date_part('day',\$column)"
                 set functions(month) "date_part('month',\$column)"
                 set functions(year) "date_part('year',\$column)"
                 set functions(mod) "\$column%\$arg1"
                 set functions(substr) "substr(\$column,\$arg1,\$arg2)"
             }
             default {
                 error "Unsupported odbcTclodbc driver. supported drivers are odbc,odbc-mysql,odbc-sqlserver,odbc-oracle"
             }
         }
         return 0
     }
     public method connect {user password database {host ""} {autoCommit 1} {environment ""}} {
         if {$environment ne ""} {
             array set ::env $environment
         }
         $connectCommand $user $password $database $host
         setAutoCommit $autoCommit
     }
     public method disconnect {} {
         eval $disconnectCommand
     }
     public method desc {tableName} {
         eval $descCommand $tableName
     }
     public method sql {sql} {
         set currentSql $sql
         set isError [catch {
             set command [string tolower [string range [string trimleft $currentSql " "] 0 5]]
             switch -glob -- $command {
                 select {
                     set result [$selectCommand $currentSql]
                 }
                 create -
                 alter* {
                     set currentSql [string trimright [
                        $formatCreate $currentSql] ;]
                     set result [$execCommand $currentSql]
                 }
                 insert -
                 delete -
                 update {
                     set result [$execCommand $currentSql]
                 }
                 drop* {
                     set result [$execCommand $currentSql]
                     catch {unset tableColumns([lindex $currentSql 2])}
                 }
                 default {
                     error "wrong sql command $command"
                 }
             }
         } msg]
         if {$isError} {
             eval $debugIn
             set result "error $msg"
             eval $debugOut
             error "error: $msg\nsql: $currentSql" 
         }
         eval $debugIn
         eval $debugOut
         return $result
     }
     public method select {sql} {
         return [eval $selectCommand]
     }
     public method toDate {seconds} {
         set mytimefmt [clock format $seconds -format "%Y-%m-%d %H:%M:%S"]
         return [subst $functions(toDate)]
     }
     public method getDate {column} {
         return [subst $functions(getDate)]
     }
     public method getSequence {sequenceName} {
         return [eval $sequenceCommand $sequenceName]
     }
     public method insertGetId {sql  {idField ""} {returnValue 1} {sequenceName ""}} {
         return [$insertGetIdCommand $sql $idField $returnValue $sequenceName]
     }
     public method now {} {
         return $functions(now)
     }
     public method dateTime {column} {
         return [subst $functions(dateTime)]
     }
     public method userDate {column} {
         return [subst $functions(userDate)]
     }
     public method time {column} {
         return [subst $functions(time)]
     }
     public method mod {column arg1} {
         return [subst $functions(mod)]
     }
     public method date {column} {
         return [subst $functions(date)]
     }
     public method year {column} {
         return [subst $functions(year)]
     }
 
     public method month {column} {
         return [subst $functions(month)]
     }
     public method day {column} {
         return [subst $functions(day)]
     }
     public method substr {column arg1 arg2} {
         return [subst $functions(substr)]
     }
     public method limitSql {sql count} {
         return [$limitCommand $sql $count]
     }
     public method sqlToFile {sql fileName {format ""}} {
         set currentSql $sql
         eval $debugIn
         set ofd [open $fileName a]
         if {$format eq "csv"} {
             set evalFormat "puts $ofd \[join \$row ,]"
         } else {
             set evalFormat "puts $ofd \$row"
         }
         set result [$selectCommand $currentSql 2 $evalFormat]
         close $ofd
         eval $debugOut
         return $result
     }
     public method foreach {sql var script} {
         $selectCommand $sql 3 $script $var
     }
     public method rollback {} {
         return [$rollbackCommand]
     }
     public method commit {} {
         return [$commitCommand]
     }
     public method setAutoCommit {value} {
         $setAutoCommitCommand $value
         set autoCommit $value
         return 0
     }
     public method getTables {{pattern ""}} {
         set tablesList [$getTablesCommand]
         if {$pattern eq ""} {
             return $tablesList
         } else {
             set newTableList ""
             ::foreach table $tablesList {
                 set table [lindex $table 0]
                 if {[regexp $pattern $table] == 1} {
                     lappend newTableList $table
                 }
             }
         }
         return $newTableList
     }
     public method resultFromFile {fileName startRow endRow} {
         if {$endRow eq "end"} {
             set endRow 9999999
         }
         set ofd [open $fileName r]
         set count 0
         while {$count!=$startRow} {
             gets $ofd
             incr count
         }
         while {[set row [gets $ofd]] ne "" && $count<$endRow} {
             lappend result $row
             incr count
         }
         close $ofd
         return $result
     }
     public method debug {status {debugFile "dbConnectDebug.log"}} {
         if {$status} {
             set debugIn "set ofd \[open $debugFile a];puts \$ofd \"sql: \$currentSql\";close \$ofd"
             set debugOut "set ofd \[open $debugFile a];puts \$ofd \"result: \$result\";close \$ofd"
         } else {
             set debugIn ""
             set debugOut ""
         }
 
     }
     private method subSelect {sql} {
         if {[regexp {\(\s*(select.*)\)} $sql -> subSelect]} {
             ::foreach id [$selectCommand $subSelect] {
                 append idList "[lindex $id 0],"
             }
             if {![info exists idList]} {
                 set idList "''"
             } else {
                 set idList [string trimright $idList ","]
             }
             regsub $subSelect $sql $idList sql
         }
         return $sql
     }
     public method lockTable {tableName} {
         return [eval $lockCommand $tableName]
     }
 
     private method mysqlSqlExec {sql} {
         ::sql exec $conn $sql
     }
     private method mysqlSqlSelect {sql {type 1} {script ""} {var ""}} {
         ::sql query $conn $sql
         set result ""
         if {$type == 1} {
             while {[set row [::sql fetchrow $conn]]!=""} {
                 lappend result $row
             }
         } else {
             set count 0
             if {$type == 2} {
                 set level 0
             } else {
                 set level 2
                 upvar 2 $var row
             }
             while {[set row [::sql fetchrow $conn]]!=""} {
                 incr count        
                 uplevel $level \[namespace eval \[namespace current] $script]
             }
             set result $count
         }
         ::sql endquery $conn
         return $result
     }
     private method mysqlSqlDisconnect {} {
         ::sql disconnect $conn
     }
     private method mysqlSqlConnect {user password database host} {
         set conn [::sql connect $host $user $password]
         ::sql selectdb $conn $database
 
     }
     private method mysqlInsertGetId {sql {idField ""} {returnValue 1} {sequenceName ""}} {
         $execCommand $sql
         set sql "select last_insert_id()"
         if {!$returnValue} {
             return ""
         }
         return [$selectCommand $sql]
     }
     private method mysqlSequence {sql} {
         set sql "UPDATE $sequenceName set id=LAST_INSERT_ID(id+1)"
         $execCommand                
         set sql "select last_insert_id() from $sequenceName"
         return [$selectCommand]
     }
     private method mysqlFormat {sql} {
         regsub -nocase -all {UNIQUE_ID} $sql {bigint auto_increment} sql
         regsub -nocase -all {\sdate} $sql { datetime} sql
         regsub -nocase -all {\svarchar\((2[5-9][0-9]|[1-9][0-9]{3,})\)} $sql { text} sql
         return $sql
     }
     private method mysqlMysqltclConnect {user password database host} {
         set conn [mysqlconnect -user $user -password $password -db $database -host $host -encoding iso8859-1]
 
     }
 
     private method mysqlMysqltclDisconnect {} {
         mysqlclose $conn
 
     }
     private method mysqlMysqltclSelect {sql {type 1} {script ""} {var ""}} {
         set sql [subSelect $sql]
         mysqlsel $conn $sql
         set result ""
         if {$type == 1} {
             while {[set row [mysqlnext $conn]] != ""} {
                 lappend result $row
             }
         } else {
             set count 0
             if {$type == 2} {
                 set level 0
             } else {
                 set level 2
                 upvar 2 $var row
             }
             while {[set row [mysqlnext $conn]] ne ""} {
                 incr count
                 uplevel $level  $script
             }
             set result $count
         }
         return $result
     }
     private method mysqlMysqltclExec {sql} {
         set sql [subSelect $sql]
         mysqlexec $conn $sql
     }
     private method mysqlDesc {tableName} {
         set descList ""
         ::foreach column [$selectCommand "desc $tableName"] {
             if {[lindex $column 5] eq "auto_increment"} {
                 set type "UNIQUE_ID"
             } else {
                 set type [lindex $column 1]
                 if {[string first "int" $type]!=-1} {
                     set type [lindex [split $type "("] 0]
                 } elseif {$type eq "datetime"} {
                     set type "date"
                 } elseif {$type eq "text"} {
                     set type varchar(text)
                 }
             }
             if {[lindex $column 2] eq ""} {
                 set isNull no
             } else {
                 set isNull yes
             }
             lappend descList [list [lindex $column 0] $type [lindex $column 4] $isNull]
         }
         return $descList
     }
 
     private method mysqlLimit {sql count} {
         append sql " limit $count"
         sql $sql
     }
     private method mysqlSetAutoCommitCommand {flag} {
         set sql "set autocommit=$flag"
         set result [$execCommand $sql]
         if {$autoCommit == 0} {
             $execCommand "unlock tables"
         }
     }
     private method mysqlCommitCommand {} {
         return [$execCommand " commit"]
     }
     private method mysqlRollbackCommand {} {
         return [$execCommand " rollback"]
     }
     private method mysqlMysqltclGetTables {} {
         return [mysqlinfo $conn tables]
     }
     private method mysqlSqlGetTables {} {
         return [$execCommand "show tables"]
     }
     public method mysqlLockTable {tableName} {
         setAutoCommit 0
         set stmt "lock table $tableName write"
         return [$execCommand $stmt]
     }
 
 
     private method oracleOratcl3Select {sql {type 1} {script ""} {var ""}} {
         set aconn [oraopen $conn]
         orasql $aconn $sql
         set result ""
         if {$type == 1} {
             while {[set row [orafetch $aconn]] ne ""} {
                 lappend result $row
             }
         } else {
             set count 0
             if {$type == 2} {
                 set level 0
             } else {
                 set level 2
                 upvar 2 $var row
             }
             while {[set row [orafetch $aconn]]!=""} {
                 incr count
                 uplevel $level $script
             }
             set result $count
         }
         return $result
     }
     private method oracleOratcl4Select {sql {type 1} {script ""} {var ""}} {
 
         if {[string match -nocase "* join *" $sql]!=0 && [oracleVersionCommand]<"9"} {
             set sql [string tolower $sql]
             regexp { (left|right) } $sql => joinType
             set joinIndex [string first " join " $sql]
             set joinTypeIndex [string first " $joinType " $sql]
             set onIndex [string first " on " $sql]
             set whereIndex [string first " where " $sql]
             set andIndex [string first " and " $sql]
             if {$whereIndex == -1} {
                  if {$andIndex>$whereIndex} {
                     set andIndex $andIndex
                  } else {
                     set andIndex end
 
                  }
             } else {
                 if {$andIndex<$whereIndex && $andIndex!=-1} {
                     set andIndex $andIndex
                 } else {
                     set andIndex $whereIndex
                 }
             }
             
             set orderIndex [string first " order by " $sql]
             set groupIndex [string first " group by " $sql]
             if {$orderIndex!=-1} {
                 set orderSql [string range $sql $orderIndex end]
                 set sql [string range $sql 0 [expr {$orderIndex -1}]]
             } else {
                 set orderSql ""
             }
             if {$groupIndex!=-1} {
                 set groupSql [string range $sql $groupIndex end]
                 set sql [string range $sql 0 [expr {$groupIndex -1}]]
             } else {
                 set groupSql ""
             }
             set onTables [string range $sql [expr {$onIndex+3}] $andIndex]
             set newSql [string range $sql 0 $joinTypeIndex],[string range $sql [expr {$joinIndex +5}] $onIndex]
             switch $joinType {
                 "left" {
                     append onTables "(+)"
                 } 
                 "right" {
                     set onTablesList [split $onTables "="]
                     set onTables "[lindex $onTablesList 0](+)=[lindex $onTablesList 1]"
                 }
             }
             if {$whereIndex == -1} {
                 append newSql " where $onTables"
             } else {
                 append newSql "[string range $sql $whereIndex end] and $onTables"
             }
             append newSql $groupSql
             append newSql $orderSql
             set currentSql $newSql
         } else {
             set currentSql $sql
         }
 
         set aconn [oraopen $conn]
         if {[catch {
             orasql $aconn $currentSql
             set result ""
             if {$type == 1} {
                 while {![orafetch $aconn -datavariable row]} {
                     lappend result $row
                 }
             } else {
                 set count 0
                 if {$type == 2} {
                     set level 0
                 } else {
                     set level 2
                     upvar 2 $var row
                 }
                 while {![orafetch $aconn -datavariable row]} {
                     incr count
                     uplevel $level $script
                 }
                 set result $count
             }
         } msg]} {
             oraclose $aconn
             error $msg
         }
         oraclose $aconn
         return $result
 
     }
     private method oracleOratclExec {sql} {
         set aconn [oraopen $conn]
         if {[catch {
         set result [orasql $aconn $sql ]
         } msg]} {
             oraclose $aconn
             error $msg
         }
         oraclose $aconn
         return $result
     }
     private method oracleOratclConnect {user password database host} {
         global tcl_platform
         if {[info exists tcl_platform(threaded)] == 1} {
             package require Thread
             thread::eval {set conn [oralogon $user/$password@$database]}
         } else {
             set conn [oralogon $user/$password@$database]
         }
     }
     private method oracleOratclDisconnect {} {
         oralogoff $conn
     }
     private method oracleFormat {sql} {
         array set Entity {
             tiny 2
             TINY 2
             small 4
             SMALL 4
             "" 9
             big 13
             BIG 13
         }
         regsub -all -nocase {varchar} $sql {varchar2} sql
         if {[regsub -all -nocase {UNIQUE_ID} $sql { number(13) } sql]} {
             set tableName [lindex $sql 2]
             catch {sql "create sequence seq_$tableName start with 1"}
         }
         regsub -all -nocase {\s([a-z]{0,5})int([\s|,|)])} $sql { number($Entity(\1))\2} sql
         set sql [subst -nocommand -nobackslash $sql]
         set tableName [lindex $sql 2]
         regsub -all -nocase {primary key} $sql "constraint pk_$tableName primary key" sql
         return $sql
     }
     private method oracleSequence {sequenceName} {
         set sql "select ${sequenceName}.nextval from dual"
         return [sql $sql]
     }
     private method oracleInsertGetId {sql idField returnValue sequenceName} {
         set firstOpenBrace [string first "(" $sql]
         set tableName [lindex [string range $sql 0 $firstOpenBrace] 2]
         if {$sequenceName eq ""} {
             set sequenceName "seq_$tableName"
         }
         set sqlSeq "select ${sequenceName}.nextval from dual"
         set sequenceValue [sql $sqlSeq]
         set valuesIndex [string first "values" $sql]
         if {$valuesIndex == -1} {
             set valuesIndex [string first "VALUES" $sql]
         }
         set firstCloseBrace [string last ")" [string range $sql 0 $valuesIndex]]
         set lastOpenBrace [string first "(" [string range $sql $valuesIndex end]]
         set lastCloseBrace [string last ")" $sql]
         set sqlInsert "insert into $tableName ($idField,[string range $sql [expr {$firstOpenBrace +1}] $firstCloseBrace] values ($sequenceValue,[string range $sql [expr {$lastOpenBrace +$valuesIndex +1}] [expr {$lastCloseBrace +$valuesIndex}]]"
         sql $sqlInsert
         return $sequenceValue
 
     }
     private method oracleLimit {sql count} {
         incr count
         set orderBy [string first " by " $sql]
 
         if {$orderBy!=-1} {
             set limitSql [string range $sql 0 [incr orderBy -6]]
         } else {
             set limitSql $sql
         }
 
         if {[string first "where" $limitSql] == -1 && [string first "WHERE" $limitSql] == -1} {
             append limitSql " where "
         } else {
             append limitSql " and "
         }
         append limitSql "rownum<$count"
         if {$orderBy!=-1} {
             append limitSql " [string range $sql $orderBy end]"
         }
         sql $limitSql
     }
     private method oracleSetAutoCommitCommand {flag} {
         return [oraautocom $conn $flag]
     }
     private method oracleCommitCommand {} {
         return [oracommit  $conn]
     }
     private method oracleRollbackCommand {} {
         return [oraroll $conn]
     }
     private method oracleOratclGetTables {} {
         return [sql "select table_name from user_tables"]
     }
     private method oracleDesc {tableName} {
         set tableName [string toupper $tableName]
         set isSequence [$selectCommand  "SELECT count(*) from user_sequences WHERE sequence_name='SEQ_$tableName'"]
         set uniqueId 0
         set result ""
         ::foreach column [$selectCommand "SELECT column_name,data_type,data_length,data_precision,data_default,nullable,data_scale 
         FROM user_tab_columns WHERE table_name='$tableName'"] {
         set type [lindex $column 1]
         if {[lindex $column 1] == "NUMBER"} {
             set precision [lindex $column 3]
             if {[lindex $column 6]!=0} {
                 set type decimal($precision,[lindex $column 6])
             } else {
                 switch $precision {
                     "13" {
                         if {$isSequence && !$uniqueId} {
                             set uniqueId 1
                             set type "UNIQUE_ID"
                         } else {
                             set type "bigint"
                         }
                     }
                     "9" {
                         set type "int"
                     }
                     "4" {
                         set type "smallint"
                     } 
                     "2" {
                         set type "tinyint"
                     }
                 }
             }
         } elseif {$type eq "DATE"} {
             set type "date"
         } else {
             set type "varchar([lindex $column 2])"
         }
         if {[lindex $column 5] eq "N"} {
             set isNull "no"
         } else {
             set isNull "yes"
         }
         lappend result [list [string tolower [lindex $column 0]] $type [string trim [string map [list ' ""] [lindex $column 4]]] $isNull]
     }
     if {$result eq ""} {
         error "table $tableName does not exist"
     }
     return $result
     }
     public method oracleLockTable {tableName} {
         setAutoCommit 0
         set stmt "lock table $tableName in EXCLUSIVE mode"
         return [$execCommand $stmt]
     }
     private method oracleVersionCommand {} {
         set data [orainfo server $conn]
         set startIndex [expr {[string first "Release" $data] +8}]
         set version [string range $data $startIndex [expr {$startIndex +8}]]
         return $version
     }
     private method sqlserverSequence {sql} {
         set sql ""
         sql $sql        
         set sql ""
         set sequenceNum [sql $sql]
 
     }
     private method sqlserverInsertGetId {sql {idField ""} {returnValue 1} {sequenceName ""}} {
         sql $sql
         if {!$returnValue} {
             return ""
         }
         set getIdSql "select @@identity"
         set id [sql $getIdSql]
 
     }
     private method sqlserverLimit {sql count} {
         $conn set maxrows $count
         set rows [sql $sql]
         $conn set maxrows 0
         return $rows
     }
     private method sqlserverFormat {sql} {
         regsub -nocase {UNIQUE_ID} $sql {bigint identity(1,1)} sql
         regsub -nocase -all  {\sdate} $sql { datetime} sql
         set tableName [lindex $sql 2]
         regsub -all -nocase {primary key} $sql "constraint pk_$tableName primary key" sql
         regsub -all -nocase { modify } $sql " alter column " sql
         return $sql
     }
     private method sqlserverTclodbcGetTables {} {
         return [sql "select distinct table_name from information_schema.columns"]
     }
 
     private method sqlserverSetAutoCommitCommand {flag} {
 
         return [$conn set autocommit $flag]
     }
     private method sqlserverCommitCommand {} {
         return [$execCommand "commit"]
     }
     private method sqlserverRollbackCommand {} {
         return [$execCommand "rollback"]
     }
 
     private method sqlserverDesc {tableName} {
         set descList ""
         set primaryColumn ""
         ::foreach column [$conn columns $tableName] {
             set type [lindex $column 5]
             if {$type eq "bigint identity"} {
                 set type UNIQUE_ID
             } elseif {[string first "int" $type] == -1} {
                 set type "varchar([lindex $column 6])"
             } else { 
                 set type [lindex $column 5]
             }
             lappend descList [list [lindex $column 3] $type [lindex $column 12] [lindex [lindex $column 17] 0]]
         }
         return $descList
     }
     public method sqlserverLockTable {tableName} {
         setAutoCommit 0
         set stmt "select top 1 'a' from $tableName with (tablockx)"
         return [$execCommand $stmt]
     }
 
     private method odbcTclodbcConnect {user password database host} {
         set conn [database conn[clock clicks] $database $user $password]
     }
     private method odbcTclodbcDisconnect {} {
         $conn disconnect
     }
     private method odbcTclodbcSelect {sql {type 1} {script ""} {var ""}} {
         if {$type == 1} {
             return [$conn $sql]
         } else {
             set count 0
             if {$type == 2} {
                 set level 0
             } else {
                 set level 2
                 upvar 2 $var row
             }
             $conn statement stmHandle $sql
             stmHandle execute
             while {[set row [stmHandle fetch]]!=""} {
                 incr count
                 uplevel $level $script
             }
             stmHandle drop
             return $count
         }
     }
     private method odbcTclodbcExec {sql} {
         return [$conn $sql]
     }
     private method odbcSetAutoCommitCommand {flag} {
 
         return [$conn set autocommit $flag]
     }
     private method odbcCommitCommand {} {
         return [$execCommand "commit"]
     }
     private method odbcRollbackCommand {} {
         return [$execCommand "rollback"]
     }
     private method odbcTclodbcGetTables {} {
         set tableList ""
         ::foreach tbl [$conn tables] {
             lappend tableList [lindex $tbl 2]
         }
 
         return $tableList
     }
 
     private method odbcLimit {sql count} {
         $conn set maxrows $count
         set rows [sql $sql]
         $conn set maxrows 0
         return $rows
     }
     private method informixIsqltclSelect {sql {type 1} {script ""} {var ""}} {
         if {[set fd [::sql open "$sql"]]<0} {
             catch {::sql close $fd}        
             error [::sql geterror]
         }
         set result ""
         if {$type == 1} {
             while {[set row [::sql fetch $fd 1]]!=""} {
                 lappend result $row
             }
         } else {
             set count 0
             if {$type == 2} {
                 set level 0
             } else {
                 set level 2
                 upvar 2 $var row
             }
             while {[set row [::sql fetch $fd 1]]!=""} {
                 incr count
                 uplevel $level $script
             }
             set result $count
         }
         ::sql close $fd
         return $result
 
     }
     private method informixIsqltclExec {sql} {
         if {$autoCommit == 0} {
             catch {::sql begin}
         }
         set command [string tolower [string range [string trimleft $sql " "] 0 5]]
         set cmd "::sql run "  
         if {$command eq "insert"} {
             set bindVals {}
             set matching [regexp -all -inline '(.*?)' $sql]
             ::foreach {match val} $matching {
                 if {[string length $val]>257} {
                     set startIndex [string first $match $sql]
                     set endIndex [expr {$startIndex -1 + [string length $match]}]
                     set sql [string replace $sql $startIndex $endIndex "?"]
                     set bindVals [concat $bindVals $val] 
                 }
             }
             lappend cmd $sql 
             if {$bindVals!=""} {
                 lappend cmd $bindVals
             }
         } else {
             lappend cmd $sql
         }
         if {[set result [eval $cmd]]!=0} {
             error [::sql geterror]
         }
         return $result
     }
     private method informixIsqltclConnect {user password database host} {
         if {[::sql database $database with_concurrent_transaction user $user using password $password]!=0} {
             error [::sql geterror]
         }
     }
     private method informixIsqltclDisconnect {} {
         ::sql finish
     }
     private method informixFormat {sql} {
         regsub -nocase -all {UNIQUE_ID} $sql {serial(100) } sql
         regsub -nocase -all {\sdate} $sql { datetime year to second} sql
         regsub -nocase -all {\snot null} $sql { tmpnull} sql
         regsub -nocase -all {\sdefault null} $sql {} sql
         regsub -nocase -all {\snull} $sql {} sql
         regsub -nocase -all {\stmpnull} $sql { not null} sql
         regsub -nocase -all {\stinyint} $sql { int} sql
         regsub -nocase -all {\sbigint} $sql { int} sql
         regsub -nocase -all {\svarchar\((2[6-9][0-9]|[1-9][0-9]{3,})\)} $sql { char(\1) } sql
         regsub -all -nocase {constraint(.*?)foreign} $sql "foreign" sql
         return $sql
     }
     private method informixSequence {sql} {
     }
     private method informixInsertGetId {sql idField returnValue sequenceName} {
 
         set firstOpenBrace [string first "(" $sql]
         set tableName [lindex [string range $sql 0 $firstOpenBrace] 2]
         set valuesIndex [string first "values" $sql]
         if {$valuesIndex == -1} {
             set valuesIndex [string first "VALUES" $sql]
         }
         set firstCloseBrace [string last ")" [string range $sql 0 $valuesIndex]]
         set lastOpenBrace [string first "(" [string range $sql $valuesIndex end]]
         set lastCloseBrace [string last ")" $sql]
         set sqlInsert "insert into $tableName ($idField,[string range $sql [expr {$firstOpenBrace +1}] $firstCloseBrace] values (0,[string range $sql [expr {$lastOpenBrace +$valuesIndex +1}] [expr {$lastCloseBrace +$valuesIndex}]]"
         sql $sqlInsert
         return [lindex [lindex [::sql sqlca] 3] 1]
 
     }
     private method informixLimit {sql count} {
         return [lrange [sql $sql] 0 [incr count -1]] 
     }
     private method informixSetAutoCommitCommand {flag} {
     }
     private method informixCommitCommand {} {
         return [catch {::sql commit}]
     }
     private method informixRollbackCommand {} {
         return [catch {::sql rollback}]
     }
     private method informixIsqltclGetTables {} {
         return [sql "SELECT tabname  FROM systables "]
     }
     private method postgresPgtclSelect {sql {type 1} {script ""} {var ""}} {
         set result ""
         if {$type == 1} {
             pg_select $conn $sql rowArray {
                 set row ""
                 ::foreach header $rowArray(.headers) {
                     lappend row $rowArray($header)
                 }
                 lappend result $row
             }
         } else {
             set count 0
             if {$type == 2} {
                 set level 0
             } else {
                 set level 2
                 upvar 2 $var row
             }
             pg_select $conn $sql row {
                 incr count
                 set row ""
                 ::foreach header $row(.headers) {
                     lappend row $rowArray($header)
                 }
                 uplevel $level $script
             }
             ps_result 
             set result $count
         }
         return $result
 
     }
     private method postgresPgtclExec {sql} {
         if {$autoCommit == 0} {
             pg_exec $conn "begin"
         }
         set handle [pg_exec $conn $sql]
         set result [pg_result $handle -status]
         if {$result eq "PGRES_FATAL_ERROR"} {
             set result [pg_result $handle  -error]
             pg_result $handle -clear
             error $result
         }
         pg_result $handle -clear
         return 
     }
     private method postgresPgtclConnect {user password database host} {
         set conn [pg_connect $database -host $host ]
     }
     private method postgresPgtclDisconnect {} {
         pg_disconnect $conn
     }
     private method postgresFormat {sql} {
         regsub -all -nocase {UNIQUE_ID} $sql { serial } sql
 
         regsub -all -nocase {\stinyint\s} $sql { smallint } sql
         regsub -nocase -all {\svarchar\((2[5-9][0-9]|[1-9][0-9]{3,})\)} $sql { text} sql
         return $sql
     }
     private method postgresInsertGetId {sql idField returnValue sequenceName} {
         set firstOpenBrace [string first "(" $sql]
         set tableName [lindex [string range $sql 0 $firstOpenBrace] 2]
         if {$sequenceName eq ""} {
             set sequenceName "${tableName}_${idField}_seq"
         }
         set sqlSeq "select nextval('$sequenceName')"
         set sequenceValue [sql $sqlSeq]
         set valuesIndex [string first "values" $sql]
         if {$valuesIndex == -1} {
             set valuesIndex [string first "VALUES" $sql]
         }
         set firstCloseBrace [string last ")" [string range $sql 0 $valuesIndex]]
         set lastOpenBrace [string first "(" [string range $sql $valuesIndex end]]
         set lastCloseBrace [string last ")" $sql]
         set sqlInsert "insert into $tableName ($idField,[string range $sql [expr {$firstOpenBrace +1}] $firstCloseBrace] values ($sequenceValue,[string range $sql [expr {$lastOpenBrace +$valuesIndex +1}] [expr {$lastCloseBrace +$valuesIndex}]]"
         sql $sqlInsert
         return $sequenceValue
 
     }
     private method postgresLimit {sql count} {
         append sql " limit $count"
         sql $sql
     }
     private method postgresSetAutoCommitCommand {flag} {
     }
     private method postgresCommitCommand {} {
         pg_exec $conn "end"
         return [pg_exec $conn "commit work"]
     }
     private method postgresRollbackCommand {} {
         return [pg_exec $conn "rollback work"]
     }
     private method postgresPgtclGetTables {} {
         return [sql "select relname from pg_class where relname not like ('pg_%') and relkind='r'"]
     }
     public method insertUpdateRow {tableName tableRecord} {
         if {[catch {
             set whereText "WHERE "
             set keyExists "false"
 
             buildDescArray $tableName
             ::foreach  {columnKey columnValue} $tableRecord {
                 if {[string first KEY: $columnKey]!=-1} {
                     set keyExists 1
                     set columnKey [lindex [split $columnKey ":"] 1]
                     switch -glob $tableColumns($tableName,$columnKey,type) {
                         "varchar*" -
                         "char*" {
                             set columnValue [string map [list ' \\\' \" \"] $columnValue]
                             set columnValue "'$columnValue'"
                         }
                         "date" {
                             set columnValue [toDate [clock scan $columnValue]]
                         }
                     }
                     append whereText "($columnKey = $columnValue) AND"
                 }
             }
             set whereText [string trimright $whereText "AND"]
 
             set recordExists 0
             if {$keyExists} {
                 set recordExists [lindex [isExists $tableName $whereText] 0]
             }        
 
             if { $recordExists } {
                 set returnValue [update $tableName $tableRecord $whereText]
             } else {
                 set returnValue [insert $tableName $tableRecord ]
             }
 
         } err]} {
             error "Error: $err"
         }
     }
     public method isExists { tableName whereText } {
 
         set queryText "SELECT count(*) FROM $tableName "
         append queryText $whereText
 
         set returnValue [sql $queryText];
 
         if {$returnValue!= 0} {
             set returnValue 1
         }
         return $returnValue;
     }
     private method buildDescArray {tableName {extend 0}} {
         if {[info exists tableColumns($tableName)]} {return}
         set tableColumns($tableName) 1
         ::foreach column [$descCommand $tableName] {
             ::foreach {name type default null} $column {break}
             set tableColumns($tableName,$name,type) $type
             if {$extend} {
                 set tableColumns($tableName,$name,default) $default
                 set tableColumns($tableName,$name,null) $null
             }
         }
     }
     public method insert {tableName tableRecord} {
         if {[catch {
             buildDescArray $tableName
             set queryText "INSERT INTO $tableName "
             set recordValue " VALUES ("
             set recordKey " ("
 
             ::foreach  {columnKey columnValue} $tableRecord {
 
                 if {[string first KEY: $columnKey]!=-1} {
                     set columnKey [lindex [split $columnKey ":"] 1]
                 }
 
 
 
                 switch -- $columnValue {
                     "now" {
                         append recordValue "[now],"
                         append recordKey "$columnKey,"        
                     }
                     "seq" {
                         set seqColumnId "$columnKey";
                     }
                     default {
                         if {![info exists tableColumns($tableName,$columnKey,type)]} {
                             error "Error: column $columnKey does not exists in table $tableName"
                         }
                         switch -glob -- $tableColumns($tableName,$columnKey,type) {
                             "varchar*" -
                             "char*" {
                                 set columnValue [string map [list ' \\\' \" \"] $columnValue]
                                 append recordValue '$columnValue',
                             }
                             "date" {
                                 append recordValue [toDate [clock scan $columnValue]],
                             }
                             default {
                                 append recordValue $columnValue,
                             }
                         }
                         append recordKey "$columnKey,"        
                     }
                 }                
             }
 
 
             set recordValue "[string trimright $recordValue ","])"
 
             set recordKey "[string trimright $recordKey ","])"
 
             append queryText "$recordKey $recordValue";
 
         } err]} {
             error $err
         }
         if {![info exists seqColumnId]} {
             set returnValue [sql $queryText];
         } else {
             set returnValue [insertGetId $queryText $seqColumnId];
         }
         return $returnValue;
     }
     public method update {tableName tableRecord whereText } {
         if {[catch {
             buildDescArray $tableName
             set queryText "UPDATE $tableName SET "
             set recordValue ""
 
             ::foreach  {columnKey columnValue} $tableRecord {
 
                 if {[string first KEY: $columnKey]!=-1} {
                     set columnKey [lindex [split $columnKey ":"] 1]
                 }
 
                 switch -- $columnValue {
                     "seq" {
                     }
                     "now" {
                         append recordValue "$columnKey = [now],"        
                     }
                     default {
                         switch -glob $tableColumns($tableName,$columnKey,type) {
                             "varchar*" -
                             "char*" {
                                 set columnValue [string map [list ' \\\' \" \"] $columnValue]
                                 append recordValue "$columnKey ='$columnValue',"
                             }
                             "date" {
                                 append recordValue "$columnKey=[toDate [clock scan $columnValue]],"
                             }
                             default {
                                 append recordValue $columnKey=$columnValue,
                             }
                         }
                     }
                 }                
             }
 
             set recordValue [string trimright $recordValue ","]
 
             append queryText "$recordValue $whereText";
         } err]} {
             error "wrong input format $tableRecord error: $err"
         }
         return [sql $queryText];
     }
     public method upgradeTable {sql {upgradeCheck 0}} {
         variable dbType
         set tableName [lindex $sql 2]
         buildDescArray $tableName 1
         if {![info exists tableColumns($tableName)]} {
             error "$tableName does not exists"
         }
       regsub -nocase {\([a-z_ ]*,[a-z_ ]*(,[a-z_ ]*)*(,[a-z_ ]*)*\)}  $sql "XXX" sql
         regsub -nocase {[(]([0-9])[,]([0-9]?)[)]}  $sql {(\1-X-\2)} sql
         set startColumns [string first "(" $sql]
         set endColumns [string last ")" $sql]
         set columns [split [string range $sql [incr startColumns] [incr endColumns -1]] ","]
         set alterList ""
         ::foreach column $columns {
         regsub -nocase -- {-X-}  $column "," column
             set alterSql ""
             set columnName [string tolower [lindex $column 0]]
             set column [string tolower $column]
             set columnCheck [lindex $column 0]
             if {$columnCheck eq "primary" || $columnCheck eq "foreign"
                || $columnCheck eq "constraint"} {continue}
             set columnType [lindex $column 1]
 
 
             if {[set findIndex [lsearch -start 2 $column "default"]]!=-1} {
                 set default [string map [list ' ""] [lindex $column [incr findIndex]]]
             } else {
                 set default ""
             }
             if {[info exists tableColumns($tableName,$columnName,type)]} {
                 if {$dbType eq "mysql"} {
                     if {$tableColumns($tableName,$columnName,type) eq "varchar(text)"} {
                         set tableColumns($tableName,$columnName,type) $columnType
                     }
                     if {$default eq "null" || $columnType eq "unique_id"} {
                         set default ""
                     } elseif {$tableColumns($tableName,$columnName,default) == 0 && $default eq ""} {
                         set default 0
                     }
                 }
 
                 if {[string first "char" $tableColumns($tableName,$columnName,type)]!=-1 && ([string first $tableColumns($tableName,$columnName,type) $columnType]!=-1 || [string first $columnType $tableColumns($tableName,$columnName,type)])} {  
                     set columnType $tableColumns($tableName,$columnName,type)
                 }
             }
 
 
 
             if {[set findIndex [lsearch -start 2 $column "not"]] == -1 && $columnType!="unique_id"} {
                 set null "yes"
             } else {
                 set null "no"
             }
             if {[info exists tableColumns($tableName,$columnName,type)] == 0} {
                 set alterSql "alter table $tableName add $columnName $columnType"
             } elseif {[string match -nocase $tableColumns($tableName,$columnName,type) $columnType] == 0 || ![string match -nocase $tableColumns($tableName,$columnName,default) $default] || $tableColumns($tableName,$columnName,null)!=$null} {
                 set alterSql "alter table $tableName [$formatCreate modify] $columnName $columnType"
             }
             if {$alterSql!=""} {
                 if {$default!=""} {
                     append alterSql " DEFAULT $default"
                 }
                 if {$null eq "no"} {
                     append alterSql " not null"
                 } else {
                     append alterSql " null"
                 }
                 lappend alterList $alterSql
             }
         }
         if {$upgradeCheck} {
             return $alterList
         }
         if {[catch {
             ::foreach alterSql $alterList {
                 $execCommand $alterSql
             }
         } msg]} {
             error "$msg. sql: $alterSql"
         }
     }
     public method getNumerator {table column {where ""}} {
         if {$where!=""} {
             set where "where $where"
         }
         lockTable $table                
         set stmt "select max($column),count($column) from $table $where"
             ::foreach {max count}  [lindex [sql $stmt] 0] {break}
             if {$count == 0} {
                 setAutoCommit 1
                 error "no record was found"
             }
             set updateStmt "update $table set $column=[incr max] $where"
             if {[catch {
                 sql $updateStmt
             } msg]} {
                 setAutoCommit 1
                 error $msg
             }
                 setAutoCommit 1
         return $max
     }
     public method getDatabaseVersion {} {
         return [$versionCommand]
     }
}

updated version to version 1.7