public static function changeSchema(array &$field, array $column_renames = array()) { // Update the field schema $old_schema = array_intersect_key($field, array('columns' => '', 'indexes' => '', 'foreign keys' => '')); module_load_install($field['module']); $new_schema = (array) module_invoke($field['module'], 'field_schema', $field); $new_schema += array('columns' => array(), 'indexes' => array(), 'foreign keys' => array()); $field['data']['columns'] = $new_schema['columns']; $field['data']['indexes'] = $new_schema['indexes']; $field['data']['foreign keys'] = $new_schema['foreign keys']; $data_table = _field_sql_storage_tablename($field); $revision_table = _field_sql_storage_revision_tablename($field); // Validate that all the columns described in the existing schema actually exist. foreach (array_keys($old_schema['columns']) as $old_column) { $old_column_name = _field_sql_storage_columnname($field['field_name'], $old_column); if (!db_field_exists($data_table, $old_column_name)) { throw new Exception(); } if (!db_field_exists($revision_table, $old_column_name)) { throw new Exception(); } // Attempt to re-use any columns that have the same name. // This can be skipped by setting $column_renames['column-name'] = FALSE; if (!empty($new_schema['columns'][$old_column]) && !isset($column_renames[$old_column])) { $column_renames[$old_column] = $old_column; } } // Validate that any columns to be renamed actually exist. foreach ($column_renames as $old_column => $new_column) { if (!isset($old_schema['columns'][$old_column])) { throw new Exception("Cannot rename field {$field['field_name']} column {$old_column} because it does not exist in the old schema."); } if (!isset($new_schema['columns'][$new_column])) { throw new Exception("Cannot rename field {$field['field_name']} column {$old_column} to {$new_column} because it does not exist in the new schema."); } } // Remove all existing indexes. foreach ($old_schema['indexes'] as $index => $index_fields) { $index_name = _field_sql_storage_indexname($field['field_name'], $index); if (db_index_exists($data_table, $index_name)) { watchdog('helper', "Dropped index {$data_table}.{$index_name}"); db_drop_index($data_table, $index_name); } if (db_index_exists($revision_table, $index_name)) { watchdog('helper', "Dropped index {$revision_table}.{$index_name}"); db_drop_index($revision_table, $index_name); } } // Rename any columns. foreach ($column_renames as $old_column => $new_column) { $old_column_name = _field_sql_storage_columnname($field['field_name'], $old_column); if ($new_column === FALSE) { db_drop_field($data_table, $old_column_name); watchdog('helper', "Dropped column {$data_table}.{$old_column_name}"); db_drop_field($revision_table, $old_column_name); watchdog('helper', "Dropped column {$revision_table}.{$old_column_name}"); unset($old_schema['columns'][$old_column]); } else { $new_column_name = _field_sql_storage_columnname($field['field_name'], $new_column); db_change_field($data_table, $old_column_name, $new_column_name, $new_schema['columns'][$new_column]); watchdog('helper', "Changed column {$data_table}.{$old_column_name}<br/><pre>" . print_r($new_schema['columns'][$new_column], TRUE) . '</pre>'); db_change_field($revision_table, $old_column_name, $new_column_name, $new_schema['columns'][$new_column]); watchdog('helper', "Changed column {$revision_table}.{$old_column_name}<br/><pre>" . print_r($new_schema['columns'][$new_column], TRUE) . '</pre>'); // Remove these fields so they aren't removed or added in the code below. unset($new_schema['columns'][$new_column]); unset($old_schema['columns'][$old_column]); } } // Remove any old columns. $old_columns = array_diff_key($old_schema['columns'], $new_schema['columns']); foreach (array_keys($old_columns) as $old_column) { $old_column_name = _field_sql_storage_columnname($field['field_name'], $old_column); db_drop_field($data_table, $old_column_name); watchdog('helper', "Dropped column {$data_table}.{$old_column_name}"); db_drop_field($revision_table, $old_column_name); watchdog('helper', "Dropped column {$revision_table}.{$old_column_name}"); } // Add any new columns. $new_columns = array_diff_key($new_schema['columns'], $old_schema['columns']); foreach (array_keys($new_columns) as $new_column) { $new_column_name = _field_sql_storage_columnname($field['field_name'], $new_column); db_add_field($data_table, $new_column_name, $new_schema['columns'][$new_column]); watchdog('helper', "Added column {$data_table}.{$new_column_name}"); db_add_field($revision_table, $new_column_name, $new_schema['columns'][$new_column]); watchdog('helper', "Added column {$revision_table}.{$new_column_name}"); } // Re-add indexes. foreach ($new_schema['indexes'] as $index => $index_fields) { foreach ($index_fields as &$index_field) { if (is_array($index_field)) { $index_field[0] = _field_sql_storage_columnname($field['field_name'], $index_field[0]); } else { $index_field = _field_sql_storage_columnname($field['field_name'], $index_field); } } $index_name = _field_sql_storage_indexname($field['field_name'], $index); db_add_index($data_table, $index_name, $index_fields); watchdog('helper', "Added index {$data_table}.{$index_name}<br/><pre>" . print_r($index_fields, TRUE) . '</pre>'); db_add_index($revision_table, $index_name, $index_fields); watchdog('helper', "Added index {$revision_table}.{$index_name}<br/><pre>" . print_r($index_fields, TRUE) . '</pre>'); } }
/** * Asserts that a given field can be added and removed from a table. * * The addition test covers both defining a field of a given specification * when initially creating at table and extending an existing table. * * @param $field_spec * The schema specification of the field. */ protected function assertFieldAdditionRemoval($field_spec) { // Try creating the field on a new table. $table_name = 'test_table_' . $this->counter++; $table_spec = array('fields' => array('serial_column' => array('type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE), 'test_field' => $field_spec), 'primary key' => array('serial_column')); db_create_table($table_name, $table_spec); $this->pass(format_string('Table %table created.', array('%table' => $table_name))); // Check the characteristics of the field. $this->assertFieldCharacteristics($table_name, 'test_field', $field_spec); // Clean-up. db_drop_table($table_name); // Try adding a field to an existing table. $table_name = 'test_table_' . $this->counter++; $table_spec = array('fields' => array('serial_column' => array('type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE)), 'primary key' => array('serial_column')); db_create_table($table_name, $table_spec); $this->pass(format_string('Table %table created.', array('%table' => $table_name))); // Insert some rows to the table to test the handling of initial values. for ($i = 0; $i < 3; $i++) { db_insert($table_name)->useDefaults(array('serial_column'))->execute(); } db_add_field($table_name, 'test_field', $field_spec); $this->pass(format_string('Column %column created.', array('%column' => 'test_field'))); // Check the characteristics of the field. $this->assertFieldCharacteristics($table_name, 'test_field', $field_spec); // Clean-up. db_drop_field($table_name, 'test_field'); // Add back the field and then try to delete a field which is also a primary // key. db_add_field($table_name, 'test_field', $field_spec); db_drop_field($table_name, 'serial_column'); db_drop_table($table_name); }
/** * Asserts that a newly added field has the correct characteristics. */ protected function assertFieldCharacteristics($table_name, $field_name, $field_spec) { // Check that the initial value has been registered. if (isset($field_spec['initial'])) { // There should be no row with a value different then $field_spec['initial']. $count = db_select($table_name)->fields($table_name, array('serial_column'))->condition($field_name, $field_spec['initial'], '<>')->countQuery()->execute()->fetchField(); $this->assertEqual($count, 0, 'Initial values filled out.'); } // Check that the default value has been registered. if (isset($field_spec['default'])) { // Try inserting a row, and check the resulting value of the new column. $id = db_insert($table_name)->useDefaults(array('serial_column'))->execute(); $field_value = db_select($table_name)->fields($table_name, array($field_name))->condition('serial_column', $id)->execute()->fetchField(); $this->assertEqual($field_value, $field_spec['default'], 'Default value registered.'); } db_drop_field($table_name, $field_name); }