Index: openacs-4/packages/acs-tcl/tcl/00-database-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/acs-tcl/tcl/00-database-procs.tcl,v diff -u -N --- openacs-4/packages/acs-tcl/tcl/00-database-procs.tcl 17 Jun 2019 10:19:23 -0000 1.126.2.6 +++ /dev/null 1 Jan 1970 00:00:00 -0000 @@ -1,3800 +0,0 @@ -ad_library { - - An API for managing database queries. - - @creation-date 15 Apr 2000 - @author Jon Salz (jsalz@arsdigita.com) - @cvs-id $Id: 00-database-procs.tcl,v 1.126.2.6 2019/06/17 10:19:23 gustafn Exp $ -} - -# Database caching. -# -# Values returned by a query are cached if you pass the "-cache_key" switch -# to the database procedure. The switch value will be used as the key in the -# ns_cache eval call used to execute the query and processing code. The -# db_flush proc should be called to flush the cache when appropriate. The -# "-cache_pool" parameter can be used to specify the cache pool to be used, -# and defaults to db_cache_pool. The # size of the default cache is governed -# by the kernel parameter "DBCacheSize" in the "caching" section. -# -# Currently db_string, db_list, db_list_of_lists, db_0or1row, and db_multirow support -# caching. -# -# Don Baccus 2/25/2006 - my 52nd birthday! - -# As originally released in (at least) ACS 4.2 through OpenACS 4.6, -# this DB API supported only a single, default database. You could -# define any number of different database drivers and pools in -# AOLserver, but could only use ONE database here. -# -# I have eliminated this restriction. Now, in OpenACS 5.0 and later, -# to access a non-default database, simply pass the optional -dbn -# (Database Name) switch to any of the DB API procs which support it. -# -# Supported AOLserver database drivers: -# -# - Oracle (nsoracle): Everything should work. -# -# - PostgreSQL (nspostgres): Everything should work. -# -# - ODBC (nsodbc): -# - Anything using bind variables will only work if you're using a -# version of the driver with bind variable emulation hacked in -# (copied from the PostgreSQL driver). -# - Some features, like LOBs, simply won't work at all. -# - The basic functionality worked fine back in Sept. 2001, but I -# have NOT tested it since then at all, so maybe there are bugs. -# -# - Any others: Basic stuff using only the standard ns_db API will -# likely work, but any special features of the driver (e.g., LOBs) -# definitely won't. Feel free to add support! -# -# --atp@piskorski.com, 2003/04/09 19:18 EDT - -# Note that "-dbn" specifies a "Database Name", NOT a database pool! -# -# I could have provided access to secondary databases via a -pool -# rather than a -dbn switch, but chose not to, as the existing DB API -# already had the nicely general feature that if you try to do nested -# queries, the DB API will transparently grab a second database handle -# from another pool to make it work. You can nest your queries as -# many levels deep as you have database pools defined for that -# database. So, the existing API essentially already supported the -# notion of "binning" database pools into logical "databases", it just -# didn't provide any way to define more than the single, default -# database! Thus I chose to preserve this "binning" by specifying -# databases via the -dbn switch rather than database pools via a -pool -# switch. - -# (JoelA, 27 Dec 2004 - replaced example config.tcl with link) -# -# see http://openacs.org/doc/openacs-5-1/tutorial-second-database -# for config and usage examples - -# TODO: The "driverkey_" overrides in the config file are NOT -# implemented yet! -# -# --atp@piskorski.com, 2003/03/16 21:30 EST - -# NOTE: don't forget to add your new pools into the -# ns_section ns/db/pools - - -# The "driverkey" indirection layer: -# -# Note that in the AOLserver config file, you may optionally add one -# entry for each database defining its "driver key". If you do NOT -# specify a driver key in the AOLserver config file, the appropriate -# key will be determined for you by calling "ns_db driver" once on -# startup for the first pool defined in each database. Therefore, -# most people should NOT bother to give a driverkey in the config -# file. -# -# So, just what is this "driverkey" thing used for anyway? AOLserver -# defines the ns_db API, and the OpenACS db_* API depends utterly on -# it. However, there are a few holes in the functionality of the -# ns_db API, and each AOLserver database driver tends to fill in those -# holes by adding extra functionality with its own, drive specific -# functions. Therefore, in order to make the db_* API work with -# multiple db drivers, we need to introduce some switches or if -# statements in our code. -# -# Currently (2003/04/08), at least for the Oracle, PostgreSQL, and -# ODBC drivers, the database driver name returned by "ns_db driver" is -# completely sufficient for these switch statements. But, rather than -# using ns_db driver directly in the switches, we add the simple -# "driver key" layer of indirection between the two, to make the -# default behavior easier to override if that should ever be -# necessary. -# -# --atp@piskorski.com, 2003/04/08 03:39 EDT - - -# We now use the following global variables: -# -# Server-Wide NSV arrays, keys: -# db_driverkey $dbn -# db_pool_to_dbn $pool -# -# Global Variables -# ::acs::default_database -# ::acs::db_pools($dbn) (used in db_available_pools) -# ::acs::db_pool_to_dbn($pool) (used for caching access to nsv db_pool_to_dbn) -# ::acs::db_driverkey($dbn) (used for caching access to nsv db_driverkey) -# -# Per-thread Tcl global variables: -# One Tcl Array per Database Name: -# db_state_${dbn} -# -# The db_available_pools and db_state arrays are used in exactly the -# same manner as they were originally (in ACS 4.0 to OpenACS 4.6 -# code), except that in the original DB API we had only one of each -# array total, while now we have one of each array per database. -# -# The db_pool_to_dbn nsv is simply a map to quickly tell use which dbn -# each AOLserver database pool belongs to. (Any pools which do not -# belong to any dbn have no entry here.) -# -# We use the procs db_state_array_name_is, db_available_pools, and -# db_driverkey to help keep track of these different arrays. -# Note that most code should now NEVER read from any of the -# db_available_pools nsvs listed above, but should instead use the -# proc db_available_pools provided for that purpose. -# -# The original implementation comments on the use of these global -# variables are below: -# -# --atp@piskorski.com, 2003/03/16 21:30 EST - - -ad_proc -private db_state_array_name_is { - {-dbn ""} -} { - @return the name of the global db_state array for the given - database name. - - @param dbn The database name to use. If empty_string, uses the - default database. - - @author Andrew Piskorski (atp@piskorski.com) - @creation-date 2003/03/16 -} { - if { $dbn eq "" } { - set dbn $::acs::default_database - } - #if {[llength [trace info variable ::db_state_${dbn}]] == 0} { - # trace add variable ::db_state_${dbn} {array read write unset} [list ::db_tracer ::db_state_${dbn}] - #} - return "::db_state_${dbn}" -} - -# proc db_tracer {varname name1 name2 op} { -# if {$name2 eq "handles"} { -# #ns_log notice "### variable $varname: $name1 ($name2) $op" -# if {$op eq "write"} { -# ns_log notice "###### handles updated to <[set ::${varname}($name2)]>" -# } -# } -# } - -ad_proc -public db_driverkey { - {-handle_p 0} - dbn -} { - Normally, a dbn is passed to this proc. Unfortunately, there are - one or two cases where a proc that needs to call this one has only - a db handle, not the dbn that handle came from. Therefore, they - instead use -handle_p 1 and pass the db handle. - - Hmm, as of 2018, it seems that in most cases, db_driverkey is - called with a handle. - - @return The driverkey for use in db_* API switch statements. - - @author Andrew Piskorski (atp@piskorski.com) - @creation-date 2003/04/08 -} { - if { $handle_p } { - # - # In the case, the passed "dbn" is actually a - # handle. Determine from the handle the "pool" and from the - # "pool" the "dbn". - # - set handle $dbn - set pool [ns_db poolname $handle] - set key ::acs::db_pool_to_dbn($pool) - if {[info exists $key]} { - # - # First, try to get the variable from the per-thread - # variable (which is part of the blueprint). - # - set dbn [set $key] - } elseif { [nsv_exists db_pool_to_dbn $pool] } { - # - # Fallback to nsv (old style), when for whatever - # reasons, the namespaced variable is not available. - # - ns_log notice "db_driverkey $handle_p dbn <$dbn> VIA NSV" - set dbn [nsv_get db_pool_to_dbn $pool] - } else { - # - # db_pool_to_dbn_init runs on startup, so other than some - # broken code deleting the nsv key (very unlikely), the - # only way this could happen is for someone to call this - # proc with a db handle from a pool which is not part of - # any dbn. - - error "No database name (dbn) found for pool '$pool'. Check the 'ns/server/[ns_info server]/acs/database' section of your config file." - } - } - - set key ::acs::db_driverkey($dbn) - if {[info exists $key]} { - return [set $key] - } - - if { ![nsv_exists db_driverkey $dbn] } { - # This ASSUMES that any overriding of this default value via - # "ns_param driverkey_dbn" has already been done: - - if { $handle_p } { - set driver [ns_db driver $handle] - } else { - db_with_handle -dbn $dbn handle { - set driver [ns_db driver $handle] - } - } - - # These are the default driverkey values, if they are not set - # in the config file: - - if { [string match "Oracle*" $driver] } { - set driverkey {oracle} - } elseif { $driver eq "PostgreSQL" } { - set driverkey "postgresql" - } elseif { $driver eq "ODBC" } { - set driverkey "nsodbc" - } else { - set driverkey {} - ns_log Error "db_driverkey: Unknown driver '$driver'." - } - - nsv_set db_driverkey $dbn $driverkey - } - - return [set $key [nsv_get db_driverkey $dbn]] -} - - -ad_proc -public db_type {} { - @return the RDBMS type (i.e. oracle, postgresql) this OpenACS installation is using. - The nsv ad_database_type is set up during the bootstrap process. -} { - # - # Currently this should always be either "oracle" or "postgresql": - # --atp@piskorski.com, 2003/03/16 22:01 EST - # - # First check, if the database type exists in the namespaced - # variable. This should be always the case. If this fail, fall - # back to the old-style nsv (which can be costly in tight db loops) - # - if {[info exists ::acs::database_type]} { - set result $::acs::database_type - } else { - set result [nsv_get ad_database_type .] - ns_log Warning "db_type '$result' had to be obtained from the nsv 'ad_database_type'" - set ::acs::database_type $result - } - return $result -} - -ad_proc -public db_compatible_rdbms_p { db_type } { - @return 1 if the given db_type is compatible with the current RDBMS. -} { - return [expr { $db_type eq "" || [db_type] eq $db_type }] -} - - - -ad_proc -private db_legacy_package_p { db_type_list } { - @return 1 if the package is a legacy package. We can only tell for certain if it explicitly supports Oracle 8.1.6 rather than the OpenACS more general oracle. -} { - if {"oracle-8.1.6" in $db_type_list} { - return 1 - } - return 0 -} - -ad_proc -public db_version {} { - @return the RDBMS version (i.e. 8.1.6 is a recent Oracle version; 7.1 a - recent PostgreSQL version) -} { - return [nsv_get ad_database_version .] -} - -ad_proc -public db_current_rdbms {} { - @return the current rdbms type and version. -} { - return [db_rdbms_create [db_type] [db_version]] -} - -ad_proc -public db_known_database_types {} { - @return a list of three-element lists describing the database engines known - to OpenACS. Each sublist contains the internal database name (used in file - paths, etc), the driver name, and a "pretty name" to be used in selection - forms displayed to the user. - - The nsv containing the list is initialized by the bootstrap script and should - never be referenced directly by user code. -} { - return $::acs::known_database_types -} - - -# db_null, db_quote, db_nullify_empty_string - were all previously -# defined Oracle only, no Postgres equivalent existed at all. So, it -# can't hurt anything to have them defined in when OpenACS is using -# Postgres too. --atp@piskorski.com, 2003/04/08 05:34 EDT - -ad_proc -deprecated db_null {} { - - @return an empty string, which Oracle thinks is null. - - Deprecated: This routine was invented to provide an RDBMS-specific null - value but doesn't actually work. I (DRB) left it in to speed porting - we - should really clean up the code and pull out the calls instead, though. - - @see "" -} { - return "" -} - -ad_proc -public db_quote { string } { - Quotes a string value to be placed in a SQL statement. -} { - regsub -all {'} "$string" {''} result - return $result -} - -ad_proc -public -deprecated db_nullify_empty_string { string } { - A convenience function that returns [db_null] if $string is the empty string. - - Deprecated: essentially just returns the passed string. - - @see: db_null -} { - return $string -} - -ad_proc -public db_boolean { bool } { - Converts a Tcl boolean (1/0) into a SQL boolean (t/f) - @return t or f -} { - if { $bool } { - return "t" - } else { - return "f" - } -} - -ad_proc -public db_nextval { - { -dbn "" } - sequence -} { - - Example: - -
-    set new_object_id [db_nextval acs_object_id_seq]
-    
- - @return the next value for a sequence. This can utilize a pool of - sequence values. - - @param sequence the name of an SQL sequence - - @param dbn The database name to use. If empty_string, uses the default database. - - @see /doc/db-api-detailed -} { - set driverkey [db_driverkey $dbn] - - # PostgreSQL has a special implementation here, any other db will - # probably work with the default: - - switch -- $driverkey { - - postgresql { - # # the following query will return a nextval if the sequnce - # # is of relkind = 'S' (a sequnce). if it is not of relkind = 'S' - # # we will try querying it as a view: - - # if { [db_0or1row -dbn $dbn nextval_sequence " - # select nextval('${sequence}') as nextval - # where (select relkind - # from pg_class - # where relname = '${sequence}') = 'S' - # "]} { - # return $nextval - # } else { - # ns_log debug "db_nextval: sequence($sequence) is not a real sequence. perhaps it uses the view hack." - # db_0or1row -dbn $dbn nextval_view "select nextval from ${sequence}" - # return $nextval - # } - # - # The code above is just for documentation, how it worked - # before the change below. We keep now a per-thread table of - # the "known" sequences to avoid at runtime the query, - # whether the specified sequence is a real sequence or a - # view. This change makes this function more than a factor - # of 2 faster than before. - # - # Note that solely the per-thread information won't work for - # freshly created sequences. Therefore, we keep the old - # code for checking at runtime in the database for such - # occurrences. - # - # Note that the sequence handling in OpenACS is quite a - # mess. Some sequences are named t_SEQUENCE (10 in - # dotlrn), others are called just SEQUENCE (18 in dotlrn), - # for some sequences, additional views are defined with an - # attribute 'nextval', and on top of this, db_nextval is - # called sometimes with the view name and sometimes with - # the sequence name. Checking this at runtime is - # unnecessary complex and costly. - # - # The best solution would certainly be to call "db_nextval" - # only with real sequence names (as defined in SQL). In that - # case, the whole function would for postgres would collapse - # to a single line, without any need for sequence name - # caching. But in that case, one should rename the sequences - # from t_SEQUENCE to SEQUENCE for postgres. - # - # However, since Oracle uses the pseudo column ".nextval", - # which is emulated via the view, it is not clear, how - # feasible this is to remove all such views without breaking - # installed applications. We keep for such cases the view, - # but nevertheless, the function "db_nextval" should always - # be called with names without the "t_" prefix to achieve - # Oracle compatibility. - - if {![info exists ::db::sequences]} { - ns_log notice "-- creating per thread sequence table" - namespace eval ::db {} - foreach s [db_list -dbn $dbn relnames "select relname, relkind from pg_class where relkind = 'S'"] { - set ::db::sequences($s) 1 - } - } - if {[info exists ::db::sequences(t_$sequence)]} { - #ns_log notice "-- found t_$sequence - #ad_log Warning "Deprecated sequence name 't_$sequence' is used. Use instead 't_$sequence'" - set nextval [db_string -dbn $dbn nextval "select nextval('t_$sequence')"] - } elseif {[info exists ::db::sequences($sequence)]} { - #ns_log notice "-- found $sequence" - set nextval [db_string -dbn $dbn nextval "select nextval('$sequence')"] - if {[string match t_* $sequence]} { - ad_log Warning "For portability, db_nextval should be called without the leading 't_' prefix: 't_$sequence'" - } - } elseif { [db_0or1row -dbn $dbn nextval_sequence " - select nextval('${sequence}') as nextval - where (select relkind - from pg_class - where relname = '${sequence}') = 'S' - "]} { - # - # We do not have an according sequence-table. Use the system catalog to check - # for the sequence - # - # ... the query sets nextval if it succeeds - # - ad_log Warning "Probably deprecated sequence name '$sequence' is used (no sequence table found)" - } else { - # - # Finally, there might be a view with a nextval - # - ns_log debug "db_nextval: sequence($sequence) is not a real sequence. perhaps it uses the view hack." - set nextval [db_string -dbn $dbn nextval "select nextval from $sequence"] - ad_log Warning "Using deprecated sequence view hack for '$sequence'. Is there not real sequence?" - } - - return $nextval - } - - oracle - - nsodbc - - default { - return [db_string -dbn $dbn nextval "select $sequence.nextval from dual"] - } - } -} - -ad_proc -public db_nth_pool_name { - { -dbn "" } - n -} { - @return the name of the pool used for the nth-nested selection (0-relative). - @param dbn The database name to use. If empty_string, uses the default database. -} { - set available_pools [db_available_pools $dbn] - - if { $n < [llength $available_pools] } { - set pool [lindex $available_pools $n] - } else { - return -code error "Ran out of database pools ($available_pools)" - } - return $pool -} - -if {[acs::icanuse "ns_db currenthandles"]} { - - ns_log notice "... I can use 'ns_db currenthandles'" - - # - # This branch uses "ns_db currenthandles" to implement - # "db_with_handle" instead of the old approach based on the global - # db_state variables. The new approach has the advantage that it - # is: - # - # - more robust (deletion and creation of the per-request variables, - # no coherency problem), - # - simpler, and - # - faster (less overhead per db_with_handle call) - # - # time {db_string . {select object_id from acs_objects limit 1}} 1000 - # old: 160-190 microseconds per iteration - # new: 150-180 microseconds per iteration - # - # time {xo::dc get_value . {select object_id from acs_objects limit 1}} 1000 - # old: 110-120 - # new: 105-110 - # - # set id -1 - # time {xo::dc get_value -prepare {int} . {select object_id from acs_objects where object_id=:id}} 1000 - # old: 80-100 - # new: 76-90 - # - # Still, more improvement can be done (GN). - # - ad_proc -public db_with_handle { - { -dbn "" } - db code_block - } { - Place a usable database handle in db and executes - code_block. - - @param dbn Database name to use. If empty_string, use the default database - @param db Name of the handle variable used in the code block - @param code_block code block to be executed with handle - } { - # - # Let the caller decide, how the handle variable is called in - # the code block. - # - upvar 1 $db dbh - - # - # Get the pools and the current allocated handles for this thread. - # - set pools [db_available_pools $dbn] - set currentHandles [ns_db currenthandles] - #ns_log notice "### pools <$pools> currentHandles <$currentHandles>" - - set db "" - set n 0 - foreach pool $pools { - # - # Do we have already handles allocated from this pool? - # - if {[dict exists $currentHandles $pool]} { - # - # Are there handles, which are not active (i.e. not in - # a currently open "ns_db select" and "ns_db getrow" - # context. - # - foreach {handle active} [dict get $currentHandles $pool] { - #ns_log notice "### FOUND pool $pool handle $handle active $active" - if {$active eq "0"} { - # - # We can use this handle - # - set db $handle - break - } - } - } else { - break - } - incr n - } - # - # In case, we got no handle above, we have to allocate a - # handle from the next pool, from which we have not got a - # handle before. - # - if {$db eq ""} { - # - # We were not successful above - # - set pool [lindex $pools $n] - if {$pool eq ""} { - ad_log error "handles from all pools <$pools> are exhausted" - error "could not obtain handle, all pools are exhausted" - } - set start_time [expr {[clock clicks -microseconds]/1000.0}] - #ns_log notice "### BEFORE gethandle $pool ($n)" - set errno [catch { - set db [ns_db gethandle $pool] - } error] - #ad_log notice "### AFTER gethandle $pool errno $errno handle <$db> currentHandles [ns_db currenthandles]" - ds_collect_db_call $db gethandle "" $pool $start_time $errno $error - if { $errno } { - ns_log notice "### RETURNING error $error" - return -code $errno -errorcode $::errorCode -errorinfo $::errorInfo $error - } - } - #ns_log notice "### db_with_handle has handle <$db>" - - set dbh $db - set errno [catch { uplevel 1 $code_block } error] - - # Unset dbh, so any subsequence use of this variable will bomb. - unset -nocomplain dbh - - # If errno is 1, it's an error, so return errorCode and errorInfo; - # if errno = 2, it's a return, so don't try to return errorCode/errorInfo - # errno = 3 or 4 give undefined results - - if { $errno == 1 } { - # A real error occurred - ns_log notice "### db_with_handle returned error <$error> for statement $code_block" - return -code $errno -errorcode $::errorCode -errorinfo $::errorInfo $error - } - - if { $errno == 2 } { - - # The code block called a "return", so pass the message through but don't try - # to return errorCode or errorInfo since they may not exist - - return -code $errno $error - } - } - - # - # db_last_used_handle - # - ad_proc -private db_last_used_handle {{-dbn ""}} { - Get the last used inactive handle. - - @param dbn database name - @return last active handle or empty string - } { - set pools [db_available_pools $dbn] - set currentHandles [ns_db currenthandles] - - set last_used_handle "" - foreach pool $pools { - if {[dict exists $currentHandles $pool]} { - foreach {handle active} [dict get $currentHandles $pool] { - #ns_log notice "### FOUND pool $pool handle $handle active $active" - if {$active eq 0} { - set last_used_handle $handle - } - } - } - } - #ns_log notice "###### db_last_used_handle: <$currentHandles> last used $last_used_handle" - return $last_used_handle - } - - # - # db_release_unused_handles - # - ad_proc -public db_release_unused_handles {{-dbn ""}} { - Releases any database handles that are presently unused. - - @param dbn The database name to use. If empty_string, uses the default database. - } { - # we need the state array still for transaction handling - upvar "#0" [db_state_array_name_is -dbn $dbn] db_state - - set pools [db_available_pools $dbn] - set currentHandles [ns_db currenthandles] - - foreach pool $pools { - if {[dict exists $currentHandles $pool]} { - foreach {handle active} [dict get $currentHandles $pool] { - #ns_log notice "### FOUND pool $pool handle $handle active $active" - if {$active eq 0} { - # Don't release handles which are part of a transaction. - if { [info exists db_state(transaction_level,$handle)] - && $db_state(transaction_level,$handle) > 0 - } { - continue - } - set start_time [expr {[clock clicks -microseconds]/1000.0}] - ns_db releasehandle $handle - #ns_log notice "### AFTER releasehandle [ns_db currenthandles $pool]" - ds_collect_db_call $handle releasehandle "" "" $start_time 0 "" - } - } - } - } - } - - -} else { - - # - # This is the legacy branch without [ns_db currenthandles], using - # the global state variables. - # - ns_log notice "... cannot use 'ns_db currenthandles'" - - ad_proc -public db_with_handle { - { -dbn "" } - db code_block - } { - - Places a usable database handle in db and executes code_block. - - @param dbn The database name to use. If empty_string, uses the default database. - } { - upvar 1 $db dbh - upvar "#0" [db_state_array_name_is -dbn $dbn] db_state - - # Initialize bookkeeping variables. - if { ![info exists db_state(handles)] } { - set db_state(handles) [list] - } - if { ![info exists db_state(n_handles_used)] } { - set db_state(n_handles_used) 0 - } - if { $db_state(n_handles_used) >= [llength $db_state(handles)] } { - set pool [db_nth_pool_name -dbn $dbn $db_state(n_handles_used)] - set start_time [expr {[clock clicks -microseconds]/1000.0}] - set errno [catch { - set db [ns_db gethandle $pool] - } error] - ds_collect_db_call $db gethandle "" $pool $start_time $errno $error - lappend db_state(handles) $db - if { $errno } { - return -code $errno -errorcode $::errorCode -errorinfo $::errorInfo $error - } - } - set my_dbh [lindex $db_state(handles) $db_state(n_handles_used)] - set dbh $my_dbh - set db_state(last_used) $my_dbh - - incr db_state(n_handles_used) - set errno [catch { uplevel 1 $code_block } error] - incr db_state(n_handles_used) -1 - - # This may have changed while the code_block was being evaluated. - set db_state(last_used) $my_dbh - - # Unset dbh, so any subsequence use of this variable will bomb. - unset -nocomplain dbh - - # If errno is 1, it's an error, so return errorCode and errorInfo; - # if errno = 2, it's a return, so don't try to return errorCode/errorInfo - # errno = 3 or 4 give undefined results - - if { $errno == 1 } { - # A real error occurred - return -code $errno -errorcode $::errorCode -errorinfo $::errorInfo $error - } - - if { $errno == 2 } { - - # The code block called a "return", so pass the message through but don't try - # to return errorCode or errorInfo since they may not exist - - return -code $errno $error - } - } - - ad_proc -private db_last_used_handle {{-dbn ""}} { - Get the last used handle - - @param dbn database name - @return last active handle or empty string - } { - upvar "#0" [db_state_array_name_is -dbn $dbn] db_state - - return $db_state(last_used) - } - - ad_proc -public db_release_unused_handles {{-dbn ""}} { - - Releases any database handles that are presently unused. - - @param dbn The database name to use. If empty_string, uses the default database. - } { - upvar "#0" [db_state_array_name_is -dbn $dbn] db_state - - if { [info exists db_state(n_handles_used)] } { - # Examine the elements at the end of db_state(handles), killing off - # handles that are unused and not engaged in a transaction. - - set index_to_examine [expr { [llength $db_state(handles)] - 1 }] - while { $index_to_examine >= $db_state(n_handles_used) } { - set db [lindex $db_state(handles) $index_to_examine] - - # Stop now if the handle is part of a transaction. - if { [info exists db_state(transaction_level,$db)] - && $db_state(transaction_level,$db) > 0 - } { - break - } - - set pool [db_nth_pool_name -dbn $dbn $db_state(n_handles_used)] - set start_time [expr {[clock clicks -microseconds]/1000.0}] - ns_db releasehandle $db - ds_collect_db_call $db releasehandle "" "" $start_time 0 "" - incr index_to_examine -1 - } - set db_state(handles) [lrange $db_state(handles) 0 $index_to_examine] - } - } - - -} - -ad_proc -public db_resultrows {{-dbn ""}} { - @return the number of rows affected by the last DML command. - - @param dbn The database name to use. If empty_string, uses the default database. -} { - set driverkey [db_driverkey $dbn] - - switch -- $driverkey { - oracle { - return [ns_ora resultrows [db_last_used_handle -dbn $dbn]] - } - postgresql { - return [ns_pg ntuples [db_last_used_handle -dbn $dbn]] - } - nsodbc { - error "db_resultrows is not supported for this database." - } - default { - error "Unknown database driver. db_resultrows is not supported for this database." - } - } -} - - - -ad_proc -public db_exec_plsql { - {-dbn ""} - statement_name - sql - args -} { - - Oracle: - Executes a PL/SQL statement, and returns the variable of bind - variable :1. - -

- PostgreSQL: - Performs a pl/pgsql function or procedure call. The caller must - perform a select query that returns the value of the function. - -

- Examples: - -

-

-    # Oracle:
-    db_exec_plsql delete_note {
-        begin  note.del(:note_id);  end;
-    }
-
-    # PostgreSQL:
-    db_exec_plsql delete_note {
-        select note__delete(:note_id);
-    }
-    
- -

- If you need the return value, then do something like this: - -

-

-    # Oracle:
-    set new_note_id [db_exec_plsql create_note {
-        begin
-        :1 := note.new(
-                       owner_id => :user_id,
-                       title    => :title,
-                       body     => :body,
-                       creation_user => :user_id,
-                       creation_ip   => :peeraddr,
-                       context_id    => :package_id
-                       );
-        end;
-    }]
-
-    # PostgreSQL:
-    set new_note_id [db_exec_plsql create_note {
-        select note__new(
-                         null,
-                         :user_id,
-                         :title,
-                         :body,
-                         'note',
-                         now(),
-                         :user_id,
-                         :peeraddr,
-                         :package_id
-                         );
-    }]
-    
- -

- You can call several pl/sql statements at once, like this: - -

-

-    # Oracle:
-    db_exec_plsql delete_note {
-        begin
-        note.del(:note_id);
-        note.del(:another_note_id);
-        note.del(:yet_another_note_id);
-        end;
-    }
-
-    # PostgreSQL:
-    db_exec_plsql delete_note {
-        select note__delete(:note_id);
-        select note__delete(:another_note_id);
-        select note__delete(:yet_another_note_id);
-    }
-    
- - If you are using xql files then put the body of the query in a - yourfilename-oracle.xql or yourfilename-postgresql.xql file, as appropriate. E.g. the first example - transformed to use xql files looks like this: - - -

- yourfilename.tcl:
-

-

-    db_exec_plsql delete_note {}
- -

- yourfilename-oracle.xql:
-

-

-    <fullquery name="delete_note">
-    <querytext>
-    begin
-    note.del(:note_id);
-    end;
-    </querytext>
-    </fullquery>
- -

- yourfilename-postgresql.xql:
-

-

-    <fullquery name="delete_note">
-    <querytext>
-    select note__delete(:note_id);
-    </querytext>
-    </fullquery>
- - - @param dbn The database name to use. If empty_string, uses the default database. - - @see /doc/db-api-detailed -} { - ad_arg_parser { bind_output bind } $args - - # Query Dispatcher (OpenACS - ben) - set full_statement_name [db_qd_get_fullname $statement_name] - - if { [info exists bind_output] } { - return -code error "the -bind_output switch is not currently supported" - } - - set driverkey [db_driverkey $dbn] - switch -- $driverkey { - postgresql { - set postgres_p 1 - } - - oracle - - nsodbc - - default { - set postgres_p 0 - } - } - - if { ! $postgres_p } { - db_with_handle -dbn $dbn db { - # Right now, use :1 as the output value if it occurs in the statement, - # or not otherwise. - set test_sql [db_qd_replace_sql $full_statement_name $sql] - if { [regexp {:1} $test_sql] } { - return [db_exec exec_plsql_bind $db $full_statement_name $sql 2 1 ""] - } else { - return [db_exec dml $db $full_statement_name $sql] - } - } - } else { - # Postgres doesn't have PL/SQL, of course, but it does have - # PL/pgSQL and other procedural languages. Rather than assign the - # result to a bind variable which is then returned to the caller, - # the Postgres version of OpenACS requires the caller to perform a - # select query that returns the value of the function. - - # We are no longer calling db_string, which screws up the bind - # variable stuff otherwise because of calling environments. (ben) - - ad_arg_parser { bind_output bind } $args - - # I'm not happy about having to get the fullname here, but right now - # I can't figure out a cleaner way to do it. I will have to - # revisit this ASAP. (ben) - set full_statement_name [db_qd_get_fullname $statement_name] - - if { [info exists bind_output] } { - return -code error "the -bind_output switch is not currently supported" - } - - db_with_handle -dbn $dbn db { - # plsql calls that are simple selects bypass the plpgsql - # mechanism for creating anonymous functions (OpenACS - Dan). - # if a table is being created, we need to bypass things, too (OpenACS - Ben). - set test_sql [db_qd_replace_sql $full_statement_name $sql] - if {[regexp -nocase -- {^\s*select} $test_sql match]} { - # ns_log Debug "PLPGSQL: bypassed anon function" - set selection [db_exec 0or1row $db $full_statement_name $sql] - } elseif {[regexp -nocase -- {^\s*(create|drop) table} $test_sql match]} { - ns_log Debug "PLPGSQL: bypassed anon function for create/drop table" - set selection [db_exec dml $db $full_statement_name $sql] - return "" - } else { - # ns_log Debug "PLPGSQL: using anonymous function" - set selection [db_exec_plpgsql $db $full_statement_name $sql $statement_name] - } - return [ns_set value $selection 0] - } - } -} - - -ad_proc -private db_exec_plpgsql { db statement_name pre_sql fname } { - - PostgreSQL only. -

- - A helper procedure to execute a SQL statement, potentially binding - depending on the value of the $bind variable in the calling environment - (if set). - -

- Low level replacement for db_exec which replaces inline code with a proc. - db proc is dropped after execution. This is a temporary fix until we can - port all of the db_exec_plsql calls to simple selects of the inline code - wrapped in function calls. - -

- emulation of plsql calls from oracle. This routine takes the plsql - statements and wraps them in a function call, calls the function, and then - drops the function. Future work might involve converting this to cache the - function calls - -

- This proc is private - use db_exec_plsql instead! - - @see db_exec_plsql - -} { - set start_time [expr {[clock clicks -microseconds]/1000.0}] - - set sql [db_qd_replace_sql $statement_name $pre_sql] - - set unique_id [db_nextval "anon_func_seq"] - - set function_name "__exec_${unique_id}_${fname}" - - # insert Tcl variable values (OpenACS - Dan) - if {$sql ne $pre_sql } { - set sql [uplevel 2 [list subst -nobackslashes $sql]] - } - ns_log Debug "PLPGSQL: converted: $sql to: select $function_name ()" - - # create a function definition statement for the inline code - # binding is emulated in tcl. (OpenACS - Dan) - - set errno [catch { - upvar bind bind - if { [info exists bind] && [llength $bind] != 0 } { - if { [llength $bind] == 1 } { - set bind_vars [list] - set len [ns_set size $bind] - for {set i 0} {$i < $len} {incr i} { - lappend bind_vars [ns_set key $bind $i] \ - [ns_set value $bind $i] - } - set proc_sql [db_bind_var_substitution $sql $bind_vars] - } else { - set proc_sql [db_bind_var_substitution $sql $bind] - } - } else { - set proc_sql [uplevel 2 [list db_bind_var_substitution $sql]] - } - - ns_db dml $db "create function $function_name () returns varchar as [::ns_dbquotevalue $proc_sql] language 'plpgsql'" - - set ret_val [ns_db 0or1row $db "select $function_name ()"] - - # drop the anonymous function (OpenACS - Dan) - # JCD: ignore return code -- maybe we should be smarter about this though. - catch {ns_db dml $db "drop function $function_name ()"} - - return $ret_val - - } error] - - set errinfo $::errorInfo - set errcode $::errorCode - - ds_collect_db_call $db 0or1row $statement_name $sql $start_time $errno $error - - if { $errno == 2 } { - return $error - } else { - catch {ns_db dml $db "drop function $function_name ()"} - } - - return -code $errno -errorinfo $errinfo -errorcode $errcode $error -} - -ad_proc -private db_get_quote_indices { sql } { - Given a piece of SQL, return the indices of single quotes. - This is useful when we do bind var substitution because we should - not attempt bind var substitution inside quotes. Examples: - -

-    sql          return value
-    {'a'}           {0 2}
-    {'a''}           {}
-    {'a'a'a'}       {0 2 4 6}
-    {a'b'c'd'}      {1 3 5 7}
-    
- - @see db_bind_var_substitution -} { - set quote_indices [list] - - # Returns a list on the format - # Example - for sql={'a'a'a'} returns - # {0 2} {0 0} {2 2} {3 6} {4 4} {6 6} - set all_indices [regexp -inline -indices -all -- {(?:^|[^'])(')(?:[^']|'')+(')(?=$|[^'])} $sql] - - for {set i 0} { $i < [llength $all_indices] } { incr i 3 } { - lappend quote_indices [lindex $all_indices $i+1 0] [lindex $all_indices $i+2 0] - } - - return $quote_indices -} - -ad_proc -private db_bind_var_quoted_p { sql bind_start_idx bind_end_idx} { - -} { - foreach {quote_start_idx quote_end_idx} [db_get_quote_indices $sql] { - if { $bind_start_idx > $quote_start_idx && $bind_end_idx < $quote_end_idx } { - return 1 - } - } - - return 0 -} - -ad_proc -private db_bind_var_substitution { sql { bind "" } } { - - This proc emulates the bind variable substitution in the PostgreSQL driver. - Since this is a temporary hack, we do it in Tcl instead of hacking up the - driver to support plsql calls. This is only used for the db_exec_plpgsql - function. - -} { - if {$bind eq ""} { - upvar __db_sql lsql - set lsql $sql - uplevel { - set __db_lst [regexp -inline -indices -all -- {:?:\w+} $__db_sql] - for {set __db_i [expr {[llength $__db_lst] - 1}]} {$__db_i >= 0} {incr __db_i -1} { - set __db_ws [lindex $__db_lst $__db_i 0] - set __db_we [lindex $__db_lst $__db_i 1] - set __db_bind_var [string range $__db_sql $__db_ws $__db_we] - if {![string match "::*" $__db_bind_var] && ![db_bind_var_quoted_p $__db_sql $__db_ws $__db_we]} { - set __db_tcl_var [string range $__db_bind_var 1 end] - set __db_tcl_var [set $__db_tcl_var] - if {$__db_tcl_var eq ""} { - set __db_tcl_var null - } else { - set __db_tcl_var "[::ns_dbquotevalue $__db_tcl_var]" - } - set __db_sql [string replace $__db_sql $__db_ws $__db_we $__db_tcl_var] - } - } - } - } else { - - array set bind_vars $bind - - set lsql $sql - set lst [regexp -inline -indices -all -- {:?:\w+} $sql] - for {set i [expr {[llength $lst] - 1}]} {$i >= 0} {incr i -1} { - set ws [lindex $lst $i 0] - set we [lindex $lst $i 1] - set bind_var [string range $sql $ws $we] - if {![string match "::*" $bind_var] && ![db_bind_var_quoted_p $lsql $ws $we]} { - set tcl_var [string range $bind_var 1 end] - set val $bind_vars($tcl_var) - if {$val eq ""} { - set val null - } else { - set val "[::ns_dbquotevalue $val]" - } - set lsql [string replace $lsql $ws $we $val] - } - } - } - - return $lsql -} - - -ad_proc -private db_getrow { db selection } { - - A helper procedure to perform an ns_db getrow, invoking developer support - routines as necessary. - -} { - set start_time [expr {[clock clicks -microseconds]/1000.0}] - set errno [catch { return [ns_db getrow $db $selection] } error] - ds_collect_db_call $db getrow "" "" $start_time $errno $error - if { $errno == 2 } { - return $error - } - return -code $errno -errorinfo $::errorInfo -errorcode $::errorCode $error -} - - -ad_proc -public db_exec { type db statement_name pre_sql {ulevel 2} args } { - - A helper procedure to execute a SQL statement, potentially binding - depending on the value of the $bind variable in the calling environment - (if set). - -} { - set start_time [expr {[clock clicks -microseconds]/1000.0}] - set driverkey [db_driverkey -handle_p 1 $db] - - # Note: Although marked as private, db_exec is in fact called - # extensively from several other packages. We DEFINITELY don't - # want to have to change all those procs to pass in the - # (redundant) $dbn just so we can use it in the call to - # db_driverkey, so db_driverkey MUST support its -handle switch. - # --atp@piskorski.com, 2003/04/09 12:13 EDT - - set sql [db_qd_replace_sql $statement_name $pre_sql] - - # insert Tcl variable values (OpenACS - Dan) - if {$sql ne $pre_sql } { - set sql [uplevel $ulevel [list subst -nobackslashes $sql]] - } - - set errno [catch { - upvar bind bind - - if { [info exists bind] && [llength $bind] != 0 } { - if { [llength $bind] == 1 } { - # $bind is an ns_set id: - - switch -- $driverkey { - oracle { - return [ns_ora $type $db -bind $bind $sql {*}$args] - } - postgresql { - return [ns_pg_bind $type $db -bind $bind $sql] - } - nsodbc { - return [ns_odbc_bind $type $db -bind $bind $sql] - } - default { - error "Unknown database driver. Bind variables not supported for this database." - } - } - - } else { - # $bind is a Tcl list, convert it to an ns_set: - set bind_vars [ns_set create] - foreach { name value } $bind { - ns_set put $bind_vars $name $value - } - } - - switch -- $driverkey { - oracle { - # TODO: Using $args outside the list is - # potentially bad here, depending on what is in - # args and if the items contain any embedded - # whitespace. Or maybe it works fine. But it's - # hard to know. Document or fix. - # --atp@piskorski.com, 2003/04/09 15:33 EDT - - return [ns_ora $type $db -bind $bind_vars $sql {*}$args] - } - postgresql { - return [ns_pg_bind $type $db -bind $bind_vars $sql] - } - nsodbc { - return [ns_odbc_bind $type $db -bind $bind_vars $sql] - } - default { - error "Unknown database driver. Bind variables not supported for this database." - } - } - - } else { - # Bind variables, if any, are defined solely as individual - # Tcl variables: - - switch -- $driverkey { - oracle { - return [uplevel $ulevel [list ns_ora $type $db $sql] $args] - } - postgresql { - return [uplevel $ulevel [list ns_pg_bind $type $db $sql]] - } - nsodbc { - return [uplevel $ulevel [list ns_odbc_bind $type $db $sql]] - } - default { - # Using plain ns_db like this will work ONLY if - # the query is NOT using bind variables: - # --atp@piskorski.com, 2001/09/03 08:41 EDT - return [uplevel $ulevel [list ns_db $type $db $sql] $args] - } - } - } - } error] - - # JCD: we log the clicks, dbname, query time, and statement to catch long running queries. - # If we took more than 3 seconds yack about it. - if { [clock clicks -milliseconds] - $start_time > 3000 } { - set duration [format %.2f [expr {[clock clicks -milliseconds] - $start_time}]] - ns_log Warning "db_exec: longdb $duration ms $db $type $statement_name" - } else { - #set duration [format %.2f [expr {[clock clicks -milliseconds] - $start_time}]] - #ns_log Debug "db_exec: timing $duration seconds $db $type $statement_name" - } - - ds_collect_db_call $db $type $statement_name $sql $start_time $errno $error - if { $errno == 2 } { - return $error - } - - return -code $errno -errorinfo $::errorInfo -errorcode $::errorCode $error -} - - -ad_proc -public db_string { - {-dbn ""} - -cache_key - {-cache_pool db_cache_pool} - statement_name - sql - args -} { - - Usage: db_string statement-name sql [ -default default ] [ -bind bind_set_id | -bind bind_value_list ] - - @return the first column of the result of the SQL query sql. If the query doesn't return a row, returns default or raises an error if no default is provided. - - @param dbn The database name to use. If empty_string, uses the default database. - @param cache_key Cache the result using given value as the key. Default is to not cache. - @param cache_pool Override the default db_cache_pool -} { - # Query Dispatcher (OpenACS - ben) - set full_name [db_qd_get_fullname $statement_name] - - ad_arg_parser { default bind } $args - - set code { - db_with_handle -dbn $dbn db { - set selection [db_exec 0or1row $db $full_name $sql] - } - if { $selection eq ""} { - if { [info exists default] } { - return $default - } - error "Selection did not return a value, and no default was provided" - } - return [ns_set value $selection 0] - } - - if { [info exists cache_key] } { - return [ns_cache eval $cache_pool $cache_key $code] - } else { - return [eval $code] - } -} - - -ad_proc -public db_list { - {-dbn ""} - -cache_key - {-cache_pool db_cache_pool} - statement_name - sql - args -} { - - Usage: db_list statement-name sql [ -bind bind_set_id | -bind bind_value_list ] - - @return a Tcl list of the values in the first column of the result of SQL query sql. - If sql doesn't return any rows, returns an empty list. - - @param dbn The database name to use. If empty_string, uses the default database. - @param cache_key Cache the result using given value as the key. Default is to not cache. - @param cache_pool Override the default db_cache_pool -} { - ad_arg_parser { bind } $args - - # Query Dispatcher (OpenACS - SDW) - set full_statement_name [db_qd_get_fullname $statement_name] - - # Can't use db_foreach in this proc, since we need to use the ns_set directly. - - set code { - db_with_handle -dbn $dbn db { - set selection [db_exec select $db $full_statement_name $sql] - set result [list] - while { [db_getrow $db $selection] } { - lappend result [ns_set value $selection 0] - } - } - return $result - } - if { [info exists cache_key] } { - return [ns_cache eval $cache_pool $cache_key $code] - } else { - return [eval $code] - } -} - - -ad_proc -public db_list_of_lists { - {-dbn ""} - -cache_key - {-cache_pool db_cache_pool} - -with_headers:boolean - statement_name - sql - args -} { - - Usage: db_list_of_lists statement-name sql [ -bind bind_set_id | -bind bind_value_list ] - - @param with_headers when specified, first line of returned list of - lists will always be the list of column names as reported by the - database. Useful when you want to dynamically assign variables to - values returned in the list of lists. - - @return a Tcl list, each element of which is a list of all column - values in a row of the result of the SQL querysql. If - sql doesn't return any rows, returns an empty list, - unless with_headers flag was specified and in this case the only - element in the list will be the list of headers. - - It checks if the element is I18N and replaces it, thereby - reducing the need to do this with every single package - - @param dbn The database name to use. If empty_string, uses the default database. - @param cache_key Cache the result using given value as the key. Default is to not cache. - @param cache_pool Override the default db_cache_pool -} { - ad_arg_parser { bind } $args - - set code { - set result [list] - foreach selection [uplevel [list db_list_of_ns_sets -dbn $dbn $statement_name $sql]] { - set selection_array [ns_set array $selection] - if {[llength $result] == 0 && $with_headers_p} { - set headers [list] - foreach {key value} $selection_array { - lappend headers $key - } - lappend result $headers - } - set row [list] - foreach {key value} $selection_array { - lappend row $value - } - lappend result $row - } - set result - } - if { [info exists cache_key] } { - return [ns_cache eval $cache_pool $cache_key $code] - } else { - return [eval $code] - } -} - - -ad_proc -public db_list_of_ns_sets { - {-dbn ""} - statement_name - sql - args -} { - Usage: db_list_of_ns_sets statement-name sql [ -bind bind_set_id | -bind bind_value_list ] - - @return a list of ns_sets with the values of each column of each row - returned by the sql query specified. - - @param statement_name The name of the query. - @param sql The SQL to be executed. - @param args Any additional arguments. - - @return list of ns_sets, one per each row return by the SQL query - - @param dbn The database name to use. If empty_string, uses the default database. -} { - ad_arg_parser { bind } $args - - set full_statement_name [db_qd_get_fullname $statement_name] - - db_with_handle -dbn $dbn db { - set result [list] - set selection [db_exec select $db $full_statement_name $sql] - - while {[db_getrow $db $selection]} { - lappend result [ns_set copy $selection] - } - } - - return $result -} - - -ad_proc -public db_foreach { - {-dbn ""} - statement_name - sql - args -} { - - Usage: -
- db_foreach statement-name sql [ -bind bind_set_id | -bind bind_value_list ] \ - [ -column_array array_name | -column_set set_name ] \ - code_block [ if_no_rows if_no_rows_block ] - -
- -

Performs the SQL query sql, executing - code_block once for each row with variables set to - column values (or a set or array populated if -column_array or - column_set is specified). If the query returns no rows, executes - if_no_rows_block (if provided). In place of 'if_no_rows' also the 'else' keyword can be used.

- -

Example: - -

db_foreach greeble_query "select foo, bar from greeble" {
-        ns_write "<li>foo=$foo; bar=$bar\n"
-    } if_no_rows {
-        # This block is optional.
-        ns_write "<li>No greebles!\n"
-    }
- - @param dbn The database name to use. If empty_string, uses the default database. -} { - ad_arg_parser { bind column_array column_set args } $args - - # Do some syntax checking. - set arglength [llength $args] - if { $arglength == 1 } { - # Have only a code block. - set code_block [lindex $args 0] - } elseif { $arglength == 3 } { - # Should have code block + if_no_rows + code block. - if { [lindex $args 1] ni {"if_no_rows" "else"}} { - return -code error "Expected if_no_rows or else as second-to-last argument" - } - lassign $args code_block . if_no_rows_code_block - } else { - return -code error "Expected 1 or 3 arguments after switches" - } - - if { [info exists column_array] && [info exists column_set] } { - return -code error "Can't specify both column_array and column_set" - } - - if { [info exists column_array] } { - upvar 1 $column_array array_val - } - - if { [info exists column_set] } { - upvar 1 $column_set selection - } - - set counter 0 - foreach selection [uplevel [list db_list_of_ns_sets -dbn $dbn $statement_name $sql]] { - incr counter - if { ![info exists column_set] } { - set set_array [ns_set array $selection] - if { [info exists column_array] } { - unset -nocomplain array_val - array set array_val $set_array - } else { - foreach {a v} $set_array { uplevel [list set $a $v] } - } - } - set errno [catch { uplevel 1 $code_block } error] - - # - # Handle or propagate the error. - # - switch -- $errno { - 0 { - # TCL_OK - } - 1 { - # TCL_ERROR - error $error $::errorInfo $::errorCode - } - 2 { - # TCL_RETURN - error "Cannot return from inside a db_foreach loop" - } - 3 { - # TCL_BREAK - break - } - 4 { - # TCL_CONTINUE - just ignore and continue looping. - } - default { - error "Unknown return code: $errno" - } - } - } - # If the if_no_rows_code is defined, go ahead and run it. - if { $counter == 0 && [info exists if_no_rows_code_block] } { - uplevel 1 $if_no_rows_code_block - } -} - - -proc db_multirow_helper {} { - uplevel 1 { - if { !$append_p || ![info exists counter]} { - set counter 0 - } - - db_with_handle -dbn $dbn db { - set selection [db_exec select $db $full_statement_name $sql] - set local_counter 0 - - # Make sure 'next_row' array doesn't exist - # The this_row and next_row variables are used to always execute the code block one result set row behind, - # so that we have the opportunity to peek ahead, which allows us to do group by's inside - # the multirow generation - # Also make the 'next_row' array available as a magic __db_multirow__next_row variable - upvar 1 __db_multirow__next_row next_row - unset -nocomplain next_row - - set more_rows_p 1 - while { 1 } { - - if { $more_rows_p } { - set more_rows_p [db_getrow $db $selection] - } else { - break - } - - # Setup the 'columns' part, now that we know the columns in the result set - # And save variables which we might clobber, if '-unclobber' switch is specified. - if { $local_counter == 0 } { - for { set i 0 } { $i < [ns_set size $selection] } { incr i } { - lappend local_columns [ns_set key $selection $i] - } - lappend local_columns {*}$extend - if { !$append_p || ![info exists columns] } { - # store the list of columns in the var_name:columns variable - set columns $local_columns - } else { - # Check that the columns match, if not throw an error - if { [join [lsort -ascii $local_columns]] ne [join [lsort -ascii $columns]] } { - error "Appending to a multirow with differing columns. - Original columns : [join [lsort -ascii $columns] ", "]. - Columns in this query: [join [lsort -ascii $local_columns] ", "]" "" "ACS_MULTIROW_APPEND_COLUMNS_MISMATCH" - } - } - - # Save values of columns which we might clobber - if { $unclobber_p && $code_block ne "" } { - foreach col $columns { - upvar 1 $col column_value __saved_$col column_save - - if { [info exists column_value] } { - if { [array exists column_value] } { - array set column_save [array get column_value] - } else { - set column_save $column_value - } - - # Clear the variable - unset column_value - } - } - } - } - - if { $code_block eq "" } { - # No code block - pull values directly into the var_name array. - - # The extra loop after the last row is only for when there's a code block - if { !$more_rows_p } { - break - } - incr counter - upvar $level_up "$var_name:$counter" array_val - set array_val(rownum) $counter - for { set i 0 } { $i < [ns_set size $selection] } { incr i } { - set array_val([ns_set key $selection $i]) \ - [ns_set value $selection $i] - } - } else { - # There is a code block to execute - - # Copy next_row to this_row, if it exists - unset -nocomplain this_row - set array_get_next_row [array get next_row] - if { $array_get_next_row ne "" } { - array set this_row [array get next_row] - } - - # Pull values from the query into next_row - unset -nocomplain next_row - if { $more_rows_p } { - for { set i 0 } { $i < [ns_set size $selection] } { incr i } { - set next_row([ns_set key $selection $i]) [ns_set value $selection $i] - } - } - - # Process the row - if { [info exists this_row] } { - # Pull values from this_row into local variables - foreach name [array names this_row] { - upvar 1 $name column_value - set column_value $this_row($name) - } - - # Initialize the "extend" columns to the empty string - foreach column_name $extend { - upvar 1 $column_name column_value - set column_value "" - } - - # Execute the code block - set errno [catch { uplevel 1 $code_block } error] - - # Handle or propagate the error. Can't use the usual - # "return -code $errno..." trick due to the db_with_handle - # wrapped around this loop, so propagate it explicitly. - switch -- $errno { - 0 { - # TCL_OK - } - 1 { - # TCL_ERROR - error $error $::errorInfo $::errorCode - } - 2 { - # TCL_RETURN - error "Cannot return from inside a db_multirow loop" - } - 3 { - # TCL_BREAK - ns_db flush $db - break - } - 4 { - # TCL_CONTINUE - continue - } - default { - error "Unknown return code: $errno" - } - } - - # Pull the local variables back out and into the array. - incr counter - upvar $level_up "$var_name:$counter" array_val - set array_val(rownum) $counter - foreach column_name $columns { - upvar 1 $column_name column_value - set array_val($column_name) $column_value - } - } - } - incr local_counter - } - } - - # Restore values of columns which we've saved - if { $unclobber_p && $code_block ne "" && $local_counter > 0 } { - foreach col $columns { - upvar 1 $col column_value __saved_$col column_save - - # Unset it first, so the road's paved to restoring - unset -nocomplain column_value - - # Restore it - if { [info exists column_save] } { - if { [array exists column_save] } { - array set column_value [array get column_save] - } else { - set column_value $column_save - } - - # And then remove the saved col - unset column_save - } - } - } - # Unset the next_row variable, just in case - unset -nocomplain next_row - } -} - -ad_proc -public db_multirow { - -local:boolean - -append:boolean - {-upvar_level 1} - -unclobber:boolean - {-extend {}} - {-dbn ""} - -cache_key - {-cache_pool db_cache_pool} - var_name - statement_name - sql - args -} { - @param dbn The database name to use. If empty_string, uses the default database. - @param cache_key Cache the result using given value as the key. Default is to not cache. - @param cache_pool Override the default db_cache_pool - - @param unclobber If set, will cause the proc to not overwrite local variables. Actually, what happens - is that the local variables will be overwritten, so you can access them within the code block. However, - if you specify -unclobber, we will revert them to their original state after execution of this proc. - - Usage: -
- db_multirow [ -local ] [ -upvar_level n_levels_up ] [ -append ] [ -extend column_list ] \ - var-name statement-name sql [ -bind bind_set_id | -bind bind_value_list ] \ - code_block [ if_no_rows if_no_rows_block ] - -
- -

Performs the SQL query sql, saving results in variables - of the form - var_name:1, var_name:2, etc, - setting var_name:rowcount to the total number - of rows, and setting var_name:columns to a - list of column names. - -

- - If "cache_key" is set, cache the array that results from the query *and* - any code block for future use. When this result is returned from cache, - THE CODE BLOCK IS NOT EXECUTED. Therefore any values calculated by the - code block that aren't listed as arguments to "extend" will - not be created. In practice this impacts relatively few queries, but do - take care. - -

- - You can not simultaneously append to and cache a non-empty multirow. - -

- - Each row also has a column, rownum, automatically - added and set to the row number, starting with 1. Note that this will - override any column in the SQL statement named 'rownum', also if you're - using the Oracle rownum pseudo-column. - -

- - If the -local is passed, the variables defined - by db_multirow will be set locally (useful if you're compiling dynamic templates - in a function or similar situations). Use the -upvar_level - switch to specify how many levels up the variable should be set. - -

- - You may supply a code block, which will be executed for each row in - the loop. This is very useful if you need to make computations that - are better done in Tcl than in SQL, for example using ns_urlencode - or ad_quotehtml, etc. When the Tcl code is executed, all the columns - from the SQL query will be set as local variables in that code. Any - changes made to these local variables will be copied back into the - multirow. - -

- - You may also add additional, computed columns to the multirow, using the - -extend { col_1 col_2 ... } switch. This is - useful for things like constructing a URL for the object retrieved by - the query. - -

- - If you're constructing your multirow through multiple queries with the - same set of columns, but with different rows, you can use the - -append switch. This causes the rows returned by this query - to be appended to the rows already in the multirow, instead of starting - a clean multirow, as is the normal behavior. The columns must match the - columns in the original multirow, or an error will be thrown. - -

- - Your code block may call continue in order to skip a row - and not include it in the multirow. Or you can call break - to skip this row and quit looping. - -

- - Notice the nonstandard numbering (everything - else in Tcl starts at 0); the reason is that the graphics designer, a non - programmer, may wish to work with row numbers. - -

- - Example: -

db_multirow -extend { user_url } users users_query {
-        select user_id first_names, last_name, email from cc_users
-    } {
-        set user_url [acs_community_member_url -user_id $user_id]
-    }
- - @see template::multirow -} { - # Query Dispatcher (OpenACS - ben) - set full_statement_name [db_qd_get_fullname $statement_name] - - if { $local_p } { - set level_up $upvar_level - } else { - set level_up \#[template::adp_level] - } - - ad_arg_parser { bind args } $args - - # Do some syntax checking. - set arglength [llength $args] - if { $arglength == 0 } { - # No code block. - set code_block "" - } elseif { $arglength == 1 } { - # Have only a code block. - set code_block [lindex $args 0] - } elseif { $arglength == 3 } { - # Should have code block + if_no_rows + code block. - if { [lindex $args 1] ne "if_no_rows" - && [lindex $args 1] ne "else" - } { - return -code error "Expected if_no_rows as second-to-last argument" - } - lassign $args code_block . if_no_rows_code_block - } else { - return -code error "Expected 1 or 3 arguments after switches" - } - - upvar $level_up "$var_name:rowcount" counter - upvar $level_up "$var_name:columns" columns - - if { [info exists cache_key] - && $append_p - && [info exists counter] && $counter > 0 - } { - return -code error "Can't append and cache a non-empty multirow datasource simultaneously" - } - - if { [info exists cache_key] } { - - set value [ns_cache eval $cache_pool $cache_key { - db_multirow_helper - - set values [list] - - for { set count 1 } { $count <= $counter } { incr count } { - upvar $level_up "$var_name:[expr {$count}]" array_val - lappend values [array get array_val] - } - - return [list $counter $columns $values] - }] - - lassign $value counter columns values - - set count 1 - foreach value $values { - upvar $level_up "$var_name:[expr {$count}]" array_val - array set array_val $value - incr count - } - } else { - db_multirow_helper - } - - - # If the if_no_rows_code is defined, go ahead and run it. - if { $counter == 0 && [info exists if_no_rows_code_block] } { - uplevel 1 $if_no_rows_code_block - } -} - -ad_proc -public db_multirow_group_last_row_p { - {-column:required} -} { - Used inside the code_block to db_multirow to ask whether this row is the last row - before the value of 'column' changes, or the last row of the result set. - -

- - This is useful when you want to build up a multirow for a master/slave table pair, - where you only want one row per row in the master table, but you want to include - data from the slave table in a column of the multirow. - -

- - Here's an example: - -

-    # Initialize the lines variable to hold a list of order line summaries
-    set lines [list]
-
-    # Start building the multirow. We add the dynamic column 'lines_pretty', which will
-    # contain the pretty summary of the order lines.
-    db_multirow -extend { lines_pretty } orders select_orders_and_lines {
-        select o.order_id,
-        o.customer_name,
-        l.item_name,
-        l.quantity
-        from   orders o,
-        order_lines l
-        where  l.order_id = o.order_id
-        order  by o.order_id, l.item_name
-    } {
-        lappend lines "$quantity $item_name"
-        if { [db_multirow_group_last_row_p -column order_id] } {
-            # Last row of this order, prepare the pretty version of the order lines
-            set lines_pretty [join $lines ", "]
-
-            # Reset the lines list, so we start from a fresh with the next row
-            set lines [list]
-        } else {
-            # There are yet more order lines to come for this order,
-            # continue until we've collected all the order lines
-            # The 'continue' keyword means this line will not be added to the resulting multirow
-            continue
-        }
-    }
-    
- - @author Lars Pind (lars@collaboraid.biz) - - @param column The name of the column defining the groups. - - @return 1 if this is the last row before the column value changes, 0 otherwise. -} { - upvar 1 __db_multirow__next_row next_row - if { ![info exists next_row] } { - # If there is no next row, this is the last row - return 1 - } - upvar 1 $column column_value - # Otherwise, it's the last row in the group if the next row has a different value than this row - return [expr {$column_value ne $next_row($column) }] -} - - -ad_proc -public db_dml {{-dbn ""} statement_name sql args } { - Do a DML statement. - -

- - args can be one of: -clobs, -blobs, -clob_files or -blob_files. See the db-api doc referenced below for more information. - - @param dbn The database name to use. If empty_string, uses the default database. - - @see /doc/db-api-detailed -} { - ad_arg_parser { clobs blobs clob_files blob_files bind } $args - set driverkey [db_driverkey $dbn] - - switch -- $driverkey { - postgresql { - set postgres_p 1 - } - oracle - - nsodbc - - default { - set postgres_p 0 - } - } - - # Query Dispatcher (OpenACS - ben) - set full_statement_name [db_qd_get_fullname $statement_name] - - # This "only one of..." check didn't exist in the PostgreSQL - # version, but it shouldn't't hurt anything: --atp@piskorski.com, - # 2003/04/08 06:19 EDT - - # Only one of clobs, blobs, clob_files, and blob_files is allowed. - # Remember which one (if any) is provided: - - set lob_argc 0 - set lob_argv [list] - set command "dml" - if { [info exists clobs] } { - set command "clob_dml" - set lob_argv $clobs - incr lob_argc - } - if { [info exists blobs] } { - set command "blob_dml" - set lob_argv $blobs - incr lob_argc - } - if { [info exists clob_files] } { - set command "clob_dml_file" - set lob_argv $clob_files - incr lob_argc - } - if { [info exists blob_files] } { - set command "blob_dml_file" - set lob_argv $blob_files - incr lob_argc - } - if { $lob_argc > 1 } { - error "Only one of -clobs, -blobs, -clob_files, or -blob_files may be specified as an argument to db_dml" - } - - if { ! $postgres_p } { - # Oracle: - db_with_handle -dbn $dbn db { - if { $lob_argc == 1 } { - # Bind :1, :2, ..., :n as LOBs (where n = [llength $lob_argv]) - set bind_vars [list] - for { set i 1 } { $i <= [llength $lob_argv] } { incr i } { - lappend bind_vars $i - } - eval [list db_exec "${command}_bind" $db $full_statement_name $sql 2 $bind_vars] $lob_argv - } else { - eval [list db_exec $command $db $full_statement_name $sql] $lob_argv - } - } - - } elseif {$command eq "blob_dml_file"} { - # PostgreSQL: - db_with_handle -dbn $dbn db { - # another ugly hack to avoid munging Tcl files. - # __lob_id needs to be set inside of a query (.xql) file for this - # to work. Say for example that you need to create a lob. In - # Oracle, you would do something like: - - # db_dml update_photo "update foo set bar = empty_blob() - # where bar = :bar - # returning foo into :1" -blob_files [list $file] - # for PostgreSQL we can do the equivalent by placing the following - # in a query file: - # update foo set bar = [set __lob_id [db_string get_id "select empty_lob()"]] - # where bar = :bar - - # __lob_id acts as a flag that signals that blob_dml_file is - # required, and it is also used to pass along the lob_id. It - # is unsert afterwards to avoid name clashes with other invocations - # of this routine. - # (DanW - Openacs) - - db_exec dml $db $full_statement_name $sql - if {[uplevel {info exists __lob_id}]} { - ns_pg blob_dml_file $db [uplevel {set __lob_id}] $blob_files - uplevel {unset __lob_id} - } - } - - } else { - # PostgreSQL: - db_with_handle -dbn $dbn db { - db_exec dml $db $full_statement_name $sql - } - } -} - - - - -ad_proc -public db_0or1row { - {-dbn ""} - -cache_key - {-cache_pool db_cache_pool} - statement_name - sql - args -} { - - Usage: -

- db_0or1row statement-name sql [ -bind bind_set_id | -bind bind_value_list ] \ - [ -column_array array_name | -column_set set_name ] - -
- -

Performs the SQL query sql. If a row is returned, sets variables - to column values (or a set or array populated if -column_array - or column_set is specified) and returns 1. If no rows are returned, - returns 0. - - @return 1 if variables are set, 0 if no rows are returned. If more than one row is returned, throws an error. - - @param dbn The database name to use. If empty_string, uses the default database. - @param cache_key Cache the result using given value as the key. Default is to not cache. - @param cache_pool Override the default db_cache_pool -} { - ad_arg_parser { bind column_array column_set } $args - - # Query Dispatcher (OpenACS - ben) - set full_statement_name [db_qd_get_fullname $statement_name] - - if { [info exists column_array] && [info exists column_set] } { - return -code error "Can't specify both column_array and column_set" - } - - if { [info exists column_array] } { - upvar 1 $column_array array_val - unset -nocomplain array_val - } - - if { [info exists column_set] } { - upvar 1 $column_set selection - } - - if { [info exists cache_key] } { - set values [ns_cache eval $cache_pool $cache_key { - db_with_handle -dbn $dbn db { - set selection [db_exec 0or1row $db $full_statement_name $sql] - } - - set values [list] - - if { $selection ne "" } { - for {set i 0} { $i < [ns_set size $selection] } {incr i} { - lappend values [list [ns_set key $selection $i] [ns_set value $selection $i]] - } - } - - set values - }] - - if { $values eq "" } { - set selection "" - } else { - set selection [ns_set create] - - foreach value $values { - ns_set put $selection [lindex $value 0] [lindex $value 1] - } - } - } else { - db_with_handle -dbn $dbn db { - set selection [db_exec 0or1row $db $full_statement_name $sql] - } - } - - if { $selection eq "" } { - return 0 - } - - if { [info exists column_array] } { - array set array_val [ns_set array $selection] - } elseif { ![info exists column_set] } { - for { set i 0 } { $i < [ns_set size $selection] } { incr i } { - uplevel 1 [list set [ns_set key $selection $i] [ns_set value $selection $i]] - } - } - - return 1 -} - - -ad_proc -public db_1row { args } { - - A wrapper for db_0or1row, which produces an error if no rows are returned. - - @param args Arguments to be passed to db_0or1row. Check db_0or1row proc doc - for details. - - @see db_0or1row - - @return 1 if variables are set. - -} { - if { ![uplevel ::db_0or1row $args] } { - return -code error "Query did not return any rows." - } -} - -if {[info commands ns_cache_transaction_begin] eq ""} { - # - # When the server has no support for ns_cache_transaction_*, - # provide dummy procs to avoid runtime "if" statements. - # - proc ns_cache_transaction_begin args {;} - proc ns_cache_transaction_commit args {;} - proc ns_cache_transaction_rollback args {;} -} - -ad_proc -public db_transaction {{ -dbn ""} transaction_code args } { - Usage: db_transaction transaction_code [ on_error { error_code_block } ] - - Executes transaction_code with transactional semantics. This means that either all of the database commands - within transaction_code are committed to the database or none of them are. Multiple db_transactions may be - nested (end transaction is transparently ns_db dml'ed when the outermost transaction completes).

- - To handle errors, use db_transaction {transaction_code} on_error {error_code_block}. Any error generated in - transaction_code will be caught automatically and process control will transfer to error_code_block - with a variable errmsg set. The error_code block can then clean up after the error, such as presenting a usable - error message to the user. Following the execution of error_code_block the transaction will be aborted. - If you want to explicitly abort the transaction, call db_abort_transaction - from within the transaction_code block or the error_code block.

- - Example 1:
- In this example, db_dml triggers an error, so control passes to the on_error block which prints a readable error. -

-    db_transaction {
-        db_dml test "nonsense"
-    } on_error {
-        ad_return_error "Error in blah/foo/bar" "The error was: $errmsg"
-    }
-    
- - Example 2:
- In this example, the second command, "nonsense" triggers an error. There is no on_error block, so the - transaction is immediately halted and aborted. -
-    db_transaction {
-        db_dml test {insert into footest values(1)}
-        nonsense
-        db_dml test {insert into footest values(2)}
-    }
-    
- - @param dbn The database name to use. If empty_string, uses the default database. -} { - upvar "#0" [db_state_array_name_is -dbn $dbn] db_state - - set syn_err "db_transaction: Invalid arguments. Use db_transaction { code } \[on_error { error_code_block }\] " - set arg_c [llength $args] - - if { $arg_c != 0 && $arg_c != 2 } { - # Either this is a transaction with no error handling or there must be an on_error { code } block. - error $syn_err - } elseif { $arg_c == 2 } { - # We think they're specifying an on_error block - if {[lindex $args 0] ne "on_error" } { - # Unexpected: they put something besides on_error as a connector. - error $syn_err - } else { - # Success! We got an on_error code block. - set on_error [lindex $args 1] - } - } - # Make the error message and database handle available to the on_error block. - upvar errmsg errmsg - - db_with_handle -dbn $dbn db { - # Preserve the handle, since db_with_handle kills it after executing - # this block. - set dbh $db - # Remember that there's a transaction happening on this handle. - if { ![info exists db_state(transaction_level,$dbh)] } { - set db_state(transaction_level,$dbh) 0 - } - set level [incr db_state(transaction_level,$dbh)] - if { $level == 1 } { - ns_db dml $dbh "begin transaction" - ns_cache_transaction_begin - } - } - # Execute the transaction code. - set errno [catch { - uplevel 1 $transaction_code - } errmsg] - incr db_state(transaction_level,$dbh) -1 - - set err_p 0 - switch -- $errno { - 0 { - # TCL_OK - } - 2 { - # TCL_RETURN - } - 3 { - # TCL_BREAK - Abort the transaction and do the break. - ns_db dml $dbh "abort transaction" - ns_cache_transaction_rollback - db_release_unused_handles -dbn $dbn - break - } - 4 { - # TCL_CONTINUE - just ignore. - } - default { - # TCL_ERROR or unknown error code: Its a real error. - set err_p 1 - } - } - - if { $err_p || [db_abort_transaction_p -dbn $dbn]} { - # An error was triggered or the transaction has been aborted. - db_abort_transaction -dbn $dbn - if { [info exists on_error] && $on_error ne "" } { - - if {"postgresql" eq [db_type]} { - - # JCD: with postgres we abort the transaction prior to - # executing the on_error block since there is nothing - # you can do to "fix it" and keeping it meant things like - # queries in the on_error block would then fail. - # - # Note that the semantics described in the proc doc - # are not possible to support on PostgreSQL. - - # DRB: I removed the db_release_unused_handles call that - # this patch included because additional aborts further - # down triggered an illegal db handle error. I'm going to - # have the code start a new transaction as well. If we - # don't, if a transaction fails and the on_error block - # fails, the on_error block DML will have been committed. - # Starting a new transaction here means that DML by both - # the transaction and on_error clause will be rolled back. - # On the other hand, if the on_error clause doesn't fail, - # any DML in that block will be committed. This seems more - # useful than simply punting ... - - ns_db dml $dbh "abort transaction" - ns_cache_transaction_rollback - ns_db dml $dbh "begin transaction" - ns_cache_transaction_begin - - } - - # An on_error block exists, so execute it. - - set errno [catch { - uplevel 1 $on_error - } on_errmsg] - - # Determine what do with the error. - set err_p 0 - switch -- $errno { - 0 { - # TCL_OK - } - - 2 { - # TCL_RETURN - } - 3 { - # TCL_BREAK - ns_db dml $dbh "abort transaction" - ns_cache_transaction_rollback - db_release_unused_handles - break - } - 4 { - # TCL_CONTINUE - just ignore. - } - default { - # TCL_ERROR or unknown error code: Its a real error. - set err_p 1 - } - } - - if { $err_p } { - # An error was generated from the $on_error block. - if { $level == 1} { - # We're at the top level, so we abort the transaction. - set db_state(db_abort_p,$dbh) 0 - ns_db dml $dbh "abort transaction" - ns_cache_transaction_rollback - } - # We throw this error because it was thrown from the error handling code that the programmer must fix. - error $on_errmsg $::errorInfo $::errorCode - } else { - # Good, no error thrown by the on_error block. - if { [db_abort_transaction_p -dbn $dbn] } { - # This means we should abort the transaction. - if { $level == 1 } { - set db_state(db_abort_p,$dbh) 0 - ns_db dml $dbh "abort transaction" - ns_cache_transaction_rollback - # We still have the transaction generated error. We don't want to throw it, so we log it. - ns_log Error "Aborting transaction due to error:\n$errmsg" - } else { - # Propagate the error up to the next level. - error $errmsg $::errorInfo $::errorCode - } - } else { - # The on_error block has resolved the transaction error. If we're at the top, commit and exit. - # Otherwise, we continue on through the lower transaction levels. - if { $level == 1} { - ns_db dml $dbh "end transaction" - ns_cache_transaction_commit - } - } - } - } else { - # There is no on_error block, yet there is an error, so we propagate it. - if { $level == 1 } { - set db_state(db_abort_p,$dbh) 0 - ns_db dml $dbh "abort transaction" - ns_cache_transaction_rollback - error "Transaction aborted: $errmsg" $::errorInfo $::errorCode - } else { - db_abort_transaction -dbn $dbn - error $errmsg $::errorInfo $::errorCode - } - } - } else { - # There was no error from the transaction code. - if { [db_abort_transaction_p -dbn $dbn] } { - # The user requested the transaction be aborted. - if { $level == 1 } { - set db_state(db_abort_p,$dbh) 0 - ns_db dml $dbh "abort transaction" - ns_cache_transaction_rollback - } - } elseif { $level == 1 } { - # Success! No errors and no requested abort. Commit. - ns_db dml $dbh "end transaction" - ns_cache_transaction_commit - } - } -} - - -ad_proc -public db_abort_transaction {{-dbn ""}} { - - Aborts all levels of a transaction. That is if this is called within - several nested transactions, all of them are terminated. Use this - instead of db_dml "abort" "abort transaction". - - @param dbn The database name to use. If empty_string, uses the default database. -} { - upvar "#0" [db_state_array_name_is -dbn $dbn] db_state - - db_with_handle -dbn $dbn db { - # We set the abort flag to true. - set db_state(db_abort_p,$db) 1 - } -} - - -ad_proc -private db_abort_transaction_p {{-dbn ""}} { - @param dbn The database name to use. If empty_string, uses the default database. -} { - upvar "#0" [db_state_array_name_is -dbn $dbn] db_state - - db_with_handle -dbn $dbn db { - if { [info exists db_state(db_abort_p,$db)] } { - return $db_state(db_abort_p,$db) - } else { - # No abort flag registered, so we assume everything is ok. - return 0 - } - } -} - - -ad_proc -public db_name {{-dbn ""}} { - - @return the name of the database as reported by the driver. - - @param dbn The database name to use. If empty_string, uses the default database. -} { - db_with_handle -dbn $dbn db { - set dbtype [ns_db dbtype $db] - } - return $dbtype -} - - -ad_proc -public db_get_username {{-dbn ""}} { - @return the username parameter from the driver section of the - first database pool for the dbn. - - @param dbn The database name to use. If empty_string, uses the default database. -} { - set pool [lindex [db_available_pools $dbn] 0] - return [ns_config "ns/db/pool/$pool" User] -} - -ad_proc -public db_get_password {{-dbn ""}} { - @return the password parameter from the driver section of the - first database pool for the dbn. - - @param dbn The database name to use. If empty_string, uses the default database. -} { - set pool [lindex [db_available_pools $dbn] 0] - return [ns_config "ns/db/pool/$pool" Password] -} - -ad_proc -public db_get_sql_user {{-dbn ""}} { - Oracle only. - -

- @return a valid Oracle user@database/password string to access a - database through sqlplus. - -

- This proc may well work for databases other than Oracle, - but its return value won't really be of any use. - - @param dbn The database name to use. If empty_string, uses the default database. -} { - set pool [lindex [db_available_pools $dbn] 0] - set datasource [ns_config "ns/db/pool/$pool" DataSource] - if { $datasource ne "" && ![string is space $datasource] } { - return "[ns_config ns/db/pool/$pool User]/[ns_config ns/db/pool/$pool Password]@$datasource" - } else { - return "[ns_config ns/db/pool/$pool User]/[ns_config ns/db/pool/$pool Password]" - } -} - -ad_proc -public db_get_pgbin {{-dbn ""}} { - PostgreSQL only. - -

- @return the pgbin parameter from the driver section of the first database pool. - - @param dbn The database name to use. If empty_string, uses the default database. -} { - set pool [lindex [db_available_pools $dbn] 0] - set driver [ns_config "ns/db/pool/$pool" Driver] - return [ns_config "ns/db/driver/$driver" pgbin] -} - - -ad_proc -public db_get_port {{-dbn ""}} { - PostgreSQL only. - -

- @return the port number from the first database pool. It assumes the - datasource is properly formatted since we've already verified that we - can connect to the pool. - It returns an empty string for an empty port value. - - @param dbn The database name to use. If empty_string, uses the default database. -} { - set pool [lindex [db_available_pools $dbn] 0] - set datasource [ns_config "ns/db/pool/$pool" DataSource] - set last_colon_pos [string last ":" $datasource] - if { $last_colon_pos == -1 } { - ns_log Error "datasource contains no \":\"? datasource = $datasource" - return "" - } - set first_colon_pos [string first ":" $datasource] - - if { $first_colon_pos == $last_colon_pos || ($last_colon_pos - $first_colon_pos) == 1 } { - # No port specified - return "" - } - - return [string range $datasource $first_colon_pos+1 $last_colon_pos-1] -} - - -ad_proc -public db_get_database {{-dbn ""}} { - PostgreSQL only. - -

- @return the database name from the first database pool. It assumes the - datasource is properly formatted since we've already verified that we - can connect to the pool. - - @param dbn The database name to use. If empty_string, uses the default database. -} { - set pool [lindex [db_available_pools $dbn] 0] - set datasource [ns_config "ns/db/pool/$pool" DataSource] - set last_colon_pos [string last ":" $datasource] - if { $last_colon_pos == -1 } { - ns_log Error "datasource contains no \":\"? datasource = $datasource" - return "" - } - return [string range $datasource $last_colon_pos+1 end] -} - - -ad_proc -public db_get_dbhost { - {-dbn ""} -} { - PostgreSQL only. - -

- @return the name of the database host from the first database pool. - It assumes the datasource is properly formatted since we've already - verified that we can connect to the pool. - - @param dbn The database name to use. If empty_string, uses the default database. -} { - set pool [lindex [db_available_pools $dbn] 0] - set datasource [ns_config "ns/db/pool/$pool" DataSource] - set first_colon_pos [string first ":" $datasource] - if { $first_colon_pos == -1 } { - ns_log Error "datasource contains no \":\"? datasource = $datasource" - return "" - } - return [string range $datasource 0 $first_colon_pos-1] -} - -ad_proc -public db_source_sql_file { - {-dbn ""} - {-callback apm_ns_write_callback} - file -} { - Sources a SQL file into Oracle (SQL*Plus format file) or - PostgreSQL (psql format file). - - @param dbn The database name to use. If empty_string, uses the default database. -} { - set proc_name {db_source_sql_file} - set driverkey [db_driverkey $dbn] - - switch -- $driverkey { - - oracle { - set user_pass [db_get_sql_user -dbn $dbn] - cd [file dirname $file] - set fp [open "|[file join $::env(ORACLE_HOME) bin sqlplus] $user_pass @$file" "r+"] - fconfigure $fp -buffering line - puts $fp "exit" - - while { [gets $fp line] >= 0 } { - # Don't bother writing out lines which are purely whitespace. - if { ![string is space $line] } { - apm_callback_and_log $callback "[ns_quotehtml $line]\n" - } - } - close $fp - } - - postgresql { - set file_name [file tail $file] - - set pguser [db_get_username] - if { $pguser ne "" } { - set pguser "-U $pguser" - } - - set pgport [db_get_port] - if { $pgport ne "" } { - set pgport "-p $pgport" - } - - set pgpass [db_get_password] - if { $pgpass ne "" } { - set pgpass "<<$pgpass" - } - - # DRB: Submitted patch was in error - the driver opens a -h hostname connection - # unless the hostname is localhost. We need to do the same here. The submitted - # patch checked for a blank hostname, which fails in the driver. Arguably the - # driver's wrong but a lot of non-OpenACS folks use it, and even though I'm the - # maintainer we shouldn't break existing code over such trivialities... - # GN: windows requires $pghost "-h ..." - - if { ([db_get_dbhost] eq "localhost" || [db_get_dbhost] eq "") - && $::tcl_platform(platform) ne "windows" - } { - set pghost "" - } else { - set pghost "-h [db_get_dbhost]" - } - - set errno [catch { - cd [file dirname $file] - set fp [open "|[file join [db_get_pgbin] psql] $pghost $pgport $pguser -f $file [db_get_database] $pgpass" "r"] - } errorMsg] - - if {$errno > 0} { - set error_found 1 - set error_lines $errorMsg - } else { - while { [gets $fp line] >= 0 } { - # Don't bother writing out lines which are purely whitespace. - if { ![string is space $line] } { - apm_callback_and_log $callback "[ns_quotehtml $line]\n" - } - } - - # PSQL dumps errors and notice information on stderr, and has no option to turn - # this off. So we have to chug through the "error" lines looking for those that - # really signal an error. - - set errno [ catch { - close $fp - } error] - - if { $errno == 2 } { - return $error - } - - # Just filter out the "NOTICE" lines, so we get the stack dump along with real - # ERRORs. This could be done with a couple of opaque-looking regexps... - - set error_found 0 - foreach line [split $error "\n"] { - if { [string first NOTICE $line] == -1 } { - append error_lines "$line\n" - set error_found [expr { $error_found - || [string first ERROR $line] != -1 - || [string first FATAL $line] != -1 } ] - } - } - } - - if { $error_found } { - return -code error -errorinfo $error_lines -errorcode $::errorCode $error_lines - } - - } - - nsodbc { - error "$proc_name is not supported for this database." - } - default { - error "$proc_name is not supported for this database." - } - } -} - -ad_proc -public db_load_sql_data { - {-dbn ""} - {-callback apm_ns_write_callback} - file -} { - Loads a CSV formatted file into a table using PostgreSQL's COPY command or - Oracle's SQL*Loader utility. The file name format consists of a sequence - number used to control the order in which tables are loaded, and the table - name with "-" replacing "_". This is a bit of a kludge but greatly speeds - the loading of large amounts of data, such as is done when various "ref-*" - packages are installed. - - @param dbn The database name to use. If empty_string, uses the default database. - @param file Filename in the format dd-table-name.ctl where 'dd' is a sequence number - used to control the order in which data is loaded. This file is an - RDBMS-specific data loader control file. - -} { - - switch [db_driverkey $dbn] { - - oracle { - set user_pass [db_get_sql_user -dbn $dbn] - set tmpnam [ad_tmpnam] - - set fd [open $file r] - set file_contents [read $fd] - close $fd - - set file_contents [subst $file_contents] - - set fd1 [open "${tmpnam}.ctl" w] - puts $fd1 $file_contents - close $fd1 - - cd [file dirname $file] - - set fd [open "|[file join $::env(ORACLE_HOME) bin sqlldr] userid=$user_pass control=$tmpnam" "r"] - - while { [gets $fd line] >= 0 } { - # Don't bother writing out lines which are purely whitespace. - if { ![string is space $line] } { - apm_callback_and_log $callback "[ns_quotehtml $line]\n" - } - } - close $fd - } - - postgresql { - set pguser [db_get_username] - if { $pguser ne "" } { - set pguser "-U $pguser" - } - - set pgport [db_get_port] - if { $pgport ne "" } { - set pgport "-p $pgport" - } - - set pgpass [db_get_password] - if { $pgpass ne "" } { - set pgpass "<<$pgpass" - } - - if { [db_get_dbhost] eq "localhost" || [db_get_dbhost] eq "" } { - set pghost "" - } else { - set pghost "-h [db_get_dbhost]" - } - - set fd [open $file r] - set copy_command [subst -nobackslashes [read $fd]] - close $fd - set copy_file [ns_mktemp [ad_tmpdir]/psql-copyfile-XXXXXX] - set fd [open $copy_file "CREAT EXCL WRONLY" 0600] - puts $fd $copy_command - close $fd - - if { $::tcl_platform(platform) eq "windows" } { - set fp [open "|[file join [db_get_pgbin] psql] -f $copy_file $pghost $pgport $pguser [db_get_database]" "r"] - } else { - set fp [open "|[file join [db_get_pgbin] psql] -f $copy_file $pghost $pgport $pguser [db_get_database] $pgpass" "r"] - } - - while { [gets $fp line] >= 0 } { - # Don't bother writing out lines which are purely whitespace. - if { ![string is space $line] } { - apm_callback_and_log $callback "[ns_quotehtml $line]\n" - } - } - - # PSQL dumps errors and notice information on stderr, and has no option to turn - # this off. So we have to chug through the "error" lines looking for those that - # really signal an error. - - set errno [ catch { - close $fp - } error] - - # remove the copy file. - file delete -force -- $copy_file - - if { $errno == 2 } { - return $error - } - - # Just filter out the "NOTICE" lines, so we get the stack dump along with real - # ERRORs. This could be done with a couple of opaque-looking regexps... - - set error_found 0 - foreach line [split $error "\n"] { - if { [string first NOTICE $line] == -1 } { - append error_lines "$line\n" - set error_found [expr { $error_found - || [string first ERROR $line] != -1 - || [string first FATAL $line] != -1 } ] - } - } - - if { $error_found } { - return -code error -errorinfo $error_lines -errorcode $::errorCode $error_lines - } - - } - - nsodbc { - error "db_load_sql_data is not supported for this database." - } - default { - error "db_load_sql_data is not supported for this database." - } - } -} - -ad_proc -public db_source_sqlj_file { - {-dbn ""} - {-callback apm_ns_write_callback} - file -} { - Oracle only. -

- Sources a SQLJ file using loadjava. - - @param dbn The database name to use. If empty_string, uses the default database. -} { - set user_pass [db_get_sql_user -dbn $dbn] - set fp [open "|[file join $::env(ORACLE_HOME) bin loadjava] -verbose -user $user_pass $file" "r"] - - # Despite the fact that this works, the text does not get written to the stream. - # The output is generated as an error when you attempt to close the input stream as - # done below. - while { [gets $fp line] >= 0 } { - # Don't bother writing out lines which are purely whitespace. - if { ![string is space $line] } { - apm_callback_and_log $callback "[ns_quotehtml $line]\n" - } - } - if { [catch { - close $fp - } errmsg] } { - apm_callback_and_log $callback "[ns_quotehtml $errmsg]\n" - } -} - - -ad_proc -public db_tables { - -pattern - {-dbn ""} -} { - @return a Tcl list of all the tables owned by the connected user. - - @param pattern Will be used as LIKE 'pattern%' to limit the number of tables returned. - - @param dbn The database name to use. If empty_string, uses the default database. - - @author Don Baccus (dhogaza@pacifier.com) - @author Lars Pind (lars@pinds.com) - - @change-log yon@arsdigita.com 20000711 changed to return lower case table names -} { - set proc_name {db_tables} - set driverkey [db_driverkey $dbn] - - switch -- $driverkey { - oracle { - set sql_table_names_with_pattern { - select lower(table_name) as table_name - from user_tables - where table_name like upper(:pattern) - } - set sql_table_names_without_pattern { - select lower(table_name) as table_name - from user_tables - } - } - - postgresql { - set sql_table_names_with_pattern { - select relname as table_name - from pg_class - where relname like lower(:pattern) and - relname !~ '^pg_' and relkind = 'r' - } - set sql_table_names_without_pattern { - select relname as table_name - from pg_class - where relname !~ '^pg_' and relkind = 'r' - } - } - - nsodbc - - default { - error "$proc_name is not supported for this database." - } - } - - set tables [list] - if { [info exists pattern] } { - db_foreach -dbn $dbn table_names_with_pattern \ - $sql_table_names_with_pattern { - lappend tables $table_name - } - } else { - db_foreach -dbn $dbn table_names_without_pattern \ - $sql_table_names_without_pattern { - lappend tables $table_name - } - } - - return $tables -} - - -ad_proc -public db_table_exists {{-dbn ""} table_name } { - @return 1 if a table with the specified name exists in the database, otherwise 0. - - @param dbn The database name to use. If empty_string, uses the default database. - - @author Don Baccus (dhogaza@pacifier.com) - @author Lars Pind (lars@pinds.com) -} { - set proc_name {db_table_exists} - set driverkey [db_driverkey $dbn] - - switch -- $driverkey { - oracle { - set n_rows [db_string -dbn $dbn table_count { - select count(*) from user_tables - where table_name = upper(:table_name) - }] - } - - postgresql { - set n_rows [db_string -dbn $dbn table_count { - select count(*) from pg_class - where relname = lower(:table_name) and - relname !~ '^pg_' and relkind = 'r' - }] - } - - nsodbc - - default { - error "$proc_name is not supported for this database." - } - } - - return $n_rows -} - - -ad_proc -public db_columns {{-dbn ""} table_name } { - @return a Tcl list of all the columns in the table with the given name. - - @param dbn The database name to use. If empty_string, uses the default database. - - @author Lars Pind (lars@pinds.com) - - @change-log yon@arsdigita.com 20000711 changed to return lower case column names -} { - set columns [list] - - # Works for both Oracle and PostgreSQL: - db_foreach -dbn $dbn table_column_names { - select lower(column_name) as column_name - from user_tab_columns - where table_name = upper(:table_name) - } { - lappend columns $column_name - } - - return $columns -} - - -ad_proc -public db_column_exists {{-dbn ""} table_name column_name } { - @return 1 if the row exists in the table, 0 if not. - - @param dbn The database name to use. If empty_string, uses the default database. - - @author Lars Pind (lars@pinds.com) -} { - set columns [list] - - # Works for both Oracle and PostgreSQL: - set n_rows [db_string -dbn $dbn column_exists { - select count(*) - from user_tab_columns - where table_name = upper(:table_name) - and column_name = upper(:column_name) - }] - - return [expr {$n_rows > 0}] -} - - -ad_proc -public db_column_type {{-dbn ""} {-complain:boolean} table_name column_name } { - - @return the Oracle Data Type for the specified column. - @return -1 if the table or column doesn't exist. - @return an error if table or column doesn't exist and -complain flag was specified - - @param dbn The database name to use. If empty_string, uses the default database. - @param complain throw an error when datatype is not found - - @author Yon Feldman (yon@arsdigita.com) - - @change-log 10 July, 2000: changed to return error - if column name doesn't exist - (mdettinger@arsdigita.com) - - @change-log 11 July, 2000: changed to return lower case data types - (yon@arsdigita.com) - - @change-log 11 July, 2000: changed to return error using the db_string default clause - (yon@arsdigita.com) - -} { - # Works for both Oracle and PostgreSQL: - set datatype [db_string -dbn $dbn column_type_select " - select data_type as data_type - from user_tab_columns - where upper(table_name) = upper(:table_name) - and upper(column_name) = upper(:column_name) - " -default "-1"] - if {$complain_p && $datatype == -1} { - error "Datatype for $table_name.$column_name not found." - } else { - return $datatype - } -} - - -ad_proc -public ad_column_type {{-dbn ""} table_name column_name } { - - @return 'numeric' for number type columns, 'text' otherwise - Throws an error if no such column exists. - - @param dbn The database name to use. If empty_string, uses the default database. - - @author Yon Feldman (yon@arsdigita.com) - -} { - set column_type [db_column_type -dbn $dbn $table_name $column_name] - - if { $column_type == -1 } { - return "Either table $table_name doesn't exist or column $column_name doesn't exist" - } elseif {$column_type ne "NUMBER" } { - return "numeric" - } else { - return "text" - } -} - - -ad_proc -public db_write_clob {{-dbn ""} statement_name sql args } { - @param dbn The database name to use. If empty_string, uses the default database. -} { - ad_arg_parser { bind } $args - set proc_name {db_write_clob} - set driverkey [db_driverkey $dbn] - - # TODO: Below, is db_qd_get_fullname necessary? Why this - # difference between Oracle and Postgres code? - # --atp@piskorski.com, 2003/04/09 10:00 EDT - - switch -- $driverkey { - oracle { - set full_statement_name [db_qd_get_fullname $statement_name] - db_with_handle -dbn $dbn db { - db_exec write_clob $db $full_statement_name $sql - } - } - - postgresql { - db_with_handle -dbn $dbn db { - db_exec write_clob $db $statement_name $sql - } - } - - nsodbc - - default { - error "$proc_name is not supported for this database." - } - } -} - - -ad_proc -public db_write_blob {{-dbn ""} statement_name sql args } { - @param dbn The database name to use. If empty_string, uses the default database. -} { - ad_arg_parser { bind } $args - set full_statement_name [db_qd_get_fullname $statement_name] - db_with_handle -dbn $dbn db { - db_exec_lob write_blob $db $full_statement_name $sql - } -} - - -ad_proc -public db_blob_get_file {{-dbn ""} statement_name sql args } { - @param dbn The database name to use. If empty_string, uses the default database. - -

- TODO: - This proc should probably be changed to take a final - file argument, only, rather than the current - args variable length argument list. Currently, it is - called only 4 places in OpenACS, and each place args, - if used at all, is always "-file $file". However, - such a change might break custom code... I'm not sure. - --atp@piskorski.com, 2003/04/09 11:39 EDT - -} { - ad_arg_parser { bind file args } $args - set proc_name {db_blob_get_file} - set driverkey [db_driverkey $dbn] - - set full_statement_name [db_qd_get_fullname $statement_name] - - switch -- $driverkey { - oracle { - db_with_handle -dbn $dbn db { - db_exec_lob blob_get_file $db $full_statement_name $sql $file - } - } - - postgresql { - db_with_handle -dbn $dbn db { - db_exec_lob blob_select_file $db $full_statement_name $sql $file - } - } - - nsodbc - - default { - error "$proc_name is not supported for this database." - } - } -} - - -ad_proc -public db_blob_get {{-dbn ""} statement_name sql args } { - PostgreSQL only. - - @param dbn The database name to use. If empty_string, uses the default database. -} { - ad_arg_parser { bind } $args - set proc_name {db_blob_get} - set driverkey [db_driverkey $dbn] - - switch -- $driverkey { - - postgresql { - set full_statement_name [db_qd_get_fullname $statement_name] - db_with_handle -dbn $dbn db { - set data [db_exec_lob blob_get $db $full_statement_name $sql] - } - return $data - } - - oracle { - set pre_sql $sql - set full_statement_name [db_qd_get_fullname $statement_name] - set sql [db_qd_replace_sql $full_statement_name $pre_sql] - - # insert Tcl variable values (borrowed from Dan W - olah) - if {$sql ne $pre_sql } { - set sql [uplevel 2 [list subst -nobackslashes $sql]] - } - - set data [db_string dummy_statement_name $sql] - return $data - } - - nsodbc - - default { - error "$proc_name is not supported for this database." - } - } -} - - -ad_proc -private db_exec_lob { - {-ulevel 2} - type - db - statement_name - pre_sql - {file ""} -} { - A helper procedure to execute a SQL statement, potentially binding - depending on the value of the $bind variable in the calling environment - (if set). -} { - set proc_name {db_exec_lob} - set driverkey [db_driverkey -handle_p 1 $db] - - # Note: db_exec_lob is marked as private and in the entire - # toolkit, is ONLY called from a few other procs defined in this - # same file. So we definitely could change it to take a -dbn - # switch and remove the passed in db handle altogether, and call - # 'db_driverkey -dbn' rather than 'db_driverkey -handle'. But, - # db_exec NEEDS 'db_driverkey -handle', so we might as well use it - # here too. --atp@piskorski.com, 2003/04/09 12:13 EDT - - # TODO: Using this as a wrapper for the separate _oracle and - # _postgresql versions of this proc is ugly. But also simplest - # and safest at this point, as it let me change as little as - # possible of those two relatively complex procs. - # --atp@piskorski.com, 2003/04/09 11:55 EDT - - switch -- $driverkey { - oracle { - set which_proc {db_exec_lob_oracle} - } - postgresql { - set which_proc {db_exec_lob_postgresql} - } - - nsodbc - - default { - error "$proc_name is not supported for this database." - } - } - - ns_log Debug "$proc_name: $which_proc -ulevel [expr {$ulevel +1}] $type $db $statement_name $pre_sql $file" - return [$which_proc -ulevel [expr {$ulevel +1}] $type $db $statement_name $pre_sql $file] -} - - -ad_proc -private db_exec_lob_oracle { - {-ulevel 2} - type - db - statement_name - pre_sql - {file ""} -} { - A helper procedure to execute a SQL statement, potentially binding - depending on the value of the $bind variable in the calling environment - (if set). -} { - set start_time [expr {[clock clicks -microseconds]/1000.0}] - - set sql [db_qd_replace_sql $statement_name $pre_sql] - - # insert Tcl variable values (OpenACS - Dan) - if {$sql ne $pre_sql } { - set sql [uplevel $ulevel [list subst -nobackslashes $sql]] - } - - set file_storage_p 0 - upvar $ulevel storage_type storage_type - - if {[info exists storage_type] && $storage_type eq "file"} { - set file_storage_p 1 - set original_type $type - set qtype 1row - ns_log Debug "db_exec_lob: file storage in use" - } else { - set qtype $type - ns_log Debug "db_exec_lob: blob storage in use" - } - - set errno [catch { - upvar bind bind - - # Below, note that 'ns_ora blob_get_file' takes 3 parameters, - # while 'ns_ora write_blob' takes only 2. So if file is empty - # string (which it always will/should be for $qtype - # write_blob), we must not pass any 3rd parameter to the - # ns_ora command: --atp@piskorski.com, 2003/04/09 15:10 EDT - - if { [info exists bind] && [llength $bind] != 0 } { - if { [llength $bind] == 1 } { - if { $file eq "" } { - # gn: not sure, why the eval was ever needed (4 times) - set selection [eval [list ns_ora $qtype $db -bind $bind $sql]] - } else { - set selection [eval [list ns_ora $qtype $db -bind $bind $sql $file]] - } - - } else { - set bind_vars [ns_set create] - foreach { name value } $bind { - ns_set put $bind_vars $name $value - } - if { $file eq "" } { - set selection [eval [list ns_ora $qtype $db -bind $bind_vars $sql]] - } else { - set selection [eval [list ns_ora $qtype $db -bind $bind_vars $sql $file]] - } - } - - } else { - if { $file eq "" } { - set selection [uplevel $ulevel [list ns_ora $qtype $db $sql]] - } else { - set selection [uplevel $ulevel [list ns_ora $qtype $db $sql $file]] - } - } - - if {$file_storage_p} { - set content [ns_set value $selection 0] - for {set i 0} {$i < [ns_set size $selection]} {incr i} { - set name [ns_set key $selection $i] - if {$name eq "content"} { - set content [ns_set value $selection $i] - } - } - - switch -- $original_type { - - blob_get_file { - if {[file exists $content]} { - file copy -- $content $file - return $selection - } else { - error "file: $content doesn't exist" - } - } - - write_blob { - - if {[file exists $content]} { - set ofp [open $content r] - fconfigure $ofp -encoding binary - ns_writefp $ofp - close $ofp - return $selection - } else { - error "file: $content doesn't exist" - } - } - } - } else { - return $selection - } - - } error] - - ds_collect_db_call $db $type $statement_name $sql $start_time $errno $error - if { $errno == 2 } { - return $error - } - - return -code $errno -errorinfo $::errorInfo -errorcode $::errorCode $error -} - - -ad_proc -private db_exec_lob_postgresql { - {-ulevel 2} - type - db - statement_name - pre_sql - {file ""} -} { - A helper procedure to execute a SQL statement, potentially binding - depending on the value of the $bind variable in the calling environment - (if set). - - Low level replacement for db_exec which emulates blob handling. - -} { - set start_time [expr {[clock clicks -microseconds]/1000.0}] - - # Query Dispatcher (OpenACS - ben) - set sql [db_qd_replace_sql $statement_name $pre_sql] - - # insert Tcl variable values (OpenACS - Dan) - if {$sql ne $pre_sql } { - set sql [uplevel $ulevel [list subst -nobackslashes $sql]] - } - # create a function definition statement for the inline code - # binding is emulated in tcl. (OpenACS - Dan) - - set errno [catch { - upvar bind bind - if { [info exists bind] && [llength $bind] != 0 } { - if { [llength $bind] == 1 } { - set bind_vars [list] - set len [ns_set size $bind] - for {set i 0} {$i < $len} {incr i} { - lappend bind_vars [ns_set key $bind $i] \ - [ns_set value $bind $i] - } - set lob_sql [db_bind_var_substitution $sql $bind_vars] - } else { - set lob_sql [db_bind_var_substitution $sql $bind] - } - } else { - set lob_sql [uplevel $ulevel [list db_bind_var_substitution $sql]] - } - - # get the content - asssume it is in column 0, or optionally it can - # be returned as "content" with the storage type indicated by the - # "storage_type" column. - - set selection [ns_db 1row $db $lob_sql] - set content [ns_set value $selection 0] - for {set i 0} {$i < [ns_set size $selection]} {incr i} { - set name [ns_set key $selection $i] - if {$name eq "storage_type"} { - set storage_type [ns_set value $selection $i] - } elseif {$name eq "content"} { - set content [ns_set value $selection $i] - } - } - - # this is an ugly hack, but it allows content to be written - # to a file/connection if it is stored as a lob or if it is - # stored in the content-repository as a file. (DanW - Openacs) - - switch -- $type { - - blob_get { - - if {[info exists storage_type]} { - switch -- $storage_type { - file { - if {[file exists $content]} { - set ifp [open $content r] - - # DRB: this could be made faster by setting the buffersize - # to the size of the file, but for very large files allocating - # that much more memory on top of that needed by Tcl for storage - # of the data might not be wise. - - fconfigure $ifp -translation binary - - set data [read $ifp] - close $ifp - return $data - } else { - error "file: $content doesn't exist" - } - } - - lob { - if {[regexp {^[0-9]+$} $content match]} { - return [ns_pg blob_get $db $content] - } else { - error "invalid lob_id: should be an integer" - } - } - - default { - error "invalid storage type" - } - } - } elseif {[file exists $content]} { - set ifp [open $content r] - fconfigure $ifp -translation binary - set data [read $ifp] - close $ifp - return $data - } elseif {[regexp {^[0-9]+$} $content match]} { - return [ns_pg blob_get $db $content] - } else { - error "invalid query" - } - } - - blob_select_file { - - if {[info exists storage_type]} { - switch -- $storage_type { - file { - if {[file exists $content]} { - file copy -- $content $file - } else { - error "file: $content doesn't exist" - } - } - - lob { - if {[regexp {^[0-9]+$} $content match]} { - ns_pg blob_select_file $db $content $file - } else { - error "invalid lob_id: should be an integer" - } - } - - default { - error "invalid storage type" - } - } - } elseif {[file exists $content]} { - file copy -- $content $file - } elseif {[regexp {^[0-9]+$} $content match]} { - ns_pg blob_select_file $db $content $file - } else { - error "invalid query" - - # TODO: Page /file-storage/download-archive/index - # fails here on cvs head both before and after my - # mult-db db_* API work, I don't know why. See bug: - # http://openacs.org/bugtracker/openacs/com/file-storage/bug?bug%5fnumber=427 - # --atp@piskorski.com, 2003/04/09 18:04 EDT - } - } - - write_blob { - - if {[info exists storage_type]} { - switch -- $storage_type { - file { - if {[file exists $content]} { - set ofp [open $content r] - fconfigure $ofp -encoding binary - ns_writefp $ofp - close $ofp - } else { - error "file: $content doesn't exist" - } - } - - text { - ns_write $content - } - - lob { - if {[regexp {^[0-9]+$} $content match]} { - ns_pg blob_write $db $content - } else { - error "invalid lob_id: should be an integer" - } - } - - default { - error "invalid storage type" - } - } - } elseif {[file exists $content]} { - set ofp [open $content r] - fconfigure $ofp -encoding binary - ns_writefp $ofp - close $ofp - } elseif {[regexp {^[0-9]+$} $content match]} { - ns_pg blob_write $db $content - } else { - ns_write $content - } - } - } - - return - - } error] - - set errinfo $::errorInfo - set errcode $::errorCode - - ds_collect_db_call $db 0or1row $statement_name $sql $start_time $errno $error - - if { $errno == 2 } { - return $error - } - - return -code $errno -errorinfo $errinfo -errorcode $errcode $error -} - -ad_proc -public db_flush_cache { - {-cache_key_pattern *} - {-cache_pool db_cache_pool} -} { - - Flush the given cache of entries with keys that match the given pattern. - - @param cache_key_pattern The "string match" pattern used to flush keys (default is to flush all entries) - @param cache_pool The pool to flush (default is to flush db_cache_pool) - @author Don Baccus (dhogasa@pacifier.com) - -} { - # - # If the key pattern has meta characters, iterate over the entries. - # Otherwise, make a direct lookup, without retrieving the all keys - # from the cache, which can cause large mutex lock times. - # - if {[regexp {[*\]\[]} $cache_key_pattern]} { - foreach key [ns_cache names $cache_pool $cache_key_pattern] { - ns_cache flush $cache_pool $key - } - } else { - ns_cache flush $cache_pool $cache_key_pattern - } -} - -ad_proc -public db_bounce_pools {{-dbn ""}} { - @return Call ns_db bouncepool on all pools for the named database. - @param dbn The database name to use. Uses the default database if not supplied. -} { - foreach pool [db_available_pools $dbn] { - ns_db bouncepool $pool - } -} - -# Local variables: -# mode: tcl -# tcl-indent-level: 4 -# indent-tabs-mode: nil -# End: