|  | @@ -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;
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | +}
 |