Contributions API

Calling all Drupal developers!

Help us get this on the first page of Digg. DIGG NOW!

Modules in 6

drush_sql.module

<?php
// $Id: drush_sql.module,v 1.12 2008/03/30 15:13:51 weitzman Exp $

/**
 * @file
 *  The drush SQL commands
 */

/**
 * Implementation of hook_help().
 */
function drush_sql_help($section) {
  switch ($section) {
    case 'drush:sql dump':
      return t('Prints the whole database to STDOUT');
    case 'drush:sql query':
      return t('Usage: drush [options] sql query <query> <skip>...\n\n<query> is a SQL statement, which can alternatively be passed via STDIN. Optional - send 1 for <skip> if you wish to omit disposable tables like cache*, search*, etc. Your skip list is specified in your drushrc.php file. Any additional arguments are passed to the mysql command directly.');
    case 'drush:sql load':
      return t('Usage: drush [options] sql load <source_dir> <target_dir> <skip>. <source_dir> and <target_dir> are names of directories under \'sites\'. These determine from where and to where you want your database copied. Optional: specify \'common\' for <skip> if you wish to omit disposable tables like cache*, search*, etc. Your skip lists are specified in your drushrc.php file. Any additional arguments are passed to the mysqldump command directly.');
  }
}

/**
 * Implementation of hook_drush_command().
 */
function drush_sql_drush_command() {
  $items['sql url'] = array(
    'callback' => 'drush_sql_url',
    'description' => 'Print database connection details.'
  );
  $items['sql version server'] = array(
    'callback' => 'drush_sql_version_server',
    'description' => 'Print database server version number.'
  );
  $items['sql version client'] = array(
    'callback' => 'drush_sql_version_client',
    'description' => 'Print database client library version number.'
  );
  $items['sql dump'] = array(
    'callback' => 'drush_sql_dump',
    'description' => 'Exports the Drupal DB as SQL using mysqldump or pg_dump.'
  );
  $items['sql query'] = array(
    'callback' => 'drush_sql_query',
    'description' => 'Execute a query against the site database.'
  );
  $items['sql load'] = array(
    'callback' => 'drush_sql_load',
    'description' => 'Copy source database to target database.'
  );
  return $items;
}


/**
 * Command callback. Displays the Drupal site's database connection string.
 */
function drush_sql_url() {
  drush_print($GLOBALS['db_url']);
}

/**
 * Command callback. Displays the MySQL or PostgreSQL server version number.
 */
function drush_sql_version_server() {
  switch (_drush_sql_get_scheme()) {
    case 'mysql':
    case 'mysqli':
      drush_print(mysql_get_server_info());
      break;
    case 'pgsql':
      // NOTE: apparently the server version is only available if PHP was
      // compiled with PostgreSQL 7.4 or later, so we'll fall back to
      // displaying the client version if that's the case.
      $info = pg_version();
      drush_print(isset($info['server_version']) ? $info['server_version'] : $info['client']);
      break;
    default:
      drush_die(_drush_sql_get_invalid_url_msg());
  }
}

/**
 * Command callback. Displays the MySQL or PostgreSQL client version number.
 */
function drush_sql_version_client() {
  switch (_drush_sql_get_scheme()) {
    case 'mysql':
    case 'mysqli':
      drush_print(mysql_get_client_info());
      break;
    case 'pgsql':
      $info = pg_version();
      drush_print($info['client']);
      break;
    default:
      drush_die(_drush_sql_get_invalid_url_msg());
  }
}

/**
 * Command callback. Outputs the entire Drupal database in SQL format using mysqldump or
 * pg_dump.
 *
 * @param db_url
 * @param execute
 * @param skip
 *    A key in the sql_skip array which specifies a list of tables to ignore when migrating.
 */
function drush_sql_dump($db_url = NULL, $execute = TRUE, $skip = NULL) {
  if (is_null($db_url)) {
    $db_url = $GLOBALS['db_url'];
  }

  switch (_drush_sql_get_scheme($db_url)) {
    case 'mysql':
    case 'mysqli':
      $exec = 'mysqldump' . (DRUSH_VERBOSE ? ' -v' : '');
      $exec .= ' --opt -Q' . _drush_sql_get_credentials($db_url);
      break;
    case 'pgsql':
      drush_die(t('Sorry, pg_dump support not implemented yet.')); // TODO: pg_dump command.
      break;
    default:
      drush_die(_drush_sql_get_invalid_url_msg($db_url));
  }

  // Get any arguments to be passed through to the SQL client program.
  if (func_num_args() > 3 && ($args = func_get_args())) {
    $args = ' ' . implode(' ', array_slice($args, 3)); // skip standard arguments
  }
  
  // Skip large core tables if instructed.  Used by 'sql load' command.
    if ($skip && strpos($exec, '--ignore-table') === FALSE) {
      $all_skip_tables = (array)drush_get_option('skip-tables');
      $skip_tables = $all_skip_tables[$skip];
      $database = _drush_sql_get_database($db_url);
      foreach ($skip_tables as $table) {
        $ignores[] = "--ignore-table=$database.$table";
      }
      $exec .= ' '. implode(' ', $ignores);
    }
  
  if (!$execute) {
    return $exec;
  }
  
  if (DRUSH_VERBOSE) {
    drush_print(t('Executing: !cmd', array('!cmd' => $exec)));
  }

  return drush_op('system', $exec) !== FALSE;
}

/**
 * Command callback. Executes the given SQL query on the Drupal database.
 * Reads from STDIN if no query is provided.
 */
function drush_sql_query($query = NULL) {
  // Get any arguments to be passed through to the SQL client program.
  if (func_num_args() > 1 && ($args = func_get_args())) {
    $args = ' ' . implode(' ', array_slice($args, 1)); // skip query argument
  }
  
  return drush_sql_query_execute($query, NULL, $args);
}

function drush_sql_query_execute($query, $db_url = NULL, $args = NULL) {
  // FIXME: currently we require the query due to an apparent inability to
  // run the mysql child process interactively. Need to look more into
  // proc_open() to see if it could be used for interactive execution.
  if (empty($query)) {
    if ($fstat = fstat(STDIN)) {
      $query = fread(STDIN,$fstat[size]);
    }
    else {
      drush_die(t('No SQL query given as argument.'));
    }
  }
  
  // Save query to a file. we will redirect it in.
  $filename = time(). '.sql';
  if ($file = file_save_data($query, $filename)) {
    switch (_drush_sql_get_scheme()) {
      case 'mysql':
      case 'mysqli':
        $exec = 'mysql' . (DRUSH_VERBOSE ? ' -v' : '');
        $exec .= _drush_sql_get_credentials($db_url);
        $exec .= " < ./$file";
        break;
      case 'pgsql':
        drush_die(t('Sorry, psql support not implemented yet.')); // TODO: psql command.
        break;
      default:
        drush_die(_drush_sql_get_invalid_url_msg($db_url));
    }
    $exec .= $args;

    if (DRUSH_VERBOSE) {
      drush_print(t('Executing: !cmd', array('!cmd' => $exec)));
    }
    $return = drush_op('system', $exec) !== FALSE;
    unlink($file);  
    return $return;
  }
}

/**
 * Copy an entire database to another database. For example, migrate from Production to dev
 * or dev to staging.
 * 
 * conf_path() uses a static var so we can't use it to figure out paths based on URIs.
 *
 * @param source
 *   The name of a subdirectory under sites. Its settings.php specifies the database which should be migrated.
 * @param target
 *    The name of a subdirectory under sites. Its settings.php specifies the database which whose tables will 
 *    be replaced with the contents of `source`.
 * @param skip
 *    A key in the sql_skip array which specifies a list of tables to ignore when migrating.
 * 
 **/
function drush_sql_load($source, $target, $skip = NULL) {
  // Don't use require_once - we need to ovewrite db_url under some circumstances.
  require "./sites/$source/settings.php";
  $db_url_source = is_array($db_url) ? $db_url['default'] : $db_url;
  require "./sites/$target/settings.php";
  $db_url_target = is_array($db_url) ? $db_url['default'] : $db_url;
  
  // Prompt for confirmation. This is destructive.
  if (!DRUSH_SIMULATE) {
    drush_print(t("You will destroy data from !target and replace with data from !source.", array('!source' => $db_url_source, '!target' => $db_url_target)));
    // TODO: actually make the backup if desired.
    drush_print(t("You might want to make a backup first, using sql_dump command."));
    if (!drush_confirm(t('Do you really want to continue?'))) {
      drush_die('Aborting.');
    }
  }
  
  // Get command to export from source.
  if ($retrieve = drush_sql_dump($db_url_source, FALSE, $skip)) {
    // Get any arguments to be passed through to the mysqldump.
    $args = array();
    if (func_num_args() > 3 && ($args = func_get_args())) {
      $retrieve .= ' ' . implode(' ', array_slice($args, 3)); // skip standard arguments
    }
    
    // Build import command for target.
    $send = 'mysql' . (DRUSH_VERBOSE ? ' -v' : '');
    $send .= _drush_sql_get_credentials($db_url_target);
    
    // Build pipe command and redirect output to the bit bucket.
    // Redirection prevented loads of SQL from writing to screen.
    $exec = "$retrieve | $send > /dev/null 2>&1";

    if (DRUSH_VERBOSE) {
      drush_print(t('Executing: !cmd', array('!cmd' => $exec)));
    }
    
    $return = drush_op('system', $exec) !== FALSE;
  }
  return $return;
}

//////////////////////////////////////////////////////////////////////////////
// SQL SERVICE HELPERS

// this sets some globals so please beware.
function drush_sql_get_path($uri) {
  $drupal_base_url = parse_url($uri);
  $_SERVER['HTTP_HOST'] = $drupal_base_url['host'];
  $_SERVER['PHP_SELF'] = $drupal_base_url['path'].'/index.php';
  return conf_path();
}

function _drush_sql_get_scheme($db_url = NULL) {
  if (is_null($db_url)) {
    $db_url = $GLOBALS['db_url'];
  }
  $url = (object)parse_url($db_url);
  return ($url->scheme);
}

function _drush_sql_get_database($db_url = NULL) {
  if (is_null($db_url)) {
    $db_url = $GLOBALS['db_url'];
  }
  $url = (object)parse_url($db_url);
  return substr($url->path, 1);
}

function _drush_sql_get_credentials($db_url = NULL) {
  if (is_null($db_url)) {
    $db_url = $GLOBALS['db_url'];
  }

  // NOTE: this regex could also parse the connection string URL:
  //   ^([\w]+)://([\w\d_]+):([^@]*)@([\w\d\.\-]*)/([\w\d_]+)$

  $url = (object)parse_url($db_url);
  $url->user = urldecode($url->user);
  $url->pass = urldecode($url->pass);
  $url->host = urldecode($url->host);
  $url->path = substr(urldecode($url->path), 1); // skip leading '/' character

  switch ($url->scheme) {
    case 'mysql':
    case 'mysqli':
      return ' -h' . $url->host .
        (!isset($url->port) ? '' : ' -P' . $url->port) .
        ' -u' . $url->user .
        (empty($url->pass) ? '' : ' -p' . $url->pass) . ' ' . $url->path;
    case 'pgsql':
      drush_die(t('Sorry, psql support not implemented yet.')); // TODO: psql credentials.
      return;
    default:
      drush_die(_drush_sql_get_invalid_url_msg());
  }
}

function _drush_sql_get_invalid_url_msg($db_url = NULL) {
  if (is_null($db_url)) {
    $db_url = $GLOBALS['db_url'];
  }
  return t('Unable to parse DB connection string: `%url\'.', array('%url' => $db_url));
}