Index: includes/common.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/common.inc,v retrieving revision 1.865 diff -u -p -r1.865 common.inc --- includes/common.inc 9 Feb 2009 03:29:53 -0000 1.865 +++ includes/common.inc 11 Feb 2009 07:05:05 -0000 @@ -3980,6 +3980,219 @@ function drupal_write_record($table, &$o } /** + * Load a record from the database, consulting the schema if necessary. + * + * @param $table + * The name of the table; this must exist in schema API. + * @param $options + * An associative array of additional options with the structure given in + * drupal_load_records(). + * @param $primary_keys + * An array of primary keys of the base table. If empty, the primary keys will + * be determined by consulting the schema. + * @param $unserialize + * If TRUE, the schema will be consulted and all fields with a 'serialize' + * value of TRUE will be unserialized. If an array of field names is given, + * these fields will be unserialized in the results. If FALSE, + * @return + * A matching record, or FALSE on failure. + */ +function drupal_load_record($table, $options, $primary_keys = array(), $unserialize = TRUE) { + $records = drupal_load_records($table, $query, $primary_keys, $unserialize); + if (!empty($records)) { + return $records[0]; + } + else { + return FALSE; + } +} + +/** + * Load one or more records from the database, consulting the schema if necessary. + * + * This method is a generic loader that can be used with any type of record set + * that needs to be loaded from one or more tables. Because queries are given an + * alter tag of 'drupal_write_records', it is possible to alter any query to, + * e.g., add joins on one or more other tables. Custom alter tags may also be + * given to increase the specificity of altering. + * + * Fields holding serialized data may be requested in unserialized form. + * + * If called with one or two arguments, the function will consult the schema to + * determine primary key fields and fields to be unserialized. To prevent schema + * loading, feed an array of $primary_key values and set $unserialize to either + * FALSE or an array of field names to be unserialized. + * + * @param $table + * The name of the base table to load records from; this must exist in schema + * API. + * @param $options + * An associative array of additional options, with the following keys: + * - 'conditions' + * An array of conditions to apply to the query. If an integer or array + * of integers is given, these are treated as primary key values. + * Conditions may also be fed as an associative array of field names and + * values to match by. Values may themselves be in array form, in which + * case the 'IN' operator will be used. If any joins are used, the fields + * must be in the form 'tablename.fieldname'. + * - 'fields' + * An array of names of field to load. If omitted, all fields from the + * base table will be loaded. If any joins are used, the fields must be in + * the form 'tablename.fieldname'. + * - 'joins' + * An array of join information. Each array value must be an array of + * arguments expected by the addJoin() method of a SelectQuery object. + * - 'range' + * An array of arguments in the form expected by the addRange() method of + * a SelectQuery object. The first value is the 'start' and the second the + * 'length' of the range of records to load. + * - 'distinct' + * Boolean: whether the query should be flagged as DISTINCT. + * - 'order_by' + * An array of order by clauses, each in the form of arguments expected by + * the orderBy() method of the SelectQuery object: the field name and the + * direction. If any joins are used, the fields must be in the form + * 'tablename.fieldname'. + * - 'alter_tags' + * An array of tags by which the query can be identified for altering. + * @param $primary_keys + * An array of primary keys of the base table. If empty, the primary keys will + * be determined by consulting the schema. + * @param $unserialize + * If TRUE, the schema will be consulted and all fields with a 'serialize' + * value of TRUE will be unserialized. If an array of field names is given, + * these fields will be unserialized in the results. If FALSE, + * @return + * An array of all matching records, or FALSE on failure. If there is a single + * primary key field, the array of results is keyed by primary key value. + */ +function drupal_load_records($table, $options, $primary_keys = array(), $unserialize = TRUE) { + + foreach (array('conditions', 'fields', 'join', 'order_by', 'alter_tags') as $option) { + if (!isset($options[$option])) { + $options[$option] = array(); + } + } + // Give a default alter tag to identify calls from this API fuction. + $options['alter_tags'][] = 'drupal_load_records'; + + // If we don't have primary keys, or need to unserialize but don't have + // a list of fields, we need to load the schema. + if (empty($primary_keys) || $unserialize === TRUE) { + $schema = drupal_get_schema($table); + if (empty($schema)) { + return array(); + } + $primary_keys = $schema['primary key']; + $unserialize = array(); + foreach ($schema['fields'] as $field_name => $field_data) { + if (!empty($field_data['serialize'])) { + $unserialize[] = $field_name; + } + } + // It's slightly more efficient to load all fields by name rather than + // by the * that will be used if an empty array is fed in. + if (empty($query['fields'])) { + $query['fields'] = array_keys($schema['fields']); + } + } + + // Accept a numeric ID key or an array of IDs as conditions. + if (is_numeric($query['conditions']) || is_numeric(key($query['conditions']))) { + if (count($primary_keys) > 1) { + return FALSE; + } + $options['conditions'] = array($primary_keys[0], $options['conditions']); + } + + $query = db_select($table); + $query->fields($table, $fields); + foreach ($options['conditions'] as $field => $value) { + $query->condition($field, $value, is_array($value) ? 'IN' : '='); + } + foreach ($options['joins'] as $join) { + list($type, $table, $alias, $condition, $arguments) = $join; + $query->addJoin($type, $table, $alias, $condition, $arguments); + } + if (isset($options['range'])) { + list($start, $length) = $options['range']; + $query->range($start, $length); + } + if (isset($options['distinct'])) { + $query->distinct($options['distinct']); + } + foreach ($options['order_by'] as $order_by) { + list($field, $direction) = $order_by; + $query->orderBy($field, $direction); + } + foreach ($options['alter_tags'] as $tag) { + $query->addTag($tag); + } + + $result = $query->execute(); + // Key results by primary key if there is a single-field primary key. + if (count($primary_keys) == 1) { + $result = $result->fetchAllAssoc($primary_keys[0]); + } + else { + $result = $result->fetchAll(); + } + if (empty($result)) { + return array(); + } + if ($unserialize) { + foreach (array_keys($result) as $key) { + // Iterate through result records. + foreach ($result[$key] as $field => $value) { + // If required, unserialize results. + if (in_array($field, $unserialize)) { + $result[$key]->$field = unserialize($value); + } + } + } + } + + return $result; +} + +/** + * Delete one or more records from the database, consulting the schema if + * necessary. + * + * @param $table + * The name of the table. + * @param $conditions + * The conditions to match for deletion. If an integer or array of integers is + * given, these are treated as primary key values with the primary key being + * determined from the schema. Matching criteria may also be fed as an array + * of key-value pairs keyed by field name, in which case the schema is not + * consulted. + * + * @return + * Failure to delete based on missing schema information will return FALSE. + * Otherwise SAVED_DELETED. + */ +function drupal_delete_records($table, $conditions) { + if (is_numeric($conditions) || is_numeric(key($conditions))) { + $schema = drupal_get_schema($table); + if (empty($schema)) { + return FALSE; + } + if (count($schema['primary keys']) > 1) { + return FALSE; + } + $primary_key = current($schema['primary keys']); + $conditions = array($primary_key => $value); + } + $query = db_delete($table); + foreach ($conditions as $field => $value) { + $query->condition($field, $value, is_array($value) ? 'IN' : '='); + } + + $query->execute(); +} + +/** * @} End of "ingroup schemaapi". */ Index: modules/simpletest/tests/common.test =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/tests/common.test,v retrieving revision 1.26 diff -u -p -r1.26 common.test --- modules/simpletest/tests/common.test 9 Feb 2009 03:29:54 -0000 1.26 +++ modules/simpletest/tests/common.test 11 Feb 2009 07:05:19 -0000 @@ -783,6 +783,60 @@ class ValidUrlTestCase extends DrupalWeb } /** + * Tests for CRUD API functions. + */ +class DrupalDataApiTest extends DrupalWebTestCase { + function getInfo() { + return array( + 'name' => t('Data API functions'), + 'description' => t('Tests the performance of CRUD APIs.'), + 'group' => t('System'), + ); + } + + function setUp() { + parent::setUp('taxonomy'); + } + + /** + * Test data API methods. + */ + function testDrupalDataApis() { + // Insert an object record for a table with a single-field primary key. + $vocabulary = new StdClass(); + $vocabulary->name = 'test'; + $insert_result = drupal_write_record('taxonomy_vocabulary', $vocabulary); + $this->assertTrue($insert_result == SAVED_NEW, t('Correct value returned when a record is inserted with drupal_write_record() for a table with a single-field primary key.')); + $this->assertTrue(isset($vocabulary->vid), t('Primary key is set on record created with drupal_write_record().')); + + // Update the initial record after changing a property. + $vocabulary->name = 'testing'; + $update_result = drupal_write_record('taxonomy_vocabulary', $vocabulary, array('vid')); + $this->assertTrue($update_result == SAVED_UPDATED, t('Correct value returned when a record updated with drupal_write_record() for table with single-field primary key.')); + + // Insert an object record for a table with a multi-field primary key. + $vocabulary_node_type = new StdClass(); + $vocabulary_node_type->vid = $vocabulary->vid; + $vocabulary_node_type->type = 'page'; + $insert_result = drupal_write_record('taxonomy_vocabulary_node_type', $vocabulary_node_type); + $this->assertTrue($insert_result == SAVED_NEW, t('Correct value returned when a record is inserted with drupal_write_record() for a table with a multi-field primary key.')); + + // Update the record. + $update_result = drupal_write_record('taxonomy_vocabulary_node_type', $vocabulary_node_type, array('vid', 'type')); + $this->assertTrue($update_result == SAVED_UPDATED, t('Correct value returned when a record is updated with drupal_write_record() for a table with a multi-field primary key.')); + + // Test loading a record by primary key. + $record = drupal_load_record('vocabulary', array('conditions' => array($vocabulary->vid))); + $this->assertTrue($record && $record->name == $vocabulary->name, t('Record loaded by ID via drupal_load_record().')); + // Test deleting a record by primary key. + $record = drupal_delete_records('vocabulary', array($vocabulary->vid)); + $record = drupal_load_record('vocabulary', array('conditions' => array($vocabulary->vid))); + $this->assertTrue($record === FALSE, t('Record deleted by ID via drupal_delete_records().')); + } + +} + +/** * Tests Simpletest error and exception collecter. */ class DrupalErrorCollectionUnitTest extends DrupalWebTestCase { @@ -853,3 +907,4 @@ class DrupalErrorCollectionUnitTest exte } } } +