save($data); * * Remove the data from the table. * $table->truncate(); * * Remove the table, but not the meta information about the table. * data_drop_table('my_table'); * */ class DataTable { // Class variables. // @todo: change $table_schema to $schema. // @todo: change $name to $id. // Unfortunately drupal_write_record does not escape field names. $table_schema instead of $schema it is. protected $name, $title, $table_schema, $meta, $export_type; /** * Instiate a DataTable object. Use this function instead of new DataTable. */ public static function instance($name) { static $tables; if (!isset($tables[$name])) { $tables[$name] = new DataTable($name); } return $tables[$name]; } /** * Constructor. Do not call directly, but use DataTable::instance($name) instead. */ protected function __construct($name) { $this->name = $name; // Try to load table information. if ($table = _data_load_table($name)) { foreach (array('title', 'name', 'table_schema', 'meta', 'export_type') as $key) { $this->$key = $table->$key; } } } /** * Create a table. * * Do not call directly but use data_create_table() instead. */ public function create($table_schema) { // Only create the table if it is not defined as data table AND it does not // physically exist. if (!_data_load_table($this->name, TRUE) && !db_table_exists($this->name)) { // Create table. db_create_table($ret, $this->name, $table_schema); if ($ret[0]['success'] != 1) { drupal_set_message(t('Error creating table.'), 'error'); return FALSE; } // If schema module is enabled, inspect and read back to make // sure our schema information is up to date. // @todo: this is slow, maybe we need to make this an explicit method // on DataTable. if (module_exists('_schema')) { $schema = schema_invoke('inspect'); if (isset($schema[$this->name])) { $table_schema = $schema[$this->name]; } } // Set table_schema and export_type. // @todo: rather user _data_table_load() ? $this->table_schema = $table_schema; $this->export_type = EXPORT_IN_DATABASE; // Save table information. // Set export_type - needs to be defined so that schema information is being passed on // to Drupal by data_schema_alter(). $table = array( 'name' => $this->name, 'table_schema' => $this->table_schema, ); drupal_write_record('data_tables', $table); // Clear caches. drupal_get_schema($this->name, TRUE); return TRUE; } return FALSE; } /** * Determine whether a table is defined. * * @return * TRUE if the table is defined, FALSE otherwise. * Note: If a table is defined it does not mean that it actually exists in the * database. */ public function defined() { return _data_load_table($this->name) ? TRUE : FALSE; } /** * Get a property of the DataTable object. * * @param $property * One of 'name', 'title', 'table_schema', 'meta'. * @return * The unserialized value of the property. */ public function get($property) { if (in_array($property, array('name', 'title', 'table_schema', 'meta', 'export_type'))) { return $this->$property; } } /** * Update table properties. * * @param $properties * Array where the key designates a property (one of 'name', 'title', 'table_schema', 'meta') * and the value is the unserialized value that this property should attain. */ public function update($properties) { _data_override($this->name); $properties['name'] = $this->name; if (drupal_write_record('data_tables', $properties, 'name')) { foreach ($properties as $key => $value) { $this->$key = $value; } } } /** * Compare this table's schema to schema of table in DB. * Requires schema module. * * @return * */ public function compareSchema() { if (module_exists('schema')) { $this->table_schema['name'] = $this->name; return schema_compare_table($this->table_schema); } } /** * Add a field. * * @todo: Check wether field name is available, otherwise change. * @todo: Return false if not successful. */ public function addField($field, $spec) { $ret = array(); db_add_field($ret, $this->name, $field, $spec); if ($ret[0]['success']) { $schema = $this->table_schema; $schema['fields'][$field] = $spec; $this->update(array('table_schema' => $schema)); // @todo: maybe move these cache clearing functions to their own method // and let take API users take care of caching. drupal_get_schema($this->name, TRUE); return $field; } return FALSE; } /** * Add an index to table. * * @todo: support more than one field. */ public function addIndex($field) { $schema = $this->table_schema; if ($schema['fields'][$field]) { $index = data_get_index_definition($field, $schema['fields'][$field]); db_add_index($ret, $this->name, $field, $index); if ($ret[0]['success']) { $schema['indexes'][$field] = $index; $this->update(array('table_schema' => $schema)); drupal_get_schema($this->name, TRUE); return TRUE; } } return FALSE; } /** * Drop an index from a table. */ public function dropIndex($field) { $ret = array(); db_drop_index($ret, $this->name, $field); if ($ret[0]['success']) { $schema = $this->table_schema; unset($schema['indexes'][$field]); $this->update(array('table_schema' => $schema)); drupal_get_schema($this->name, TRUE); return TRUE; } return FALSE; } /** * Add a unique key to a field. */ public function addUniqueKey($field) { $schema = $this->table_schema; if ($schema['fields'][$field]) { $ret = array(); $index = data_get_index_definition($field, $schema['fields'][$field]); db_add_unique_key($ret, $this->name, $field, $index); if ($ret[0]['success']) { $schema['unique keys'][$field] = array($field); $this->update(array('table_schema' => $schema)); drupal_get_schema($this->name, TRUE); return TRUE; } } return FALSE; } /** * Drop a unique key from a table. */ public function dropUniqueKey($field) { $ret = array(); db_drop_unique_key($ret, $this->name, $field); if ($ret[0]['success']) { $schema = $this->table_schema; unset($schema['unique keys'][$field]); $this->update(array('table_schema' => $schema)); drupal_get_schema($this->name, TRUE); return TRUE; } return FALSE; } /** * Change indexes of a table. */ public function changeIndex($fields) { $schema = $this->table_schema; // @TODO: This array_keys() reduces indexes to single field indexes. // Will need adjustment when multi-field indexes are implemented. $indexes = isset($schema['indexes']) ? array_keys($schema['indexes']) : array(); $add = array_diff($fields, $indexes); $drop = array_diff($indexes, $fields); foreach ($add as $field) { if (!$this->addIndex($field)) { return FALSE; } } foreach ($drop as $field) { if (!$this->dropIndex($field)) { return FALSE; } } return TRUE; } /** * Add a primary key to table. */ public function addPrimaryKey($fields) { $ret = array(); db_add_primary_key($ret, $this->name, $fields); if ($ret[0]['success']) { $schema = $this->table_schema; $schema['primary key'] = $fields; $this->update(array('table_schema' => $schema)); drupal_get_schema($this->name, TRUE); return TRUE; } return FALSE; } /** * Drop all primary keys from a table. */ public function dropPrimaryKey() { $ret = array(); db_drop_primary_key($ret, $this->name); if ($ret[0]['success']) { $schema = $this->table_schema; $schema['primary key'] = array(); $this->update(array('table_schema' => $schema)); drupal_get_schema($this->name, TRUE); return TRUE; } return FALSE; } /** * Change the primary keys of a table. */ public function changePrimaryKey($fields) { $schema = $this->table_schema; if (!empty($schema['primary key'])) { if (!$this->dropPrimaryKey()) { return FALSE; } } if (!empty($fields)) { return $this->addPrimaryKey($fields); } return TRUE; } /** * Change a field. */ public function changeField($field, $spec) { $ret = array(); db_change_field($ret, $this->name, $field, $field, $spec); if ($ret[0]['success']) { $schema = $this->table_schema; $schema['fields'][$field] = $spec; $this->update(array('table_schema' => $schema)); drupal_get_schema($this->name, TRUE); return TRUE; } return FALSE; } /** * Delete a field. */ public function dropField($field) { $ret = array(); db_drop_field($ret, $this->name, $field); if ($ret[0]['success']) { $schema = $this->table_schema; unset($schema['fields'][$field]); $meta = $this->meta; unset($meta['fields'][$field]); $this->update(array('table_schema' => $schema), array('meta' => $meta)); drupal_get_schema($this->name, TRUE); return TRUE; } return FALSE; } /** * Drop a table. Does not drop a table if its defined in code. * * @return * TRUE if the table was dropped, FALSE otherwise. */ public function drop() { if ($this->export_type == EXPORT_IN_DATABASE) { db_drop_table($ret, $this->name); $this->update(array('table_schema' => array())); drupal_get_schema($this->name, TRUE); db_query('DELETE FROM {data_tables} WHERE name = "%s"', $this->name); $this->title = ''; $this->table_schema = $this->meta = array(); return TRUE; } return FALSE; } /** * Revert a table to its definition in code. * * Does not revert a table if it is not defined in code. * * @return * TRUE if the table was reverted, FALSE otherwise. */ public function revert() { if ($this->export_type & EXPORT_IN_CODE) { db_query('DELETE FROM {data_tables} WHERE name = "%s"', $this->name); return TRUE; } return FALSE; } /** * Link this table to another table. Linking a table to another one is * to define how data in these tables should be joined to each other. * * There can be more than one link to the left of a table. However, * for views integration, only the first join created will be used. * * @todo: Get rid of link() language, use setJoin()/removeJoin() instead. */ public function link($left_table, $left_field, $field = NULL, $inner_join = TRUE) { if ($field == NULL) { $field = $left_table; } $this->meta['join'][$left_table] = array( 'left_field' => $left_field, 'field' => $field, 'inner_join' => $inner_join, ); $this->update(array('meta' => $this->meta)); } /** * Unlink this table from another table. */ public function unlink($left_table) { unset($this->meta['join'][$left_table]); $this->update(array('meta' => $this->meta)); } /** * Load a record. */ public function load($keys) { $where = $values = array(); $fields = $this->table_schema['fields']; foreach ($keys as $key => $value) { // Return if a key does not exist. if (!isset($fields[$key]['type'])) { return FALSE; } $where[] = db_escape_string($key) ." = ". db_type_placeholder($fields[$key]['type']); $values[] = $value; } if (!empty($where)) { $result = db_query('SELECT * FROM {'. db_escape_table($this->name) .'} WHERE '. implode(' AND ', $where), $values); $results = array(); while ($row = db_fetch_array($result)) { $results[] = $row; } return count($results) ? $results : FALSE; } return FALSE; } /** * Save to data table. */ public function save(&$record, $update = array()) { if (count($update)) { $keys = array(); foreach ($update as $key) { $keys[$key] = $record[$key]; } // Update if available. if ($this->load($keys)) { return drupal_write_record($this->name, $record, $update); } } // Otherwise insert. return drupal_write_record($this->name, $record); } /** * Delete from data table. */ public function delete($keys) { $where = $values = array(); $fields = $this->table_schema['fields']; foreach ($keys as $key => $value) { $where[] = db_escape_string($key) ." = ". db_type_placeholder($fields[$key]['type']); $values[] = $value; } if (!empty($where)) { db_query('DELETE FROM {'. db_escape_table($this->name) .'} WHERE '. implode(' AND ', $where), $values); } } /** * Empty data table. */ public function truncate() { db_query('TRUNCATE TABLE {'. db_escape_table($this->name) .'}'); } }