rivet/packages/dio/dio_Tdbc.tcl (148 lines of code) (raw):

# tdbc.tcl -- connector for tdbc, the Tcl database abstraction layer # # Copyright 2024 The Apache Software Foundation # # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. package require tdbc package require DIO 1.2 package provide dio_Tdbc 1.2 namespace eval DIO { ::itcl::class Tdbc { inherit Database private common connector_n 0 private variable connector private variable tdbc_connector private variable tdbc_arguments [list -encoding \ -isolation \ -readonly \ -timeout] constructor {interface_name args} {eval configure -interface $interface_name $args} { set connector "" # I should check this one: we only accept connector # interfaces that map into one of tdbc's supported drivers #puts "tdbc interface: $interface_name" set connector_name [::string tolower $interface_name] if {$connector_name == "oracle"} { set connector_name "odbc" } elseif {$connector_name == "postgresql"} { set connector_name "postgres" } set tdbc_connector "tdbc::${connector_name}" uplevel #0 package require ${tdbc_connector} } destructor { } private method check_connector {} { if {$connector == ""} { open } } public method open {} { set connector_cmd "${tdbc_connector}::connection create ${tdbc_connector}#$connector_n" if {$user != ""} { lappend connector_cmd -user $user } if {$db != ""} { lappend connector_cmd -db $db } if {$pass != ""} { lappend connector_cmd -password $pass } if {$port != ""} { lappend connector_cmd -port $port } if {$host != ""} { lappend connector_cmd -host $host } if {$clientargs != ""} { lappend connector_cmd {*}$clientargs } #puts "evaluating $connector_cmd" set connector [eval $connector_cmd] incr connector_n } public method close {} { if {$connector == ""} { return } $connector close set connector "" } protected method handle_client_arguments {cargs} { set clientargs {} lmap {k v} $cargs { if {[lsearch $k $tdbcarguments] >= 0} { lappend clientargs $k $v } } } public method exec {sql} { $this check_connector # tdbc doesn't like ';' at the end of a SQL statement if {[::string index $sql end] == ";"} {set sql [::string range $sql 0 end-1]} set is_select [regexp -nocase {^\(*\s*select\s+} $sql] set tdbc_statement [$connector prepare $sql] # errorinfo is a public variable of the # parent class Database. Not a good # object design practice if {[catch {set tdbc_result [$tdbc_statement execute]} errorinfo]} { set result_obj [$this result TDBC -error 1 -errorinfo [::list $errorinfo] -isselect false] } else { # we must store also the TDBC SQL statement as it owns # the TDBC results set represented by tdbc_result. Closing # a tdbc::statement closes also any active tdbc::resultset # owned by it set result_obj [$this result TDBC -resultid $tdbc_result \ -statement $tdbc_statement \ -isselect $is_select \ -fields [::list [$tdbc_result columns]]] } return $result_obj } } ::itcl::class TDBCResult { inherit Result public variable isselect false public variable statement public variable rowid public variable cached_rows public variable columns constructor {args} { eval configure $args set cached_rows {} set columns {} set rowid 0 set rownum 0 set statement "" } destructor {} public method destroy {} { if {$statement != ""} { $statement close } Result::destroy } public method current_row {} {return $rowid} public method cached_results {} {return $cached_rows} public method nextrow {} { if {[llength $cached_rows] == 0} { if {![$resultid nextrow -as lists row]} { return "" } } else { set cached_rows [lassign $cached_rows row] } incr rowid return $row } public method numrows {} { if {$isselect} { # this is not scaling well at all but tdbc is not telling # the number of columns for a select so must determine it # from the whole set of results if {[llength $cached_rows] == 0} { set cached_rows [$resultid allrows -as lists -columnsvariable columns] } return [expr [llength $cached_rows] + $rowid] } else { return [$resultid rowcount] } } } }