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