Forum OpenACS Q&A: db_dml in a foreach loop isn't working

I am working on a skinny table implementation where I am placing db_dmls in a foreach loop, but I get an error if the loop exceeds one iteration. My test form has two elements, and the inserts work if I:

a) have a value for the first one, and leave the second as an empty string
b) have a value for the second, and leave the first as an empty string

It breaks when both elements have a value.


set sql_query "
select   adt.adt, 
         adt.sqltype,
         w.widget, 
         a.attribute_name,
         a.attribute_id,
         a.pretty_name,
         a.presentation_options, 
         a.default_value,
         a.min_n_values,
         a.mapping_object_type_id
from     es_skinny_adt adt, 
         es_form_widget w, 
         es_skinny_attribute a
where    a.form_number = 1
  and    a.widget_id = w.widget_id
  and    a.adt_id = adt.adt_id
  and    a.object_type_id = $object_type_id
order by a.form_sort_order
"

db_foreach get_attributes $sql_query {
	    
	element create object $attribute_name 
	    -label "$pretty_name" 
	    -datatype text 
	    -widget $widget 
	    -options "$element_options" 
	    $optional


	if { [form is_request object] } {

	    element set_properties object $attribute_name -value
$default_value
	    
	}


	if { [form is_valid object] &&
	     ![string equal [set $attribute_name] ""]} {
		# don't allow all null values

	    # init values
	    set int_value [db_null]
	    set real_value [db_null]
	    set bool_value [db_null]
	    set text_value [db_null]
	    set timestamp_value [db_null]
	    
	    # set actual value
	    set ${sqltype}_value [element::get_value object $attribute_name]
	    	    
	    lappend data_insert_sql_list "
            insert into es_skinny_data (
              object_id,
              attribute_id,
              int_value,       
              real_value,
              bool_value,
              text_value,
              timestamp_value
            ) values (
              :object_id,
              :attribute_id,
              :int_value,
              :real_value,
              :bool_value,
              :text_value,
              :timestamp_value
            )"
	}
}

if { [form is_valid object] } {

    set object_insert_sql "
    insert into es_skinny_object (
      object_id,
      object_type_id,
      name,
      package_id
    ) values (
      :object_id,
      :object_type_id,
      :name,
      :package_id
    )"

    db_transaction {
	db_dml insert_object $object_insert_sql

	### error occurs here ###
	foreach data_insert_sql $data_insert_sql_list {
	    db_dml insert_data $data_insert_sql
	}
    }
	

  template::forward .
}


Collapse
Posted by Dan Wickstrom on
What's the error message?
Collapse
Posted by James Thornton on
Database operation "dml" failed
        while executing
    "ns_pg_bind dml nsdb0 {
                insert into es_skinny_data (
                  object_id,
                  attribute_id,
                  int_value,       
     ..."
        ("uplevel" body line 1)
        invoked from within
    "uplevel $ulevel [list ns_pg_bind $type $db $sql"
        invoked from within
    "db_exec dml $db $full_statement_name $sql"
        ("uplevel" body line 2)
        invoked from within
    "uplevel 1 $code_block "
        invoked from within
    "db_with_handle db {
                db_exec dml $db $full_statement_name $sql
            }"
        (procedure "db_dml" line 37)
        invoked from within
    "db_dml insert_data $data_insert_sql"
        ("foreach" body line 2)
        invoked from within
    "foreach data_insert_sql $data_insert_sql_list {
                db_dml insert_data $data_insert_sql
            }"
        ("uplevel" body line 4)
        invoked from within
    "uplevel 1 $transaction_code "
        (procedure "db_transaction" line 1)
        invoked from within
    "db_transaction {
            db_dml insert_object $object_insert_sql

            foreach data_insert_sql $data_insert_sql_list {
                db_dml insert_data $data_insert_sql
            ..."
        invoked from within
    "if { [form is_valid object] } {

        set object_insert_sql "
        insert into es_skinny_object (
          object_id,
          object_type_id,
          name,
       ..."
        ("uplevel" body line 234)
        invoked from within
    "uplevel {
              proc es_set_skinny_form_vars {} {

        # modified from set_form_variables

        uplevel { if { [ns_getform] == "" } {
            ns_returnerror ..."
        (procedure "code::tcl::/web/openacs4/www/wpower/www/object" line 2)
        invoked from within
    "code::tcl::$__adp_stub"
        invoked from within
    "if { [file exists $__adp_stub.tcl] } {

          # ensure that data source preparation procedure exists and is up-to-date
          adp_init tcl $__adp_stub
    ..."
        ("uplevel" body line 3)
        invoked from within
    "uplevel {

        if { [file exists $__adp_stub.tcl] } {

          # ensure that data source preparation procedure exists and is up-to-date
          adp_init t..."
        (procedure "adp_prepare" line 2)
        invoked from within
    "adp_prepare "
        (procedure "template::adp_parse" line 30)
        invoked from within
    "template::adp_parse [file root [ad_conn file]] {}"
        (procedure "adp_parse_ad_conn_file" line 7)
        invoked from within
    "$handler"
        ("uplevel" body line 2)
        invoked from within
    "uplevel $code"
        invoked from within
    "ad_try {
            $handler
          } ad_script_abort val {
            # do nothing
          }"
        invoked from within
    "rp_serve_concrete_file [ad_conn file]"
        (procedure "rp_serve_abstract_file" line 60)
        invoked from within
    "rp_serve_abstract_file "$root/$path""
        ("uplevel" body line 2)
        invoked from within
    "uplevel $code"
        invoked from within
    "ad_try {
            rp_serve_abstract_file "$root/$path"
            set tcl_url2file([ad_conn url]) [ad_conn file]
            set tcl_url2path_info([ad_conn url]) [ad_conn path_inf..."
Collapse
Posted by Jeff Davis on
You should look in the server error log since a lot of times you will see a much more informative error from the db driver.
Collapse
Posted by James Thornton on
I may have found the problem -- the bind vars are binding at the time of insert, not when they are set so the db_dml loop is trying to insert the last element twice.
Collapse
Posted by Dan Wickstrom on
Use regular tcl vars instead of bind vars.  The driver doesn't look at the variable value until the insert is performed.