Browse Source

Fixed merge conflicts

Stephen Ficklin 6 years ago
parent
commit
0a45a8fafa

+ 327 - 0
tests/tripal_chado/api/ChadoRecordTest.php

@@ -0,0 +1,327 @@
+<?php
+
+namespace Tests;
+
+use PHPUnit\Exception;
+use Faker\Factory;
+use StatonLab\TripalTestSuite\DBTransaction;
+use StatonLab\TripalTestSuite\TripalTestCase;
+
+module_load_include('inc', 'tripal_chado', 'includes/api/ChadoRecord');
+
+
+class ChadoRecordTest extends TripalTestCase {
+
+  use DBTransaction;
+
+
+  /**
+   * Data provider.  A variety of chado records.
+   *
+   * @return array
+   */
+  public function recordProvider() {
+    //table, factory or NULL, record_id or NULL
+
+    $faker = \Faker\Factory::create();
+    $analysis = [
+      'name' => $faker->word,
+      'description' => $faker->text,
+      'program' => $faker->word,
+      'programversion' => $faker->word,
+    ];
+    $organism = [
+      'genus' => $faker->word,
+      'species' => $faker->word,
+      'common_name' => $faker->word,
+    ];
+
+    return [
+      ['analysis', $analysis],
+      ['organism', $organism],
+    ];
+  }
+
+  /**
+   * Tests that the class can be initiated with or without a record specified
+   *
+   * @group api
+   * @group chado
+   * @group wip
+   * @dataProvider recordProvider
+   */
+  public function testInitClass($table, $values) {
+    $record = new \ChadoRecord($table);
+    $this->assertNotNull($record);
+    $chado_record = factory('chado.' . $table)->create($values);
+    $record_column = $table.'_id';
+
+    $record = new \ChadoRecord($table, $chado_record->$record_column);
+    $this->assertNotNull($record);
+  }
+
+
+  /**
+   * @group api
+   * @group chado
+   * @group wip
+   * @throws \Exception
+   * @dataProvider recordProvider
+   */
+
+  public function testGetTable($table, $values) {
+    $record = new \ChadoRecord($table);
+    $this->assertEquals($table, $record->getTable());
+  }
+
+  /**
+   * @group wip
+   * @group api
+   * @group chado
+   * @dataProvider recordProvider
+   *
+   * @throws \Exception
+   */
+  public function testGetID($table, $values) {
+    $chado_record = factory('chado.' . $table)->create();
+    $record_column = $table.'_id';
+    $id = $chado_record->$record_column;
+
+    $record = new \ChadoRecord($table, $id);
+    $returned_id = $record->getID();
+    $this->assertEquals($id, $returned_id);
+  }
+
+  /**
+   * @group api
+   * @group wip
+   * @group chado
+   * @dataProvider recordProvider
+   *
+   *
+   */
+  public function testGetValues($table, $values) {
+    $chado_record = factory('chado.' . $table)->create($values);
+    $record_column = $table.'_id';
+    $id = $chado_record->$record_column;
+    $record = new \ChadoRecord($table, $id);
+
+    $values = $record->getValues();
+    $this->assertNotEmpty($values);
+    foreach ($values as $key => $value) {
+      $this->assertArrayHasKey($key, $values);
+      $this->assertEquals($value, $values[$key]);
+    }
+  }
+
+  /**
+   * @group api
+   * @group wip
+   * @group chado
+   * @dataProvider recordProvider
+   *
+   */
+  public function testGetValue($table, $values) {
+
+    $chado_record = factory('chado.' . $table)->create($values);
+    $record_column = $table.'_id';
+    $id = $chado_record->$record_column;
+
+    $record = new \ChadoRecord($table, $id);
+    foreach ($values as $key => $value) {
+      $returned_value = $record->getValue($key);
+      $this->assertEquals($value, $returned_value);
+    }
+  }
+
+  /**
+   * @group wip
+   * @group chado
+   * @group api
+   * @dataProvider recordProvider
+   */
+
+  public function testFind($table, $values) {
+
+    $chado_record = factory('chado.' . $table)->create($values);
+    $record_column = $table.'_id';
+    $id = $chado_record->$record_column;
+
+    $record = new \ChadoRecord($table);
+
+    $record->setValues($values);
+    $found = $record->find();
+
+    $this->assertNotNull($found);
+    $this->assertEquals(1, $found);
+
+  }
+
+  /**
+   * Check that the find method throws an exception when it cant find anything.
+   *
+   * @throws \Exception
+   */
+
+  public function testFindFail() {
+    $table = 'organism';
+    $record = new \ChadoRecord($table);
+
+    $record->setValue($table . '_id', 'unfindable');
+    $this->expectException(Exception);
+    $found = $record->find();
+  }
+
+  /**
+   *
+   *
+   * @group chado
+   * @group api
+   *
+   * @dataProvider recordProvider
+   *
+   * @throws \Exception
+   */
+  public function testSetandGetValue($table, $values) {
+
+    $record = new \ChadoRecord($table);
+    $record->setValues($values);
+    $vals = $record->getValues();
+
+    foreach ($vals as $val_key => $val) {
+      $this->assertEquals($values[$val_key], $val, "The getValues did not match what was provided for setValues");
+    }
+  }
+
+  /**
+   * Save should work for both an update and an insert
+   *
+   * @group chado
+   * @group api
+   *
+   * @dataProvider  recordProvider
+   */
+  public function testSave($table, $values){
+    //first, test the insert case
+    $record = new \ChadoRecord($table);
+    $record->setValues($values);
+    $record->save();
+    $record_column = $table.'_id';
+
+    $query = db_select('chado.' . $table, 't')
+      ->fields('t', [$record_column]);
+    foreach ($values as $key => $val){
+      $query->condition($key, $val);
+    }
+    $result =$query->execute()->fetchAll();
+    $this->assertNotEmpty($result, 'we couldnt insert our record on a save!');
+
+    //change the last key
+    //NOTE this will break if the last key isn't a string!
+    $values[$key] = 'new_value_that_i_wantTOBEUNIQUE';
+    $record->setValues($values);
+    $record->save();
+
+    $query = db_select('chado.' . $table, 't')
+      ->fields('t', [$record_column]);
+    foreach ($values as $key => $val){
+      $query->condition($key, $val);
+    }
+    $result =$query->execute()->fetchAll();
+    $this->assertNotEmpty($result, 'Our record wasnt updated when saving!');
+  }
+
+  /**
+   *
+   * @group chado
+   * @group api
+   *
+   * @dataProvider  recordProvider
+   */
+  public function testInsert($table, $values){
+    //first, test the insert case
+    $record = new \ChadoRecord($table);
+    $record->setValues($values);
+    $record->insert();
+    $record_column = $table.'_id';
+
+    $query = db_select('chado.' . $table, 't')
+      ->fields('t', [$record_column]);
+    foreach ($values as $key => $val){
+      $query->condition($key, $val);
+    }
+    $result =$query->execute()->fetchAll();
+    $this->assertNotEmpty($result, 'we couldnt insert our record on a save!');
+
+    //If we insert again, it should fail
+    $this->expectException(EXCEPTION);
+    $record->insert();
+  }
+  /**
+   *
+   * @group chado
+   * @group api
+   *
+   * @dataProvider  recordProvider
+   */
+  public function testUpdate($table, $values){
+    $id = $this->genChadoRecord($table, $values);
+    $record = new \ChadoRecord($table, $id);
+    $record_column = $table.'_id';
+
+    //$dump_vals = $record->getValues();
+   // var_dump($dump_vals);
+
+    $key = array_keys($values)[0];
+    $string = 'some_random_new_string34792387';
+    $values[$key] = $string;
+
+    $record->setValues($values);
+    $record->update();
+
+    //$dump_vals = $record->getValues();
+   // var_dump($dump_vals);
+
+    $query = db_select('chado.' . $table, 't')
+      ->fields('t', [$key]);
+    foreach ($values as $key => $val){
+      $query->condition($key, $val);
+    }
+    $result =$query->execute()->fetchField();
+    $this->assertNotFalse($result, 'we couldnt update our record.');
+    $this->assertEquals($string, $result);
+  }
+
+
+  /**
+   * 
+   * @group chado
+   * @group api
+   *
+   * @dataProvider recordProvider
+   *
+   */
+  public function testDelete($table, $values){
+    $id = $this->genChadoRecord($table, $values);
+    $record = new \ChadoRecord($table, $id);
+    $record_column = $table.'_id';
+
+    $record->delete();
+    $query = db_select('chado.' . $table, 't')
+      ->fields('t', [$record_column]);
+    foreach ($values as $key => $val){
+   $query->condition($key, $val);
+    }
+    $result =$query->execute()->fetchAll();
+    $this->assertEmpty($result, 'we couldnt delete our record!');
+  }
+
+
+  private function genChadoRecord($table, $values){
+    $chado_record = factory('chado.' . $table)->create($values);
+    $record_column = $table.'_id';
+    $id = $chado_record->$record_column;
+    return $id;
+  }
+
+}

+ 244 - 80
tripal_chado/api/ChadoRecord.inc

@@ -1,62 +1,152 @@
 <?php
 
+/**
+ * Provide a class for basic querying of Chado.
+ * 
+ * Specifically tihs class provides select, insert, update and delete.
+ *
+ * Eventually this class is meants to replace the existing 
+ * chado_select_record(), chado_insert_record(), chado_update_record() and 
+ * chado_delete_record() API functions to create a cleaner, more maintainable 
+ * and more easily tested interface to querying Chado.
+ *
+ * @todo Add documentation for save() and delete().
+ *
+ * Basic Usage:
+ * - Select/Find
+ *     The following example selects an organism with the scientific name
+ *     "Tripalus databasica" from the organism table of Chado.
+ *     @code
+             // First we create an instance of the ChadoRecord class
+             // specifying the table we want to query.
+             $record = new \ChadoRecord('organism');
+
+             // Next we indicate the values we know.
+             $record->setValues([
+               'genus' => 'Tripalus',
+               'species' => 'databasica',
+             ]);
+
+             // And finally we simply ask the class to find the chado record
+             // we indicated when we set the values above.
+             $success = $record->find();
+             if ($success) {
+               // Retrieve the values if we were successful in finding the record.
+               $result = $record->getValues();
+             }
+ *     @endcode
+ * - Insert:
+ *     The following example inserts a sample record into the stock table.
+ *     @code
+             // First we create an instance of the ChadoRecord class
+             // specifying the table we want to query.
+             $record = new \ChadoRecord('stock');
+
+             // Next we indicate the values we know.
+             $record->setValues([
+               'name' => 'My Favourite Plant',
+               'uniquename' => 'Plectranthus scutellarioides Trailing Plum Brocade',
+               'organism_id' => [ 'genus' => 'Tripalus', 'species' => 'databasica' ],
+               'type_id' => [ 'name' => 'sample', 'cv_id' => [ 'name' => 'Sample processing and separation techniques' ] ],
+             ]);
+
+             // And finally, we ask the class to insert the chado record
+             // we described when we set the values above.
+             $result = $record->insert();
+ *     @endcode
+ * - Update:
+ *     The following example updates the "Tripalus databasica" record to specify the common name.
+ *     @code
+             // For brevity we're going to hardcode the original record
+             // including the id although you would Never do this in practice.
+             // Rather you would first find the record as shown in a previous example.
+             $original_record = [
+               'organism_id' => 1,
+               'genus' => 'Tripalus',
+               'species' => 'databasica',
+             ];
+
+             // First we create an instance of the ChadoRecord class
+             // specifying the table we want to query.
+             // NOTICE: this time we set the record_id when creating the instance.
+             $record = new \ChadoRecord('organism', $original_record['organism_id']);
+
+             // Now we set the values we want to change.
+             $record->setValues([
+               'common_name' => 'Tripal',
+             ]);
+
+             // And then tell the class to update the record.
+             $record->update();
+ *     @endcode
+ */
 class ChadoRecord {
+
   /**
    * @var string
    *   Holds the name of the table that this record belogns to.
    */
   protected $table_name = '';
+
   /**
    * @var array
    *   Holds the Drupal schema definition for this table.
    */
   protected $schema = [];
+
   /**
    * @var array
    *   Holds the values for the columns of the record
    */
   protected $values = [];
-  
+
   /**
    * @var array
    *   An array of required columns.
    */
   protected $required_cols = [];
-  
+
+  /**
+   * @var boolean
+   *   An array of required columns which have yet to be set.
+   */
+  protected $missing_required_col = [];
+
   /**
    * @var integer
    *   The numeric Id for this record.
    */
   protected $record_id = NULL;
-  
-  
+
   /**
    * @var string
    *   The column name for the primary key.
    */
   protected $pkey = '';
-  
-  
+
   /**
    * The list of column names in the table.
    * @var array
    */
   protected $column_names = [];
-  
-  
+
+
   /**
    * The ChadoRecord constructor
-   * 
+   *
    * @param string $table_name
    *   The name of the table that the record belongs to.
+   *
+   * @param string $record_id
+   *  An optional record ID if this record is already present in Chado.
    */
   public function __construct($table_name, $record_id = NULL) {
-    
+
     if (!$table_name) {
       $message = t('ChadoRecord::_construct(). The $table_name argument is required for a ChadoRecord instance.');
       throw new Exception($message);
     }
-    
+
     // Set the table name and schema.
     $this->table_name = $table_name;
     $this->schema = chado_get_schema($this->table_name);
@@ -65,16 +155,16 @@ class ChadoRecord {
         ['!table' => $this->table_name]);
       throw new Exception($message);
     }
-    
+
     // Chado tables never have more than one column as a primary key so
     // we are good just getting the first element.
     $this->pkey = $this->schema['primary key'][0];
-    
+
     // Save the column names.
     foreach ($this->schema['fields'] as $column_name => $col_details) {
       $this->column_names[] = $column_name;
     }
-    
+
     // Get the required columns.
     foreach ($this->schema['fields'] as $column => $col_schema) {
       foreach ($col_schema as $param => $val) {
@@ -83,7 +173,9 @@ class ChadoRecord {
         }
       }
     }
-    
+    // Currently all required columns are missing.
+    $this->missing_required_col = $this->required_cols;
+
     // If a record_id was provided then lookup the record and set the values.
     if ($record_id) {
       try {
@@ -105,29 +197,29 @@ class ChadoRecord {
       }
     }
   }
-  
+
   /**
    * Retrieves the record ID.
-   * 
+   *
    * @return number
    */
   public function getID() {
     return $this->record_id;
   }
-  
+
   /**
    * Retrieves the table name.
-   * 
+   *
    * @return string
    *   The name of the table that the record belongs to.
    */
   public function getTable() {
-    return $this->table;
+    return $this->table_name;
   }
-  
+
   /**
    * Retrieves the table schema.
-   * 
+   *
    * @return array
    *   The Drupal schema array for the table.
    */
@@ -137,17 +229,17 @@ class ChadoRecord {
 
   /**
    * Performs either an update or insert into the table using the values.
-   * 
+   *
    * If the record already exists it will be updated. If the record does not
    * exist it will be inserted.  This function adds a bit more overhead by
-   * checking for the existance of the record and performing the appropriate
+   * checking for the existence of the record and performing the appropriate
    * action. You can save time by using the insert or update functions directly
    * if you only need to do one of those actions specifically.
-   * 
+   *
    * @throws Exception
    */
   public function save() {
-    
+
     // Determine if we need to perform an update or an insert.
     $num_matches = $this->find();
     if ($num_matches == 1) {
@@ -157,27 +249,38 @@ class ChadoRecord {
       $this->insert();
     }
     if ($num_matches > 1) {
-      $message = t('ChadoRecord::save(). Could not save the record into the table, !table. '. 
-        'Multiple records already exist that match the values: !values. '. 
+      $message = t('ChadoRecord::save(). Could not save the record into the table, !table. '.
+        'Multiple records already exist that match the values: !values. '.
         'Please provide a set of values that can uniquely identify a record.',
         ['!table' => $this->table_name, '!values' => print_r($this->values, TRUE), '!error' => $e->getMessage()]);
       throw new Exception($message);
     }
   }
+
   /**
    * Inserts the values of this object as a new record.
-   * 
-   * @throws Exception 
+   *
+   * @todo Support options from chado_insert_record: return_record.
+   * @todo check for violation of unique constraint.
+   *
+   * @throws Exception
    */
   public function insert() {
-    
+
     // Make sure we have values for this record before inserting.
     if (empty($this->values)) {
       $message = t('ChadoRecord::insert(). Could not insert a record into the table, !table, without any values.',
         ['!table' => $this->table_name]);
       throw new Exception($message);
     }
-    
+
+    // Additionally, make sure we have all the required values!
+    if (!empty($this->missing_required_col)) {
+      $message = t('ChadoRecord::insert(). The columns named, "!columns", require a value for the table: "!table". You can set these values using ChadoRecord::setValues().',
+        ['!columns' => implode('", "', $this->missing_required_col), '!table' => $this->table_name]);
+      throw new Exception($message);
+    }
+
     // Build the SQL statement for insertion.
     $insert_cols = [];
     $insert_vals = [];
@@ -187,14 +290,16 @@ class ChadoRecord {
       $insert_vals[] = ':' . $column;
       $insert_args[':' . $column] = $value;
     }
-    $sql = 'INSERT INTO {' . $this->table_name . '} (' . 
-      implode(", ", $insert_cols) . ') VALUES (' . 
+    $sql = 'INSERT INTO {' . $this->table_name . '} (' .
+      implode(", ", $insert_cols) . ') VALUES (' .
       implode(", ", $insert_vals) . ')';
+
     try {
       chado_query($sql, $insert_args);
-      // TODO: we can speed up inserts if we can find a way to not have to
+      // @todo we can speed up inserts if we can find a way to not have to
       // run the find(), but get the newly inserted record_id directly
       // from the insert command.
+      // One option may be to use the `RETURNING [pkey]` keywords in the SQL statement.
       $this->find();
     }
     catch (Exception $e) {
@@ -203,28 +308,40 @@ class ChadoRecord {
       throw new Exception($message);
     }
   }
-  
+
   /**
    * Updates the values of this object as a new record.
-   * 
+   *
+   * @todo set defaults for columns not already set in values.
+   * @todo Support options from chado_update_record: return_record.
+   * @todo check for violation of unique constraint.
+   * @todo if record_id not set then try finding it.
+   *
    * @throws Exception
    */
   public function update() {
-    
+
     // Make sure we have values for this record before updating.
     if (empty($this->values)) {
       $message = t('ChadoRecord::update(). Could not update a record into the table, !table, without any values.',
         ['!table' => $this->table_name]);
       throw new Exception($message);
     }
-    
+
+    // Additionally, make sure we have all the required values!
+    if (!empty($this->missing_required_col)) {
+      $message = t('ChadoRecord::update(). The columns named, "!columns", require a value for the table: "!table". You can set these values using ChadoRecord::setValues().',
+        ['!columns' => implode('", "', $this->missing_required_col), '!table' => $this->table_name]);
+      throw new Exception($message);
+    }
+
     // We have to have a record ID for the record to update.
     if (!$this->record_id) {
       $message = t('ChadoRecord::update(). Could not update a record in the table, !table, without a record ID.',
         ['!table' => $this->table_name]);
       throw new Exception($message);
     }
-    
+
     // Build the SQL statement for updating.
     $update_args = [];
     $sql = 'UPDATE {' . $this->table_name . '}  SET ';
@@ -233,44 +350,44 @@ class ChadoRecord {
       if ($column == $this->pkey) {
         continue;
       }
-      $sql .= $column . ' = :' . $column . ', '; 
+      $sql .= $column . ' = :' . $column . ', ';
       $update_args[':' . $column] = $value;
     }
     // Remove the trailing comma and space.
     $sql = substr($sql, 0, -2);
     $sql .= ' WHERE ' . $this->pkey . ' = :record_id';
     $update_args[':record_id'] = $this->record_id;
-    
+
     // Now try the update.
     try {
       chado_query($sql, $update_args);
     }
     catch (Exception $e) {
       $message = t('ChadoRecord::update(). Could not update a record in the table, !table, with !record_id as the record ID and the following values: !values. ERROR: !error',
-        ['!table' => $this->table_name, 
-         '!record_id' => $this->record_id, 
-         '!values' => print_r($this->values, TRUE), 
+        ['!table' => $this->table_name,
+         '!record_id' => $this->record_id,
+         '!values' => print_r($this->values, TRUE),
          '!error' => $e->getMessage()]);
       throw new Exception($message);
     }
   }
-  
+
   /**
    * Deletes the record that matches the given values.
-   * 
+   *
    * A record ID must be part of the current values.
-   * 
+   *
    * @throws Exception
    */
   public function delete() {
-    
+
     // We have to have a record ID for the record to be deleted.
     if (!$this->record_id) {
       $message = t('ChadoRecord::delete(). Could not delete a record in the table, !table, without a record ID.',
         ['!table' => $this->table_name]);
       throw new Exception($message);
     }
-    
+
     try {
       $sql = 'DELETE FROM {' . $this->table_name . '} WHERE ' . $this->pkey . ' = :record_id';
       chado_query($sql, [':record_id' => $this->record_id]);
@@ -283,84 +400,107 @@ class ChadoRecord {
         throw new Exception($message);
     }
   }
+
   /**
    * A general-purpose setter function to set the column values for the record.
-   * 
+   *
    * This function should be used prior to insert or update of a record. For
    * an update, be sure to include the record ID in the list of values passed
    * to the function.
-   * 
+   *
+   * @todo Support options from chado_insert_record: skip_validation.
+   * @todo Validate the types match what is expected based on the schema.
+   * @todo Set default values for columns not in this array?
+   * @todo Support foreign key relationships: lookup the key.
+   * @todo Support value = [a, b, c] for IN select statements?
+   *
    * @param array $values
    *    An associative array where the keys are the table column names and
    *    the values are the record values for each column.
-   *    
+   *
    * @throws Exception
    */
   public function setValues($values) {
-    
+
     // Intiailze the values array.
     $this->values = [];
-    
+
     // Add the values provided into the values property.
     foreach ($values as $column => $value) {
       if (in_array($column, $this->column_names)) {
         $this->values[$column] = $value;
       }
       else {
-        $message = t('ChadoRecord::setValues(). The column named, "!column", does not exist in table: "!table". Values: !values".', 
+        $message = t('ChadoRecord::setValues(). The column named, "!column", does not exist in table: "!table". Values: !values".',
           ['!column' => $column, '!table' => $this->table_name, '!values' => print_r($values, TRUE)]);
         throw new Exception($message);
       }
     }
-  
-    // Make sure that the user did not miss any required columns or has
-    // set a column to be NULL when it doesn't allow NULLs.
+
+    // Check whether all required columns are set and indicate using the
+    // $required_values_set flag for faster checking in insert/update.
+    $this->missing_required_col = [];
     foreach ($this->required_cols as $rcol) {
       // It's okay if the primary key is missing, esepecially if the user
       // wants to use the find() or insert() functions.
       if ($rcol == $this->pkey) {
         continue;
       }
-      
+
       if (in_array($rcol, array_keys($this->values)) and $this->values[$rcol] === '__NULL__') {
-        $message = t('ChadoRecord::setValues(). The column named, "!column", requires a value for the table: "!table".',
-          ['!column' => $rcol, '!table' => $this->table_name]);
-        throw new Exception($message);
+        $this->missing_required_col[$rcol] = $rcol;
       }
     }
-    
+
     // Check to see if the user provided the primary key (record_id).
     if (in_array($this->pkey, array_keys($values))) {
       $this->record_id = $values[$this->pkey];
     }
+
+    // Ensure that no values are arrays.
+    foreach ($values as $column => $value) {
+      if (is_array($value)) {
+        $message = t('ChadoRecord::setValues(). The column named, "!column", must be a single value but is currently: "!values". NOTE: we currently don\'t support expanding foreign key relationships or multiple values for a given column.',
+          ['!column' => $column, '!table' => $this->table_name, '!values' => implode('", "', $value)]);
+        throw new Exception($message);
+      }
+    }
   }
-  
+
   /**
    * Returns all values for the record.
-   * 
+   *
+   * @todo We need to follow foreign key constraints.
+   *
    * @return array
    */
   public function getValues() {
     return $this->values;
   }
-  
+
   /**
    * Sets the value for a specific column.
-   * 
+   *
+   * @todo Support options from chado_insert_record: skip_validation.
+   * @todo Validate the types match what is expected based on the schema.
+   * @todo Set default values for columns not in this array?
+   * @todo Support foreign key relationships: lookup the key.
+   * @todo Support value = [a, b, c] for IN select statements?
+   *
    * @param string $column_name
    *   The name of the column to which the value should be set.
    * @param $value
    *   The value to set.
    */
   public function setValue($column_name, $value) {
-    
+
     // Make sure the column is valid.
     if (!in_array($column_name, $this->column_names)) {
       $message = t('ChadoRecord::setValue(). The column named, "!column", does not exist in table: "!table".',
         ['!column' => $column_name, '!table' => $this->table_name]);
       throw new Exception($message);
     }
-    
+
     // Make sure that the value is not NULL if this is a required field.
     if (!in_array($column_name, $this->required_cols) and $value == '__NULL__') {
       $message = t('ChadoRecord::setValue(). The column named, "!column", requires a value for the table: "!table".',
@@ -368,23 +508,36 @@ class ChadoRecord {
       throw new Exception($message);
     }
     
+    // Remove from the missing list if it was there.
+    elseif (isset($this->missing_required_cols[$column])) {
+      unset($this->missing_required_cols[$column]);
+    }
+
+    // Ensure that no values are arrays.
+    if (is_array($value)) {
+      $message = t('ChadoRecord::setValue(). The column named, "!column", must be a single value but is currently: "!values". NOTE: we currently don\'t support expanding foreign key relationships or multiple values for a given column.',
+        ['!column' => $column, '!table' => $this->table_name, '!values' => implode('", "', $value)]);
+      throw new Exception($message);
+    }
+
     $this->values[$column_name] = $value;
   }
+
   /**
    * Returns the value of a specific column.
-   * 
+   *
    * @param string $column_name
    *   The name of a column from the table from which to retrieve the value.
    */
   public function getValue($column_name) {
-    
+
     // Make sure the column is valid.
     if (!in_array($column_name, $this->column_names)) {
       $message = t('ChadoRecord::getValue(). The column named, "!column", does not exist in table: "!table".',
         ['!column' => $column_name, '!table' => $this->table_name]);
       throw new Exception($message);
     }
-    
+
     return $this->values[$column_name];
   }
 
@@ -394,22 +547,29 @@ class ChadoRecord {
    * Use the setValues function first to set values for searching, then call
    * this function to find matching record.  The values provided to the
    * setValues function must uniquely identify a record.
-   * 
+   *
+   * @todo Support options from chado_select_record: skip_validation, has_record,
+   *   return_sql, case_insensitive_columns, regex_columns, order_by, is_duplicate,
+   *   pager, limit, offset.
+   * @todo Support following the foreign key
+   * @todo Support complex filtering (e.g. fmin > 50)
+   * @todo Support multiple records being returned?
+   *
    * @return
    *   The number of matches found.  If 1 is returned then the query
    *   successfully found a match. If 0 then no matching records were found.
-   *   
+   *
    * @throws Exception
    */
   public function find() {
-    
+
     // Make sure we have values for this record before searching.
     if (empty($this->values)) {
       $message = t('ChadoRecord::find(). Could not find a record from the table, !table, without any values.',
         ['!table' => $this->table_name]);
       throw new Exception($message);
     }
-    
+
     // Build the SQL statement for searching.
     $select_args = [];
     $sql = 'SELECT * FROM {' . $this->table_name . '} WHERE 1=1 ';
@@ -425,7 +585,7 @@ class ChadoRecord {
         ['!table' => $this->table_name, '!values' => print_r($this->values, TRUE), '!error' => $e->getMessage()]);
       throw new Exception($message);
     }
-    
+
     // If we only have a single match then we're good and we can update the
     // values for this object.
     $num_matches = $results->rowCount();
@@ -436,9 +596,13 @@ class ChadoRecord {
         $this->values[$column] = $value;
       }
       $this->record_id = $record[$this->pkey];
+      
+      // We are no longer missing any required columns because we loaded
+      // from the database record.
+      $this->missing_required_col = [];
     }
-    
+
     // Return the number of matches.
     return $num_matches;
   }
-}
+}

+ 0 - 1
tripal_chado/tripal_chado.module

@@ -38,7 +38,6 @@ require_once 'api/tripal_chado.migrate.api.inc';
 require_once 'api/tripal_chado.DEPRECATED.api.inc';
 require_once 'api/ChadoRecord.inc';
 
-
 // Chado module specific API functions
 require_once 'api/modules/tripal_chado.analysis.api.inc';
 require_once 'api/modules/tripal_chado.contact.api.inc';