Example #1
0
 /**
  * Creates a `WHERE` clause to ensure a database call is only selecting from rows that are part of the same set when an ordering field is in multi-column `UNIQUE` constraint.
  * 
  * @param  string $table          The table the `WHERE` clause is for
  * @param  array  $other_columns  The other columns in the multi-column unique constraint
  * @param  array  &$values        The values to match with
  * @return string  An SQL `WHERE` clause for the other columns in a multi-column `UNIQUE` constraint
  */
 private static function createOtherFieldsWhereClause($table, $other_columns, &$values)
 {
     $conditions = array();
     foreach ($other_columns as $other_column) {
         $conditions[] = $other_column . fORMDatabase::escapeBySchema($table, $other_column, $values[$other_column], '=');
     }
     return join(' AND ', $conditions);
 }
Example #2
0
 /**
  * Creates a `WHERE` clause for the primary keys of this record set
  * 
  * @param  string $route  The route to this table from another table
  * @return string  The `WHERE` clause
  */
 private function constructWhereClause($route = NULL)
 {
     $table = fORM::tablize($this->class);
     $table_with_route = $route ? $table . '{' . $route . '}' : $table;
     $pk_columns = fORMSchema::retrieve()->getKeys($table, 'primary');
     $sql = '';
     // We have a multi-field primary key, making things kinda ugly
     if (sizeof($pk_columns) > 1) {
         $conditions = array();
         foreach ($this->getPrimaryKeys() as $primary_key) {
             $sub_conditions = array();
             foreach ($pk_columns as $pk_column) {
                 $sub_conditions[] = $table_with_route . '.' . $pk_column . fORMDatabase::escapeBySchema($table, $pk_column, $primary_key[$pk_column], '=');
             }
             $conditions[] = join(' AND ', $sub_conditions);
         }
         $sql .= '(' . join(') OR (', $conditions) . ')';
         // We have a single primary key field, making things nice and easy
     } else {
         $first_pk_column = $pk_columns[0];
         $values = array();
         foreach ($this->getPrimaryKeys() as $primary_key) {
             $values[] = fORMDatabase::escapeBySchema($table, $first_pk_column, $primary_key);
         }
         $sql .= $table_with_route . '.' . $first_pk_column . ' IN (' . join(', ', $values) . ')';
     }
     return $sql;
 }
Example #3
0
 /**
  * Stores a record in the database, whether existing or new
  * 
  * This method will start database and filesystem transactions if they have
  * not already been started.
  * 
  * @throws fValidationException  When ::validate() throws an exception
  * 
  * @return fActiveRecord  The record object, to allow for method chaining
  */
 public function store()
 {
     $class = get_class($this);
     if (fORM::getActiveRecordMethod($class, 'store')) {
         return $this->__call('store', array());
     }
     fORM::callHookCallbacks($this, 'pre::store()', $this->values, $this->old_values, $this->related_records, $this->cache);
     try {
         $table = fORM::tablize($class);
         $column_info = fORMSchema::retrieve()->getColumnInfo($table);
         // New auto-incrementing records require lots of special stuff, so we'll detect them here
         $new_autoincrementing_record = FALSE;
         if (!$this->exists()) {
             $pk_columns = fORMSchema::retrieve()->getKeys($table, 'primary');
             if (sizeof($pk_columns) == 1 && $column_info[$pk_columns[0]]['auto_increment'] && !$this->values[$pk_columns[0]]) {
                 $new_autoincrementing_record = TRUE;
                 $pk_column = $pk_columns[0];
             }
         }
         $inside_db_transaction = fORMDatabase::retrieve()->isInsideTransaction();
         if (!$inside_db_transaction) {
             fORMDatabase::retrieve()->translatedQuery('BEGIN');
         }
         fORM::callHookCallbacks($this, 'post-begin::store()', $this->values, $this->old_values, $this->related_records, $this->cache);
         $this->validate();
         fORM::callHookCallbacks($this, 'post-validate::store()', $this->values, $this->old_values, $this->related_records, $this->cache);
         // Storing main table
         $sql_values = array();
         foreach ($column_info as $column => $info) {
             $value = fORM::scalarize($class, $column, $this->values[$column]);
             $sql_values[$column] = fORMDatabase::escapeBySchema($table, $column, $value);
         }
         // Most databases don't like the auto incrementing primary key to be set to NULL
         if ($new_autoincrementing_record && $sql_values[$pk_column] == 'NULL') {
             unset($sql_values[$pk_column]);
         }
         if (!$this->exists()) {
             $sql = $this->constructInsertSQL($sql_values);
         } else {
             $sql = $this->constructUpdateSQL($sql_values);
         }
         $result = fORMDatabase::retrieve()->translatedQuery($sql);
         // If there is an auto-incrementing primary key, grab the value from the database
         if ($new_autoincrementing_record) {
             $this->set($pk_column, $result->getAutoIncrementedValue());
         }
         // Storing *-to-many relationships
         fORMRelated::store($class, $this->values, $this->related_records);
         fORM::callHookCallbacks($this, 'pre-commit::store()', $this->values, $this->old_values, $this->related_records, $this->cache);
         if (!$inside_db_transaction) {
             fORMDatabase::retrieve()->translatedQuery('COMMIT');
         }
         fORM::callHookCallbacks($this, 'post-commit::store()', $this->values, $this->old_values, $this->related_records, $this->cache);
     } catch (fException $e) {
         if (!$inside_db_transaction) {
             fORMDatabase::retrieve()->translatedQuery('ROLLBACK');
         }
         fORM::callHookCallbacks($this, 'post-rollback::store()', $this->values, $this->old_values, $this->related_records, $this->cache);
         if ($new_autoincrementing_record && self::hasOld($this->old_values, $pk_column)) {
             $this->values[$pk_column] = self::retrieveOld($this->old_values, $pk_column);
             unset($this->old_values[$pk_column]);
         }
         throw $e;
     }
     fORM::callHookCallbacks($this, 'post::store()', $this->values, $this->old_values, $this->related_records, $this->cache);
     $was_new = !$this->exists();
     // If we got here we succefully stored, so update old values to make exists() work
     foreach ($this->values as $column => $value) {
         $this->old_values[$column] = array($value);
     }
     // If the object was just inserted into the database, save it to the identity map
     if ($was_new) {
         $hash = self::hash($this->values, $class);
         if (!isset(self::$identity_map[$class])) {
             self::$identity_map[$class] = array();
         }
         self::$identity_map[$class][$hash] = $this;
     }
     return $this;
 }
Example #4
0
 /**
  * Associates a set of many-to-many related records with the current record
  * 
  * @internal
  * 
  * @param  array &$values       The current values for the main record being stored
  * @param  array $relationship  The information about the relationship between this object and the records in the record set
  * @param  array $related_info  An array containing the keys `'record_set'`, `'count'`, `'primary_keys'` and `'associate'` 
  * @return void
  */
 public static function storeManyToMany(&$values, $relationship, $related_info)
 {
     $column_value = $values[$relationship['column']];
     // First, we remove all existing relationships between the two tables
     $join_table = $relationship['join_table'];
     $join_column = $relationship['join_column'];
     $join_column_value = fORMDatabase::escapeBySchema($join_table, $join_column, $column_value);
     $delete_sql = 'DELETE FROM ' . $join_table;
     $delete_sql .= ' WHERE ' . $join_column . ' = ' . $join_column_value;
     fORMDatabase::retrieve()->translatedQuery($delete_sql);
     // Then we add back the ones in the record set
     $join_related_column = $relationship['join_related_column'];
     $related_pk_columns = fORMSchema::retrieve()->getKeys($relationship['related_table'], 'primary');
     $related_column_values = array();
     // If the related column is the primary key, we can just use the primary keys if we have them
     if ($related_pk_columns[0] == $relationship['related_column'] && $related_info['primary_keys']) {
         $related_column_values = $related_info['primary_keys'];
         // Otherwise we need to pull the related values out of the record set
     } else {
         // If there is no record set, build it from the primary keys
         if (!$related_info['record_set']) {
             $related_class = fORM::classize($relationship['related_table']);
             $related_info['record_set'] = fRecordSet::build($related_class, array($related_pk_columns[0] . '=' => $related_info['primary_keys']));
         }
         $get_related_method_name = 'get' . fGrammar::camelize($relationship['related_column'], TRUE);
         foreach ($related_info['record_set'] as $record) {
             $related_column_values[] = $record->{$get_related_method_name}();
         }
     }
     // Ensure we aren't storing duplicates
     $related_column_values = array_unique($related_column_values);
     foreach ($related_column_values as $related_column_value) {
         $related_column_value = fORMDatabase::escapeBySchema($join_table, $join_related_column, $related_column_value);
         $insert_sql = 'INSERT INTO ' . $join_table . ' (' . $join_column . ', ' . $join_related_column . ') ';
         $insert_sql .= 'VALUES (' . $join_column_value . ', ' . $related_column_value . ')';
         fORMDatabase::retrieve()->translatedQuery($insert_sql);
     }
 }
Example #5
0
 /**
  * Creates a `WHERE` clause condition for primary keys of the table specified
  * 
  * This method requires the `$primary_keys` parameter to be one of:
  * 
  *  - A scalar value for a single-column primary key
  *  - An array of values for a single-column primary key
  *  - An associative array of values for a multi-column primary key (`column => value`)
  *  - An array of associative arrays of values for a multi-column primary key (`key => array(column => value)`)
  * 
  * If you are looking to build a primary key where clause from the `$values`
  * and `$old_values` arrays, please see ::createPrimaryKeyWhereClause()
  * 
  * @internal
  * 
  * @param  string $table         The table to build the where clause for
  * @param  string $table_alias   The alias for the table
  * @param  array  &$values       The values array for the fActiveRecord object
  * @param  array  &$old_values   The old values array for the fActiveRecord object
  * @return string  The `WHERE` clause that will specify the fActiveRecord as it currently exists in the database
  */
 public static function createPrimaryKeyWhereClause($table, $table_alias, &$values, &$old_values)
 {
     $primary_keys = fORMSchema::retrieve()->getKeys($table, 'primary');
     $sql = '';
     foreach ($primary_keys as $primary_key) {
         if ($sql) {
             $sql .= " AND ";
         }
         $value = isset($old_values[$primary_key]) ? $old_values[$primary_key][0] : $values[$primary_key];
         $sql .= $table . '.' . $primary_key . fORMDatabase::escapeBySchema($table, $primary_key, $value, '=');
     }
     return $sql;
 }
Example #6
0
 /**
  * Validates values against unique constraints
  *
  * @param  fActiveRecord  $object       The instance of the class to check
  * @param  array          &$values      The values to check
  * @param  array          &$old_values  The old values for the record
  * @return string  An error message for the unique constraints
  */
 private static function checkUniqueConstraints($object, &$values, &$old_values)
 {
     $class = get_class($object);
     $table = fORM::tablize($class);
     $key_info = fORMSchema::retrieve()->getKeys($table);
     $primary_keys = $key_info['primary'];
     $unique_keys = $key_info['unique'];
     foreach ($unique_keys as $unique_columns) {
         settype($unique_columns, 'array');
         // NULL values are unique
         $found_not_null = FALSE;
         foreach ($unique_columns as $unique_column) {
             if ($values[$unique_column] !== NULL) {
                 $found_not_null = TRUE;
             }
         }
         if (!$found_not_null) {
             continue;
         }
         $sql = "SELECT " . join(', ', $key_info['primary']) . " FROM " . $table . " WHERE ";
         $first = TRUE;
         foreach ($unique_columns as $unique_column) {
             if ($first) {
                 $first = FALSE;
             } else {
                 $sql .= " AND ";
             }
             $value = $values[$unique_column];
             if (self::isCaseInsensitive($class, $unique_column) && self::stringlike($value)) {
                 $sql .= 'LOWER(' . $unique_column . ')' . fORMDatabase::escapeBySchema($table, $unique_column, fUTF8::lower($value), '=');
             } else {
                 $sql .= $unique_column . fORMDatabase::escapeBySchema($table, $unique_column, $value, '=');
             }
         }
         if ($object->exists()) {
             foreach ($primary_keys as $primary_key) {
                 $value = fActiveRecord::retrieveOld($old_values, $primary_key, $values[$primary_key]);
                 $sql .= ' AND ' . $primary_key . fORMDatabase::escapeBySchema($table, $primary_key, $value, '<>');
             }
         }
         try {
             $result = fORMDatabase::retrieve()->translatedQuery($sql);
             $result->tossIfNoRows();
             // If an exception was not throw, we have existing values
             $column_names = array();
             foreach ($unique_columns as $unique_column) {
                 $column_names[] = fORM::getColumnName($class, $unique_column);
             }
             if (sizeof($column_names) == 1) {
                 return self::compose('%sThe value specified must be unique, however it already exists', fValidationException::formatField(join('', $column_names)));
             } else {
                 return self::compose('%sThe values specified must be a unique combination, however the specified combination already exists', fValidationException::formatField(join(', ', $column_names)));
             }
         } catch (fNoRowsException $e) {
         }
     }
 }