|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
package require TclOO |
|
|
|
namespace eval ::tdbc { |
|
namespace export connection statement resultset |
|
variable generalError [list TDBC GENERAL_ERROR HY000 {}] |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
proc tdbc::ParseConvenienceArgs {argv optsVar} { |
|
|
|
variable generalError |
|
upvar 1 $optsVar opts |
|
|
|
set opts [dict create -as dicts] |
|
set i 0 |
|
|
|
|
|
|
|
foreach {key value} $argv { |
|
if {[string index $key 0] eq {-}} { |
|
switch -regexp -- $key { |
|
-as? { |
|
if {$value ne {dicts} && $value ne {lists}} { |
|
set errorcode $generalError |
|
lappend errorcode badVarType $value |
|
return -code error \ |
|
-errorcode $errorcode \ |
|
"bad variable type \"$value\":\ |
|
must be lists or dicts" |
|
} |
|
dict set opts -as $value |
|
} |
|
-c(?:o(?:l(?:u(?:m(?:n(?:s(?:v(?:a(?:r(?:i(?:a(?:b(?:le?)?)?)?)?)?)?)?)?)?)?)?)?) { |
|
dict set opts -columnsvariable $value |
|
} |
|
-- { |
|
incr i |
|
break |
|
} |
|
default { |
|
set errorcode $generalError |
|
lappend errorcode badOption $key |
|
return -code error \ |
|
-errorcode $errorcode \ |
|
"bad option \"$key\":\ |
|
must be -as or -columnsvariable" |
|
} |
|
} |
|
} else { |
|
break |
|
} |
|
incr i 2 |
|
} |
|
|
|
return [lrange $argv[set argv {}] $i end] |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
oo::class create ::tdbc::connection { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
variable statementSeq primaryKeysStatement foreignKeysStatement |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
constructor {} { |
|
set statementSeq 0 |
|
namespace eval Stmt {} |
|
} |
|
|
|
|
|
|
|
|
|
method close {} { |
|
my destroy |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
method prepare {sqlcode} { |
|
return [my statementCreate Stmt::[incr statementSeq] [self] $sqlcode] |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
method statementCreate {name instance sqlcode} { |
|
my variable statementClass |
|
return [$statementClass create $name $instance $sqlcode] |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
method statements {} { |
|
info commands Stmt::* |
|
} |
|
|
|
|
|
|
|
|
|
method resultsets {} { |
|
set retval {} |
|
foreach statement [my statements] { |
|
foreach resultset [$statement resultsets] { |
|
lappend retval $resultset |
|
} |
|
} |
|
return $retval |
|
} |
|
|
|
|
|
|
|
|
|
method transaction {script} { |
|
my begintransaction |
|
set status [catch {uplevel 1 $script} result options] |
|
if {$status in {0 2 3 4}} { |
|
set status2 [catch {my commit} result2 options2] |
|
if {$status2 == 1} { |
|
set status 1 |
|
set result $result2 |
|
set options $options2 |
|
} |
|
} |
|
switch -exact -- $status { |
|
0 { |
|
|
|
} |
|
2 - 3 - 4 { |
|
set options [dict merge {-level 1} $options[set options {}]] |
|
dict incr options -level |
|
} |
|
default { |
|
my rollback |
|
} |
|
} |
|
return -options $options $result |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
method allrows args { |
|
|
|
variable ::tdbc::generalError |
|
|
|
|
|
|
|
set args [::tdbc::ParseConvenienceArgs $args[set args {}] opts] |
|
|
|
|
|
|
|
set cmd [list [self] prepare] |
|
if {[llength $args] == 1} { |
|
set sqlcode [lindex $args 0] |
|
} elseif {[llength $args] == 2} { |
|
lassign $args sqlcode dict |
|
} else { |
|
set errorcode $generalError |
|
lappend errorcode wrongNumArgs |
|
return -code error -errorcode $errorcode \ |
|
"wrong # args: should be [lrange [info level 0] 0 1]\ |
|
?-option value?... ?--? sqlcode ?dictionary?" |
|
} |
|
lappend cmd $sqlcode |
|
|
|
|
|
|
|
set stmt [uplevel 1 $cmd] |
|
|
|
|
|
|
|
set cmd [list $stmt allrows {*}$opts --] |
|
if {[info exists dict]} { |
|
lappend cmd $dict |
|
} |
|
set status [catch { |
|
uplevel 1 $cmd |
|
} result options] |
|
|
|
|
|
|
|
catch { |
|
$stmt close |
|
} |
|
|
|
return -options $options $result |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
method foreach args { |
|
|
|
variable ::tdbc::generalError |
|
|
|
|
|
|
|
set args [::tdbc::ParseConvenienceArgs $args[set args {}] opts] |
|
|
|
|
|
|
|
set cmd [list [self] prepare] |
|
if {[llength $args] == 3} { |
|
lassign $args varname sqlcode script |
|
} elseif {[llength $args] == 4} { |
|
lassign $args varname sqlcode dict script |
|
} else { |
|
set errorcode $generalError |
|
lappend errorcode wrongNumArgs |
|
return -code error -errorcode $errorcode \ |
|
"wrong # args: should be [lrange [info level 0] 0 1]\ |
|
?-option value?... ?--? varname sqlcode ?dictionary? script" |
|
} |
|
lappend cmd $sqlcode |
|
|
|
|
|
|
|
set stmt [uplevel 1 $cmd] |
|
|
|
|
|
|
|
set cmd [list $stmt foreach {*}$opts -- $varname] |
|
if {[info exists dict]} { |
|
lappend cmd $dict |
|
} |
|
lappend cmd $script |
|
set status [catch { |
|
uplevel 1 $cmd |
|
} result options] |
|
|
|
|
|
|
|
catch { |
|
$stmt close |
|
} |
|
|
|
|
|
|
|
if {$status == 2} { |
|
set options [dict merge {-level 1} $options[set options {}]] |
|
dict incr options -level |
|
} |
|
return -options $options $result |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
method BuildPrimaryKeysStatement {} { |
|
|
|
|
|
|
|
|
|
|
|
set catalogClause {} |
|
if {[lindex [set count [my allrows -as lists { |
|
SELECT COUNT(*) |
|
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS |
|
WHERE CONSTRAINT_CATALOG IS NOT NULL}]] 0 0] != 0} { |
|
set catalogClause \ |
|
{AND xtable.CONSTRAINT_CATALOG = xcolumn.CONSTRAINT_CATALOG} |
|
} |
|
set primaryKeysStatement [my prepare " |
|
SELECT xtable.TABLE_SCHEMA AS \"tableSchema\", |
|
xtable.TABLE_NAME AS \"tableName\", |
|
xtable.CONSTRAINT_CATALOG AS \"constraintCatalog\", |
|
xtable.CONSTRAINT_SCHEMA AS \"constraintSchema\", |
|
xtable.CONSTRAINT_NAME AS \"constraintName\", |
|
xcolumn.COLUMN_NAME AS \"columnName\", |
|
xcolumn.ORDINAL_POSITION AS \"ordinalPosition\" |
|
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS xtable |
|
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE xcolumn |
|
ON xtable.CONSTRAINT_SCHEMA = xcolumn.CONSTRAINT_SCHEMA |
|
AND xtable.TABLE_NAME = xcolumn.TABLE_NAME |
|
AND xtable.CONSTRAINT_NAME = xcolumn.CONSTRAINT_NAME |
|
$catalogClause |
|
WHERE xtable.TABLE_NAME = :tableName |
|
AND xtable.CONSTRAINT_TYPE = 'PRIMARY KEY' |
|
"] |
|
} |
|
|
|
|
|
|
|
|
|
|
|
method primarykeys {tableName} { |
|
if {![info exists primaryKeysStatement]} { |
|
my BuildPrimaryKeysStatement |
|
} |
|
tailcall $primaryKeysStatement allrows [list tableName $tableName] |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
method BuildForeignKeysStatement {} { |
|
|
|
|
|
|
|
|
|
|
|
set catalogClause1 {} |
|
set catalogClause2 {} |
|
if {[lindex [set count [my allrows -as lists { |
|
SELECT COUNT(*) |
|
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS |
|
WHERE CONSTRAINT_CATALOG IS NOT NULL}]] 0 0] != 0} { |
|
set catalogClause1 \ |
|
{AND fkc.CONSTRAINT_CATALOG = rc.CONSTRAINT_CATALOG} |
|
set catalogClause2 \ |
|
{AND pkc.CONSTRAINT_CATALOG = rc.CONSTRAINT_CATALOG} |
|
} |
|
|
|
foreach {exists1 clause1} { |
|
0 {} |
|
1 { AND pkc.TABLE_NAME = :primary} |
|
} { |
|
foreach {exists2 clause2} { |
|
0 {} |
|
1 { AND fkc.TABLE_NAME = :foreign} |
|
} { |
|
set stmt [my prepare " |
|
SELECT rc.CONSTRAINT_CATALOG AS \"foreignConstraintCatalog\", |
|
rc.CONSTRAINT_SCHEMA AS \"foreignConstraintSchema\", |
|
rc.CONSTRAINT_NAME AS \"foreignConstraintName\", |
|
rc.UNIQUE_CONSTRAINT_CATALOG |
|
AS \"primaryConstraintCatalog\", |
|
rc.UNIQUE_CONSTRAINT_SCHEMA AS \"primaryConstraintSchema\", |
|
rc.UNIQUE_CONSTRAINT_NAME AS \"primaryConstraintName\", |
|
rc.UPDATE_RULE AS \"updateAction\", |
|
rc.DELETE_RULE AS \"deleteAction\", |
|
pkc.TABLE_CATALOG AS \"primaryCatalog\", |
|
pkc.TABLE_SCHEMA AS \"primarySchema\", |
|
pkc.TABLE_NAME AS \"primaryTable\", |
|
pkc.COLUMN_NAME AS \"primaryColumn\", |
|
fkc.TABLE_CATALOG AS \"foreignCatalog\", |
|
fkc.TABLE_SCHEMA AS \"foreignSchema\", |
|
fkc.TABLE_NAME AS \"foreignTable\", |
|
fkc.COLUMN_NAME AS \"foreignColumn\", |
|
pkc.ORDINAL_POSITION AS \"ordinalPosition\" |
|
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc |
|
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE fkc |
|
ON fkc.CONSTRAINT_NAME = rc.CONSTRAINT_NAME |
|
AND fkc.CONSTRAINT_SCHEMA = rc.CONSTRAINT_SCHEMA |
|
$catalogClause1 |
|
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE pkc |
|
ON pkc.CONSTRAINT_NAME = rc.UNIQUE_CONSTRAINT_NAME |
|
AND pkc.CONSTRAINT_SCHEMA = rc.UNIQUE_CONSTRAINT_SCHEMA |
|
$catalogClause2 |
|
AND pkc.ORDINAL_POSITION = fkc.ORDINAL_POSITION |
|
WHERE 1=1 |
|
$clause1 |
|
$clause2 |
|
ORDER BY \"foreignConstraintCatalog\", \"foreignConstraintSchema\", \"foreignConstraintName\", \"ordinalPosition\" |
|
"] |
|
dict set foreignKeysStatement $exists1 $exists2 $stmt |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
method foreignkeys {args} { |
|
|
|
variable ::tdbc::generalError |
|
|
|
|
|
|
|
set argdict {} |
|
if {[llength $args] % 2 != 0} { |
|
set errorcode $generalError |
|
lappend errorcode wrongNumArgs |
|
return -code error -errorcode $errorcode \ |
|
"wrong # args: should be [lrange [info level 0] 0 1]\ |
|
?-option value?..." |
|
} |
|
foreach {key value} $args { |
|
if {$key ni {-primary -foreign}} { |
|
set errorcode $generalError |
|
lappend errorcode badOption |
|
return -code error -errorcode $errorcode \ |
|
"bad option \"$key\", must be -primary or -foreign" |
|
} |
|
set key [string range $key 1 end] |
|
if {[dict exists $argdict $key]} { |
|
set errorcode $generalError |
|
lappend errorcode dupOption |
|
return -code error -errorcode $errorcode \ |
|
"duplicate option \"$key\" supplied" |
|
} |
|
dict set argdict $key $value |
|
} |
|
|
|
|
|
|
|
|
|
|
|
if {![info exists foreignKeysStatement]} { |
|
my BuildForeignKeysStatement |
|
} |
|
set stmt [dict get $foreignKeysStatement \ |
|
[dict exists $argdict primary] \ |
|
[dict exists $argdict foreign]] |
|
tailcall $stmt allrows $argdict |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
oo::class create tdbc::statement { |
|
|
|
|
|
|
|
|
|
|
|
variable resultSetClass resultSetSeq |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
constructor {} { |
|
set resultSetSeq 0 |
|
namespace eval ResultSet {} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if {0 && [package vsatisfies [package provide Tcl] 8.6]} { |
|
method execute args { |
|
tailcall my resultSetCreate \ |
|
[namespace current]::ResultSet::[incr resultSetSeq] \ |
|
[self] {*}$args |
|
} |
|
} else { |
|
method execute args { |
|
return \ |
|
[uplevel 1 \ |
|
[list \ |
|
[self] resultSetCreate \ |
|
[namespace current]::ResultSet::[incr resultSetSeq] \ |
|
[self] {*}$args]] |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
method resultSetCreate {name instance args} { |
|
return [uplevel 1 [list $resultSetClass create \ |
|
$name $instance {*}$args]] |
|
} |
|
|
|
|
|
|
|
|
|
method resultsets {} { |
|
info commands ResultSet::* |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
method allrows args { |
|
|
|
variable ::tdbc::generalError |
|
|
|
|
|
|
|
set args [::tdbc::ParseConvenienceArgs $args[set args {}] opts] |
|
|
|
|
|
|
|
set cmd [list [self] execute] |
|
if {[llength $args] == 0} { |
|
|
|
} elseif {[llength $args] == 1} { |
|
lappend cmd [lindex $args 0] |
|
} else { |
|
set errorcode $generalError |
|
lappend errorcode wrongNumArgs |
|
return -code error -errorcode $errorcode \ |
|
"wrong # args: should be [lrange [info level 0] 0 1]\ |
|
?-option value?... ?--? ?dictionary?" |
|
} |
|
|
|
|
|
|
|
set resultSet [uplevel 1 $cmd] |
|
|
|
|
|
|
|
|
|
set cmd [list $resultSet allrows {*}$opts] |
|
set status [catch { |
|
uplevel 1 $cmd |
|
} result options] |
|
|
|
|
|
|
|
catch { |
|
rename $resultSet {} |
|
} |
|
|
|
|
|
|
|
if {$status == 2} { |
|
set options [dict merge {-level 1} $options[set options {}]] |
|
dict incr options -level |
|
} |
|
return -options $options $result |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
method foreach args { |
|
|
|
variable ::tdbc::generalError |
|
|
|
|
|
|
|
set args [::tdbc::ParseConvenienceArgs $args[set args {}] opts] |
|
|
|
|
|
|
|
set cmd [list [self] execute] |
|
if {[llength $args] == 2} { |
|
lassign $args varname script |
|
} elseif {[llength $args] == 3} { |
|
lassign $args varname dict script |
|
lappend cmd $dict |
|
} else { |
|
set errorcode $generalError |
|
lappend errorcode wrongNumArgs |
|
return -code error -errorcode $errorcode \ |
|
"wrong # args: should be [lrange [info level 0] 0 1]\ |
|
?-option value?... ?--? varName ?dictionary? script" |
|
} |
|
|
|
|
|
|
|
set resultSet [uplevel 1 $cmd] |
|
|
|
|
|
|
|
|
|
set cmd [list $resultSet foreach {*}$opts -- $varname $script] |
|
set status [catch { |
|
uplevel 1 $cmd |
|
} result options] |
|
|
|
|
|
|
|
catch { |
|
rename $resultSet {} |
|
} |
|
|
|
|
|
|
|
if {$status == 2} { |
|
set options [dict merge {-level 1} $options[set options {}]] |
|
dict incr options -level |
|
} |
|
return -options $options $result |
|
} |
|
|
|
|
|
|
|
method close {} { |
|
my destroy |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
oo::class create tdbc::resultset { |
|
|
|
constructor {} { } |
|
|
|
|
|
|
|
|
|
method allrows args { |
|
|
|
variable ::tdbc::generalError |
|
|
|
|
|
|
|
set args [::tdbc::ParseConvenienceArgs $args[set args {}] opts] |
|
if {[llength $args] != 0} { |
|
set errorcode $generalError |
|
lappend errorcode wrongNumArgs |
|
return -code error -errorcode $errorcode \ |
|
"wrong # args: should be [lrange [info level 0] 0 1]\ |
|
?-option value?... ?--? varName script" |
|
} |
|
|
|
|
|
|
|
if {[dict exists $opts -columnsvariable]} { |
|
upvar 1 [dict get $opts -columnsvariable] columns |
|
} |
|
|
|
|
|
|
|
if {[dict get $opts -as] eq {lists}} { |
|
set delegate nextlist |
|
} else { |
|
set delegate nextdict |
|
} |
|
set results [list] |
|
while {1} { |
|
set columns [my columns] |
|
while {[my $delegate row]} { |
|
lappend results $row |
|
} |
|
if {![my nextresults]} break |
|
} |
|
return $results |
|
|
|
} |
|
|
|
|
|
|
|
method foreach args { |
|
|
|
variable ::tdbc::generalError |
|
|
|
|
|
|
|
set args [::tdbc::ParseConvenienceArgs $args[set args {}] opts] |
|
|
|
|
|
|
|
if {[llength $args] != 2} { |
|
set errorcode $generalError |
|
lappend errorcode wrongNumArgs |
|
return -code error -errorcode $errorcode \ |
|
"wrong # args: should be [lrange [info level 0] 0 1]\ |
|
?-option value?... ?--? varName script" |
|
} |
|
|
|
|
|
|
|
if {[dict exists $opts -columnsvariable]} { |
|
upvar 1 [dict get $opts -columnsvariable] columns |
|
} |
|
|
|
|
|
while {1} { |
|
|
|
|
|
|
|
set columns [my columns] |
|
|
|
|
|
|
|
upvar 1 [lindex $args 0] row |
|
if {[dict get $opts -as] eq {lists}} { |
|
set delegate nextlist |
|
} else { |
|
set delegate nextdict |
|
} |
|
while {[my $delegate row]} { |
|
set status [catch { |
|
uplevel 1 [lindex $args 1] |
|
} result options] |
|
switch -exact -- $status { |
|
0 - 4 { # OK or CONTINUE |
|
} |
|
2 { # RETURN |
|
set options \ |
|
[dict merge {-level 1} $options[set options {}]] |
|
dict incr options -level |
|
return -options $options $result |
|
} |
|
3 { # BREAK |
|
set broken 1 |
|
break |
|
} |
|
default { # ERROR or unknown status |
|
return -options $options $result |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
if {[info exists broken] || ![my nextresults]} { |
|
break |
|
} |
|
} |
|
|
|
return |
|
} |
|
|
|
|
|
|
|
|
|
|
|
method nextrow {args} { |
|
|
|
variable ::tdbc::generalError |
|
|
|
set opts [dict create -as dicts] |
|
set i 0 |
|
|
|
|
|
|
|
foreach {key value} $args { |
|
if {[string index $key 0] eq {-}} { |
|
switch -regexp -- $key { |
|
-as? { |
|
dict set opts -as $value |
|
} |
|
-- { |
|
incr i |
|
break |
|
} |
|
default { |
|
set errorcode $generalError |
|
lappend errorcode badOption $key |
|
return -code error -errorcode $errorcode \ |
|
"bad option \"$key\":\ |
|
must be -as or -columnsvariable" |
|
} |
|
} |
|
} else { |
|
break |
|
} |
|
incr i 2 |
|
} |
|
|
|
set args [lrange $args $i end] |
|
if {[llength $args] != 1} { |
|
set errorcode $generalError |
|
lappend errorcode wrongNumArgs |
|
return -code error -errorcode $errorcode \ |
|
"wrong # args: should be [lrange [info level 0] 0 1]\ |
|
?-option value?... ?--? varName" |
|
} |
|
upvar 1 [lindex $args 0] row |
|
if {[dict get $opts -as] eq {lists}} { |
|
set delegate nextlist |
|
} else { |
|
set delegate nextdict |
|
} |
|
return [my $delegate row] |
|
} |
|
|
|
|
|
|
|
|
|
method nextresults {} { |
|
return 0 |
|
} |
|
|
|
|
|
|
|
|
|
method outputparams {} { |
|
return {} |
|
} |
|
|
|
|
|
|
|
method close {} { |
|
my destroy |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |