Index: openacs-4/contrib/misc/db-cache-procs.tcl
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/contrib/misc/db-cache-procs.tcl,v
diff -u -N
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ openacs-4/contrib/misc/db-cache-procs.tcl 21 Feb 2003 17:33:07 -0000 1.1
@@ -0,0 +1,508 @@
+ad_library {
+
+ Greenpeace Planet customized database procs
+
+ @author Don Baccus (dhogaza@pacifier.com)
+}
+
+# DRB: Notes on caching strategy in Planet (2002-06-28)
+
+# These versions of the database API implement caching of results, flagged by the
+# inclusion of a "-cache" argument in the call. They're written to take an optional
+# cache pool name but as of this writing Planet does not make use of this facility and
+# will fail if you do so without providing for selective flushing of cache contents.
+
+# In most cases use of the cached query results is indistinguishable from the use of
+# query results taken from the database. The exception is in the multirow database
+# call.
+
+# When a multirow datasource is cached, all of the columns listed as arguments to "-extend",
+# along with those created by the query itself, are cached for each row in the resultset after
+# executiing the code block associated with the multirow (if any).
+
+# 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.
+
+# The cache is currently flushed every five minutes, a compromise between performance and
+# the desire to make site content truly dynamic. Selective cache flushing based on DML and
+# PL/SQL calls to create or update content would be much better, but involves solving the
+# multiserver cache synch issue as Planet runs on two web servers.
+
+# This strategy is not adequate for general use but the details as to how caching is
+# implemented, particular for db_multirow, db_0or1row and db_string may be of interest
+# since they're a bit tricky.
+
+# Ignore the "cache_pool" stuff. Originally I was planning to divvy content types
+# (news, multimedia, etc) into their own cache pools. Then we'd just flush the appropriate
+# pool when new content of that type was added. However I wasn't able to come up with a
+# quick way to synchronize cached content on both of the webservers that run Planet so I
+# abandoned that approach. Any general solution for caching will have to address the
+# multiple webserver issue.
+
+# (Non-Greenpeace users who only have a single webserver may find it useful)
+
+# You must initialize the cache pools yourself in an appropriate *-init.tcl file. As
+# mentioned above the cache pool used in Greenpeace Planet is set to time out contents
+# every five minutes. Here's our init code:
+
+# ns_cache create db_cache_pool -size 2000000
+# ad_schedule_proc 300 db_flush_cache_pool
+
+
+ad_proc -public db_list {
+ -cache:boolean
+ {-cache_pool db_cache_pool}
+ statement_name
+ sql
+ args
+} {
+
+ Returns a list containing the first column of each row returned by the SQL query $sql.
+
+} {
+ ad_arg_parser { bind } $args
+
+ # Query Dispatcher (OpenACS - SDW)
+ set full_statement_name [db_qd_get_fullname $statement_name]
+
+ if { $cache_p } {
+ set key "db_list::$sql"
+
+ set pair [ns_cache eval $cache_pool $key {
+ db_with_handle 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 [list [ns_time] $result]
+ }]
+
+ set result [lindex $pair 1]
+ } else {
+ # Can't use db_foreach here, since we need to use the ns_set directly.
+ db_with_handle 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
+}
+
+proc_doc db_list_of_lists {
+ -cache:boolean
+ {-cache_pool db_cache_pool}
+ statement_name
+ sql
+ args
+} {
+
+ Returns a list containing lists of the values in each column of each row returned by the SQL query $sql.
+
+} {
+ ad_arg_parser { bind } $args
+
+ # Query Dispatcher (OpenACS - SDW)
+ set full_statement_name [db_qd_get_fullname $statement_name]
+
+ if { $cache_p } {
+ set key "db_list_of_lists::$sql"
+
+ set pair [ns_cache eval $cache_pool $key {
+ db_with_handle db {
+ set selection [db_exec select $db $full_statement_name $sql]
+
+ set result [list]
+
+ while { [db_getrow $db $selection] } {
+ set this_result [list]
+ for { set i 0 } { $i < [ns_set size $selection] } { incr i } {
+ lappend this_result [ns_set value $selection $i]
+ }
+ lappend result $this_result
+ }
+ }
+
+ return [list [ns_time] $result]
+ }]
+
+ set result [lindex $pair 1]
+ } else {
+ db_with_handle db {
+ set selection [db_exec select $db $full_statement_name $sql]
+
+ set result [list]
+
+ while { [db_getrow $db $selection] } {
+ set this_result [list]
+ for { set i 0 } { $i < [ns_set size $selection] } { incr i } {
+ lappend this_result [ns_set value $selection $i]
+ }
+ lappend result $this_result
+ }
+ }
+ }
+
+ return $result
+}
+
+proc db_multirow_helper {} {
+ uplevel 1 {
+ if { !$append_p } {
+ set counter 0
+ set columns [list]
+ }
+
+ db_with_handle db {
+ set selection [db_exec select $db $full_statement_name $sql]
+
+ while { [db_getrow $db $selection] } {
+
+ if { $counter == 0 } {
+ for { set i 0 } { $i < [ns_set size $selection] } { incr i } {
+ lappend columns [ns_set key $selection $i]
+ }
+ set columns [concat $columns $extend]
+ }
+
+ if { [empty_string_p $code_block] } {
+ # No code block - pull values directly into the var_name array.
+ upvar $level_up "$var_name:[expr {$counter+1}]" array_val
+ 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 {
+ # Pull values from the query into local variables
+ for { set i 0 } { $i < [ns_set size $selection] } { incr i } {
+ upvar 1 [ns_set key $selection $i] column_value
+ set column_value [ns_set value $selection $i]
+ }
+
+ # 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
+ global errorInfo errorCode
+ 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.
+ upvar $level_up "$var_name:[expr {$counter + 1}]" array_val
+ foreach column_name $columns {
+ upvar 1 $column_name column_value
+ set array_val($column_name) $column_value
+ }
+ }
+ incr counter
+ set array_val(rownum) $counter
+ }
+ }
+ }
+}
+
+ad_proc -public db_multirow {
+ -local:boolean
+ -append:boolean
+ -cache:boolean
+ {-cache_pool db_cache_pool}
+ {-extend {}}
+ var_name
+ statement_name
+ sql
+ args } {
+
+ Performs the SQL query $sql, saving results in variables of the form
+ var_name:1
, var_name:2
, etc,
+ and setting var_name:rowcount
to the total number
+ of rows. 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.
+
+ If "cache" is set true, cache the array that results from the query *and*
+ any code block for future use. The key for caching is the query itself
+ so don't use any bindvars in queries you intend to cache.
+
+ The cache_pool parameter can probably (?) be used to flush, say, all news
+ queries when a new news story is posted but that's an exercise for launch
+ day if all goes well. Ever the optimist ...
+
+} {
+ # Query Dispatcher (OpenACS - ben)
+ set full_statement_name [db_qd_get_fullname $statement_name]
+
+ if { $local_p } {
+ set level_up 1
+ } else {
+ set level_up \#[template::adp_level]
+ }
+
+ ad_arg_parser { bind args } $args
+
+ if { $cache_p && $append_p } {
+ return -code error "Can't append and cache a multirow datasource simultaneously"
+ }
+
+ # 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 { ![string equal [lindex $args 1] "if_no_rows"] \
+ && ![string equal [lindex $args 1] "else"] } {
+ return -code error "Expected if_no_rows as second-to-last argument"
+ }
+ set code_block [lindex $args 0]
+ set if_no_rows_code_block [lindex $args 2]
+ } 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 { $cache_p } {
+ set key "db_multirow::$sql"
+
+ set pair [ns_cache eval $cache_pool $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 [ns_time] [list $counter $columns $values]]
+ }]
+
+ set counter [lindex [lindex $pair 1] 0]
+ set columns [lindex [lindex $pair 1] 1]
+ set values [lindex [lindex $pair 1] 2]
+
+ 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 db_0or1row {
+ -cache:boolean
+ {-cache_pool db_cache_pool}
+ statement_name
+ sql
+ args
+} {
+
+ Performs the SQL query $sql, setting variables to column values. Returns 1 if a row is returned, or 0 if no row is returned.
+
+} {
+ 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
+ if { [info exists array_val] } {
+ unset array_val
+ }
+ }
+
+ if { [info exists column_set] } {
+ upvar 1 $column_set selection
+ }
+
+ if { $cache_p } {
+ set key "db_0or1row::$sql"
+
+ set pair [ns_cache eval $cache_pool $key {
+ db_with_handle db {
+ set selection [db_exec 0or1row $db $full_statement_name $sql]
+ }
+
+ set cached_values [list]
+
+ if { ![string equal $selection ""] } {
+ for {set i 0} { $i < [ns_set size $selection] } {incr i} {
+ lappend cached_values [list [ns_set key $selection $i] [ns_set value $selection $i]]
+ }
+ }
+
+ return [list [ns_time] $cached_values]
+ }]
+
+ set cached_values [lindex $pair 1]
+
+ if { [string equal $cached_values ""] } {
+ set selection ""
+ } else {
+ set selection [ns_set create]
+
+ foreach cached_value $cached_values {
+ ns_set put $selection [lindex $cached_value 0] [lindex $cached_value 1]
+ }
+ }
+ } else {
+ db_with_handle db {
+ set selection [db_exec 0or1row $db $full_statement_name $sql]
+ }
+ }
+
+ if { [empty_string_p $selection] } {
+ return 0
+ }
+
+ if { [info exists column_array] } {
+ for { set i 0 } { $i < [ns_set size $selection] } { incr i } {
+ set array_val([ns_set key $selection $i]) [ns_set value $selection $i]
+ }
+ } elseif { ![info exists column_set] } {
+ for { set i 0 } { $i < [ns_set size $selection] } { incr i } {
+ upvar 1 [ns_set key $selection $i] value
+ set value [ns_set value $selection $i]
+ }
+ }
+
+ return 1
+}
+
+ad_proc db_1row { args } {
+
+ Performs the SQL query $sql, setting variables to column values. Raises an error if no rows are returned.
+
+} {
+ if { ![uplevel db_0or1row $args] } {
+ return -code error "Query did not return any rows."
+ }
+}
+
+
+
+ad_proc db_string {
+ -cache:boolean
+ {-cache_pool db_cache_pool}
+ statement_name
+ sql
+ args
+} {
+
+ Returns 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).
+
+} {
+ # Query Dispatcher (OpenACS - ben)
+ set full_name [db_qd_get_fullname $statement_name]
+
+ ad_arg_parser { default bind } $args
+
+ if { $cache_p } {
+ set key "db_string::$sql"
+
+ set pair [ns_cache eval $cache_pool $key {
+ db_with_handle db {
+ set selection [db_exec 0or1row $db $full_name $sql]
+ }
+
+ if { [string equal $selection ""] } {
+ return [list [ns_time] ""]
+ } else {
+ return [list [ns_time] [list [ns_set key $selection 0] [ns_set value $selection 0]]]
+ }
+ }]
+
+ set cached_value [lindex $pair 1]
+
+ if { [string equal $cached_value ""] } {
+ set selection ""
+ } else {
+ set selection [ns_set create]
+ ns_set put $selection [lindex $cached_value 0] [lindex $cached_value 1]
+ }
+ } else {
+ db_with_handle db {
+ set selection [db_exec 0or1row $db $full_name $sql]
+ }
+ }
+
+ if { [empty_string_p $selection] } {
+ if { [info exists default] } {
+ return $default
+ }
+ return -code error "Selection did not return a value, and no default was provided"
+ }
+ return [ns_set value $selection 0]
+}
+
+ad_proc -public db_flush_cache_pool {
+ {-cache_pool db_cache_pool}
+} {
+
+ Flush the values for the given pool
+
+ @author Don Baccus (dhogasa@pacifier.com)
+} {
+ foreach key [ns_cache names $cache_pool] {
+ ns_cache flush $cache_pool $key
+ }
+}