1# pgin.tcl - PostgreSQL Tcl Interface direct to protocol v2 backend 2# $Id: pgin.tcl,v 1.27 2003-06-30 23:05:42+00 lbayuk Exp $ 3# 4# Copyright 2003 by ljb (lbayuk@mindspring.com) 5# May be freely distributed with or without modification; must retain this 6# notice; provided with no warranties. 7# See the file COPYING for complete information on usage and redistribution 8# of this file, and for a disclaimer of all warranties. 9# 10# Also includes: 11# md5.tcl - Compute MD5 Checksum 12 13namespace eval pgtcl { 14 # Debug flag: 15 variable debug 0 16 17 # Internal version number: 18 variable version 1.5.0 19 20 # Counter for making uniquely named result structures: 21 variable rn 0 22 23 # Function OID cache, indexed by function name, self initializing: 24 variable fnoids 25 26 # Array of notification information, indexed on $conn,$relname: 27 variable notify 28 29 # Value to use for NULL results: 30 variable nulls {} 31 32 # Command to execute when a NOTICE message arrives. 33 # The message text argument will be appended to the command. 34 # Like libpq, we expect the message to already have a newline. 35 variable notice {puts -nonewline stderr} 36} 37 38# Internal procedure to set a default value from the environment: 39proc pgtcl::default {default args} { 40 global env 41 foreach a $args { 42 if {[info exists env($a)]} { 43 return $env($a) 44 } 45 } 46 return $default 47} 48 49# Internal routine to read a null-terminated string from the PostgreSQL backend. 50# String is stored in the 2nd argument if given, else it is returned. 51# I wish there was a more efficient way to do this! 52proc pgtcl::gets {sock {s_name ""}} { 53 if {$s_name != ""} { 54 upvar $s_name s 55 } 56 set s "" 57 while {[set c [read $sock 1]] != "\000"} { 58 append s $c 59 } 60 if {$s_name == ""} { 61 return $s 62 } 63} 64 65# Internal procedure to parse a connection info string. 66# This has to handle quoting and escaping. See the PostgreSQL Programmer's 67# Guide, Client Interfaces, Libpq, Database Connection Functions. 68# The definitive reference is the PostgreSQL source code in: 69# interface/libpq/fe-connect.c:conninfo_parse() 70# One quirk to note: backslash escapes work in quoted values, and also in 71# unquoted values, but you cannot use backslash-space in an unquoted value, 72# because the space ends the value regardless of the backslash. 73# 74# Stores the results in an array $result(paramname)=value. It will not 75# create a new index in the array; if paramname does not already exist, 76# it means a bad parameter was given (one not defined by pg_conndefaults). 77# Returns an error message on error, else an empty string if OK. 78proc pgtcl::parse_conninfo {conninfo result_name} { 79 upvar $result_name result 80 while {[regexp {^ *([^=]*)= *(.+)} $conninfo unused name conninfo]} { 81 set name [string trim $name] 82 if {[regexp {^'(.*)} $conninfo unused conninfo]} { 83 set value "" 84 set n [string length $conninfo] 85 for {set i 0} {$i < $n} {incr i} { 86 if {[set c [string index $conninfo $i]] == "\\"} { 87 set c [string index $conninfo [incr i]] 88 } elseif {$c == "'"} break 89 append value $c 90 } 91 if {$i >= $n} { 92 return "unterminated quoted string in connection info string" 93 } 94 set conninfo [string range $conninfo [incr i] end] 95 } else { 96 regexp {^([^ ]*)(.*)} $conninfo unused value conninfo 97 regsub -all {\\(.)} $value {\1} value 98 } 99 if {$pgtcl::debug} { puts "+parse_conninfo name=$name value=$value" } 100 if {![info exists result($name)]} { 101 return "invalid connection option \"$name\"" 102 } 103 set result($name) $value 104 } 105 if {[string trim $conninfo] != ""} { 106 return "syntax error in connection info string '...$conninfo'" 107 } 108 return "" 109} 110 111# Internal procedure to check for valid result handle. This returns 112# the fully qualified name of the result array. 113# Usage: upvar #0 [pgtcl::checkres $res] result 114proc pgtcl::checkres {res} { 115 if {![info exists pgtcl::result$res]} { 116 error "Invalid result handle\n$res is not a valid query result" 117 } 118 return "pgtcl::result$res" 119} 120 121# Return connection defaults as {optname label dispchar dispsize value}... 122proc pg_conndefaults {} { 123 set user [pgtcl::default user PGUSER USER LOGNAME USERNAME] 124 set result [list \ 125 [list user Database-User {} 20 $user] \ 126 [list password Database-Password * 20 [pgtcl::default {} PGPASSWORD]] \ 127 [list host Database-Host {} 40 [pgtcl::default localhost PGHOST]] \ 128 {hostaddr Database-Host-IPv4-Address {} 15 {}} \ 129 [list port Database-Port {} 6 [pgtcl::default 5432 PGPORT]] \ 130 [list dbname Database-Name {} 20 [pgtcl::default $user PGDATABASE]] \ 131 [list tty Backend-Debug-TTY D 40 [pgtcl::default {} PGTTY]] \ 132 [list options Backend-Debug-Options D 40 [pgtcl::default {} PGOPTIONS]] \ 133 ] 134 if {$pgtcl::debug} { puts "+pg_conndefaults: $result" } 135 return $result 136} 137 138# Connect to database. Only the new form, with -conninfo, is recognized. 139# We speak backend protocol v2, and only handle clear-text password and 140# MD5 authentication (messages R 3, and R 5). 141proc pg_connect {args} { 142 143 if {[llength $args] != 2 || [lindex $args 0] != "-conninfo"} { 144 error "Connection to database failed\nMust use pg_connect -conninfo form" 145 } 146 147 # Get connection defaults into an array opt(), then merge caller params: 148 foreach o [pg_conndefaults] { 149 set opt([lindex $o 0]) [lindex $o 4] 150 } 151 if {[set msg [pgtcl::parse_conninfo [lindex $args 1] opt]] != ""} { 152 error "Connection to database failed\n$msg" 153 } 154 155 # Hostaddr overrides host, per documentation, and we need host below. 156 if {$opt(hostaddr) != ""} { 157 set opt(host) $opt(hostaddr) 158 } 159 160 if {$pgtcl::debug} { 161 puts "+pg_connect to $opt(dbname)@$opt(host):$opt(port) as $opt(user)" 162 } 163 164 if {[catch {socket $opt(host) $opt(port)} sock]} { 165 error "Connection to database failed\n$sock" 166 } 167 fconfigure $sock -buffering none -translation binary 168 puts -nonewline $sock [binary format "I S S a64 a32 a64 x64 a64" \ 169 296 2 0 $opt(dbname) $opt(user) $opt(options) $opt(tty)] 170 171 set msg {} 172 while {[set c [read $sock 1]] != "Z"} { 173 switch $c { 174 E { 175 pgtcl::gets $sock msg 176 break 177 } 178 R { 179 set n -1 180 binary scan [read $sock 4] I n 181 if {$n == 3} { 182 set n [expr "5 + [string length $opt(password)]"] 183 puts -nonewline $sock [binary format "I a* x" $n $opt(password)] 184 } elseif {$n == 5} { 185 set salt [read $sock 4] 186 # This is from PostgreSQL source backend/libpq/crypt.c: 187 set md5_response \ 188 "md5[md5::digest [md5::digest $opt(password)$opt(user)]$salt]" 189 if {$pgtcl::debug} { puts "+pg_connect MD5 sending: $md5_response" } 190 puts -nonewline $sock [binary format "I a* x" 40 $md5_response] 191 192 } elseif {$n != 0} { 193 set msg "Unknown database authentication request($n)" 194 break 195 } 196 } 197 K { 198 binary scan [read $sock 8] II pid key 199 if {$pgtcl::debug} { puts "+server pid=$pid key=$key" } 200 } 201 default { 202 set msg "Unexpected reply from database: $c" 203 break 204 } 205 } 206 } 207 if {$msg != ""} { 208 close $sock 209 error "Connection to database failed\n$msg" 210 } 211 return $sock 212} 213 214# Disconnect from the database. Free all result structures and notify 215# functions for this connection. 216proc pg_disconnect {db} { 217 if {$pgtcl::debug} { puts "+Disconnecting $db from database" } 218 puts -nonewline $db X 219 catch {close $db} 220 foreach v [info vars pgtcl::result*] { 221 upvar #0 $v result 222 if {$result(conn) == $db} { 223 if {$pgtcl::debug} { puts "+Freeing left-over result structure $v" } 224 unset result 225 } 226 } 227 if {[array exists pgtcl::notify]} { 228 foreach v [array names pgtcl::notify $db,*] { 229 if {$pgtcl::debug} { puts "+Forgetting notify callback $v" } 230 unset pgtcl::notify($v) 231 } 232 } 233} 234 235# Internal procedure to read a tuple (row) from the backend, ASCII or Binary. 236proc pgtcl::gettuple {db result_name is_binary} { 237 upvar $result_name result 238 239 if {$result(nattr) == 0} { 240 unset result 241 error "Protocol error, data before descriptor" 242 } 243 if {$is_binary} { 244 set size_includes_size 0 245 } else { 246 set size_includes_size -4 247 } 248 set irow $result(ntuple) 249 # Read the Null Mask Bytes and make a string of [10]* in $nulls: 250 binary scan [read $db $result(nmb)] "B$result(nattr)" nulls 251 252 set nattr $result(nattr) 253 for {set icol 0} {$icol < $nattr} {incr icol} { 254 if {[string index $nulls $icol]} { 255 binary scan [read $db 4] I nbytes 256 incr nbytes $size_includes_size 257 set result($irow,$icol) [read $db $nbytes] 258 } else { 259 set result($irow,$icol) $pgtcl::nulls 260 } 261 } 262 incr result(ntuple) 263} 264 265# Handle a notification ('A') message. 266# The notifying backend pid is read but ignored. 267proc pgtcl::gotnotify {db} { 268 read $db 4 269 pgtcl::gets $db notify_rel 270 if {$pgtcl::debug} { puts "+pgtcl got notify: $notify_rel" } 271 if {[info exists pgtcl::notify($db,$notify_rel)]} { 272 after idle $pgtcl::notify($db,$notify_rel) 273 } 274} 275 276# Internal procedure to handle common backend utility message types: 277# C : Completion status E : Error 278# N : Notice message A : Notification 279# This can be given any message type. If it handles the message, 280# it returns 1. If it doesn't handle the message, it returns 0. 281# 282proc pgtcl::common_message {msgchar db result_name} { 283 upvar $result_name result 284 if {$msgchar == "C"} { 285 pgtcl::gets $db result(complete) 286 } elseif {$msgchar == "E"} { 287 set result(status) PGRES_FATAL_ERROR 288 pgtcl::gets $db result(error) 289 } elseif {$msgchar == "N"} { 290 eval $pgtcl::notice {[pgtcl::gets $db]} 291 } elseif {$msgchar == "A"} { 292 pgtcl::gotnotify $db 293 } else { 294 return 0 295 } 296 return 1 297} 298 299# Execute SQL and return a result handle. See the documentation for a 300# description of the innards of a result structure. This proc implements 301# most of the backend response protocol. The important reply codes are: 302# T : RowDescriptor describes the attributes (columns) of each data row. 303# Followed by descriptor for each attribute: name, type, size, modifier 304# Also compute result(nmb), number of bytes in the NULL-value maps. 305# D : AsciiRow has data for 1 tuple. 306# B : BinaryRow has data for 1 tuple, result of a Binary Cursor. 307# Z : Operation complete 308# H : Ready for Copy Out 309# G : Ready for Copy In 310# Plus the C E N A codes handled by pgtcl::common_message. 311# 312proc pg_exec {db query} { 313 if {$pgtcl::debug} { puts "+pg_exec $query" } 314 puts -nonewline $db [binary format "a* x" Q$query] 315 316 upvar #0 pgtcl::result[incr pgtcl::rn] result 317 set result(conn) $db 318 set result(nattr) 0 319 set result(attrs) {} 320 set result(types) {} 321 set result(sizes) {} 322 set result(modifs) {} 323 set result(ntuple) 0 324 set result(error) {} 325 set result(complete) {} 326 set result(status) PGRES_COMMAND_OK 327 328 while {[set c [read $db 1]] != "Z"} { 329 switch $c { 330 D { 331 pgtcl::gettuple $db result 0 332 } 333 B { 334 pgtcl::gettuple $db result 1 335 } 336 T { 337 if {$result(nattr) != 0} { 338 unset result 339 error "Protocol failure, multiple descriptors" 340 } 341 set result(status) PGRES_TUPLES_OK 342 binary scan [read $db 2] S nattr 343 set result(nattr) $nattr 344 for {set icol 0} {$icol < $nattr} {incr icol} { 345 lappend result(attrs) [pgtcl::gets $db] 346 binary scan [read $db 10] ISI type size modif 347 lappend result(types) $type 348 lappend result(sizes) $size 349 lappend result(modifs) $modif 350 } 351 set result(nmb) [expr {($nattr+7)/8}] 352 } 353 I { 354 pgtcl::gets $db 355 set result(status) PGRES_EMPTY_QUERY 356 } 357 P { 358 pgtcl::gets $db 359 } 360 H { 361 set result(status) PGRES_COPY_OUT 362 fconfigure $db -buffering line -translation lf 363 if {$pgtcl::debug} { puts "+pg_exec begin copy out" } 364 break 365 } 366 G { 367 set result(status) PGRES_COPY_IN 368 if {$pgtcl::debug} { puts "+pg_exec begin copy in" } 369 break 370 } 371 default { 372 if {![pgtcl::common_message $c $db result]} { 373 unset result 374 error "Unexpected reply from database: $c" 375 } 376 } 377 } 378 } 379 return $pgtcl::rn 380} 381 382# I/O routines to support COPY. These are not yet needed, because you can read 383# and write directly to the I/O channel, but will be needed with PostgreSQL 384# protocol v3. They are included here to help transition to a future version 385# of pgin.tcl. 386# These do not currently check that COPY is actually in progress. 387 388# Read line from COPY TO. Returns the line read if OK, else "" at the end. 389proc pg_copy_read {res} { 390 upvar #0 [pgtcl::checkres $res] result 391 if {[gets $result(conn) line] < 0} { 392 error "Unexpected end of data during COPY OUT" 393 } 394 if {$line == "\\."} { 395 return "" 396 } 397 incr result(ntuple) 398 return $line 399} 400 401# Write line for COPY FROM. Do not call with "\\." - just call pg_endcopy. 402proc pg_copy_write {res line} { 403 upvar #0 [pgtcl::checkres $res] result 404 puts $result(conn) $line 405 incr result(ntuple) 406} 407 408# End a Copy In/Out. This is needed because Tcl cannot do channel magic in 409# Tcl like it can from C code. 410# Call this after writing "\\." on Copy In, or after reading "\\." on Copy Out. 411# Or, call this after reading "" from pg_copy_read, or when done with 412# pg_copy_write. (This knows if pg_copy_write was used because ntuples will 413# be > 0, in which case the ending "\\." needs to be written.) 414# When it returns, the result structure (res) will be updated. 415proc pg_endcopy {res} { 416 upvar #0 [pgtcl::checkres $res] result 417 set db $result(conn) 418 if {$pgtcl::debug} { puts "+pg_endcopy end $result(status)" } 419 420 if {$result(status) == "PGRES_COPY_OUT"} { 421 fconfigure $db -buffering none -translation binary 422 } elseif {$result(status) != "PGRES_COPY_IN"} { 423 error "pg_endcopy called but connection is not doing a COPY" 424 } elseif {$result(ntuple) > 0} { 425 puts $db "\\." 426 } 427 428 # We're looking for C COPY and Z here, but other things can happen. 429 set result(status) PGRES_COMMAND_OK 430 while {[set c [read $db 1]] != "Z"} { 431 if {![pgtcl::common_message $c $db result]} { 432 error "Unexpected reply from database: $c" 433 } 434 } 435} 436 437# Extract data from a pg_exec result structure. 438# -cmdTuples, -list, and -llist are extensions to the baseline libpgtcl which 439# have appeared or will appear in beta or future versions. 440 441proc pg_result {res option args} { 442 upvar #0 [pgtcl::checkres $res] result 443 set argc [llength $args] 444 set ntuple $result(ntuple) 445 set nattr $result(nattr) 446 switch -- $option { 447 -status { return $result(status) } 448 -error { return $result(error) } 449 -conn { return $result(conn) } 450 -oid { 451 if {[regexp {^INSERT +([0-9]*)} $result(complete) unused oid]} { 452 return $oid 453 } 454 return 0 455 } 456 -cmdTuples { 457 if {[regexp {^INSERT +[0-9]* +([0-9]*)} $result(complete) x num] \ 458 || [regexp {^(UPDATE|DELETE) +([0-9]*)} $result(complete) x y num]} { 459 return $num 460 } 461 return "" 462 } 463 -numTuples { return $ntuple } 464 -numAttrs { return $nattr } 465 -assign { 466 if {$argc != 1} { 467 error "-assign option must be followed by a variable name" 468 } 469 upvar $args a 470 set icol 0 471 foreach attr $result(attrs) { 472 for {set irow 0} {$irow < $ntuple} {incr irow} { 473 set a($irow,$attr) $result($irow,$icol) 474 } 475 incr icol 476 } 477 } 478 -assignbyidx { 479 if {$argc != 1 && $argc != 2} { 480 error "-assignbyidxoption requires an array name and optionally an\ 481 append string" 482 } 483 upvar [lindex $args 0] a 484 if {$argc == 2} { 485 set suffix [lindex $args 1] 486 } else { 487 set suffix {} 488 } 489 set attr_first [lindex $result(attrs) 0] 490 set attr_rest [lrange $result(attrs) 1 end] 491 for {set irow 0} {$irow < $ntuple} {incr irow} { 492 set val_first $result($irow,0) 493 set icol 1 494 foreach attr $attr_rest { 495 set a($val_first,$attr$suffix) $result($irow,$icol) 496 incr icol 497 } 498 } 499 } 500 -getTuple { 501 if {$argc != 1} { 502 error "-getTuple option must be followed by a tuple number" 503 } 504 set irow $args 505 if {$irow < 0 || $irow >= $ntuple} { 506 error "argument to getTuple cannot exceed number of tuples - 1" 507 } 508 set list {} 509 for {set icol 0} {$icol < $nattr} {incr icol} { 510 lappend list $result($irow,$icol) 511 } 512 return $list 513 } 514 -tupleArray { 515 if {$argc != 2} { 516 error "-tupleArray option must be followed by a tuple number and\ 517 array name" 518 } 519 set irow [lindex $args 0] 520 if {$irow < 0 || $irow >= $ntuple} { 521 error "argument to tupleArray cannot exceed number of tuples - 1" 522 } 523 upvar [lindex $args 1] a 524 set icol 0 525 foreach attr $result(attrs) { 526 set a($attr) $result($irow,$icol) 527 incr icol 528 } 529 } 530 -list { 531 set list {} 532 for {set irow 0} {$irow < $ntuple} {incr irow} { 533 for {set icol 0} {$icol < $nattr} {incr icol} { 534 lappend list $result($irow,$icol) 535 } 536 } 537 return $list 538 } 539 -llist { 540 set list {} 541 for {set irow 0} {$irow < $ntuple} {incr irow} { 542 set sublist {} 543 for {set icol 0} {$icol < $nattr} {incr icol} { 544 lappend sublist $result($irow,$icol) 545 } 546 lappend list $sublist 547 } 548 return $list 549 } 550 -attributes { 551 return $result(attrs) 552 } 553 -lAttributes { 554 set list {} 555 foreach attr $result(attrs) type $result(types) size $result(sizes) { 556 lappend list [list $attr $type $size] 557 } 558 return $list 559 } 560 -clear { 561 unset result 562 } 563 default { error "Invalid option to pg_result: $option" } 564 } 565} 566 567# Run a select query and iterate over the results. Uses pg_exec to run the 568# query and build the result structure, but we cheat and directly use the 569# result array rather than calling pg_result. 570# Each returned tuple is stored into the caller's array, then the caller's 571# proc is called. 572# If the caller's proc does "break", "return", or gets an error, get out 573# of the processing loop. Tcl codes: 0=OK 1=error 2=return 3=break 4=continue 574proc pg_select {db query var_name proc} { 575 upvar $var_name var 576 global errorCode errorInfo 577 578 set res [pg_exec $db $query] 579 upvar #0 pgtcl::result$res result 580 if {$result(status) != "PGRES_TUPLES_OK"} { 581 set msg $result(error) 582 unset result 583 error $msg 584 } 585 set code 0 586 set var(.headers) $result(attrs) 587 set var(.numcols) $result(nattr) 588 set ntuple $result(ntuple) 589 for {set irow 0} {$irow < $ntuple} {incr irow} { 590 set var(.tupno) $irow 591 set icol 0 592 foreach attr $result(attrs) { 593 set var($attr) $result($irow,$icol) 594 incr icol 595 } 596 set code [catch {uplevel 1 $proc} s] 597 if {$code != 0 && $code != 4} break 598 } 599 unset result var 600 if {$code == 1} { 601 return -code error -errorinfo $errorInfo -errorcode $errorCode $s 602 } elseif {$code == 2 || $code > 4} { 603 return -code $code $s 604 } 605} 606 607# Register a listener for backend notification, or cancel a listener. 608proc pg_listen {db name {proc ""}} { 609 if {$proc != ""} { 610 set pgtcl::notify($db,$name) $proc 611 set r [pg_exec $db "listen $name"] 612 pg_result $r -clear 613 } elseif {[info exists pgtcl::notify($db,$name)]} { 614 unset pgtcl::notify($db,$name) 615 set r [pg_exec $db "unlisten $name"] 616 pg_result $r -clear 617 } 618} 619 620# pg_execute: Execute a query, optionally iterating over the results. 621# 622# Returns the number of tuples selected or affected by the query. 623# Usage: pg_execute ?options? connection query ?proc? 624# Options: -array ArrayVar 625# -oid OidVar 626# If -array is not given with a SELECT, the data is put in variables 627# named by the fields. This is generally a bad idea and could be dangerous. 628# 629# If there is no proc body and the query return 1 or more rows, the first 630# row is stored in the array or variables and we return (as does libpgtcl). 631# 632# Notes: Handles proc return codes of: 633# 0(OK) 1(error) 2(return) 3(break) 4(continue) 634# Uses pg_exec and pg_result, but also makes direct access to the 635# structures used by them. 636 637proc pg_execute {args} { 638 global errorCode errorInfo 639 640 set usage "pg_execute ?-array arrayname?\ 641 ?-oid varname? connection queryString ?loop_body?" 642 643 # Set defaults and parse command arguments: 644 set use_array 0 645 set set_oid 0 646 set do_proc 0 647 set last_option_arg {} 648 set n_nonswitch_args 0 649 set conn {} 650 set query {} 651 set proc {} 652 foreach arg $args { 653 if {$last_option_arg != ""} { 654 if {$last_option_arg == "-array"} { 655 set use_array 1 656 upvar $arg data 657 } elseif {$last_option_arg == "-oid"} { 658 set set_oid 1 659 upvar $arg oid 660 } else { 661 error "Unknown option $last_option_arg\n$usage" 662 } 663 set last_option_arg {} 664 } elseif {[regexp ^- $arg]} { 665 set last_option_arg $arg 666 } else { 667 if {[incr n_nonswitch_args] == 1} { 668 set conn $arg 669 } elseif {$n_nonswitch_args == 2} { 670 set query $arg 671 } elseif {$n_nonswitch_args == 3} { 672 set do_proc 1 673 set proc $arg 674 } else { 675 error "Wrong # of arguments\n$usage" 676 } 677 } 678 } 679 if {$last_option_arg != "" || $n_nonswitch_args < 2} { 680 error "Bad arguments\n$usage" 681 } 682 683 set res [pg_exec $conn $query] 684 upvar #0 pgtcl::result$res result 685 686 # For non-SELECT query, just process oid and return value. 687 # Let pg_result do the decoding. 688 if {[regexp {^PGRES_(COMMAND_OK|COPY|EMPTY_QUERY)} $result(status)]} { 689 if {$set_oid} { 690 set oid [pg_result $res -oid] 691 } 692 set ntuple [pg_result $res -cmdTuples] 693 pg_result $res -clear 694 return $ntuple 695 } 696 697 if {$result(status) != "PGRES_TUPLES_OK"} { 698 set status [list $result(status) $result(error)] 699 pg_result $res -clear 700 error $status 701 } 702 703 # Handle a SELECT query. This is like pg_select, except the proc is optional, 704 # and the fields can go in an array or variables. 705 # With no proc, store the first row only. 706 set code 0 707 if {!$use_array} { 708 foreach attr $result(attrs) { 709 upvar $attr data_$attr 710 } 711 } 712 set ntuple $result(ntuple) 713 for {set irow 0} {$irow < $ntuple} {incr irow} { 714 set icol 0 715 if {$use_array} { 716 foreach attr $result(attrs) { 717 set data($attr) $result($irow,$icol) 718 incr icol 719 } 720 } else { 721 foreach attr $result(attrs) { 722 set data_$attr $result($irow,$icol) 723 incr icol 724 } 725 } 726 if {!$do_proc} break 727 set code [catch {uplevel 1 $proc} s] 728 if {$code != 0 && $code != 4} break 729 } 730 pg_result $res -clear 731 if {$code == 1} { 732 return -code error -errorInfo $errorInfo -errorCode $s 733 } elseif {$code == 2 || $code > 4} { 734 return -code $code $s 735 } 736 return $ntuple 737} 738 739# pg_configure: Configure options for PostgreSQL connections 740# This is an extension and not available in libpgtcl. 741# Usage: pg_configure connection option ?value? 742# connection Which connection the option applies to. 743# This is currently ignored, as all options are global. 744# option One of the following options. 745# nulls Set the string to be returned for NULL values 746# Default is "" 747# notice A command to execute when a NOTICE message comes in. 748# Default is a procedure which prints to stderr. 749# value If supplied, the new value of the option. 750# If not supplied, return the current value. 751# Returns the previous value of the option. 752 753proc pg_configure {db option args} { 754 if {[set nargs [llength $args]] == 0} { 755 set modify 0 756 } elseif {$nargs == 1} { 757 set modify 1 758 set newvalue [lindex $args 0] 759 } else { 760 error "Wrong # args: should be \"pg_configure connection option ?value?\"" 761 } 762 763 set options {nulls notice debug} 764 if {[lsearch -exact $options $option] < 0} { 765 error "Bad option \"$option\": must be one of [join $options {, }]" 766 } 767 eval set return_value \$pgtcl::$option 768 if {$modify} { 769 eval set pgtcl::$option {$newvalue} 770 } 771 return $return_value 772} 773 774# pg_escape_string: Escape a string for use as a quoted SQL string 775# Returns the escaped string. This was added to PostgreSQL after 7.3.2 776# and to libpgtcl after 1.4b3. 777# Note: string map requires Tcl >= 8.1 but is faster than regsub here. 778proc pg_escape_string {s} { 779 return [string map {' '' \\ \\\\} $s] 780} 781 782# ===== Large Object Interface ==== 783 784# Internal procedure to lookup, cache, and return a PostgreSQL function OID. 785# This assumes all connections have the same function OIDs, which might not be 786# true if you connect to servers running different versions of PostgreSQL. 787# Throws an error if the OID is not found by PostgreSQL. 788# To call overloaded functions, argument types must be specified in parentheses 789# after the function name, in the the exact same format as psql "\df". 790# This is a list of types separated by a comma and one space. 791# For example: fname="like(text, text)". 792# The return type cannot be specified. I don't think there are any functions 793# distinguished only by return type. 794proc pgtcl::getfnoid {db fname} { 795 variable fnoids 796 797 if {![info exists fnoids($fname)]} { 798 799 # Separate the function name from the (arg type list): 800 if {[regexp {^([^(]*)\(([^)]*)\)$} $fname unused fcn arglist]} { 801 set amatch " and oidvectortypes(proargtypes)='$arglist'" 802 } else { 803 set fcn $fname 804 set amatch "" 805 } 806 pg_select $db "select oid from pg_proc where proname='$fcn' $amatch" d { 807 set fnoids($fname) $d(oid) 808 } 809 if {![info exists fnoids($fname)]} { 810 error "Unable to get OID of database function $fname" 811 } 812 } 813 return $fnoids($fname) 814} 815 816# Internal procedure to implement PostgreSQL "fast-path" function calls. 817# $fn_oid is the OID of the PostgreSQL function. See pgtcl::getfnoid. 818# $result_name is the name of the variable to store the backend function 819# result into. 820# $arginfo is a list of argument descriptors, each is I or S or a number. 821# I means the argument is an integer32. 822# S means the argument is a string, and its actual length is used. 823# A number means send exactly that many bytes (null-pad if needed) from 824# the argument. 825# $arglist is a list of arguments to the PostgreSQL function. (This 826# is actually a pass-through argument 'args' from the wrappers.) 827# Throws Tcl error on error, otherwise returns size of the result 828# stored into the $result_name variable. 829 830proc pgtcl::callfn {db fn_oid result_name arginfo arglist} { 831 upvar $result_name result 832 833 set nargs [llength $arginfo] 834 if {$pgtcl::debug} { 835 puts "+callfn oid=$fn_oid nargs=$nargs info=$arginfo args=$arglist" 836 } 837 838 # Function call: F " " oid argcount {arglen arg}... 839 set out [binary format a2xII {F } $fn_oid $nargs] 840 foreach k $arginfo arg $arglist { 841 if {$k == "I"} { 842 append out [binary format II 4 $arg] 843 } elseif {$k == "S"} { 844 append out [binary format I [string length $arg]] $arg 845 } else { 846 append out [binary format Ia$k $k $arg] 847 } 848 } 849 puts -nonewline $db $out 850 851 set result {} 852 set result_size 0 853 # Fake up a partial result structure for pgtcl::common_message : 854 set res(error) "" 855 856 # Function response: VG...0 (OK, data); V0 (OK, null) or E or ... 857 # Also handles common messages (notify, notice). 858 while {[set c [read $db 1]] != "Z"} { 859 if {$c == "V"} { 860 set c2 [read $db 1] 861 if {$c2 == "G"} { 862 binary scan [read $db 4] I result_size 863 set result [read $db $result_size] 864 set c2 [read $db 1] 865 } 866 if {$c2 != "0"} { 867 error "Unexpected reply from database: V$c2" 868 } 869 } elseif {![pgtcl::common_message $c $db res]} { 870 error "Unexpected reply from database: $c" 871 } 872 } 873 if {$res(error) != ""} { 874 error $res(error) 875 } 876 return $result_size 877} 878 879# Public interface to pgtcl::callfn. 880proc pg_callfn {db fname result_name arginfo args} { 881 upvar $result_name result 882 return [pgtcl::callfn $db [pgtcl::getfnoid $db $fname] result $arginfo $args] 883} 884 885# Public, simplified interface to pgtcl::callfn when an int32 return value is 886# expected. Returns the backend function return value. 887proc pg_callfn_int {db fname arginfo args} { 888 set n [pgtcl::callfn $db [pgtcl::getfnoid $db $fname] result $arginfo $args] 889 if {$n != 4} { 890 error "Unexpected response size ($result_size) to pg function call $fname" 891 } 892 binary scan $result I val 893 return $val 894} 895 896# Convert a LO mode string into the value of the constants used by libpq. 897# Note: libpgtcl uses a mode like INV_READ|INV_WRITE for lo_creat, but 898# r, w, or rw for lo_open (which it translates to INV_READ|INV_WRITE). 899# This seems like a mistake. The code here accepts either form for either. 900proc pgtcl::lomode {mode} { 901 set imode 0 902 if {[string match -nocase *INV_* $mode]} { 903 if {[string match -nocase *INV_READ* $mode]} { 904 set imode 0x40000 905 } 906 if {[string match -nocase *INV_WRITE* $mode]} { 907 set imode [expr {$imode + 0x20000}] 908 } 909 } else { 910 if {[string match -nocase *r* $mode]} { 911 set imode 0x40000 912 } 913 if {[string match -nocase *w* $mode]} { 914 set imode [expr {$imode + 0x20000}] 915 } 916 } 917 if {$imode == 0} { 918 error "pgtcl: Invalid large object mode $mode" 919 } 920 return $imode 921} 922 923# Create large object and return OID. 924# See note regarding mode above at pgtcl::lomode. 925proc pg_lo_creat {db mode} { 926 return [pg_callfn_int $db lo_creat I [pgtcl::lomode $mode]] 927} 928 929# Open large object and return large object file descriptor. 930# See note regarding mode above at pgtcl::lomode. 931proc pg_lo_open {db loid mode} { 932 return [pg_callfn_int $db lo_open "I I" $loid [pgtcl::lomode $mode]] 933} 934 935# Close large object file descriptor. 936proc pg_lo_close {db lofd} { 937 return [pg_callfn_int $db lo_close I $lofd] 938} 939 940# Delete large object: 941proc pg_lo_unlink {db loid} { 942 return [pg_callfn_int $db lo_unlink I $loid] 943} 944 945# Read from large object. 946proc pg_lo_read {db lofd buf_name maxlen} { 947 upvar $buf_name buf 948 return [pg_callfn $db loread buf "I I" $lofd $maxlen] 949} 950 951# Write to large object. At most $len bytes are written. 952proc pg_lo_write {db lofd buf len} { 953 if {[set buflen [string length $buf]] < $len} { 954 set len $buflen 955 } 956 return [pg_callfn_int $db lowrite "I $len" $lofd $buf] 957} 958 959# Seek to offset inside large object: 960proc pg_lo_lseek {db lofd offset whence} { 961 switch $whence { 962 SEEK_SET { set iwhence 0 } 963 SEEK_CUR { set iwhence 1 } 964 SEEK_END { set iwhence 2 } 965 default { error "Invalid whence argument ($whence) in pg_lo_lseek" } 966 } 967 return [pg_callfn_int $db lo_lseek "I I I" $lofd $offset $iwhence] 968} 969 970# Return location of file offset in large object: 971proc pg_lo_tell {db lofd} { 972 return [pg_callfn_int $db lo_tell I $lofd] 973} 974 975# Import large object. Wrapper for lo_creat, lo_open, lo_write. 976# Returns Large Object OID, which should be stored in a table somewhere. 977proc pg_lo_import {db filename} { 978 set f [open $filename] 979 fconfigure $f -translation binary 980 set loid [pg_lo_creat $db INV_READ|INV_WRITE] 981 set lofd [pg_lo_open $db $loid w] 982 while {1} { 983 set buf [read $f 32768] 984 if {[set len [string length $buf]] == 0} break 985 if {[pg_lo_write $db $lofd $buf $len] != $len} { 986 error "pg_lo_import failed to write $len bytes" 987 } 988 } 989 pg_lo_close $db $lofd 990 close $f 991 return $loid 992} 993 994# Export large object. Wrapper for lo_open, lo_read. 995proc pg_lo_export {db loid filename} { 996 set f [open $filename w] 997 fconfigure $f -translation binary 998 set lofd [pg_lo_open $db $loid r] 999 while {[set len [pg_lo_read $db $lofd buf 32768]] > 0} { 1000 puts -nonewline $f $buf 1001 } 1002 pg_lo_close $db $lofd 1003 close $f 1004} 1005 1006# ===== MD5 Checksum ==== 1007 1008# Coded in Tcl by ljb <lbayuk@mindspring.com>, using these sources: 1009# RFC1321 1010# PostgreSQL: src/backend/libpq/md5.c 1011# If you want a better/faster MD5 implementation, see tcllib. 1012 1013namespace eval md5 { } 1014 1015# Round 1 helper, e.g.: 1016# a = b + ROT_LEFT((a + F(b, c, d) + X[0] + 0xd76aa478), 7) 1017# p1 p2 p1 p3 p4 p5 p6 p7 1018# Where F(x,y,z) = (x & y) | (~x & z) 1019# 1020proc md5::round1 {p1 p2 p3 p4 p5 p6 p7} { 1021 set r [expr {$p2 + ($p1 & $p3 | ~$p1 & $p4) + $p5 + $p6}] 1022 return [expr {$p1 + ($r << $p7 | (($r >> (32 - $p7)) & ((1 << $p7) - 1)))}] 1023} 1024 1025# Round 2 helper, e.g.: 1026# a = b + ROT_LEFT((a + G(b, c, d) + X[1] + 0xf61e2562), 5) 1027# p1 p2 p1 p3 p4 p5 p6 p7 1028# Where G(x,y,z) = (x & z) | (y & ~z) 1029# 1030proc md5::round2 {p1 p2 p3 p4 p5 p6 p7} { 1031 set r [expr {$p2 + ($p1 & $p4 | $p3 & ~$p4) + $p5 + $p6}] 1032 return [expr {$p1 + ($r << $p7 | (($r >> (32 - $p7)) & ((1 << $p7) - 1)))}] 1033} 1034 1035# Round 3 helper, e.g.: 1036# a = b + ROT_LEFT((a + H(b, c, d) + X[5] + 0xfffa3942), 4) 1037# p1 p2 p1 p3 p4 p5 p6 p7 1038# Where H(x, y, z) = x ^ y ^ z 1039# 1040proc md5::round3 {p1 p2 p3 p4 p5 p6 p7} { 1041 set r [expr {$p2 + ($p1 ^ $p3 ^ $p4) + $p5 + $p6}] 1042 return [expr {$p1 + ($r << $p7 | (($r >> (32 - $p7)) & ((1 << $p7) - 1)))}] 1043} 1044 1045# Round 4 helper, e.g.: 1046# a = b + ROT_LEFT((a + I(b, c, d) + X[0] + 0xf4292244), 6) 1047# p1 p2 p1 p3 p4 p5 p6 p7 1048# Where I(x, y, z) = y ^ (x | ~z) 1049# 1050proc md5::round4 {p1 p2 p3 p4 p5 p6 p7} { 1051 set r [expr {$p2 + ($p3 ^ ($p1 | ~$p4)) + $p5 + $p6}] 1052 return [expr {$p1 + ($r << $p7 | (($r >> (32 - $p7)) & ((1 << $p7) - 1)))}] 1053} 1054 1055# Do one set of rounds. Updates $state(0:3) with results from $x(0:16). 1056proc md5::round {x_name state_name} { 1057 upvar $x_name x $state_name state 1058 set a $state(0) 1059 set b $state(1) 1060 set c $state(2) 1061 set d $state(3) 1062 1063 # Round 1, steps 1-16 1064 set a [round1 $b $a $c $d $x(0) 0xd76aa478 7] 1065 set d [round1 $a $d $b $c $x(1) 0xe8c7b756 12] 1066 set c [round1 $d $c $a $b $x(2) 0x242070db 17] 1067 set b [round1 $c $b $d $a $x(3) 0xc1bdceee 22] 1068 set a [round1 $b $a $c $d $x(4) 0xf57c0faf 7] 1069 set d [round1 $a $d $b $c $x(5) 0x4787c62a 12] 1070 set c [round1 $d $c $a $b $x(6) 0xa8304613 17] 1071 set b [round1 $c $b $d $a $x(7) 0xfd469501 22] 1072 set a [round1 $b $a $c $d $x(8) 0x698098d8 7] 1073 set d [round1 $a $d $b $c $x(9) 0x8b44f7af 12] 1074 set c [round1 $d $c $a $b $x(10) 0xffff5bb1 17] 1075 set b [round1 $c $b $d $a $x(11) 0x895cd7be 22] 1076 set a [round1 $b $a $c $d $x(12) 0x6b901122 7] 1077 set d [round1 $a $d $b $c $x(13) 0xfd987193 12] 1078 set c [round1 $d $c $a $b $x(14) 0xa679438e 17] 1079 set b [round1 $c $b $d $a $x(15) 0x49b40821 22] 1080 1081 # Round 2, steps 17-32 1082 set a [round2 $b $a $c $d $x(1) 0xf61e2562 5] 1083 set d [round2 $a $d $b $c $x(6) 0xc040b340 9] 1084 set c [round2 $d $c $a $b $x(11) 0x265e5a51 14] 1085 set b [round2 $c $b $d $a $x(0) 0xe9b6c7aa 20] 1086 set a [round2 $b $a $c $d $x(5) 0xd62f105d 5] 1087 set d [round2 $a $d $b $c $x(10) 0x02441453 9] 1088 set c [round2 $d $c $a $b $x(15) 0xd8a1e681 14] 1089 set b [round2 $c $b $d $a $x(4) 0xe7d3fbc8 20] 1090 set a [round2 $b $a $c $d $x(9) 0x21e1cde6 5] 1091 set d [round2 $a $d $b $c $x(14) 0xc33707d6 9] 1092 set c [round2 $d $c $a $b $x(3) 0xf4d50d87 14] 1093 set b [round2 $c $b $d $a $x(8) 0x455a14ed 20] 1094 set a [round2 $b $a $c $d $x(13) 0xa9e3e905 5] 1095 set d [round2 $a $d $b $c $x(2) 0xfcefa3f8 9] 1096 set c [round2 $d $c $a $b $x(7) 0x676f02d9 14] 1097 set b [round2 $c $b $d $a $x(12) 0x8d2a4c8a 20] 1098 1099 # Round 3, steps 33-48 1100 set a [round3 $b $a $c $d $x(5) 0xfffa3942 4] 1101 set d [round3 $a $d $b $c $x(8) 0x8771f681 11] 1102 set c [round3 $d $c $a $b $x(11) 0x6d9d6122 16] 1103 set b [round3 $c $b $d $a $x(14) 0xfde5380c 23] 1104 set a [round3 $b $a $c $d $x(1) 0xa4beea44 4] 1105 set d [round3 $a $d $b $c $x(4) 0x4bdecfa9 11] 1106 set c [round3 $d $c $a $b $x(7) 0xf6bb4b60 16] 1107 set b [round3 $c $b $d $a $x(10) 0xbebfbc70 23] 1108 set a [round3 $b $a $c $d $x(13) 0x289b7ec6 4] 1109 set d [round3 $a $d $b $c $x(0) 0xeaa127fa 11] 1110 set c [round3 $d $c $a $b $x(3) 0xd4ef3085 16] 1111 set b [round3 $c $b $d $a $x(6) 0x04881d05 23] 1112 set a [round3 $b $a $c $d $x(9) 0xd9d4d039 4] 1113 set d [round3 $a $d $b $c $x(12) 0xe6db99e5 11] 1114 set c [round3 $d $c $a $b $x(15) 0x1fa27cf8 16] 1115 set b [round3 $c $b $d $a $x(2) 0xc4ac5665 23] 1116 1117 # Round 4, steps 49-64 1118 set a [round4 $b $a $c $d $x(0) 0xf4292244 6] 1119 set d [round4 $a $d $b $c $x(7) 0x432aff97 10] 1120 set c [round4 $d $c $a $b $x(14) 0xab9423a7 15] 1121 set b [round4 $c $b $d $a $x(5) 0xfc93a039 21] 1122 set a [round4 $b $a $c $d $x(12) 0x655b59c3 6] 1123 set d [round4 $a $d $b $c $x(3) 0x8f0ccc92 10] 1124 set c [round4 $d $c $a $b $x(10) 0xffeff47d 15] 1125 set b [round4 $c $b $d $a $x(1) 0x85845dd1 21] 1126 set a [round4 $b $a $c $d $x(8) 0x6fa87e4f 6] 1127 set d [round4 $a $d $b $c $x(15) 0xfe2ce6e0 10] 1128 set c [round4 $d $c $a $b $x(6) 0xa3014314 15] 1129 set b [round4 $c $b $d $a $x(13) 0x4e0811a1 21] 1130 set a [round4 $b $a $c $d $x(4) 0xf7537e82 6] 1131 set d [round4 $a $d $b $c $x(11) 0xbd3af235 10] 1132 set c [round4 $d $c $a $b $x(2) 0x2ad7d2bb 15] 1133 set b [round4 $c $b $d $a $x(9) 0xeb86d391 21] 1134 1135 incr state(0) $a 1136 incr state(1) $b 1137 incr state(2) $c 1138 incr state(3) $d 1139} 1140 1141# Pad out buffer per MD5 spec: 1142proc md5::pad {buf_name} { 1143 upvar $buf_name buf 1144 1145 # Length in bytes: 1146 set len [string length $buf] 1147 # Length in bits as 2 32 bit words: 1148 set len64hi [expr {$len >> 29 & 7}] 1149 set len64lo [expr {$len << 3}] 1150 1151 # Append 1 special byte, then append 0 or more 0 bytes until 1152 # (length in bytes % 64) == 56 1153 set pad [expr {64 - ($len + 8) % 64}] 1154 append buf [binary format a$pad "\x80"] 1155 1156 # Append the length in bits as a 64 bit value, low bytes first. 1157 append buf [binary format i1i1 $len64lo $len64hi] 1158 1159} 1160 1161# Calculate MD5 Digest over a string, return as 32 hex digit string. 1162proc md5::digest {buf} { 1163 # This is 0123456789abcdeffedcba9876543210 in byte-swapped order: 1164 set state(0) 0x67452301 1165 set state(1) 0xEFCDAB89 1166 set state(2) 0x98BADCFE 1167 set state(3) 0x10325476 1168 1169 # Pad buffer per RFC to exact multiple of 64 bytes. 1170 pad buf 1171 1172 # Calculate digest in 64 byte chunks: 1173 set nwords 0 1174 set nbytes 0 1175 set word 0 1176 binary scan $buf c* bytes 1177 # Unclear, but the data seems to get byte swapped here. 1178 foreach c $bytes { 1179 set word [expr {$c << 24 | ($word >> 8 & 0xffffff) }] 1180 if {[incr nbytes] == 4} { 1181 set nbytes 0 1182 set x($nwords) $word 1183 set word 0 1184 if {[incr nwords] == 16} { 1185 round x state 1186 set nwords 0 1187 } 1188 } 1189 } 1190 1191 # Result is state(0:3), but each word is taken low byte first. 1192 set result {} 1193 for {set i 0} {$i <= 3} {incr i} { 1194 set w $state($i) 1195 append result [format %02x%02x%02x%02x \ 1196 [expr {$w & 255}] \ 1197 [expr {$w >> 8 & 255}] \ 1198 [expr {$w >> 16 & 255}] \ 1199 [expr {$w >> 24 & 255}]] 1200 } 1201 return $result 1202} 1203