You are here

function content_alter_db in Content Construction Kit (CCK) 6.3

Same name and namespace in other branches
  1. 6 includes/content.admin.inc \content_alter_db()
  2. 6.2 includes/content.admin.inc \content_alter_db()

Perform adds, alters, and drops as needed to synchronize the database with new field definitions.

2 calls to content_alter_db()
content_alter_fields in includes/content.admin.inc
Batching process for changing the field schema, running each affected node through node_save() first, to fire all hooks.
content_alter_schema in includes/content.admin.inc
Content Schema Alter
1 string reference to 'content_alter_db'
content_alter_fields in includes/content.admin.inc
Batching process for changing the field schema, running each affected node through node_save() first, to fire all hooks.

File

includes/content.admin.inc, line 1448
Administrative interface for content type creation.

Code

function content_alter_db($previous_field, $new_field) {
  $ret = array();

  // One or the other of these must be valid.
  if (empty($previous_field) && empty($new_field)) {
    return $ret;
  }

  // Gather relevant information : schema, table name...
  $previous_schema = !empty($previous_field) ? content_table_schema($previous_field) : array();
  $new_schema = !empty($new_field) ? content_table_schema($new_field) : array();
  if (!empty($previous_field)) {
    $previous_db_info = content_database_info($previous_field);
    $previous_table = $previous_db_info['table'];
  }
  if (!empty($new_field)) {
    $new_db_info = content_database_info($new_field);
    $new_table = $new_db_info['table'];
  }

  // Deletion of a field instance: drop relevant columns and tables and return.
  if (empty($new_field)) {
    if ($previous_field['db_storage'] == CONTENT_DB_STORAGE_PER_FIELD) {
      db_drop_table($ret, $previous_table);
    }
    else {
      foreach ($previous_schema['fields'] as $column => $attributes) {
        if (!in_array($column, array(
          'nid',
          'vid',
          'delta',
        ))) {
          db_drop_field($ret, $previous_table, $column);
        }
      }
    }
    content_alter_db_cleanup();
    return $ret;
  }

  // Check that content types that have fields do have a per-type table.
  if (!empty($new_field)) {
    $base_tablename = _content_tablename($new_field['type_name'], CONTENT_DB_STORAGE_PER_CONTENT_TYPE);
    if (!db_table_exists($base_tablename)) {
      db_create_table($ret, $base_tablename, content_table_schema());
    }
  }

  // Create new table and columns, if not already created.
  if (!db_table_exists($new_table)) {
    db_create_table($ret, $new_table, $new_schema);
  }
  else {

    // Or add fields and/or indexes to an existing table.
    foreach ($new_schema['fields'] as $column => $attributes) {
      if (!in_array($column, array(
        'nid',
        'vid',
        'delta',
      ))) {

        // Create the column if it does not exist.
        if (!db_column_exists($new_table, $column)) {
          db_add_field($ret, $new_table, $column, $attributes);
        }

        // Create the index if requested to, and it does not exist.
        if (isset($new_schema['indexes'][$column]) && !content_db_index_exists($new_table, $column)) {
          db_add_index($ret, $new_table, $column, $new_schema['indexes'][$column]);
        }
      }
    }
  }

  // If this is a new field, we're done.
  if (empty($previous_field)) {
    content_alter_db_cleanup();
    return $ret;
  }

  // If the previous table doesn't exist, we're done.
  // Could happen if someone tries to run a schema update from an
  // content.install update function more than once.
  if (!db_table_exists($previous_table)) {
    content_alter_db_cleanup();
    return $ret;
  }

  // If changing data from one schema to another, see if changes require that
  // we drop multiple values or migrate data from one storage type to another.
  $migrate_columns = array_intersect_assoc($new_schema['fields'], $previous_schema['fields']);
  unset($migrate_columns['nid'], $migrate_columns['vid'], $migrate_columns['delta']);

  // If we're going from one multiple value a smaller one or to single,
  // drop all delta values higher than the new maximum delta value.
  // Not needed if the new multiple is unlimited or if the new table is the content table.
  if ($new_table != $base_tablename && $new_field['multiple'] < $previous_field['multiple'] && $new_field['multiple'] != 1) {
    db_query("DELETE FROM {" . $new_table . "} WHERE delta >= " . max(1, $new_field['multiple']));
  }

  // If going from multiple to non-multiple, make sure the field tables have
  // the right database structure to accept migrated data.
  if ($new_field['db_storage'] == CONTENT_DB_STORAGE_PER_FIELD) {
    if ($previous_field['db_storage'] == CONTENT_DB_STORAGE_PER_FIELD && count($previous_schema['fields'])) {

      // Already using per-field storage; change multiplicity if needed.
      if ($previous_field['multiple'] > 0 && $new_field['multiple'] == 0) {
        db_drop_field($ret, $new_table, 'delta');
        db_drop_primary_key($ret, $new_table);
        db_add_primary_key($ret, $new_table, array(
          'vid',
        ));
      }
      else {
        if ($previous_field['multiple'] == 0 && $new_field['multiple'] > 0) {
          db_add_field($ret, $new_table, 'delta', array(
            'type' => 'int',
            'unsigned' => TRUE,
            'not null' => TRUE,
            'default' => 0,
          ));
          db_drop_primary_key($ret, $new_table);
          db_add_primary_key($ret, $new_table, array(
            'vid',
            'delta',
          ));
        }
      }
    }
  }

  // Migrate data from per-content-type storage.
  if ($previous_field['db_storage'] == CONTENT_DB_STORAGE_PER_CONTENT_TYPE && $new_field['db_storage'] == CONTENT_DB_STORAGE_PER_FIELD) {
    $columns = array_keys($migrate_columns);
    if ($new_field['multiple']) {
      db_query('INSERT INTO {' . $new_table . '} (vid, nid, delta, ' . implode(', ', $columns) . ') ' . ' SELECT vid, nid, 0, ' . implode(', ', $columns) . ' FROM {' . $previous_table . '}');
    }
    else {
      db_query('INSERT INTO {' . $new_table . '} (vid, nid, ' . implode(', ', $columns) . ') ' . ' SELECT vid, nid, ' . implode(', ', $columns) . ' FROM {' . $previous_table . '}');
    }
    foreach ($columns as $column_name) {
      db_drop_field($ret, $previous_table, $column_name);
    }
  }

  // Migrate data from per-field storage, and drop per-field table.
  if ($previous_field['db_storage'] == CONTENT_DB_STORAGE_PER_FIELD && $new_field['db_storage'] == CONTENT_DB_STORAGE_PER_CONTENT_TYPE) {

    // In order to be able to use drupal_write_record, we need to
    // rebuild the schema now.
    content_alter_db_cleanup();
    if ($previous_field['multiple']) {
      $result = db_query("SELECT * FROM {" . $previous_table . "} c JOIN {node} n ON c.nid = n.nid WHERE delta = 0 AND n.type = '%s'", $new_field['type_name']);
    }
    else {
      $result = db_query("SELECT * FROM {" . $previous_table . "} c JOIN {node} n ON c.nid = n.nid WHERE n.type = '%s'", $new_field['type_name']);
    }
    $record = array();
    while ($data = db_fetch_array($result)) {
      $record['nid'] = $data['nid'];
      $record['vid'] = $data['vid'];
      if ($previous_field['multiple']) {
        $record['delta'] = $data['delta'];
      }
      foreach ($migrate_columns as $column => $attributes) {
        if (is_null($data[$column])) {
          $record[$column] = NULL;
        }
        else {
          $record[$column] = $data[$column];

          // Prevent double serializtion in drupal_write_record.
          if (isset($attributes['serialize']) && $attributes['serialize']) {
            $record[$column] = unserialize($record[$column]);
          }
        }
      }
      if (db_result(db_query('SELECT COUNT(*) FROM {' . $new_table . '} WHERE vid = %d AND nid = %d', $data['vid'], $data['nid']))) {
        $keys = $new_field['multiple'] ? array(
          'vid',
          'delta',
        ) : array(
          'vid',
        );
        drupal_write_record($new_table, $record, $keys);
      }
      else {
        drupal_write_record($new_table, $record);
      }
    }
    db_drop_table($ret, $previous_table);
  }

  // Change modified columns that don't involve storage changes.
  foreach ($new_schema['fields'] as $column => $attributes) {
    if (isset($previous_schema['fields'][$column]) && $previous_field['db_storage'] == $new_field['db_storage']) {
      if ($attributes != $previous_schema['fields'][$column]) {
        if (!in_array($column, array(
          'nid',
          'vid',
          'delta',
        ))) {
          db_change_field($ret, $new_table, $column, $column, $attributes);
        }
      }
    }
  }

  // Remove obsolete columns.
  foreach ($previous_schema['fields'] as $column => $attributes) {
    if (!isset($new_schema['fields'][$column])) {
      if (!in_array($column, array(
        'nid',
        'vid',
        'delta',
      ))) {
        db_drop_field($ret, $previous_table, $column);
      }
    }
  }

  // TODO: debugging stuff - should be removed
  if (module_exists('devel')) {

    //dsm($ret);
  }
  return $ret;
}