/**
  * Get the columsn which can save for the specidied form
  * @param I2CE_Form $form
  * @returns array keys are the field names and values are the column names
  */
 protected function getSaveColumns($form)
 {
     $formName = $form->getName();
     if (array_key_exists($formName, $this->saveCols)) {
         return $this->saveCols[$formName];
     }
     $table = $this->getTable($formName);
     if (!$table) {
         I2CE::raiseError("No table specified for {$form}");
         $this->saveCols[$formName] = array();
         return array();
     }
     $writable = false;
     $options = $this->getStorageOptions($formName);
     $options->setIfIsSet($writable, 'writable');
     if (!$writable) {
         I2CE::raiseError("Trying to save non-writable form");
         $this->saveCols[$formName] = array();
         return array();
     }
     if (!$options instanceof I2CE_MagicDataNode) {
         I2CE::raiseError("Invalid storage options for {$formName}");
         $this->saveCols[$formName] = array();
         return array();
     }
     $form_prepended = true;
     $options->setIfIsSet($form_prepended, 'id/form_prepended');
     if ($form_prepended) {
         $this->saveCols[$formName] = array();
         return array();
     }
     $id_col = 'id';
     if (!$options->setIfIsSet($id_col, 'id/col') || !$id_col) {
         if ($options->is_scalar('id/function') && $options->id->function) {
             //the id is being read in as a function.  and the col is not overwritten
             //so cannot save to a function value
             $this->saveCols[$formName] = array();
             return array();
         }
     }
     $rows = $this->db->queryAll("SHOW FULL COLUMNS FROM {$table} WHERE Field='{$id_col}'");
     if (!is_array($rows) || count($rows) != 1) {
         //the id column was not there
         $this->saveCols[$formName] = array();
         return array();
     }
     $cols['id'] = $id_col;
     $parent_col = false;
     $parent_enabled = true;
     $options->setIfIsSet($parent_enabled, "parent/enabled");
     if ($parent_enabled) {
         if (!$options->setIfIsSet($parent_col, 'parent/col') || !$parent_col) {
             if ($options->is_scalar('parent/function') && $options->parent->function) {
                 //the parent is being read in as a function.  and the col is not overwritten
                 //so cannot save to a function value
                 //do nothing. can't save the parent id.
             } else {
                 $cols['parent'] = $parent_col;
             }
         } else {
             $cols['parent'] = $parent_col;
         }
     }
     $fields = $form->getFieldNames();
     foreach ($fields as $field) {
         $field_col = $field;
         $field_enabled = true;
         $options->setIfIsSet($field_enabled, "fields/{$field}/enabled");
         if ($field_enabled) {
             if (!$options->setIfIsSet($field_col, "fields/{$field}/col") || !$field_col) {
                 if ($options->is_scalar("fields/{$field}/function") && $options->fields->{$field}->function) {
                     //the parent is being read in as a function.  and the col is not overwritten
                     //so cannot save to a function value
                     //do nothing. can't save the parent id.
                 } else {
                     $cols[$field] = $field_col;
                 }
             } else {
                 $cols[$field] = $field_col;
             }
         }
         //now we need to make sure that the col is "atomic"
         $rows = $this->db->queryAll("SHOW FULL COLUMNS FROM {$table} WHERE Field='{$cols[$field]}'");
         if (!is_array($rows) || count($rows) != 1) {
             //the column was not there
             unset($cols[$field]);
         }
         $row = current($rows);
         if ($row->null == 'NO' && !$row->default) {
             //it is not allowed to be null and there is no default value set.  non-atomic
             unset($cols[$field]);
         }
     }
     $this->saveCols[$formName] = $cols;
     return $cols;
 }
 /**
  * Checks a limit boolean expression for a field based on  limit data
  * @param I2CE_Form $formObj
  * @param mixed $limit_data
  * array.
  * @param callback $field_refernece_callback.  A callback function whose first arguement is the form, the second arguements
  * is the field and which returns the way the field value should be references as a field.  If the callback is null (the default) then
  * the reference used is $data["$field']
  * @returns string  or false on failure
  */
 public function createCheckLimitString($formObj, $limit_data = array(), $field_reference_callback = null)
 {
     if (!is_array($limit_data)) {
         I2CE::raiseError("Expected array for generating where sub-expression, but not received");
         return false;
     }
     if (!array_key_exists('field', $limit_data) || !is_string($limit_data['field'])) {
         I2CE::raiseError("Field name is not given at 'field' ");
         return false;
     }
     if (!($fieldObj = $formObj->getField($limit_data['field'])) instanceof I2CE_FormField) {
         I2CE::raiseError("Field named  {$limit_data['field']} is not a field of " . $formObj->getName() . "\nValid fields are:\n\t" . implode(",", $formObj->getFieldNames()));
         return false;
     }
     if (!array_key_exists('style', $limit_data) || !is_string($limit_data['style'])) {
         I2CE::raiseError("Style is not given at 'style' ");
         return false;
     }
     if (!array_key_exists('data', $limit_data) || !is_array($limit_data['data'])) {
         $limit_data['data'] = array();
     }
     $method = 'checkLimitString_' . $limit_data['style'];
     if (!$fieldObj->_hasMethod($method)) {
         I2CE::raiseError("Not able to check limit (via {$method})  for style " . $limit_data['style'] . " for class " . get_class($fieldObj));
         return false;
     }
     if ($field_reference_callback !== null) {
         if (!is_string($ref = call_user_func($field_reference_callback, $formObj->getName(), $field)) === false) {
             I2CE::raiseError("Invalid field reference callback function");
             return false;
         }
     } else {
         $ref = '$data[\'' . $limit_data['field'] . '\']';
     }
     $ret = $fieldObj->{$method}($limit_data['data'], $ref);
     if (is_string($ret)) {
         return $ret;
     } else {
         I2CE::raiseError("Unexpected return from limit {$style}");
         return false;
     }
 }