diff --git a/commands/sql/sql.drush.inc b/commands/sql/sql.drush.inc index af209e1..1dda24b 100644 --- a/commands/sql/sql.drush.inc +++ b/commands/sql/sql.drush.inc @@ -290,6 +290,82 @@ function drush_sql_get_table_selection() { return array('skip' => $skip_tables, 'structure' => $structure_tables, 'tables' => $tables); } +function drush_sql_expand_table_selection($table_selection, $db_spec, $site_record = NULL) { + if (isset($table_selection['skip'])) { + $table_selection['skip'] = drush_sql_expand_wildcard_tables($table_selection['skip'], $db_spec, $site_record); + } + if (isset($table_selection['structure'])) { + $table_selection['structure'] = drush_sql_expand_wildcard_tables($table_selection['structure'], $db_spec, $site_record); + } + if (isset($table_selection['tables'])) { + $table_selection['tables'] = drush_sql_validate_tables($table_selection['tables'], $db_spec, $site_record); + } + return $table_selection; +} + +function drush_sql_get_expanded_table_selection($db_spec, $site_record = NULL) { + return drush_sql_expand_table_selection(drush_sql_get_table_selection(), $db_spec, $site_record); +} + +/** + * Expand wildcard tables. + * + * @param $tables + * Array of table names, some of which may contain wildcards (`*`). + * @param $db_spec + * @param $site_record + * @return + * Array with the corresponding expanded table names, all of which actually + * exist in the database, none of which still contains a wildcard (`*`). + */ +function drush_sql_expand_wildcard_tables($tables, $db_spec, $site_record = NULL) { + // Get the existing table names contained in the given database. + $db_tables = _drush_sql_get_db_table_list($db_spec, $site_record); + + // Table name expansion based on `*` wildcard. + $expanded_db_tables = array(); + foreach ($tables as $k => $table) { + // Only deal with table names containing a wildcard. + if (strpos($table, '*') !== FALSE) { + $pattern = '/^' . str_replace('*', '.*', $table) . '$/i'; + // Merge those existing tables which match the pattern with the rest of + // the expanded table names. + $expanded_db_tables += preg_grep($pattern, $db_tables); + } + } + $tables += $expanded_db_tables; + $tables = drush_sql_validate_tables($tables, $db_spec); + $tables = array_unique($tables); + sort($tables); + + return $tables; +} + +/** + * Validate tables. + * + * @param $tables + * Array of table names to validate. + * @param $db_spec + * @param $site_record + * @return + * Array with only valid table names (i.e. all of which actually exist in + * the database). + */ +function drush_sql_validate_tables($tables, $db_spec, $site_record = NULL) { + // Get the existing table names contained in the current database. + $db_tables = _drush_sql_get_db_table_list($db_spec, $site_record); + + // Ensure all the tables actually exist in the database. + foreach ($tables as $k => $table) { + if (!in_array($table, $db_tables)) { + unset($tables[$k]); + } + } + + return $tables; +} + /** * Build a mysqldump/pg_dump/sqlite statement. * @@ -301,7 +377,11 @@ function drush_sql_get_table_selection() { * 2. The filepath where the dump will be saved. */ function drush_sql_dump($db_spec = NULL) { - return drush_sql_build_dump_command(drush_sql_get_table_selection(), $db_spec, drush_get_option('result-file', FALSE)); + if (is_null($db_spec)) { + $db_spec = _drush_sql_get_db_spec(); + } + $table_selection = drush_sql_get_expanded_table_selection($db_spec); + return drush_sql_build_dump_command($table_selection, $db_spec, drush_get_option('result-file', FALSE)); } /** @@ -329,9 +409,6 @@ function drush_sql_build_dump_command($table_selection, $db_spec = NULL, $file = // @see drush_get_option_help() in drush.inc $ordered_dump = drush_get_option('ordered-dump'); - if (is_null($db_spec)) { - $db_spec = _drush_sql_get_db_spec(); - } $database = $db_spec['database']; // $file is passed in to us usually via --result-file. If the user @@ -519,6 +596,110 @@ function _drush_sql_get_table_list($option_name) { } /** + * Extract the name of all existing tables in the given database. + * + * @param $db_spec + * @param $site_record + * Necessary for remote databases. + * @return array + * A list of table names which exist in the current database. + */ +function _drush_sql_get_db_table_list($db_spec, $site_record = NULL) { + $db_tables = drush_get_context('DRUSH_SQL_DB_TABLES', array()); + + $cache_key = serialize($db_spec); + + if (isset($db_tables[$cache_key])) { + return $db_tables[$cache_key]; + } + + $suffix = ''; + $scheme = _drush_sql_get_scheme($db_spec); + switch ($scheme) { + case 'pgsql': + $query = drush_sql_show_tables_pgsql(); + break; + case 'sqlite': + $query = '.tables'; + break; + case 'sqlsrv': + $query = 'SELECT TABLE_NAME FROM information_schema.tables'; + break; + case 'oracle': + $query = "SELECT TABLE_NAME FROM USER_TABLES WHERE TABLE_NAME NOT IN ('BLOBS','LONG_IDENTIFIERS')"; + $suffix = '.sql'; + break; + default: + $query = 'SHOW TABLES;'; + } + + // Actually run this prep query no matter if in SIMULATE. + $old = drush_get_context('DRUSH_SIMULATE'); + drush_set_context('DRUSH_SIMULATE', FALSE); + + // Remote. + if (isset($db_spec['remote-host'])) { + if (is_null($site_record)) { + drush_log(dt('Retrieving the table list for a remote DB, but the site record is missing.'), 'error'); + exit; + } + $result = drush_invoke_process($site_record, 'sqlq', array($query), array(), array('override-simulated' => TRUE)); + $tables_raw = explode("\n", rtrim($result['output'])); + // Shift off the "Calling system(…);" line at the top of the output. + array_shift($tables_raw); + } + // Local. + else { + $filename = drush_save_data_to_temp_file($query, $suffix); + $exec = drush_sql_build_exec($db_spec, $filename); + drush_shell_exec($exec); + $tables_raw = drush_shell_exec_output(); + // This fails. See #698264-34. + // $result = drush_invoke_process('@self', 'sqlq', array($query), array(), array('override-simulated' => TRUE)); + } + + // Restore the old 'simulate' state. + drush_set_context('DRUSH_SIMULATE', $old); + + $tables = array(); + if (!empty($tables_raw)) { + if ($scheme === 'sqlite') { + // SQLite's '.tables' command always outputs the table names in a column + // format, like this: + // table_alpha table_charlie table_echo + // table_bravo table_delta table_foxtrot + // …and there doesn't seem to be a way to fix that. So we need to do some + // clean-up. + $sql = ''; + foreach ($tables_raw as $line) { + preg_match_all('/[^\s]+/', $line, $matches); + if (!empty($matches[0])) { + foreach ($matches[0] as $match) { + $tables[] = $match; + } + } + } + } + elseif ($scheme === 'sqlsrv') { + // Shift off the header of the column of data returned. + array_pop($tables_raw); + array_pop($tables_raw); + $tables = $tables_raw; + } + else { + // Shift off the header of the column of data returned. + array_shift($tables_raw); + $tables = $tables_raw; + } + } + + $db_tables[$cache_key] = $tables; + drush_set_context('DRUSH_SQL_DB_TABLES', $db_tables); + + return $db_tables[$cache_key]; +} + +/** * Command callback. Executes the given SQL query on the Drupal database. */ @@ -604,57 +785,18 @@ function drush_sql_drop() { // drop database; create database fails. If _drush_sql_drop // is rewritten to also use that technique, it should maintain // the drop tables code here as a fallback. -function _drush_sql_drop($db_spec = NULL) { - // TODO: integrate with _drush_sql_get_table_list? +function _drush_sql_drop($db_spec) { + $tables = _drush_sql_get_db_table_list($db_spec); - $suffix = ''; $scheme = _drush_sql_get_scheme($db_spec); - switch ($scheme) { - case 'pgsql': - $query = drush_sql_show_tables_pgsql(); - break; - case 'sqlite': - $query = '.tables'; - break; - case 'sqlsrv': - $query = 'SELECT TABLE_NAME FROM information_schema.tables'; - break; - case 'oracle': - $query = "SELECT TABLE_NAME FROM USER_TABLES WHERE TABLE_NAME NOT IN ('BLOBS','LONG_IDENTIFIERS')"; - $suffix = '.sql'; - break; - default: - $query = 'SHOW TABLES;'; - } - - $filename = drush_save_data_to_temp_file($query, $suffix); - $exec = drush_sql_build_exec($db_spec, $filename); - // Actually run this prep query no matter if in SIMULATE. - $old = drush_get_context('DRUSH_SIMULATE'); - drush_set_context('DRUSH_SIMULATE', FALSE); - drush_shell_exec($exec); - drush_set_context('DRUSH_SIMULATE', $old); - if ($tables = drush_shell_exec_output()) { + if (count($tables)) { if ($scheme === 'sqlite') { - // SQLite's '.tables' command always outputs the table names in a column - // format, like this: - // table_alpha table_charlie table_echo - // table_bravo table_delta table_foxtrot - // …and there doesn't seem to be a way to fix that. So we need to do some - // clean-up. - // Since we're already doing iteration here, might as well build the SQL - // too, since SQLite only wants one table per DROP TABLE command (so we have - // to do "DROP TABLE foo; DROP TABLE bar;" instead of - // "DROP TABLE foo, bar;"). $sql = ''; - foreach ($tables as $line) { - preg_match_all('/[^\s]+/', $line, $matches); - if (!empty($matches[0])) { - foreach ($matches[0] as $match) { - $sql .= "DROP TABLE {$match};"; - } - } + // SQLite only wants one table per DROP TABLE command (so we have to do + // "DROP TABLE foo; DROP TABLE bar;" instead of "DROP TABLE foo, bar;"). + foreach ($tables as $table) { + $sql .= "DROP TABLE {$match};"; } // We can't use drush_op('db_query', $sql) because it will only perform one // SQL command and we're technically performing several. @@ -662,16 +804,7 @@ function _drush_sql_drop($db_spec = NULL) { $exec .= " '{$sql}'"; return drush_op_system($exec) == 0; } - elseif ($scheme === 'sqlsrv') { - // Shift off the header of the column of data returned. - array_pop($tables); - array_pop($tables); - $sql = 'DROP TABLE '. implode(', ', $tables); - return _drush_sql_query($sql, $db_spec); - } else { - // Shift off the header of the column of data returned. - array_shift($tables); $sql = 'DROP TABLE '. implode(', ', $tables); return _drush_sql_query($sql, $db_spec); } diff --git a/commands/sql/sync.sql.inc b/commands/sql/sync.sql.inc index 2f5a872..9efddc2 100644 --- a/commands/sql/sync.sql.inc +++ b/commands/sql/sync.sql.inc @@ -243,7 +243,7 @@ function drush_sql_sync($source = NULL, $destination = NULL) { $table_selection = array(); if (!isset($no_dump)) { - $table_selection = drush_sql_get_table_selection(); + $table_selection = drush_sql_get_expanded_table_selection($source_db_url, $source_settings); } // Prompt for confirmation. This is destructive. diff --git a/examples/example.drushrc.php b/examples/example.drushrc.php index 9447088..c376ec1 100644 --- a/examples/example.drushrc.php +++ b/examples/example.drushrc.php @@ -268,7 +268,7 @@ * commands when the "--structure-tables-key=common" option is provided. * You may add specific tables to the existing array or add a new element. */ -# $options['structure-tables']['common'] = array('cache', 'cache_filter', 'cache_menu', 'cache_page', 'history', 'sessions', 'watchdog'); +# $options['structure-tables']['common'] = array('cache', 'cache_*', 'history', 'search_*', 'sessions', 'watchdog'); /** * List of tables to be omitted entirely from SQL dumps made by the 'sql-dump'