Index: openacs-4/packages/acs-tcl/tcl/00-icanuse-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/acs-tcl/tcl/Attic/00-icanuse-procs.tcl,v diff -u -N -r1.1.2.4 -r1.1.2.5 --- openacs-4/packages/acs-tcl/tcl/00-icanuse-procs.tcl 3 May 2020 16:32:55 -0000 1.1.2.4 +++ openacs-4/packages/acs-tcl/tcl/00-icanuse-procs.tcl 8 May 2020 13:12:58 -0000 1.1.2.5 @@ -58,7 +58,7 @@ # # Register features provided by the server, available to all packages. -# These features can typically not easily be provided by compatiblity +# These features can typically not easily be provided by compatibility # routines. # # Note that packages can register some optional features during bootup Index: openacs-4/packages/acs-tcl/tcl/01-database-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/acs-tcl/tcl/Attic/01-database-procs.tcl,v diff -u -N -r1.1.2.7 -r1.1.2.8 --- openacs-4/packages/acs-tcl/tcl/01-database-procs.tcl 3 May 2020 16:32:10 -0000 1.1.2.7 +++ openacs-4/packages/acs-tcl/tcl/01-database-procs.tcl 8 May 2020 13:12:58 -0000 1.1.2.8 @@ -1677,161 +1677,184 @@ } } +ad_proc -private db_multirow_helper {} { -proc db_multirow_helper {} { + Helper function for db_multirow, performing the actual DB queries. + +} { 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 + set local_counter -1 + # + # Make sure 'next_row' array doesn't exist. + # + # The variables 'this_row' and 'next_row' 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 - # 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 + # + # Execute the query in one sweep, similar to 'db_foreach'. + # + set __selections [uplevel 1 [list db_list_of_ns_sets -dbn $dbn $full_statement_name $sql]] + if {[llength $__selections] == 0} { + return + } - set more_rows_p 1 - while { 1 } { + set more_rows_p 1 + while { 1 } { + incr local_counter - if { $more_rows_p } { - set more_rows_p [db_getrow $db $selection] - } else { - break - } + if { $more_rows_p } { + set more_rows_p [expr {$local_counter < [llength $__selections]}] + set selection [lindex $__selections $local_counter] + } 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. + # + # Setup the 'columns' part, now that we know the columns + # in the result set in the first iteration (when + # $local_counter == 0). + # + 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 + # In case the '-unclobber' switch is specified, save + # variables 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 { [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. + if { $code_block eq "" } { + # + # There is 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 + } - # 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 + 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 + if {[info exists next_row]} { + set this_row $next_row + } - # 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 } { + set next_row [ns_set array $selection] + } + + # Process the row + if { [info exists this_row] } { + # Pull values from this_row into local variables + foreach name [dict keys $this_row] { + upvar 1 $name column_value + set column_value [dict get $this_row $name] } - # 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] - } + # Initialize the "extend" columns to the empty string + foreach column_name $extend { + upvar 1 $column_name column_value + set column_value "" } - # 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) - } + # Execute the code block + set errno [catch { uplevel 1 $code_block } error] + #ns_log notice ".... code block returns errno $errno" - # Initialize the "extend" columns to the empty string - foreach column_name $extend { - upvar 1 $column_name column_value - set column_value "" + # 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 } - - # 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" - } + 1 { + # TCL_ERROR + error $error $::errorInfo $::errorCode } - - # 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 + 2 { + # TCL_RETURN + error "Cannot return from inside a db_multirow loop" } + 3 { + # TCL_BREAK + #### CHECK? #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 } } @@ -1861,6 +1884,7 @@ } } + ad_proc -public db_multirow { -local:boolean -append:boolean @@ -1922,7 +1946,7 @@ 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 + in a function or similar situations). Use the -upvar_level switch to specify how many levels up the variable should be set.

@@ -2042,7 +2066,6 @@ 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 @@ -3810,8 +3833,6 @@ } return $sql } -} else { - ns_log notice "=====================================================" } # Local variables: Index: openacs-4/packages/acs-tcl/tcl/test/db-proc-test-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/acs-tcl/tcl/test/Attic/db-proc-test-procs.tcl,v diff -u -N --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/acs-tcl/tcl/test/db-proc-test-procs.tcl 8 May 2020 13:12:58 -0000 1.1.2.1 @@ -0,0 +1,102 @@ +ad_library { + + test db_* procs + @author Keith Paskett + @creation-date 2020-04-25 + +} + +aa_register_case \ + -cats {api db smoke} \ + -error_level "error" \ + -procs { + ::db_transaction ::db_multirow + } \ + db__transaction_bug_3440 { + + This tests for the case when a db_ call in a db_multirow in a + db_transaction, breaks out of the transaction. + + } { + # Not using -rollback option because we don't want to start out in a db_transaction + aa_run_with_teardown \ + -test_code { + + aa_log "Test Begin" + aa_log "Create fixture" + + set dml "CREATE TABLE test_tbl1 (id serial, value text)" + db_dml noxql $dml + + aa_log "Start test section 1" + + db_transaction { + # + # Insert an element to the test table + # + set dml "INSERT INTO test_tbl1 (value) values('val1') RETURNING id;" + set row_id [db_string noxql $dml] + set sql_row_id "SELECT value FROM test_tbl1 where id = :row_id" + + # + # Retrieve it once. + # + set sql "SELECT value FROM test_tbl1 where id = :row_id" + set res1 [db_string noxql $sql -default "None"] + aa_equals "New row exists before db_multirow call" $res1 "val1" + + # + # Run a query returning more than one row in a + # "db_foreach" loop, performing as well SQL queries + # and try to get value inserted above after the loop. + # + set sql "SELECT privilege FROM acs_privileges limit 2" + db_foreach noxql $sql { + set temp1 [db_string noxql "SELECT 1 FROM dual"] + aa_log "... db_foreach got '$temp1'" + } + set res2 [db_string noxql $sql_row_id -default "None"] + aa_equals "New row exists after db_foreach" $res2 "val1" + + # + # Run a query returning a single row in a + # "db_multirow" loop, performing as well SQL queries + # and try to get value inserted above after the loop. + # + set sql "SELECT privilege FROM acs_privileges limit 1" + db_multirow -local mrow noxql $sql { + # Code executed for each row. Set extended columns, etc. + set temp1 [db_string noxql "SELECT 1 FROM dual"] + } + set res2 [db_string noxql $sql_row_id -default "None"] + aa_equals "New row exists after db_multirow with 1 tuple" $res2 "val1" + + # + # Run a query returning more than a row in a + # "db_multirow" loop, performing as well SQL queries + # and try to get value inserted above after the loop. + # + set sql "SELECT privilege FROM acs_privileges limit 2" + db_multirow -local mrow noxql $sql { + # Code executed for each row. Set extended columns, etc. + set temp1 [db_string noxql "SELECT 1 FROM dual"] + } + + # Asof acs-tcl 5.10.0d31 + # If db_multirow above is limited to 1 row, the following succeeds. + # If the db_multirow has more than 1 row, it fails. + set res2 [db_string noxql $sql_row_id -default "None"] + aa_equals "New row exists after db_multirow with 2 tuples" $res2 "val1" + + } + + aa_log "End test section 1" + aa_log "Test End" + + } -teardown_code { + set dml "DROP TABLE test_tbl1" + db_dml noxql $dml + # this is an optional parameter if there is code that should run to clean things up. + # It will run whether or not the -test_code succeeds, and runs after the DB transaction has been rolled back. + } + }; # db_transaction_bug_3440