Commit dde5c67b authored by Dries's avatar Dries

- Patch #302327 by Josh Waihi, noahb, Crell, hswong3i: support...

- Patch #302327 by Josh Waihi, noahb, Crell, hswong3i: support cross-schema/database prefixing like we claim to.
parent 626e6402
......@@ -26,3 +26,19 @@ Note that the database must be created with UTF-8 (Unicode) encoding.
createdb --encoding=UTF8 --owner=username databasename
If there are no errors then the command was successful
3. CREATE A SCHEMA OR SCHEMAS (Optional advanced)
Drupal will run across different schemas within your database if you so wish.
By default, Drupal runs inside the 'public' schema but you can use $db_prefix
inside settings.php to define a schema for Drupal to inside of or specify tables
that are shared inside of a separate schema. Drupal will not create schemas for
you, infact the user that Drupal runs as should not be allowed to. You'll need
execute the SQL below as a superuser (such as a postgres user) and replace
'drupaluser' with the username that Drupal uses to connect to PostgreSQL with
and replace schema_name with a schema name you wish to use such as 'shared':
CREATE SCHEMA schema_name AUTHORIZATION drupaluser;
Do this for as many schemas as you need. See default.settings.php for how to
set which tables use which schemas.
......@@ -383,6 +383,26 @@ public function prefixTables($sql) {
}
}
/**
* Find the prefix for a table.
*
* This function is for when you want to know the prefix of a table. This
* is not used in prefixTables due to performance reasons.
*/
public function tablePrefix($table = 'default') {
global $db_prefix;
if (is_array($db_prefix)) {
if (isset($db_prefix[$table])) {
return $db_prefix[$table];
}
elseif (isset($db_prefix['default'])) {
return $db_prefix['default'];
}
return '';
}
return $db_prefix;
}
/**
* Prepares a query string and returns the prepared statement.
*
......
......@@ -24,6 +24,26 @@ class DatabaseSchema_mysql extends DatabaseSchema {
*/
const COMMENT_MAX_COLUMN = 255;
/**
* Get information about the table and database name from the db_prefix.
*
* @return
* A keyed array with information about the database, table name and prefix.
*/
protected function getPrefixInfo($table = 'default') {
$info = array('prefix' => $this->connection->tablePrefix($table));
if (($pos = strpos($info['prefix'], '.')) !== FALSE) {
$info['database'] = substr($info['prefix'], 0, $pos);
$info['table'] = substr($info['prefix'], ++$pos) . $table;
}
else {
$db_info = Database::getConnectionInfo();
$info['database'] = $db_info['default']['database'];
$info['table'] = $info['prefix'] . $table;
}
return $info;
}
/**
* Build a condition to match a table name against a standard information_schema.
*
......@@ -35,16 +55,11 @@ class DatabaseSchema_mysql extends DatabaseSchema {
protected function buildTableNameCondition($table_name, $operator = '=') {
$info = $this->connection->getConnectionOptions();
if (strpos($table_name, '.')) {
list($schema, $table_name) = explode('.', $table_name);
}
else {
$schema = $info['database'];
}
$table_info = $this->getPrefixInfo($table_name);
$condition = new DatabaseCondition('AND');
$condition->condition('table_schema', $schema);
$condition->condition('table_name', $table_name, $operator);
$condition->condition('table_schema', $table_info['database']);
$condition->condition('table_name', $table_info['table'], $operator);
return $condition;
}
......@@ -273,7 +288,8 @@ public function renameTable($table, $new_name) {
throw new DatabaseSchemaObjectExistsException(t("Cannot rename %table to %table_new: table %table_new already exists.", array('%table' => $table, '%table_new' => $new_name)));
}
$this->connection->query('ALTER TABLE {' . $table . '} RENAME TO {' . $new_name . '}');
$info = $this->getPrefixInfo($new_name);
return $this->connection->query('ALTER TABLE {' . $table . '} RENAME TO `' . $info['table'] . '`');
}
public function dropTable($table) {
......@@ -446,7 +462,7 @@ public function prepareComment($comment, $length = NULL) {
* Retrieve a table or column comment.
*/
public function getComment($table, $column = NULL) {
$condition = $this->buildTableNameCondition($this->connection->prefixTables('{' . $table . '}'));
$condition = $this->buildTableNameCondition($table);
if (isset($column)) {
$condition->condition('column_name', $column);
$condition->compile($this->connection, $this);
......
......@@ -95,7 +95,7 @@ protected function createTableSql($name, $table) {
}
if (isset($table['unique keys']) && is_array($table['unique keys'])) {
foreach ($table['unique keys'] as $key_name => $key) {
$sql_keys[] = 'CONSTRAINT {' . $name . '}_' . $key_name . '_key UNIQUE (' . implode(', ', $key) . ')';
$sql_keys[] = 'CONSTRAINT ' . $this->prefixNonTable($name, $key_name, 'key') . ' UNIQUE (' . implode(', ', $key) . ')';
}
}
......@@ -297,7 +297,9 @@ function renameTable($table, $new_name) {
}
// Now rename the table.
$this->connection->query('ALTER TABLE {' . $table . '} RENAME TO {' . $new_name . '}');
// Ensure the new table name does not include schema syntax.
$prefixInfo = $this->getPrefixInfo($new_name);
$this->connection->query('ALTER TABLE {' . $table . '} RENAME TO ' . $prefixInfo['table']);
}
public function dropTable($table) {
......@@ -409,7 +411,7 @@ public function dropPrimaryKey($table) {
return FALSE;
}
$this->connection->query('ALTER TABLE {' . $table . '} DROP CONSTRAINT {' . $table . '}_pkey');
$this->connection->query('ALTER TABLE {' . $table . '} DROP CONSTRAINT ' . $this->prefixNonTable($table, 'pkey'));
return TRUE;
}
......@@ -421,8 +423,7 @@ function addUniqueKey($table, $name, $fields) {
throw new DatabaseSchemaObjectExistsException(t("Cannot add unique key %name to table %table: unique key already exists.", array('%table' => $table, '%name' => $name)));
}
$name = '{' . $table . '}_' . $name . '_key';
$this->connection->query('ALTER TABLE {' . $table . '} ADD CONSTRAINT "' . $name . '" UNIQUE (' . implode(',', $fields) . ')');
$this->connection->query('ALTER TABLE {' . $table . '} ADD CONSTRAINT "' . $this->prefixNonTable($table, $name, 'key') . '" UNIQUE (' . implode(',', $fields) . ')');
}
public function dropUniqueKey($table, $name) {
......@@ -430,8 +431,7 @@ public function dropUniqueKey($table, $name) {
return FALSE;
}
$name = '{' . $table . '}_' . $name . '_key';
$this->connection->query('ALTER TABLE {' . $table . '} DROP CONSTRAINT "' . $name . '"');
$this->connection->query('ALTER TABLE {' . $table . '} DROP CONSTRAINT "' . $this->prefixNonTable($table, $name, 'key') . '"');
return TRUE;
}
......@@ -451,8 +451,7 @@ public function dropIndex($table, $name) {
return FALSE;
}
$name = '{' . $table . '}_' . $name . '_idx';
$this->connection->query('DROP INDEX ' . $name);
$this->connection->query('DROP INDEX ' . $this->prefixNonTable($table, $name, 'idx'));
return TRUE;
}
......@@ -495,7 +494,7 @@ public function changeField($table, $field, $field_new, $spec, $new_keys = array
}
protected function _createIndexSql($table, $name, $fields) {
$query = 'CREATE INDEX "{' . $table . '}_' . $name . '_idx" ON {' . $table . '} (';
$query = 'CREATE INDEX "' . $this->prefixNonTable($table, $name, 'idx') . '" ON {' . $table . '} (';
$query .= $this->_createKeySql($fields) . ')';
return $query;
}
......@@ -520,13 +519,13 @@ protected function _createKeys($table, $new_keys) {
* Retrieve a table or column comment.
*/
public function getComment($table, $column = NULL) {
$table = $this->connection->prefixTables('{' . $table . '}');
$info = $this->getPrefixInfo($table);
// Don't use {} around pg_class, pg_attribute tables.
if (isset($column)) {
return $this->connection->query('SELECT col_description(oid, attnum) FROM pg_class, pg_attribute WHERE attrelid = oid AND relname = ? AND attname = ?', array($table, $column))->fetchField();
return $this->connection->query('SELECT col_description(oid, attnum) FROM pg_class, pg_attribute WHERE attrelid = oid AND relname = ? AND attname = ?', array($info['table'], $column))->fetchField();
}
else {
return $this->connection->query('SELECT obj_description(oid, ?) FROM pg_class WHERE relname = ?', array('pg_class', $table))->fetchField();
return $this->connection->query('SELECT obj_description(oid, ?) FROM pg_class WHERE relname = ?', array('pg_class', $info['table']))->fetchField();
}
}
}
......@@ -150,6 +150,16 @@ abstract class DatabaseSchema implements QueryPlaceholderInterface {
*/
protected $placeholder = 0;
/**
* Definition of prefixInfo array structure.
*
* Rather than redefining DatabaseSchema::getPrefixInfo() for each driver,
* by defining the defaultSchema variable only MySQL has to re-write the
* method.
*
* @see DatabaseSchema::getPrefixInfo()
*/
protected $defaultSchema = 'public';
public function __construct($connection) {
$this->connection = $connection;
......@@ -159,6 +169,48 @@ public function nextPlaceholder() {
return $this->placeholder++;
}
/**
* Get information about the table name and schema from the db_prefix.
*
* @param
* Name of table to look prefix up for. Defaults to 'default' because thats
* default key for db_prefix.
* @return
* A keyed array with information about the schema, table name and prefix.
*/
protected function getPrefixInfo($table = 'default') {
$info = array(
'schema' => $this->defaultSchema,
'prefix' => $this->connection->tablePrefix($table),
);
// If the prefix contains a period in it, then that means the prefix also
// contains a schema reference in which case we will change the schema key
// to the value before the period in the prefix. Everything after the dot
// will be prefixed onto the front of the table.
if ($pos = strpos($info['prefix'], '.') !== FALSE) {
// Grab everything before the period.
$info['schema'] = substr($info['prefix'], 0, $pos);
// Grab everything after the dot, and prefix on to the table.
$info['table'] = substr($info['prefix'], ++$pos) . $table;
}
else {
$info['table'] = $info['prefix'] . $table;
}
return $info;
}
/**
* Create names for indexes, primary keys and constraints.
*
* This prevents using {} around non-table names like indexes and keys.
*/
function prefixNonTable($table) {
$args = func_get_args();
$info = $this->getPrefixInfo($table);
$args[0] = $info['table'];
return implode('_', $args);
}
/**
* Build a condition to match a table name against a standard information_schema.
*
......@@ -175,7 +227,7 @@ public function nextPlaceholder() {
* to make all the others work. For example see includes/databases/mysql/schema.inc.
*
* @param $table_name
* The name of the table to explode.
* The name of the table in question.
* @param $operator
* The operator to apply on the 'table' part of the condition.
*
......@@ -185,18 +237,13 @@ public function nextPlaceholder() {
protected function buildTableNameCondition($table_name, $operator = '=') {
$info = $this->connection->getConnectionOptions();
// The table name may describe the schema eg. schema.table.
if (strpos($table_name, '.')) {
list($schema, $table_name) = explode('.', $table_name);
}
else {
$schema = 'public';
}
// Retrive the table name and schema
$table_info = $this->getPrefixInfo($table_name);
$condition = new DatabaseCondition('AND');
$condition->condition('table_catalog', $info['database']);
$condition->condition('table_schema', $schema);
$condition->condition('table_name', $table_name, $operator);
$condition->condition('table_schema', $table_info['schema']);
$condition->condition('table_name', $table_info['table'], $operator);
return $condition;
}
......@@ -210,7 +257,7 @@ protected function buildTableNameCondition($table_name, $operator = '=') {
* TRUE if the given table exists, otherwise FALSE.
*/
public function tableExists($table) {
$condition = $this->buildTableNameCondition($this->connection->prefixTables('{' . $table . '}'));
$condition = $this->buildTableNameCondition($table);
$condition->compile($this->connection, $this);
// Normally, we would heartily discourage the use of string
// concatenation for conditionals like this however, we
......@@ -253,7 +300,7 @@ public function findTables($table_expression) {
* TRUE if the given column exists, otherwise FALSE.
*/
public function fieldExists($table, $column) {
$condition = $this->buildTableNameCondition($this->connection->prefixTables('{' . $table . '}'));
$condition = $this->buildTableNameCondition($table);
$condition->condition('column_name', $column);
$condition->compile($this->connection, $this);
// Normally, we would heartily discourage the use of string
......
......@@ -14,6 +14,11 @@
class DatabaseSchema_sqlite extends DatabaseSchema {
/**
* Override DatabaseSchema::$defaultSchema
*/
protected $defaultSchema = 'main';
public function tableExists($table) {
// Don't use {} around sqlite_master table.
return (bool) $this->connection->query("SELECT name FROM sqlite_master WHERE type = 'table' AND name LIKE '{" . $table . "}'", array(), array())->fetchField();
......@@ -45,14 +50,19 @@ public function createTableSql($name, $table) {
*/
protected function createIndexSql($tablename, $schema) {
$sql = array();
$info = $this->getPrefixInfo($tablename);
if (!empty($schema['unique keys'])) {
foreach ($schema['unique keys'] as $key => $fields) {
$sql[] = 'CREATE UNIQUE INDEX "{' . $tablename . '}_' . $key . '" ON {' . $tablename . '} (' . $this->createKeySql($fields) . "); \n";
// Normally we don't escape double quotes (we use single quotes) but
// describing the index name like this is faster and is readable.
$index = "\"{$info['schema']}\".\"{$info['table']}_$key\"";
$sql[] = 'CREATE UNIQUE INDEX ' . $index . ' ON ' . $info['table'] . ' (' . $this->createKeySql($fields) . "); \n";
}
}
if (!empty($schema['indexes'])) {
foreach ($schema['indexes'] as $index => $fields) {
$sql[] = 'CREATE INDEX "{' . $tablename . '}_' . $index . '" ON {' . $tablename . '} (' . $this->createKeySql($fields) . "); \n";
foreach ($schema['indexes'] as $key => $fields) {
$index = "\"{$info['schema']}\".\"{$info['table']}_$key\"";
$sql[] = 'CREATE INDEX ' . $index . ' ON ' . $info['table'] . ' (' . $this->createKeySql($fields) . "); \n";
}
}
return $sql;
......@@ -228,7 +238,13 @@ public function renameTable($table, $new_name) {
$schema = $this->introspectSchema($table);
$this->connection->query('ALTER TABLE {' . $table . '} RENAME TO {' . $new_name . '}');
// SQLite doesn't allow you to rename tables outside of the current
// database. So the syntax '...RENAME TO database.table' would fail.
// So we must determine the full table name here rather than surrounding
// the table with curly braces incase the db_prefix contains a reference
// to a database outside of our existsing database.
$info = $this->getPrefixInfo($new_name);
$this->connection->query('ALTER TABLE {' . $table . '} RENAME TO ' . $info['table']);
// Drop the indexes, there is no RENAME INDEX command in SQLite.
if (!empty($schema['unique keys'])) {
......@@ -445,7 +461,7 @@ public function dropIndex($table, $name) {
return FALSE;
}
$this->connection->query('DROP INDEX ' . '{' . $table . '}_' . $name);
$this->connection->query('DROP INDEX ' . $this->prefixNonTable($table, $name));
return TRUE;
}
......@@ -469,7 +485,7 @@ public function dropUniqueKey($table, $name) {
return FALSE;
}
$this->connection->query('DROP INDEX ' . '{' . $table . '}_' . $name);
$this->connection->query('DROP INDEX ' . $this->prefixNonTable($table, $name));
return TRUE;
}
......
......@@ -24,7 +24,7 @@ class ModuleTestCase extends DrupalWebTestCase {
* specified base table. Defaults to TRUE.
*/
function assertTableCount($base_table, $count = TRUE) {
$tables = db_find_tables(Database::getConnection()->prefixTables('{' . $base_table . '}') . '%');
$tables = db_find_tables($base_table . '%');
if ($count) {
return $this->assertTrue($tables, t('Tables matching "@base_table" found.', array('@base_table' => $base_table)));
......
......@@ -131,6 +131,22 @@
* 'authmap' => 'shared_',
* );
*
* You can also use db_prefix as a reference to a schema/database. This maybe
* useful if your Drupal installation exists in a schema that is not the default
* or you want to access several databases from the same code base at the same
* time.
* Example:
*
* $db_prefix = array(
* 'default' => 'main.',
* 'users' => 'shared.',
* 'sessions' => 'shared.',
* 'role' => 'shared.',
* 'authmap' => 'shared.',
* );
*
* NOTE: MySQL and SQLite's definition of a schema is a database.
*
* Database configuration format:
* $databases['default']['default'] = array(
* 'driver' => 'mysql',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment