From ec90f2b380a598a3dbf7ada0e95878d9d1b85cbe Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Sat, 6 Dec 2014 16:34:02 +0100 Subject: [ticket/13421] Move tools to subdirectory PHPBB3-13421 --- phpBB/phpbb/db/tools/tools.php | 2776 ++++++++++++++++++++++++++++++ phpBB/phpbb/db/tools/tools_interface.php | 202 +++ 2 files changed, 2978 insertions(+) create mode 100644 phpBB/phpbb/db/tools/tools.php create mode 100644 phpBB/phpbb/db/tools/tools_interface.php (limited to 'phpBB/phpbb/db/tools') diff --git a/phpBB/phpbb/db/tools/tools.php b/phpBB/phpbb/db/tools/tools.php new file mode 100644 index 0000000000..63c43c07ef --- /dev/null +++ b/phpBB/phpbb/db/tools/tools.php @@ -0,0 +1,2776 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\tools; + +/** +* Database Tools for handling cross-db actions such as altering columns, etc. +* Currently not supported is returning SQL for creating tables. +*/ +class tools implements tools_interface +{ + /** + * Current sql layer + */ + var $sql_layer = ''; + + /** + * @var object DB object + */ + var $db = null; + + /** + * The Column types for every database we support + * @var array + */ + var $dbms_type_map = array(); + + /** + * Is the used MS SQL Server a SQL Server 2000? + * @var bool + */ + protected $is_sql_server_2000; + + /** + * Get the column types for every database we support + * + * @return array + */ + public static function get_dbms_type_map() + { + return array( + 'mysql_41' => array( + 'INT:' => 'int(%d)', + 'BINT' => 'bigint(20)', + 'UINT' => 'mediumint(8) UNSIGNED', + 'UINT:' => 'int(%d) UNSIGNED', + 'TINT:' => 'tinyint(%d)', + 'USINT' => 'smallint(4) UNSIGNED', + 'BOOL' => 'tinyint(1) UNSIGNED', + 'VCHAR' => 'varchar(255)', + 'VCHAR:' => 'varchar(%d)', + 'CHAR:' => 'char(%d)', + 'XSTEXT' => 'text', + 'XSTEXT_UNI'=> 'varchar(100)', + 'STEXT' => 'text', + 'STEXT_UNI' => 'varchar(255)', + 'TEXT' => 'text', + 'TEXT_UNI' => 'text', + 'MTEXT' => 'mediumtext', + 'MTEXT_UNI' => 'mediumtext', + 'TIMESTAMP' => 'int(11) UNSIGNED', + 'DECIMAL' => 'decimal(5,2)', + 'DECIMAL:' => 'decimal(%d,2)', + 'PDECIMAL' => 'decimal(6,3)', + 'PDECIMAL:' => 'decimal(%d,3)', + 'VCHAR_UNI' => 'varchar(255)', + 'VCHAR_UNI:'=> 'varchar(%d)', + 'VCHAR_CI' => 'varchar(255)', + 'VARBINARY' => 'varbinary(255)', + ), + + 'mysql_40' => array( + 'INT:' => 'int(%d)', + 'BINT' => 'bigint(20)', + 'UINT' => 'mediumint(8) UNSIGNED', + 'UINT:' => 'int(%d) UNSIGNED', + 'TINT:' => 'tinyint(%d)', + 'USINT' => 'smallint(4) UNSIGNED', + 'BOOL' => 'tinyint(1) UNSIGNED', + 'VCHAR' => 'varbinary(255)', + 'VCHAR:' => 'varbinary(%d)', + 'CHAR:' => 'binary(%d)', + 'XSTEXT' => 'blob', + 'XSTEXT_UNI'=> 'blob', + 'STEXT' => 'blob', + 'STEXT_UNI' => 'blob', + 'TEXT' => 'blob', + 'TEXT_UNI' => 'blob', + 'MTEXT' => 'mediumblob', + 'MTEXT_UNI' => 'mediumblob', + 'TIMESTAMP' => 'int(11) UNSIGNED', + 'DECIMAL' => 'decimal(5,2)', + 'DECIMAL:' => 'decimal(%d,2)', + 'PDECIMAL' => 'decimal(6,3)', + 'PDECIMAL:' => 'decimal(%d,3)', + 'VCHAR_UNI' => 'blob', + 'VCHAR_UNI:'=> array('varbinary(%d)', 'limit' => array('mult', 3, 255, 'blob')), + 'VCHAR_CI' => 'blob', + 'VARBINARY' => 'varbinary(255)', + ), + + 'mssql' => array( + 'INT:' => '[int]', + 'BINT' => '[float]', + 'UINT' => '[int]', + 'UINT:' => '[int]', + 'TINT:' => '[int]', + 'USINT' => '[int]', + 'BOOL' => '[int]', + 'VCHAR' => '[varchar] (255)', + 'VCHAR:' => '[varchar] (%d)', + 'CHAR:' => '[char] (%d)', + 'XSTEXT' => '[varchar] (1000)', + 'STEXT' => '[varchar] (3000)', + 'TEXT' => '[varchar] (8000)', + 'MTEXT' => '[text]', + 'XSTEXT_UNI'=> '[varchar] (100)', + 'STEXT_UNI' => '[varchar] (255)', + 'TEXT_UNI' => '[varchar] (4000)', + 'MTEXT_UNI' => '[text]', + 'TIMESTAMP' => '[int]', + 'DECIMAL' => '[float]', + 'DECIMAL:' => '[float]', + 'PDECIMAL' => '[float]', + 'PDECIMAL:' => '[float]', + 'VCHAR_UNI' => '[varchar] (255)', + 'VCHAR_UNI:'=> '[varchar] (%d)', + 'VCHAR_CI' => '[varchar] (255)', + 'VARBINARY' => '[varchar] (255)', + ), + + 'mssqlnative' => array( + 'INT:' => '[int]', + 'BINT' => '[float]', + 'UINT' => '[int]', + 'UINT:' => '[int]', + 'TINT:' => '[int]', + 'USINT' => '[int]', + 'BOOL' => '[int]', + 'VCHAR' => '[varchar] (255)', + 'VCHAR:' => '[varchar] (%d)', + 'CHAR:' => '[char] (%d)', + 'XSTEXT' => '[varchar] (1000)', + 'STEXT' => '[varchar] (3000)', + 'TEXT' => '[varchar] (8000)', + 'MTEXT' => '[text]', + 'XSTEXT_UNI'=> '[varchar] (100)', + 'STEXT_UNI' => '[varchar] (255)', + 'TEXT_UNI' => '[varchar] (4000)', + 'MTEXT_UNI' => '[text]', + 'TIMESTAMP' => '[int]', + 'DECIMAL' => '[float]', + 'DECIMAL:' => '[float]', + 'PDECIMAL' => '[float]', + 'PDECIMAL:' => '[float]', + 'VCHAR_UNI' => '[varchar] (255)', + 'VCHAR_UNI:'=> '[varchar] (%d)', + 'VCHAR_CI' => '[varchar] (255)', + 'VARBINARY' => '[varchar] (255)', + ), + + 'oracle' => array( + 'INT:' => 'number(%d)', + 'BINT' => 'number(20)', + 'UINT' => 'number(8)', + 'UINT:' => 'number(%d)', + 'TINT:' => 'number(%d)', + 'USINT' => 'number(4)', + 'BOOL' => 'number(1)', + 'VCHAR' => 'varchar2(255)', + 'VCHAR:' => 'varchar2(%d)', + 'CHAR:' => 'char(%d)', + 'XSTEXT' => 'varchar2(1000)', + 'STEXT' => 'varchar2(3000)', + 'TEXT' => 'clob', + 'MTEXT' => 'clob', + 'XSTEXT_UNI'=> 'varchar2(300)', + 'STEXT_UNI' => 'varchar2(765)', + 'TEXT_UNI' => 'clob', + 'MTEXT_UNI' => 'clob', + 'TIMESTAMP' => 'number(11)', + 'DECIMAL' => 'number(5, 2)', + 'DECIMAL:' => 'number(%d, 2)', + 'PDECIMAL' => 'number(6, 3)', + 'PDECIMAL:' => 'number(%d, 3)', + 'VCHAR_UNI' => 'varchar2(765)', + 'VCHAR_UNI:'=> array('varchar2(%d)', 'limit' => array('mult', 3, 765, 'clob')), + 'VCHAR_CI' => 'varchar2(255)', + 'VARBINARY' => 'raw(255)', + ), + + 'sqlite' => array( + 'INT:' => 'int(%d)', + 'BINT' => 'bigint(20)', + 'UINT' => 'INTEGER UNSIGNED', //'mediumint(8) UNSIGNED', + 'UINT:' => 'INTEGER UNSIGNED', // 'int(%d) UNSIGNED', + 'TINT:' => 'tinyint(%d)', + 'USINT' => 'INTEGER UNSIGNED', //'mediumint(4) UNSIGNED', + 'BOOL' => 'INTEGER UNSIGNED', //'tinyint(1) UNSIGNED', + 'VCHAR' => 'varchar(255)', + 'VCHAR:' => 'varchar(%d)', + 'CHAR:' => 'char(%d)', + 'XSTEXT' => 'text(65535)', + 'STEXT' => 'text(65535)', + 'TEXT' => 'text(65535)', + 'MTEXT' => 'mediumtext(16777215)', + 'XSTEXT_UNI'=> 'text(65535)', + 'STEXT_UNI' => 'text(65535)', + 'TEXT_UNI' => 'text(65535)', + 'MTEXT_UNI' => 'mediumtext(16777215)', + 'TIMESTAMP' => 'INTEGER UNSIGNED', //'int(11) UNSIGNED', + 'DECIMAL' => 'decimal(5,2)', + 'DECIMAL:' => 'decimal(%d,2)', + 'PDECIMAL' => 'decimal(6,3)', + 'PDECIMAL:' => 'decimal(%d,3)', + 'VCHAR_UNI' => 'varchar(255)', + 'VCHAR_UNI:'=> 'varchar(%d)', + 'VCHAR_CI' => 'varchar(255)', + 'VARBINARY' => 'blob', + ), + + 'sqlite3' => array( + 'INT:' => 'INT(%d)', + 'BINT' => 'BIGINT(20)', + 'UINT' => 'INTEGER UNSIGNED', + 'UINT:' => 'INTEGER UNSIGNED', + 'TINT:' => 'TINYINT(%d)', + 'USINT' => 'INTEGER UNSIGNED', + 'BOOL' => 'INTEGER UNSIGNED', + 'VCHAR' => 'VARCHAR(255)', + 'VCHAR:' => 'VARCHAR(%d)', + 'CHAR:' => 'CHAR(%d)', + 'XSTEXT' => 'TEXT(65535)', + 'STEXT' => 'TEXT(65535)', + 'TEXT' => 'TEXT(65535)', + 'MTEXT' => 'MEDIUMTEXT(16777215)', + 'XSTEXT_UNI'=> 'TEXT(65535)', + 'STEXT_UNI' => 'TEXT(65535)', + 'TEXT_UNI' => 'TEXT(65535)', + 'MTEXT_UNI' => 'MEDIUMTEXT(16777215)', + 'TIMESTAMP' => 'INTEGER UNSIGNED', //'int(11) UNSIGNED', + 'DECIMAL' => 'DECIMAL(5,2)', + 'DECIMAL:' => 'DECIMAL(%d,2)', + 'PDECIMAL' => 'DECIMAL(6,3)', + 'PDECIMAL:' => 'DECIMAL(%d,3)', + 'VCHAR_UNI' => 'VARCHAR(255)', + 'VCHAR_UNI:'=> 'VARCHAR(%d)', + 'VCHAR_CI' => 'VARCHAR(255)', + 'VARBINARY' => 'BLOB', + ), + + 'postgres' => array( + 'INT:' => 'INT4', + 'BINT' => 'INT8', + 'UINT' => 'INT4', // unsigned + 'UINT:' => 'INT4', // unsigned + 'USINT' => 'INT2', // unsigned + 'BOOL' => 'INT2', // unsigned + 'TINT:' => 'INT2', + 'VCHAR' => 'varchar(255)', + 'VCHAR:' => 'varchar(%d)', + 'CHAR:' => 'char(%d)', + 'XSTEXT' => 'varchar(1000)', + 'STEXT' => 'varchar(3000)', + 'TEXT' => 'varchar(8000)', + 'MTEXT' => 'TEXT', + 'XSTEXT_UNI'=> 'varchar(100)', + 'STEXT_UNI' => 'varchar(255)', + 'TEXT_UNI' => 'varchar(4000)', + 'MTEXT_UNI' => 'TEXT', + 'TIMESTAMP' => 'INT4', // unsigned + 'DECIMAL' => 'decimal(5,2)', + 'DECIMAL:' => 'decimal(%d,2)', + 'PDECIMAL' => 'decimal(6,3)', + 'PDECIMAL:' => 'decimal(%d,3)', + 'VCHAR_UNI' => 'varchar(255)', + 'VCHAR_UNI:'=> 'varchar(%d)', + 'VCHAR_CI' => 'varchar_ci', + 'VARBINARY' => 'bytea', + ), + ); + } + + /** + * A list of types being unsigned for better reference in some db's + * @var array + */ + var $unsigned_types = array('UINT', 'UINT:', 'USINT', 'BOOL', 'TIMESTAMP'); + + /** + * A list of supported DBMS. We change this class to support more DBMS, the DBMS itself only need to follow some rules. + * @var array + */ + var $supported_dbms = array('mssql', 'mssqlnative', 'mysql_40', 'mysql_41', 'oracle', 'postgres', 'sqlite', 'sqlite3'); + + /** + * This is set to true if user only wants to return the 'to-be-executed' SQL statement(s) (as an array). + * This mode has no effect on some methods (inserting of data for example). This is expressed within the methods command. + */ + var $return_statements = false; + + /** + * Constructor. Set DB Object and set {@link $return_statements return_statements}. + * + * @param \phpbb\db\driver\driver_interface $db Database connection + * @param bool $return_statements True if only statements should be returned and no SQL being executed + */ + public function __construct(\phpbb\db\driver\driver_interface $db, $return_statements = false) + { + $this->db = $db; + $this->return_statements = $return_statements; + + $this->dbms_type_map = self::get_dbms_type_map(); + + // Determine mapping database type + switch ($this->db->get_sql_layer()) + { + case 'mysql': + $this->sql_layer = 'mysql_40'; + break; + + case 'mysql4': + if (version_compare($this->db->sql_server_info(true), '4.1.3', '>=')) + { + $this->sql_layer = 'mysql_41'; + } + else + { + $this->sql_layer = 'mysql_40'; + } + break; + + case 'mysqli': + $this->sql_layer = 'mysql_41'; + break; + + case 'mssql': + case 'mssql_odbc': + $this->sql_layer = 'mssql'; + break; + + case 'mssqlnative': + $this->sql_layer = 'mssqlnative'; + break; + + default: + $this->sql_layer = $this->db->get_sql_layer(); + break; + } + } + + /** + * Setter for {@link $return_statements return_statements}. + * + * @param bool $return_statements True if SQL should not be executed but returned as strings + * @return null + */ + public function set_return_statements($return_statements) + { + $this->return_statements = $return_statements; + } + + /** + * {@inheritDoc} + */ + function sql_list_tables() + { + switch ($this->db->get_sql_layer()) + { + case 'mysql': + case 'mysql4': + case 'mysqli': + $sql = 'SHOW TABLES'; + break; + + case 'sqlite': + $sql = 'SELECT name + FROM sqlite_master + WHERE type = "table"'; + break; + + case 'sqlite3': + $sql = 'SELECT name + FROM sqlite_master + WHERE type = "table" + AND name <> "sqlite_sequence"'; + break; + + case 'mssql': + case 'mssql_odbc': + case 'mssqlnative': + $sql = "SELECT name + FROM sysobjects + WHERE type='U'"; + break; + + case 'postgres': + $sql = 'SELECT relname + FROM pg_stat_user_tables'; + break; + + case 'oracle': + $sql = 'SELECT table_name + FROM USER_TABLES'; + break; + } + + $result = $this->db->sql_query($sql); + + $tables = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $name = current($row); + $tables[$name] = $name; + } + $this->db->sql_freeresult($result); + + return $tables; + } + + /** + * {@inheritDoc} + */ + function sql_table_exists($table_name) + { + $this->db->sql_return_on_error(true); + $result = $this->db->sql_query_limit('SELECT * FROM ' . $table_name, 1); + $this->db->sql_return_on_error(false); + + if ($result) + { + $this->db->sql_freeresult($result); + return true; + } + + return false; + } + + /** + * {@inheritDoc} + */ + function sql_create_table($table_name, $table_data) + { + // holds the DDL for a column + $columns = $statements = array(); + + if ($this->sql_table_exists($table_name)) + { + return $this->_sql_run_sql($statements); + } + + // Begin transaction + $statements[] = 'begin'; + + // Determine if we have created a PRIMARY KEY in the earliest + $primary_key_gen = false; + + // Determine if the table requires a sequence + $create_sequence = false; + + // Begin table sql statement + switch ($this->sql_layer) + { + case 'mssql': + case 'mssqlnative': + $table_sql = 'CREATE TABLE [' . $table_name . '] (' . "\n"; + break; + + default: + $table_sql = 'CREATE TABLE ' . $table_name . ' (' . "\n"; + break; + } + + if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative') + { + if (!isset($table_data['PRIMARY_KEY'])) + { + $table_data['COLUMNS']['mssqlindex'] = array('UINT', null, 'auto_increment'); + $table_data['PRIMARY_KEY'] = 'mssqlindex'; + } + } + + // Iterate through the columns to create a table + foreach ($table_data['COLUMNS'] as $column_name => $column_data) + { + // here lies an array, filled with information compiled on the column's data + $prepared_column = $this->sql_prepare_column_data($table_name, $column_name, $column_data); + + if (isset($prepared_column['auto_increment']) && $prepared_column['auto_increment'] && strlen($column_name) > 26) // "${column_name}_gen" + { + trigger_error("Index name '${column_name}_gen' on table '$table_name' is too long. The maximum auto increment column length is 26 characters.", E_USER_ERROR); + } + + // here we add the definition of the new column to the list of columns + switch ($this->sql_layer) + { + case 'mssql': + case 'mssqlnative': + $columns[] = "\t [{$column_name}] " . $prepared_column['column_type_sql_default']; + break; + + default: + $columns[] = "\t {$column_name} " . $prepared_column['column_type_sql']; + break; + } + + // see if we have found a primary key set due to a column definition if we have found it, we can stop looking + if (!$primary_key_gen) + { + $primary_key_gen = isset($prepared_column['primary_key_set']) && $prepared_column['primary_key_set']; + } + + // create sequence DDL based off of the existance of auto incrementing columns + if (!$create_sequence && isset($prepared_column['auto_increment']) && $prepared_column['auto_increment']) + { + $create_sequence = $column_name; + } + } + + // this makes up all the columns in the create table statement + $table_sql .= implode(",\n", $columns); + + // Close the table for two DBMS and add to the statements + switch ($this->sql_layer) + { + case 'mssql': + case 'mssqlnative': + $table_sql .= "\n);"; + $statements[] = $table_sql; + break; + } + + // we have yet to create a primary key for this table, + // this means that we can add the one we really wanted instead + if (!$primary_key_gen) + { + // Write primary key + if (isset($table_data['PRIMARY_KEY'])) + { + if (!is_array($table_data['PRIMARY_KEY'])) + { + $table_data['PRIMARY_KEY'] = array($table_data['PRIMARY_KEY']); + } + + switch ($this->sql_layer) + { + case 'mysql_40': + case 'mysql_41': + case 'postgres': + case 'sqlite': + case 'sqlite3': + $table_sql .= ",\n\t PRIMARY KEY (" . implode(', ', $table_data['PRIMARY_KEY']) . ')'; + break; + + case 'mssql': + case 'mssqlnative': + // We need the data here + $old_return_statements = $this->return_statements; + $this->return_statements = true; + + $primary_key_stmts = $this->sql_create_primary_key($table_name, $table_data['PRIMARY_KEY']); + foreach ($primary_key_stmts as $pk_stmt) + { + $statements[] = $pk_stmt; + } + + $this->return_statements = $old_return_statements; + break; + + case 'oracle': + $table_sql .= ",\n\t CONSTRAINT pk_{$table_name} PRIMARY KEY (" . implode(', ', $table_data['PRIMARY_KEY']) . ')'; + break; + } + } + } + + // close the table + switch ($this->sql_layer) + { + case 'mysql_41': + // make sure the table is in UTF-8 mode + $table_sql .= "\n) CHARACTER SET `utf8` COLLATE `utf8_bin`;"; + $statements[] = $table_sql; + break; + + case 'mysql_40': + case 'sqlite': + case 'sqlite3': + $table_sql .= "\n);"; + $statements[] = $table_sql; + break; + + case 'postgres': + // do we need to add a sequence for auto incrementing columns? + if ($create_sequence) + { + $statements[] = "CREATE SEQUENCE {$table_name}_seq;"; + } + + $table_sql .= "\n);"; + $statements[] = $table_sql; + break; + + case 'oracle': + $table_sql .= "\n)"; + $statements[] = $table_sql; + + // do we need to add a sequence and a tigger for auto incrementing columns? + if ($create_sequence) + { + // create the actual sequence + $statements[] = "CREATE SEQUENCE {$table_name}_seq"; + + // the trigger is the mechanism by which we increment the counter + $trigger = "CREATE OR REPLACE TRIGGER t_{$table_name}\n"; + $trigger .= "BEFORE INSERT ON {$table_name}\n"; + $trigger .= "FOR EACH ROW WHEN (\n"; + $trigger .= "\tnew.{$create_sequence} IS NULL OR new.{$create_sequence} = 0\n"; + $trigger .= ")\n"; + $trigger .= "BEGIN\n"; + $trigger .= "\tSELECT {$table_name}_seq.nextval\n"; + $trigger .= "\tINTO :new.{$create_sequence}\n"; + $trigger .= "\tFROM dual;\n"; + $trigger .= "END;"; + + $statements[] = $trigger; + } + break; + } + + // Write Keys + if (isset($table_data['KEYS'])) + { + foreach ($table_data['KEYS'] as $key_name => $key_data) + { + if (!is_array($key_data[1])) + { + $key_data[1] = array($key_data[1]); + } + + $old_return_statements = $this->return_statements; + $this->return_statements = true; + + $key_stmts = ($key_data[0] == 'UNIQUE') ? $this->sql_create_unique_index($table_name, $key_name, $key_data[1]) : $this->sql_create_index($table_name, $key_name, $key_data[1]); + + foreach ($key_stmts as $key_stmt) + { + $statements[] = $key_stmt; + } + + $this->return_statements = $old_return_statements; + } + } + + // Commit Transaction + $statements[] = 'commit'; + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function perform_schema_changes($schema_changes) + { + if (empty($schema_changes)) + { + return; + } + + $statements = array(); + $sqlite = false; + + // For SQLite we need to perform the schema changes in a much more different way + if (($this->db->get_sql_layer() == 'sqlite' || $this->db->get_sql_layer() == 'sqlite3') && $this->return_statements) + { + $sqlite_data = array(); + $sqlite = true; + } + + // Drop tables? + if (!empty($schema_changes['drop_tables'])) + { + foreach ($schema_changes['drop_tables'] as $table) + { + // only drop table if it exists + if ($this->sql_table_exists($table)) + { + $result = $this->sql_table_drop($table); + if ($this->return_statements) + { + $statements = array_merge($statements, $result); + } + } + } + } + + // Add tables? + if (!empty($schema_changes['add_tables'])) + { + foreach ($schema_changes['add_tables'] as $table => $table_data) + { + $result = $this->sql_create_table($table, $table_data); + if ($this->return_statements) + { + $statements = array_merge($statements, $result); + } + } + } + + // Change columns? + if (!empty($schema_changes['change_columns'])) + { + foreach ($schema_changes['change_columns'] as $table => $columns) + { + foreach ($columns as $column_name => $column_data) + { + // If the column exists we change it, else we add it ;) + if ($column_exists = $this->sql_column_exists($table, $column_name)) + { + $result = $this->sql_column_change($table, $column_name, $column_data, true); + } + else + { + $result = $this->sql_column_add($table, $column_name, $column_data, true); + } + + if ($sqlite) + { + if ($column_exists) + { + $sqlite_data[$table]['change_columns'][] = $result; + } + else + { + $sqlite_data[$table]['add_columns'][] = $result; + } + } + else if ($this->return_statements) + { + $statements = array_merge($statements, $result); + } + } + } + } + + // Add columns? + if (!empty($schema_changes['add_columns'])) + { + foreach ($schema_changes['add_columns'] as $table => $columns) + { + foreach ($columns as $column_name => $column_data) + { + // Only add the column if it does not exist yet + if ($column_exists = $this->sql_column_exists($table, $column_name)) + { + continue; + // This is commented out here because it can take tremendous time on updates +// $result = $this->sql_column_change($table, $column_name, $column_data, true); + } + else + { + $result = $this->sql_column_add($table, $column_name, $column_data, true); + } + + if ($sqlite) + { + if ($column_exists) + { + continue; +// $sqlite_data[$table]['change_columns'][] = $result; + } + else + { + $sqlite_data[$table]['add_columns'][] = $result; + } + } + else if ($this->return_statements) + { + $statements = array_merge($statements, $result); + } + } + } + } + + // Remove keys? + if (!empty($schema_changes['drop_keys'])) + { + foreach ($schema_changes['drop_keys'] as $table => $indexes) + { + foreach ($indexes as $index_name) + { + if (!$this->sql_index_exists($table, $index_name)) + { + continue; + } + + $result = $this->sql_index_drop($table, $index_name); + + if ($this->return_statements) + { + $statements = array_merge($statements, $result); + } + } + } + } + + // Drop columns? + if (!empty($schema_changes['drop_columns'])) + { + foreach ($schema_changes['drop_columns'] as $table => $columns) + { + foreach ($columns as $column) + { + // Only remove the column if it exists... + if ($this->sql_column_exists($table, $column)) + { + $result = $this->sql_column_remove($table, $column, true); + + if ($sqlite) + { + $sqlite_data[$table]['drop_columns'][] = $result; + } + else if ($this->return_statements) + { + $statements = array_merge($statements, $result); + } + } + } + } + } + + // Add primary keys? + if (!empty($schema_changes['add_primary_keys'])) + { + foreach ($schema_changes['add_primary_keys'] as $table => $columns) + { + $result = $this->sql_create_primary_key($table, $columns, true); + + if ($sqlite) + { + $sqlite_data[$table]['primary_key'] = $result; + } + else if ($this->return_statements) + { + $statements = array_merge($statements, $result); + } + } + } + + // Add unique indexes? + if (!empty($schema_changes['add_unique_index'])) + { + foreach ($schema_changes['add_unique_index'] as $table => $index_array) + { + foreach ($index_array as $index_name => $column) + { + if ($this->sql_unique_index_exists($table, $index_name)) + { + continue; + } + + $result = $this->sql_create_unique_index($table, $index_name, $column); + + if ($this->return_statements) + { + $statements = array_merge($statements, $result); + } + } + } + } + + // Add indexes? + if (!empty($schema_changes['add_index'])) + { + foreach ($schema_changes['add_index'] as $table => $index_array) + { + foreach ($index_array as $index_name => $column) + { + if ($this->sql_index_exists($table, $index_name)) + { + continue; + } + + $result = $this->sql_create_index($table, $index_name, $column); + + if ($this->return_statements) + { + $statements = array_merge($statements, $result); + } + } + } + } + + if ($sqlite) + { + foreach ($sqlite_data as $table_name => $sql_schema_changes) + { + // Create temporary table with original data + $statements[] = 'begin'; + + $sql = "SELECT sql + FROM sqlite_master + WHERE type = 'table' + AND name = '{$table_name}' + ORDER BY type DESC, name;"; + $result = $this->db->sql_query($sql); + + if (!$result) + { + continue; + } + + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + // Create a backup table and populate it, destroy the existing one + $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $row['sql']); + $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name; + $statements[] = 'DROP TABLE ' . $table_name; + + // Get the columns... + preg_match('#\((.*)\)#s', $row['sql'], $matches); + + $plain_table_cols = trim($matches[1]); + $new_table_cols = preg_split('/,(?![\s\w]+\))/m', $plain_table_cols); + $column_list = array(); + + foreach ($new_table_cols as $declaration) + { + $entities = preg_split('#\s+#', trim($declaration)); + if ($entities[0] == 'PRIMARY') + { + continue; + } + $column_list[] = $entities[0]; + } + + // note down the primary key notation because sqlite only supports adding it to the end for the new table + $primary_key = false; + $_new_cols = array(); + + foreach ($new_table_cols as $key => $declaration) + { + $entities = preg_split('#\s+#', trim($declaration)); + if ($entities[0] == 'PRIMARY') + { + $primary_key = $declaration; + continue; + } + $_new_cols[] = $declaration; + } + + $new_table_cols = $_new_cols; + + // First of all... change columns + if (!empty($sql_schema_changes['change_columns'])) + { + foreach ($sql_schema_changes['change_columns'] as $column_sql) + { + foreach ($new_table_cols as $key => $declaration) + { + $entities = preg_split('#\s+#', trim($declaration)); + if (strpos($column_sql, $entities[0] . ' ') === 0) + { + $new_table_cols[$key] = $column_sql; + } + } + } + } + + if (!empty($sql_schema_changes['add_columns'])) + { + foreach ($sql_schema_changes['add_columns'] as $column_sql) + { + $new_table_cols[] = $column_sql; + } + } + + // Now drop them... + if (!empty($sql_schema_changes['drop_columns'])) + { + foreach ($sql_schema_changes['drop_columns'] as $column_name) + { + // Remove from column list... + $new_column_list = array(); + foreach ($column_list as $key => $value) + { + if ($value === $column_name) + { + continue; + } + + $new_column_list[] = $value; + } + + $column_list = $new_column_list; + + // Remove from table... + $_new_cols = array(); + foreach ($new_table_cols as $key => $declaration) + { + $entities = preg_split('#\s+#', trim($declaration)); + if (strpos($column_name . ' ', $entities[0] . ' ') === 0) + { + continue; + } + $_new_cols[] = $declaration; + } + $new_table_cols = $_new_cols; + } + } + + // Primary key... + if (!empty($sql_schema_changes['primary_key'])) + { + $new_table_cols[] = 'PRIMARY KEY (' . implode(', ', $sql_schema_changes['primary_key']) . ')'; + } + // Add a new one or the old primary key + else if ($primary_key !== false) + { + $new_table_cols[] = $primary_key; + } + + $columns = implode(',', $column_list); + + // create a new table and fill it up. destroy the temp one + $statements[] = 'CREATE TABLE ' . $table_name . ' (' . implode(',', $new_table_cols) . ');'; + $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;'; + $statements[] = 'DROP TABLE ' . $table_name . '_temp'; + + $statements[] = 'commit'; + } + } + + if ($this->return_statements) + { + return $statements; + } + } + + /** + * {@inheritDoc} + */ + function sql_list_columns($table_name) + { + $columns = array(); + + switch ($this->sql_layer) + { + case 'mysql_40': + case 'mysql_41': + $sql = "SHOW COLUMNS FROM $table_name"; + break; + + // PostgreSQL has a way of doing this in a much simpler way but would + // not allow us to support all versions of PostgreSQL + case 'postgres': + $sql = "SELECT a.attname + FROM pg_class c, pg_attribute a + WHERE c.relname = '{$table_name}' + AND a.attnum > 0 + AND a.attrelid = c.oid"; + break; + + // same deal with PostgreSQL, we must perform more complex operations than + // we technically could + case 'mssql': + case 'mssqlnative': + $sql = "SELECT c.name + FROM syscolumns c + LEFT JOIN sysobjects o ON c.id = o.id + WHERE o.name = '{$table_name}'"; + break; + + case 'oracle': + $sql = "SELECT column_name + FROM user_tab_columns + WHERE LOWER(table_name) = '" . strtolower($table_name) . "'"; + break; + + case 'sqlite': + case 'sqlite3': + $sql = "SELECT sql + FROM sqlite_master + WHERE type = 'table' + AND name = '{$table_name}'"; + + $result = $this->db->sql_query($sql); + + if (!$result) + { + return false; + } + + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + preg_match('#\((.*)\)#s', $row['sql'], $matches); + + $cols = trim($matches[1]); + $col_array = preg_split('/,(?![\s\w]+\))/m', $cols); + + foreach ($col_array as $declaration) + { + $entities = preg_split('#\s+#', trim($declaration)); + if ($entities[0] == 'PRIMARY') + { + continue; + } + + $column = strtolower($entities[0]); + $columns[$column] = $column; + } + + return $columns; + break; + } + + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $column = strtolower(current($row)); + $columns[$column] = $column; + } + $this->db->sql_freeresult($result); + + return $columns; + } + + /** + * {@inheritDoc} + */ + function sql_column_exists($table_name, $column_name) + { + $columns = $this->sql_list_columns($table_name); + + return isset($columns[$column_name]); + } + + /** + * {@inheritDoc} + */ + function sql_index_exists($table_name, $index_name) + { + if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative') + { + $sql = "EXEC sp_statistics '$table_name'"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + if ($row['TYPE'] == 3) + { + if (strtolower($row['INDEX_NAME']) == strtolower($index_name)) + { + $this->db->sql_freeresult($result); + return true; + } + } + } + $this->db->sql_freeresult($result); + + return false; + } + + switch ($this->sql_layer) + { + case 'postgres': + $sql = "SELECT ic.relname as index_name + FROM pg_class bc, pg_class ic, pg_index i + WHERE (bc.oid = i.indrelid) + AND (ic.oid = i.indexrelid) + AND (bc.relname = '" . $table_name . "') + AND (i.indisunique != 't') + AND (i.indisprimary != 't')"; + $col = 'index_name'; + break; + + case 'mysql_40': + case 'mysql_41': + $sql = 'SHOW KEYS + FROM ' . $table_name; + $col = 'Key_name'; + break; + + case 'oracle': + $sql = "SELECT index_name + FROM user_indexes + WHERE table_name = '" . strtoupper($table_name) . "' + AND generated = 'N' + AND uniqueness = 'NONUNIQUE'"; + $col = 'index_name'; + break; + + case 'sqlite': + case 'sqlite3': + $sql = "PRAGMA index_list('" . $table_name . "');"; + $col = 'name'; + break; + } + + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if (($this->sql_layer == 'mysql_40' || $this->sql_layer == 'mysql_41') && !$row['Non_unique']) + { + continue; + } + + // These DBMS prefix index name with the table name + switch ($this->sql_layer) + { + case 'oracle': + case 'postgres': + case 'sqlite': + case 'sqlite3': + $row[$col] = substr($row[$col], strlen($table_name) + 1); + break; + } + + if (strtolower($row[$col]) == strtolower($index_name)) + { + $this->db->sql_freeresult($result); + return true; + } + } + $this->db->sql_freeresult($result); + + return false; + } + + /** + * {@inheritDoc} + */ + function sql_unique_index_exists($table_name, $index_name) + { + if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative') + { + $sql = "EXEC sp_statistics '$table_name'"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + // Usually NON_UNIQUE is the column we want to check, but we allow for both + if ($row['TYPE'] == 3) + { + if (strtolower($row['INDEX_NAME']) == strtolower($index_name)) + { + $this->db->sql_freeresult($result); + return true; + } + } + } + $this->db->sql_freeresult($result); + return false; + } + + switch ($this->sql_layer) + { + case 'postgres': + $sql = "SELECT ic.relname as index_name, i.indisunique + FROM pg_class bc, pg_class ic, pg_index i + WHERE (bc.oid = i.indrelid) + AND (ic.oid = i.indexrelid) + AND (bc.relname = '" . $table_name . "') + AND (i.indisprimary != 't')"; + $col = 'index_name'; + break; + + case 'mysql_40': + case 'mysql_41': + $sql = 'SHOW KEYS + FROM ' . $table_name; + $col = 'Key_name'; + break; + + case 'oracle': + $sql = "SELECT index_name, table_owner + FROM user_indexes + WHERE table_name = '" . strtoupper($table_name) . "' + AND generated = 'N' + AND uniqueness = 'UNIQUE'"; + $col = 'index_name'; + break; + + case 'sqlite': + case 'sqlite3': + $sql = "PRAGMA index_list('" . $table_name . "');"; + $col = 'name'; + break; + } + + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if (($this->sql_layer == 'mysql_40' || $this->sql_layer == 'mysql_41') && ($row['Non_unique'] || $row[$col] == 'PRIMARY')) + { + continue; + } + + if (($this->sql_layer == 'sqlite' || $this->sql_layer == 'sqlite3') && !$row['unique']) + { + continue; + } + + if ($this->sql_layer == 'postgres' && $row['indisunique'] != 't') + { + continue; + } + + // These DBMS prefix index name with the table name + switch ($this->sql_layer) + { + case 'oracle': + // Two cases here... prefixed with U_[table_owner] and not prefixed with table_name + if (strpos($row[$col], 'U_') === 0) + { + $row[$col] = substr($row[$col], strlen('U_' . $row['table_owner']) + 1); + } + else if (strpos($row[$col], strtoupper($table_name)) === 0) + { + $row[$col] = substr($row[$col], strlen($table_name) + 1); + } + break; + + case 'postgres': + case 'sqlite': + case 'sqlite3': + $row[$col] = substr($row[$col], strlen($table_name) + 1); + break; + } + + if (strtolower($row[$col]) == strtolower($index_name)) + { + $this->db->sql_freeresult($result); + return true; + } + } + $this->db->sql_freeresult($result); + + return false; + } + + /** + * Private method for performing sql statements (either execute them or return them) + * @access private + */ + function _sql_run_sql($statements) + { + if ($this->return_statements) + { + return $statements; + } + + // We could add error handling here... + foreach ($statements as $sql) + { + if ($sql === 'begin') + { + $this->db->sql_transaction('begin'); + } + else if ($sql === 'commit') + { + $this->db->sql_transaction('commit'); + } + else + { + $this->db->sql_query($sql); + } + } + + return true; + } + + /** + * Function to prepare some column information for better usage + * @access private + */ + function sql_prepare_column_data($table_name, $column_name, $column_data) + { + if (strlen($column_name) > 30) + { + trigger_error("Column name '$column_name' on table '$table_name' is too long. The maximum is 30 characters.", E_USER_ERROR); + } + + // Get type + list($column_type, $orig_column_type) = $this->get_column_type($column_data[0]); + + // Adjust default value if db-dependent specified + if (is_array($column_data[1])) + { + $column_data[1] = (isset($column_data[1][$this->sql_layer])) ? $column_data[1][$this->sql_layer] : $column_data[1]['default']; + } + + $sql = ''; + + $return_array = array(); + + switch ($this->sql_layer) + { + case 'mssql': + case 'mssqlnative': + $sql .= " {$column_type} "; + $sql_default = " {$column_type} "; + + // For adding columns we need the default definition + if (!is_null($column_data[1])) + { + // For hexadecimal values do not use single quotes + if (strpos($column_data[1], '0x') === 0) + { + $return_array['default'] = 'DEFAULT (' . $column_data[1] . ') '; + $sql_default .= $return_array['default']; + } + else + { + $return_array['default'] = 'DEFAULT (' . ((is_numeric($column_data[1])) ? $column_data[1] : "'{$column_data[1]}'") . ') '; + $sql_default .= $return_array['default']; + } + } + + if (isset($column_data[2]) && $column_data[2] == 'auto_increment') + { +// $sql .= 'IDENTITY (1, 1) '; + $sql_default .= 'IDENTITY (1, 1) '; + } + + $return_array['textimage'] = $column_type === '[text]'; + + if (!is_null($column_data[1]) || (isset($column_data[2]) && $column_data[2] == 'auto_increment')) + { + $sql .= 'NOT NULL'; + $sql_default .= 'NOT NULL'; + } + else + { + $sql .= 'NULL'; + $sql_default .= 'NULL'; + } + + $return_array['column_type_sql_default'] = $sql_default; + + break; + + case 'mysql_40': + case 'mysql_41': + $sql .= " {$column_type} "; + + // For hexadecimal values do not use single quotes + if (!is_null($column_data[1]) && substr($column_type, -4) !== 'text' && substr($column_type, -4) !== 'blob') + { + $sql .= (strpos($column_data[1], '0x') === 0) ? "DEFAULT {$column_data[1]} " : "DEFAULT '{$column_data[1]}' "; + } + + if (!is_null($column_data[1]) || (isset($column_data[2]) && $column_data[2] == 'auto_increment')) + { + $sql .= 'NOT NULL'; + } + else + { + $sql .= 'NULL'; + } + + if (isset($column_data[2])) + { + if ($column_data[2] == 'auto_increment') + { + $sql .= ' auto_increment'; + } + else if ($this->sql_layer === 'mysql_41' && $column_data[2] == 'true_sort') + { + $sql .= ' COLLATE utf8_unicode_ci'; + } + } + + break; + + case 'oracle': + $sql .= " {$column_type} "; + $sql .= (!is_null($column_data[1])) ? "DEFAULT '{$column_data[1]}' " : ''; + + // In Oracle empty strings ('') are treated as NULL. + // Therefore in oracle we allow NULL's for all DEFAULT '' entries + // Oracle does not like setting NOT NULL on a column that is already NOT NULL (this happens only on number fields) + if (!preg_match('/number/i', $column_type)) + { + $sql .= ($column_data[1] === '' || $column_data[1] === null) ? '' : 'NOT NULL'; + } + + $return_array['auto_increment'] = false; + if (isset($column_data[2]) && $column_data[2] == 'auto_increment') + { + $return_array['auto_increment'] = true; + } + + break; + + case 'postgres': + $return_array['column_type'] = $column_type; + + $sql .= " {$column_type} "; + + $return_array['auto_increment'] = false; + if (isset($column_data[2]) && $column_data[2] == 'auto_increment') + { + $default_val = "nextval('{$table_name}_seq')"; + $return_array['auto_increment'] = true; + } + else if (!is_null($column_data[1])) + { + $default_val = "'" . $column_data[1] . "'"; + $return_array['null'] = 'NOT NULL'; + $sql .= 'NOT NULL '; + } + else + { + $default_val = "'" . $column_data[1] . "'"; + $return_array['null'] = 'NULL'; + $sql .= 'NULL '; + } + + $return_array['default'] = $default_val; + + $sql .= "DEFAULT {$default_val}"; + + // Unsigned? Then add a CHECK contraint + if (in_array($orig_column_type, $this->unsigned_types)) + { + $return_array['constraint'] = "CHECK ({$column_name} >= 0)"; + $sql .= " CHECK ({$column_name} >= 0)"; + } + + break; + + case 'sqlite': + case 'sqlite3': + $return_array['primary_key_set'] = false; + if (isset($column_data[2]) && $column_data[2] == 'auto_increment') + { + $sql .= ' INTEGER PRIMARY KEY'; + $return_array['primary_key_set'] = true; + + if ($this->sql_layer === 'sqlite3') + { + $sql .= ' AUTOINCREMENT'; + } + } + else + { + $sql .= ' ' . $column_type; + } + + if (!is_null($column_data[1])) + { + $sql .= ' NOT NULL '; + $sql .= "DEFAULT '{$column_data[1]}'"; + } + + break; + } + + $return_array['column_type_sql'] = $sql; + + return $return_array; + } + + /** + * Get the column's database type from the type map + * + * @param string $column_map_type + * @return array column type for this database + * and map type without length + */ + function get_column_type($column_map_type) + { + if (strpos($column_map_type, ':') !== false) + { + list($orig_column_type, $column_length) = explode(':', $column_map_type); + if (!is_array($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'])) + { + $column_type = sprintf($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'], $column_length); + } + else + { + if (isset($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['rule'])) + { + switch ($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['rule'][0]) + { + case 'div': + $column_length /= $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['rule'][1]; + $column_length = ceil($column_length); + $column_type = sprintf($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'][0], $column_length); + break; + } + } + + if (isset($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'])) + { + switch ($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][0]) + { + case 'mult': + $column_length *= $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][1]; + if ($column_length > $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][2]) + { + $column_type = $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][3]; + } + else + { + $column_type = sprintf($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'][0], $column_length); + } + break; + } + } + } + $orig_column_type .= ':'; + } + else + { + $orig_column_type = $column_map_type; + $column_type = $this->dbms_type_map[$this->sql_layer][$column_map_type]; + } + + return array($column_type, $orig_column_type); + } + + /** + * {@inheritDoc} + */ + function sql_column_add($table_name, $column_name, $column_data, $inline = false) + { + $column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data); + $statements = array(); + + switch ($this->sql_layer) + { + case 'mssql': + case 'mssqlnative': + // Does not support AFTER, only through temporary table + $statements[] = 'ALTER TABLE [' . $table_name . '] ADD [' . $column_name . '] ' . $column_data['column_type_sql_default']; + break; + + case 'mysql_40': + case 'mysql_41': + $after = (!empty($column_data['after'])) ? ' AFTER ' . $column_data['after'] : ''; + $statements[] = 'ALTER TABLE `' . $table_name . '` ADD COLUMN `' . $column_name . '` ' . $column_data['column_type_sql'] . $after; + break; + + case 'oracle': + // Does not support AFTER, only through temporary table + $statements[] = 'ALTER TABLE ' . $table_name . ' ADD ' . $column_name . ' ' . $column_data['column_type_sql']; + break; + + case 'postgres': + // Does not support AFTER, only through temporary table + if (version_compare($this->db->sql_server_info(true), '8.0', '>=')) + { + $statements[] = 'ALTER TABLE ' . $table_name . ' ADD COLUMN "' . $column_name . '" ' . $column_data['column_type_sql']; + } + else + { + // old versions cannot add columns with default and null information + $statements[] = 'ALTER TABLE ' . $table_name . ' ADD COLUMN "' . $column_name . '" ' . $column_data['column_type'] . ' ' . $column_data['constraint']; + + if (isset($column_data['null'])) + { + if ($column_data['null'] == 'NOT NULL') + { + $statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN ' . $column_name . ' SET NOT NULL'; + } + } + + if (isset($column_data['default'])) + { + $statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN ' . $column_name . ' SET DEFAULT ' . $column_data['default']; + } + } + + break; + + case 'sqlite': + if ($inline && $this->return_statements) + { + return $column_name . ' ' . $column_data['column_type_sql']; + } + + $recreate_queries = $this->sqlite_get_recreate_table_queries($table_name); + if (empty($recreate_queries)) + { + break; + } + + $statements[] = 'begin'; + + $sql_create_table = array_shift($recreate_queries); + + // Create a backup table and populate it, destroy the existing one + $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $sql_create_table); + $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name; + $statements[] = 'DROP TABLE ' . $table_name; + + preg_match('#\((.*)\)#s', $sql_create_table, $matches); + + $new_table_cols = trim($matches[1]); + $old_table_cols = preg_split('/,(?![\s\w]+\))/m', $new_table_cols); + $column_list = array(); + + foreach ($old_table_cols as $declaration) + { + $entities = preg_split('#\s+#', trim($declaration)); + if ($entities[0] == 'PRIMARY') + { + continue; + } + $column_list[] = $entities[0]; + } + + $columns = implode(',', $column_list); + + $new_table_cols = $column_name . ' ' . $column_data['column_type_sql'] . ',' . $new_table_cols; + + // create a new table and fill it up. destroy the temp one + $statements[] = 'CREATE TABLE ' . $table_name . ' (' . $new_table_cols . ');'; + $statements = array_merge($statements, $recreate_queries); + + $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;'; + $statements[] = 'DROP TABLE ' . $table_name . '_temp'; + + $statements[] = 'commit'; + break; + + case 'sqlite3': + if ($inline && $this->return_statements) + { + return $column_name . ' ' . $column_data['column_type_sql']; + } + + $statements[] = 'ALTER TABLE ' . $table_name . ' ADD ' . $column_name . ' ' . $column_data['column_type_sql']; + break; + } + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_column_remove($table_name, $column_name, $inline = false) + { + $statements = array(); + + switch ($this->sql_layer) + { + case 'mssql': + case 'mssqlnative': + // We need the data here + $old_return_statements = $this->return_statements; + $this->return_statements = true; + + $indexes = $this->get_existing_indexes($table_name, $column_name); + $indexes = array_merge($indexes, $this->get_existing_indexes($table_name, $column_name, true)); + + // Drop any indexes + $recreate_indexes = array(); + if (!empty($indexes)) + { + foreach ($indexes as $index_name => $index_data) + { + $result = $this->sql_index_drop($table_name, $index_name); + $statements = array_merge($statements, $result); + if (sizeof($index_data) > 1) + { + // Remove this column from the index and recreate it + $recreate_indexes[$index_name] = array_diff($index_data, array($column_name)); + } + } + } + + // Drop default value constraint + $result = $this->mssql_get_drop_default_constraints_queries($table_name, $column_name); + $statements = array_merge($statements, $result); + + // Remove the column + $statements[] = 'ALTER TABLE [' . $table_name . '] DROP COLUMN [' . $column_name . ']'; + + if (!empty($recreate_indexes)) + { + // Recreate indexes after we removed the column + foreach ($recreate_indexes as $index_name => $index_data) + { + $result = $this->sql_create_index($table_name, $index_name, $index_data); + $statements = array_merge($statements, $result); + } + } + + $this->return_statements = $old_return_statements; + break; + + case 'mysql_40': + case 'mysql_41': + $statements[] = 'ALTER TABLE `' . $table_name . '` DROP COLUMN `' . $column_name . '`'; + break; + + case 'oracle': + $statements[] = 'ALTER TABLE ' . $table_name . ' DROP COLUMN ' . $column_name; + break; + + case 'postgres': + $statements[] = 'ALTER TABLE ' . $table_name . ' DROP COLUMN "' . $column_name . '"'; + break; + + case 'sqlite': + case 'sqlite3': + + if ($inline && $this->return_statements) + { + return $column_name; + } + + $recreate_queries = $this->sqlite_get_recreate_table_queries($table_name, $column_name); + if (empty($recreate_queries)) + { + break; + } + + $statements[] = 'begin'; + + $sql_create_table = array_shift($recreate_queries); + + // Create a backup table and populate it, destroy the existing one + $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $sql_create_table); + $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name; + $statements[] = 'DROP TABLE ' . $table_name; + + preg_match('#\((.*)\)#s', $sql_create_table, $matches); + + $new_table_cols = trim($matches[1]); + $old_table_cols = preg_split('/,(?![\s\w]+\))/m', $new_table_cols); + $column_list = array(); + + foreach ($old_table_cols as $declaration) + { + $entities = preg_split('#\s+#', trim($declaration)); + if ($entities[0] == 'PRIMARY' || $entities[0] === $column_name) + { + continue; + } + $column_list[] = $entities[0]; + } + + $columns = implode(',', $column_list); + + $new_table_cols = trim(preg_replace('/' . $column_name . '\b[^,]+(?:,|$)/m', '', $new_table_cols)); + if (substr($new_table_cols, -1) === ',') + { + // Remove the comma from the last entry again + $new_table_cols = substr($new_table_cols, 0, -1); + } + + // create a new table and fill it up. destroy the temp one + $statements[] = 'CREATE TABLE ' . $table_name . ' (' . $new_table_cols . ');'; + $statements = array_merge($statements, $recreate_queries); + + $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;'; + $statements[] = 'DROP TABLE ' . $table_name . '_temp'; + + $statements[] = 'commit'; + break; + } + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_index_drop($table_name, $index_name) + { + $statements = array(); + + switch ($this->sql_layer) + { + case 'mssql': + case 'mssqlnative': + $statements[] = 'DROP INDEX ' . $table_name . '.' . $index_name; + break; + + case 'mysql_40': + case 'mysql_41': + $statements[] = 'DROP INDEX ' . $index_name . ' ON ' . $table_name; + break; + + case 'oracle': + case 'postgres': + case 'sqlite': + case 'sqlite3': + $statements[] = 'DROP INDEX ' . $table_name . '_' . $index_name; + break; + } + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_table_drop($table_name) + { + $statements = array(); + + if (!$this->sql_table_exists($table_name)) + { + return $this->_sql_run_sql($statements); + } + + // the most basic operation, get rid of the table + $statements[] = 'DROP TABLE ' . $table_name; + + switch ($this->sql_layer) + { + case 'oracle': + $sql = 'SELECT A.REFERENCED_NAME + FROM USER_DEPENDENCIES A, USER_TRIGGERS B + WHERE A.REFERENCED_TYPE = \'SEQUENCE\' + AND A.NAME = B.TRIGGER_NAME + AND B.TABLE_NAME = \'' . strtoupper($table_name) . "'"; + $result = $this->db->sql_query($sql); + + // any sequences ref'd to this table's triggers? + while ($row = $this->db->sql_fetchrow($result)) + { + $statements[] = "DROP SEQUENCE {$row['referenced_name']}"; + } + $this->db->sql_freeresult($result); + break; + + case 'postgres': + // PGSQL does not "tightly" bind sequences and tables, we must guess... + $sql = "SELECT relname + FROM pg_class + WHERE relkind = 'S' + AND relname = '{$table_name}_seq'"; + $result = $this->db->sql_query($sql); + + // We don't even care about storing the results. We already know the answer if we get rows back. + if ($this->db->sql_fetchrow($result)) + { + $statements[] = "DROP SEQUENCE {$table_name}_seq;\n"; + } + $this->db->sql_freeresult($result); + break; + } + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_create_primary_key($table_name, $column, $inline = false) + { + $statements = array(); + + switch ($this->sql_layer) + { + case 'postgres': + case 'mysql_40': + case 'mysql_41': + $statements[] = 'ALTER TABLE ' . $table_name . ' ADD PRIMARY KEY (' . implode(', ', $column) . ')'; + break; + + case 'mssql': + case 'mssqlnative': + $sql = "ALTER TABLE [{$table_name}] WITH NOCHECK ADD "; + $sql .= "CONSTRAINT [PK_{$table_name}] PRIMARY KEY CLUSTERED ("; + $sql .= '[' . implode("],\n\t\t[", $column) . ']'; + $sql .= ')'; + + $statements[] = $sql; + break; + + case 'oracle': + $statements[] = 'ALTER TABLE ' . $table_name . ' add CONSTRAINT pk_' . $table_name . ' PRIMARY KEY (' . implode(', ', $column) . ')'; + break; + + case 'sqlite': + case 'sqlite3': + + if ($inline && $this->return_statements) + { + return $column; + } + + $recreate_queries = $this->sqlite_get_recreate_table_queries($table_name); + if (empty($recreate_queries)) + { + break; + } + + $statements[] = 'begin'; + + $sql_create_table = array_shift($recreate_queries); + + // Create a backup table and populate it, destroy the existing one + $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $sql_create_table); + $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name; + $statements[] = 'DROP TABLE ' . $table_name; + + preg_match('#\((.*)\)#s', $sql_create_table, $matches); + + $new_table_cols = trim($matches[1]); + $old_table_cols = preg_split('/,(?![\s\w]+\))/m', $new_table_cols); + $column_list = array(); + + foreach ($old_table_cols as $declaration) + { + $entities = preg_split('#\s+#', trim($declaration)); + if ($entities[0] == 'PRIMARY') + { + continue; + } + $column_list[] = $entities[0]; + } + + $columns = implode(',', $column_list); + + // create a new table and fill it up. destroy the temp one + $statements[] = 'CREATE TABLE ' . $table_name . ' (' . $new_table_cols . ', PRIMARY KEY (' . implode(', ', $column) . '));'; + $statements = array_merge($statements, $recreate_queries); + + $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;'; + $statements[] = 'DROP TABLE ' . $table_name . '_temp'; + + $statements[] = 'commit'; + break; + } + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_create_unique_index($table_name, $index_name, $column) + { + $statements = array(); + + $table_prefix = substr(CONFIG_TABLE, 0, -6); // strlen(config) + if (strlen($table_name . '_' . $index_name) - strlen($table_prefix) > 24) + { + $max_length = strlen($table_prefix) + 24; + trigger_error("Index name '{$table_name}_$index_name' on table '$table_name' is too long. The maximum is $max_length characters.", E_USER_ERROR); + } + + switch ($this->sql_layer) + { + case 'postgres': + case 'oracle': + case 'sqlite': + case 'sqlite3': + $statements[] = 'CREATE UNIQUE INDEX ' . $table_name . '_' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')'; + break; + + case 'mysql_40': + case 'mysql_41': + $statements[] = 'ALTER TABLE ' . $table_name . ' ADD UNIQUE INDEX ' . $index_name . '(' . implode(', ', $column) . ')'; + break; + + case 'mssql': + case 'mssqlnative': + $statements[] = 'CREATE UNIQUE INDEX [' . $index_name . '] ON [' . $table_name . ']([' . implode('], [', $column) . '])'; + break; + } + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_create_index($table_name, $index_name, $column) + { + $statements = array(); + + $table_prefix = substr(CONFIG_TABLE, 0, -6); // strlen(config) + if (strlen($table_name . $index_name) - strlen($table_prefix) > 24) + { + $max_length = strlen($table_prefix) + 24; + trigger_error("Index name '{$table_name}_$index_name' on table '$table_name' is too long. The maximum is $max_length characters.", E_USER_ERROR); + } + + // remove index length unless MySQL4 + if ('mysql_40' != $this->sql_layer) + { + $column = preg_replace('#:.*$#', '', $column); + } + + switch ($this->sql_layer) + { + case 'postgres': + case 'oracle': + case 'sqlite': + case 'sqlite3': + $statements[] = 'CREATE INDEX ' . $table_name . '_' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')'; + break; + + case 'mysql_40': + // add index size to definition as required by MySQL4 + foreach ($column as $i => $col) + { + if (false !== strpos($col, ':')) + { + list($col, $index_size) = explode(':', $col); + $column[$i] = "$col($index_size)"; + } + } + // no break + case 'mysql_41': + $statements[] = 'ALTER TABLE ' . $table_name . ' ADD INDEX ' . $index_name . '(' . implode(', ', $column) . ')'; + break; + + case 'mssql': + case 'mssqlnative': + $statements[] = 'CREATE INDEX [' . $index_name . '] ON [' . $table_name . ']([' . implode('], [', $column) . '])'; + break; + } + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_list_index($table_name) + { + $index_array = array(); + + if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative') + { + $sql = "EXEC sp_statistics '$table_name'"; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if ($row['TYPE'] == 3) + { + $index_array[] = $row['INDEX_NAME']; + } + } + $this->db->sql_freeresult($result); + } + else + { + switch ($this->sql_layer) + { + case 'postgres': + $sql = "SELECT ic.relname as index_name + FROM pg_class bc, pg_class ic, pg_index i + WHERE (bc.oid = i.indrelid) + AND (ic.oid = i.indexrelid) + AND (bc.relname = '" . $table_name . "') + AND (i.indisunique != 't') + AND (i.indisprimary != 't')"; + $col = 'index_name'; + break; + + case 'mysql_40': + case 'mysql_41': + $sql = 'SHOW KEYS + FROM ' . $table_name; + $col = 'Key_name'; + break; + + case 'oracle': + $sql = "SELECT index_name + FROM user_indexes + WHERE table_name = '" . strtoupper($table_name) . "' + AND generated = 'N' + AND uniqueness = 'NONUNIQUE'"; + $col = 'index_name'; + break; + + case 'sqlite': + case 'sqlite3': + $sql = "PRAGMA index_info('" . $table_name . "');"; + $col = 'name'; + break; + } + + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if (($this->sql_layer == 'mysql_40' || $this->sql_layer == 'mysql_41') && !$row['Non_unique']) + { + continue; + } + + switch ($this->sql_layer) + { + case 'oracle': + case 'postgres': + case 'sqlite': + case 'sqlite3': + $row[$col] = substr($row[$col], strlen($table_name) + 1); + break; + } + + $index_array[] = $row[$col]; + } + $this->db->sql_freeresult($result); + } + + return array_map('strtolower', $index_array); + } + + /** + * Removes table_name from the index_name if it is at the beginning + * + * @param $table_name + * @param $index_name + * @return string + */ + protected function strip_table_name_from_index_name($table_name, $index_name) + { + return (strpos(strtoupper($index_name), strtoupper($table_name)) === 0) ? substr($index_name, strlen($table_name) + 1) : $index_name; + } + + /** + * {@inheritDoc} + */ + function sql_column_change($table_name, $column_name, $column_data, $inline = false) + { + $original_column_data = $column_data; + $column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data); + $statements = array(); + + switch ($this->sql_layer) + { + case 'mssql': + case 'mssqlnative': + // We need the data here + $old_return_statements = $this->return_statements; + $this->return_statements = true; + + $indexes = $this->get_existing_indexes($table_name, $column_name); + $unique_indexes = $this->get_existing_indexes($table_name, $column_name, true); + + // Drop any indexes + if (!empty($indexes) || !empty($unique_indexes)) + { + $drop_indexes = array_merge(array_keys($indexes), array_keys($unique_indexes)); + foreach ($drop_indexes as $index_name) + { + $result = $this->sql_index_drop($table_name, $index_name); + $statements = array_merge($statements, $result); + } + } + + // Drop default value constraint + $result = $this->mssql_get_drop_default_constraints_queries($table_name, $column_name); + $statements = array_merge($statements, $result); + + // Change the column + $statements[] = 'ALTER TABLE [' . $table_name . '] ALTER COLUMN [' . $column_name . '] ' . $column_data['column_type_sql']; + + if (!empty($column_data['default'])) + { + // Add new default value constraint + $statements[] = 'ALTER TABLE [' . $table_name . '] ADD CONSTRAINT [DF_' . $table_name . '_' . $column_name . '_1] ' . $this->db->sql_escape($column_data['default']) . ' FOR [' . $column_name . ']'; + } + + if (!empty($indexes)) + { + // Recreate indexes after we changed the column + foreach ($indexes as $index_name => $index_data) + { + $result = $this->sql_create_index($table_name, $index_name, $index_data); + $statements = array_merge($statements, $result); + } + } + + if (!empty($unique_indexes)) + { + // Recreate unique indexes after we changed the column + foreach ($unique_indexes as $index_name => $index_data) + { + $result = $this->sql_create_unique_index($table_name, $index_name, $index_data); + $statements = array_merge($statements, $result); + } + } + + $this->return_statements = $old_return_statements; + break; + + case 'mysql_40': + case 'mysql_41': + $statements[] = 'ALTER TABLE `' . $table_name . '` CHANGE `' . $column_name . '` `' . $column_name . '` ' . $column_data['column_type_sql']; + break; + + case 'oracle': + // We need the data here + $old_return_statements = $this->return_statements; + $this->return_statements = true; + + // Get list of existing indexes + $indexes = $this->get_existing_indexes($table_name, $column_name); + $unique_indexes = $this->get_existing_indexes($table_name, $column_name, true); + + // Drop any indexes + if (!empty($indexes) || !empty($unique_indexes)) + { + $drop_indexes = array_merge(array_keys($indexes), array_keys($unique_indexes)); + foreach ($drop_indexes as $index_name) + { + $result = $this->sql_index_drop($table_name, $this->strip_table_name_from_index_name($table_name, $index_name)); + $statements = array_merge($statements, $result); + } + } + + $temp_column_name = 'temp_' . substr(md5($column_name), 0, 25); + // Add a temporary table with the new type + $result = $this->sql_column_add($table_name, $temp_column_name, $original_column_data); + $statements = array_merge($statements, $result); + + // Copy the data to the new column + $statements[] = 'UPDATE ' . $table_name . ' SET ' . $temp_column_name . ' = ' . $column_name; + + // Drop the original column + $result = $this->sql_column_remove($table_name, $column_name); + $statements = array_merge($statements, $result); + + // Recreate the original column with the new type + $result = $this->sql_column_add($table_name, $column_name, $original_column_data); + $statements = array_merge($statements, $result); + + if (!empty($indexes)) + { + // Recreate indexes after we changed the column + foreach ($indexes as $index_name => $index_data) + { + $result = $this->sql_create_index($table_name, $this->strip_table_name_from_index_name($table_name, $index_name), $index_data); + $statements = array_merge($statements, $result); + } + } + + if (!empty($unique_indexes)) + { + // Recreate unique indexes after we changed the column + foreach ($unique_indexes as $index_name => $index_data) + { + $result = $this->sql_create_unique_index($table_name, $this->strip_table_name_from_index_name($table_name, $index_name), $index_data); + $statements = array_merge($statements, $result); + } + } + + // Copy the data to the original column + $statements[] = 'UPDATE ' . $table_name . ' SET ' . $column_name . ' = ' . $temp_column_name; + + // Drop the temporary column again + $result = $this->sql_column_remove($table_name, $temp_column_name); + $statements = array_merge($statements, $result); + + $this->return_statements = $old_return_statements; + break; + + case 'postgres': + $sql = 'ALTER TABLE ' . $table_name . ' '; + + $sql_array = array(); + $sql_array[] = 'ALTER COLUMN ' . $column_name . ' TYPE ' . $column_data['column_type']; + + if (isset($column_data['null'])) + { + if ($column_data['null'] == 'NOT NULL') + { + $sql_array[] = 'ALTER COLUMN ' . $column_name . ' SET NOT NULL'; + } + else if ($column_data['null'] == 'NULL') + { + $sql_array[] = 'ALTER COLUMN ' . $column_name . ' DROP NOT NULL'; + } + } + + if (isset($column_data['default'])) + { + $sql_array[] = 'ALTER COLUMN ' . $column_name . ' SET DEFAULT ' . $column_data['default']; + } + + // we don't want to double up on constraints if we change different number data types + if (isset($column_data['constraint'])) + { + $constraint_sql = "SELECT consrc as constraint_data + FROM pg_constraint, pg_class bc + WHERE conrelid = bc.oid + AND bc.relname = '{$table_name}' + AND NOT EXISTS ( + SELECT * + FROM pg_constraint as c, pg_inherits as i + WHERE i.inhrelid = pg_constraint.conrelid + AND c.conname = pg_constraint.conname + AND c.consrc = pg_constraint.consrc + AND c.conrelid = i.inhparent + )"; + + $constraint_exists = false; + + $result = $this->db->sql_query($constraint_sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if (trim($row['constraint_data']) == trim($column_data['constraint'])) + { + $constraint_exists = true; + break; + } + } + $this->db->sql_freeresult($result); + + if (!$constraint_exists) + { + $sql_array[] = 'ADD ' . $column_data['constraint']; + } + } + + $sql .= implode(', ', $sql_array); + + $statements[] = $sql; + break; + + case 'sqlite': + case 'sqlite3': + + if ($inline && $this->return_statements) + { + return $column_name . ' ' . $column_data['column_type_sql']; + } + + $recreate_queries = $this->sqlite_get_recreate_table_queries($table_name); + if (empty($recreate_queries)) + { + break; + } + + $statements[] = 'begin'; + + $sql_create_table = array_shift($recreate_queries); + + // Create a temp table and populate it, destroy the existing one + $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $sql_create_table); + $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name; + $statements[] = 'DROP TABLE ' . $table_name; + + preg_match('#\((.*)\)#s', $sql_create_table, $matches); + + $new_table_cols = trim($matches[1]); + $old_table_cols = preg_split('/,(?![\s\w]+\))/m', $new_table_cols); + $column_list = array(); + + foreach ($old_table_cols as $key => $declaration) + { + $declaration = trim($declaration); + + // Check for the beginning of the constraint section and stop + if (preg_match('/[^\(]*\s*PRIMARY KEY\s+\(/', $declaration) || + preg_match('/[^\(]*\s*UNIQUE\s+\(/', $declaration) || + preg_match('/[^\(]*\s*FOREIGN KEY\s+\(/', $declaration) || + preg_match('/[^\(]*\s*CHECK\s+\(/', $declaration)) + { + break; + } + + $entities = preg_split('#\s+#', $declaration); + $column_list[] = $entities[0]; + if ($entities[0] == $column_name) + { + $old_table_cols[$key] = $column_name . ' ' . $column_data['column_type_sql']; + } + } + + $columns = implode(',', $column_list); + + // Create a new table and fill it up. destroy the temp one + $statements[] = 'CREATE TABLE ' . $table_name . ' (' . implode(',', $old_table_cols) . ');'; + $statements = array_merge($statements, $recreate_queries); + + $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;'; + $statements[] = 'DROP TABLE ' . $table_name . '_temp'; + + $statements[] = 'commit'; + + break; + } + + return $this->_sql_run_sql($statements); + } + + /** + * Get queries to drop the default constraints of a column + * + * We need to drop the default constraints of a column, + * before being able to change their type or deleting them. + * + * @param string $table_name + * @param string $column_name + * @return array Array with SQL statements + */ + protected function mssql_get_drop_default_constraints_queries($table_name, $column_name) + { + $statements = array(); + if ($this->mssql_is_sql_server_2000()) + { + // http://msdn.microsoft.com/en-us/library/aa175912%28v=sql.80%29.aspx + // Deprecated in SQL Server 2005 + $sql = "SELECT so.name AS def_name + FROM sysobjects so + JOIN sysconstraints sc ON so.id = sc.constid + WHERE object_name(so.parent_obj) = '{$table_name}' + AND so.xtype = 'D' + AND sc.colid = (SELECT colid FROM syscolumns + WHERE id = object_id('{$table_name}') + AND name = '{$column_name}')"; + } + else + { + $sql = "SELECT dobj.name AS def_name + FROM sys.columns col + LEFT OUTER JOIN sys.objects dobj ON (dobj.object_id = col.default_object_id AND dobj.type = 'D') + WHERE col.object_id = object_id('{$table_name}') + AND col.name = '{$column_name}' + AND dobj.name IS NOT NULL"; + } + + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $statements[] = 'ALTER TABLE [' . $table_name . '] DROP CONSTRAINT [' . $row['def_name'] . ']'; + } + $this->db->sql_freeresult($result); + + return $statements; + } + + /** + * Get a list with existing indexes for the column + * + * @param string $table_name + * @param string $column_name + * @param bool $unique Should we get unique indexes or normal ones + * @return array Array with Index name => columns + */ + public function get_existing_indexes($table_name, $column_name, $unique = false) + { + switch ($this->sql_layer) + { + case 'mysql_40': + case 'mysql_41': + case 'postgres': + case 'sqlite': + case 'sqlite3': + // Not supported + throw new \Exception('DBMS is not supported'); + break; + } + + $sql = ''; + $existing_indexes = array(); + + switch ($this->sql_layer) + { + case 'mssql': + case 'mssqlnative': + if ($this->mssql_is_sql_server_2000()) + { + // http://msdn.microsoft.com/en-us/library/aa175912%28v=sql.80%29.aspx + // Deprecated in SQL Server 2005 + $sql = "SELECT DISTINCT ix.name AS phpbb_index_name + FROM sysindexes ix + INNER JOIN sysindexkeys ixc + ON ixc.id = ix.id + AND ixc.indid = ix.indid + INNER JOIN syscolumns cols + ON cols.colid = ixc.colid + AND cols.id = ix.id + WHERE ix.id = object_id('{$table_name}') + AND cols.name = '{$column_name}' + AND INDEXPROPERTY(ix.id, ix.name, 'IsUnique') = " . ($unique ? '1' : '0'); + } + else + { + $sql = "SELECT DISTINCT ix.name AS phpbb_index_name + FROM sys.indexes ix + INNER JOIN sys.index_columns ixc + ON ixc.object_id = ix.object_id + AND ixc.index_id = ix.index_id + INNER JOIN sys.columns cols + ON cols.column_id = ixc.column_id + AND cols.object_id = ix.object_id + WHERE ix.object_id = object_id('{$table_name}') + AND cols.name = '{$column_name}' + AND ix.is_unique = " . ($unique ? '1' : '0'); + } + break; + + case 'oracle': + $sql = "SELECT ix.index_name AS phpbb_index_name, ix.uniqueness AS is_unique + FROM all_ind_columns ixc, all_indexes ix + WHERE ix.index_name = ixc.index_name + AND ixc.table_name = '" . strtoupper($table_name) . "' + AND ixc.column_name = '" . strtoupper($column_name) . "'"; + break; + } + + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if (!isset($row['is_unique']) || ($unique && $row['is_unique'] == 'UNIQUE') || (!$unique && $row['is_unique'] == 'NONUNIQUE')) + { + $existing_indexes[$row['phpbb_index_name']] = array(); + } + } + $this->db->sql_freeresult($result); + + if (empty($existing_indexes)) + { + return array(); + } + + switch ($this->sql_layer) + { + case 'mssql': + case 'mssqlnative': + if ($this->mssql_is_sql_server_2000()) + { + $sql = "SELECT DISTINCT ix.name AS phpbb_index_name, cols.name AS phpbb_column_name + FROM sysindexes ix + INNER JOIN sysindexkeys ixc + ON ixc.id = ix.id + AND ixc.indid = ix.indid + INNER JOIN syscolumns cols + ON cols.colid = ixc.colid + AND cols.id = ix.id + WHERE ix.id = object_id('{$table_name}') + AND " . $this->db->sql_in_set('ix.name', array_keys($existing_indexes)); + } + else + { + $sql = "SELECT DISTINCT ix.name AS phpbb_index_name, cols.name AS phpbb_column_name + FROM sys.indexes ix + INNER JOIN sys.index_columns ixc + ON ixc.object_id = ix.object_id + AND ixc.index_id = ix.index_id + INNER JOIN sys.columns cols + ON cols.column_id = ixc.column_id + AND cols.object_id = ix.object_id + WHERE ix.object_id = object_id('{$table_name}') + AND " . $this->db->sql_in_set('ix.name', array_keys($existing_indexes)); + } + break; + + case 'oracle': + $sql = "SELECT index_name AS phpbb_index_name, column_name AS phpbb_column_name + FROM all_ind_columns + WHERE table_name = '" . strtoupper($table_name) . "' + AND " . $this->db->sql_in_set('index_name', array_keys($existing_indexes)); + break; + } + + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $existing_indexes[$row['phpbb_index_name']][] = $row['phpbb_column_name']; + } + $this->db->sql_freeresult($result); + + return $existing_indexes; + } + + /** + * Is the used MS SQL Server a SQL Server 2000? + * + * @return bool + */ + protected function mssql_is_sql_server_2000() + { + if ($this->is_sql_server_2000 === null) + { + $sql = "SELECT CAST(SERVERPROPERTY('productversion') AS VARCHAR(25)) AS mssql_version"; + $result = $this->db->sql_query($sql); + $properties = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + $this->is_sql_server_2000 = $properties['mssql_version'][0] == '8'; + } + + return $this->is_sql_server_2000; + } + + /** + * Returns the Queries which are required to recreate a table including indexes + * + * @param string $table_name + * @param string $remove_column When we drop a column, we remove the column + * from all indexes. If the index has no other + * column, we drop it completly. + * @return array + */ + protected function sqlite_get_recreate_table_queries($table_name, $remove_column = '') + { + $queries = array(); + + $sql = "SELECT sql + FROM sqlite_master + WHERE type = 'table' + AND name = '{$table_name}'"; + $result = $this->db->sql_query($sql); + $sql_create_table = $this->db->sql_fetchfield('sql'); + $this->db->sql_freeresult($result); + + if (!$sql_create_table) + { + return array(); + } + $queries[] = $sql_create_table; + + $sql = "SELECT sql + FROM sqlite_master + WHERE type = 'index' + AND tbl_name = '{$table_name}'"; + $result = $this->db->sql_query($sql); + while ($sql_create_index = $this->db->sql_fetchfield('sql')) + { + if ($remove_column) + { + $match = array(); + preg_match('#(?:[\w ]+)\((.*)\)#', $sql_create_index, $match); + if (!isset($match[1])) + { + continue; + } + + // Find and remove $remove_column from the index + $columns = explode(', ', $match[1]); + $found_column = array_search($remove_column, $columns); + if ($found_column !== false) + { + unset($columns[$found_column]); + + // If the column list is not empty add the index to the list + if (!empty($columns)) + { + $queries[] = str_replace($match[1], implode(', ', $columns), $sql_create_index); + } + } + else + { + $queries[] = $sql_create_index; + } + } + else + { + $queries[] = $sql_create_index; + } + } + $this->db->sql_freeresult($result); + + return $queries; + } +} diff --git a/phpBB/phpbb/db/tools/tools_interface.php b/phpBB/phpbb/db/tools/tools_interface.php new file mode 100644 index 0000000000..f153f73a54 --- /dev/null +++ b/phpBB/phpbb/db/tools/tools_interface.php @@ -0,0 +1,202 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\tools; + +/** + * Interface for a Database Tools for handling cross-db actions such as altering columns, etc. + */ +interface tools_interface +{ + /** + * Handle passed database update array. + * Expected structure... + * Key being one of the following + * drop_tables: Drop tables + * add_tables: Add tables + * change_columns: Column changes (only type, not name) + * add_columns: Add columns to a table + * drop_keys: Dropping keys + * drop_columns: Removing/Dropping columns + * add_primary_keys: adding primary keys + * add_unique_index: adding an unique index + * add_index: adding an index (can be column:index_size if you need to provide size) + * + * The values are in this format: + * {TABLE NAME} => array( + * {COLUMN NAME} => array({COLUMN TYPE}, {DEFAULT VALUE}, {OPTIONAL VARIABLES}), + * {KEY/INDEX NAME} => array({COLUMN NAMES}), + * ) + * + * + * @param array $schema_changes + * @return null + */ + public function perform_schema_changes($schema_changes); + + /** + * Gets a list of tables in the database. + * + * @return array Array of table names (all lower case) + */ + public function sql_list_tables(); + + /** + * Check if table exists + * + * @param string $table_name The table name to check for + * @return bool true if table exists, else false + */ + public function sql_table_exists($table_name); + + /** + * Create SQL Table + * + * @param string $table_name The table name to create + * @param array $table_data Array containing table data. + * @return array|true Statements to run, or true if the statements have been executed + */ + public function sql_create_table($table_name, $table_data); + + /** + * Drop Table + * + * @param string $table_name The table name to drop + * @return array|true Statements to run, or true if the statements have been executed + */ + public function sql_table_drop($table_name); + + /** + * Gets a list of columns of a table. + * + * @param string $table_name Table name + * @return array Array of column names (all lower case) + */ + public function sql_list_columns($table_name); + + /** + * Check whether a specified column exist in a table + * + * @param string $table_name Table to check + * @param string $column_name Column to check + * @return bool True if column exists, false otherwise + */ + public function sql_column_exists($table_name, $column_name); + + /** + * Add new column + * + * @param string $table_name Table to modify + * @param string $column_name Name of the column to add + * @param array $column_data Column data + * @param bool $inline Whether the query should actually be run, + * or return a string for adding the column + * @return array|true Statements to run, or true if the statements have been executed + */ + public function sql_column_add($table_name, $column_name, $column_data, $inline = false); + + /** + * Change column type (not name!) + * + * @param string $table_name Table to modify + * @param string $column_name Name of the column to modify + * @param array $column_data Column data + * @param bool $inline Whether the query should actually be run, + * or return a string for modifying the column + * @return array|true Statements to run, or true if the statements have been executed + */ + public function sql_column_change($table_name, $column_name, $column_data, $inline = false); + + /** + * Drop column + * + * @param string $table_name Table to modify + * @param string $column_name Name of the column to drop + * @param bool $inline Whether the query should actually be run, + * or return a string for deleting the column + * @return array|true Statements to run, or true if the statements have been executed + */ + public function sql_column_remove($table_name, $column_name, $inline = false); + + /** + * List all of the indices that belong to a table + * + * NOTE: does not list + * - UNIQUE indices + * - PRIMARY keys + * + * @param string $table_name Table to check + * @return array Array with index names + */ + public function sql_list_index($table_name); + + /** + * Check if a specified index exists in table. Does not return PRIMARY KEY and UNIQUE indexes. + * + * @param string $table_name Table to check the index at + * @param string $index_name The index name to check + * @return bool True if index exists, else false + */ + public function sql_index_exists($table_name, $index_name); + + /** + * Add index + * + * @param string $table_name Table to modify + * @param string $index_name Name of the index to create + * @param string|array $column Either a string with a column name, or an array with columns + * @return array|true Statements to run, or true if the statements have been executed + */ + public function sql_create_index($table_name, $index_name, $column); + + /** + * Drop Index + * + * @param string $table_name Table to modify + * @param string $index_name Name of the index to delete + * @return array|true Statements to run, or true if the statements have been executed + */ + public function sql_index_drop($table_name, $index_name); + + /** + * Check if a specified index exists in table. + * + * NOTE: Does not return normal and PRIMARY KEY indexes + * + * @param string $table_name Table to check the index at + * @param string $index_name The index name to check + * @return bool True if index exists, else false + */ + public function sql_unique_index_exists($table_name, $index_name); + + /** + * Add unique index + * + * @param string $table_name Table to modify + * @param string $index_name Name of the unique index to create + * @param string|array $column Either a string with a column name, or an array with columns + * @return array|true Statements to run, or true if the statements have been executed + */ + public function sql_create_unique_index($table_name, $index_name, $column); + + /** + * Add primary key + * + * @param string $table_name Table to modify + * @param string|array $column Either a string with a column name, or an array with columns + * @param bool $inline Whether the query should actually be run, + * or return a string for creating the key + * @return array|true Statements to run, or true if the statements have been executed + */ + public function sql_create_primary_key($table_name, $column, $inline = false); +} -- cgit v1.2.1 From 11b6bc5722a6a4aed1cd45ce65f2d9cf16ae7dd5 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Sat, 10 Jan 2015 15:43:18 +0100 Subject: [ticket/13487] Add factory for db tool class This allows us to split up the large file one by one easily. PHPBB3-13487 --- phpBB/phpbb/db/tools/factory.php | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 phpBB/phpbb/db/tools/factory.php (limited to 'phpBB/phpbb/db/tools') diff --git a/phpBB/phpbb/db/tools/factory.php b/phpBB/phpbb/db/tools/factory.php new file mode 100644 index 0000000000..60db2ade03 --- /dev/null +++ b/phpBB/phpbb/db/tools/factory.php @@ -0,0 +1,35 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\tools; + +/** + * A factory which serves the suitable tools instance for the given dbal + */ +class factory +{ + /** + * @param mixed $db_driver + * @param bool $return_statements + * @return \phpbb\db\tools\tools_interface + */ + public function get($db_driver, $return_statements = false) + { + if ($db_driver instanceof \phpbb\db\driver\driver_interface) + { + return new \phpbb\db\tools\tools($db_driver, $return_statements); + } + + throw new \InvalidArgumentException('Invalid database driver given'); + } +} -- cgit v1.2.1 From 4ea90ca44d6d274f1ff6e6e3b4821f9e8cd1e9da Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Sat, 24 Jan 2015 10:51:59 +0100 Subject: [ticket/10748] Split MS SQL from the db tools file into it's own PHPBB3-10748 --- phpBB/phpbb/db/tools/factory.php | 6 +- phpBB/phpbb/db/tools/mssql.php | 791 +++++++++++++++++++++++++++++++++++++++ phpBB/phpbb/db/tools/tools.php | 636 ++++--------------------------- 3 files changed, 859 insertions(+), 574 deletions(-) create mode 100644 phpBB/phpbb/db/tools/mssql.php (limited to 'phpBB/phpbb/db/tools') diff --git a/phpBB/phpbb/db/tools/factory.php b/phpBB/phpbb/db/tools/factory.php index 60db2ade03..24e2a4d875 100644 --- a/phpBB/phpbb/db/tools/factory.php +++ b/phpBB/phpbb/db/tools/factory.php @@ -25,7 +25,11 @@ class factory */ public function get($db_driver, $return_statements = false) { - if ($db_driver instanceof \phpbb\db\driver\driver_interface) + if ($db_driver instanceof \phpbb\db\driver\mssql || $db_driver instanceof \phpbb\db\driver\mssql_base) + { + return new \phpbb\db\tools\mssql($db_driver, $return_statements); + } + else if ($db_driver instanceof \phpbb\db\driver\driver_interface) { return new \phpbb\db\tools\tools($db_driver, $return_statements); } diff --git a/phpBB/phpbb/db/tools/mssql.php b/phpBB/phpbb/db/tools/mssql.php new file mode 100644 index 0000000000..b4a09143eb --- /dev/null +++ b/phpBB/phpbb/db/tools/mssql.php @@ -0,0 +1,791 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\tools; + +/** +* Database Tools for handling cross-db actions such as altering columns, etc. +* Currently not supported is returning SQL for creating tables. +*/ +class mssql extends tools +{ + /** + * Is the used MS SQL Server a SQL Server 2000? + * @var bool + */ + protected $is_sql_server_2000; + + /** + * Get the column types for every database we support + * + * @return array + */ + public static function get_dbms_type_map() + { + return array( + 'mssql' => array( + 'INT:' => '[int]', + 'BINT' => '[float]', + 'UINT' => '[int]', + 'UINT:' => '[int]', + 'TINT:' => '[int]', + 'USINT' => '[int]', + 'BOOL' => '[int]', + 'VCHAR' => '[varchar] (255)', + 'VCHAR:' => '[varchar] (%d)', + 'CHAR:' => '[char] (%d)', + 'XSTEXT' => '[varchar] (1000)', + 'STEXT' => '[varchar] (3000)', + 'TEXT' => '[varchar] (8000)', + 'MTEXT' => '[text]', + 'XSTEXT_UNI'=> '[varchar] (100)', + 'STEXT_UNI' => '[varchar] (255)', + 'TEXT_UNI' => '[varchar] (4000)', + 'MTEXT_UNI' => '[text]', + 'TIMESTAMP' => '[int]', + 'DECIMAL' => '[float]', + 'DECIMAL:' => '[float]', + 'PDECIMAL' => '[float]', + 'PDECIMAL:' => '[float]', + 'VCHAR_UNI' => '[varchar] (255)', + 'VCHAR_UNI:'=> '[varchar] (%d)', + 'VCHAR_CI' => '[varchar] (255)', + 'VARBINARY' => '[varchar] (255)', + ), + + 'mssqlnative' => array( + 'INT:' => '[int]', + 'BINT' => '[float]', + 'UINT' => '[int]', + 'UINT:' => '[int]', + 'TINT:' => '[int]', + 'USINT' => '[int]', + 'BOOL' => '[int]', + 'VCHAR' => '[varchar] (255)', + 'VCHAR:' => '[varchar] (%d)', + 'CHAR:' => '[char] (%d)', + 'XSTEXT' => '[varchar] (1000)', + 'STEXT' => '[varchar] (3000)', + 'TEXT' => '[varchar] (8000)', + 'MTEXT' => '[text]', + 'XSTEXT_UNI'=> '[varchar] (100)', + 'STEXT_UNI' => '[varchar] (255)', + 'TEXT_UNI' => '[varchar] (4000)', + 'MTEXT_UNI' => '[text]', + 'TIMESTAMP' => '[int]', + 'DECIMAL' => '[float]', + 'DECIMAL:' => '[float]', + 'PDECIMAL' => '[float]', + 'PDECIMAL:' => '[float]', + 'VCHAR_UNI' => '[varchar] (255)', + 'VCHAR_UNI:'=> '[varchar] (%d)', + 'VCHAR_CI' => '[varchar] (255)', + 'VARBINARY' => '[varchar] (255)', + ), + ); + } + + /** + * Constructor. Set DB Object and set {@link $return_statements return_statements}. + * + * @param \phpbb\db\driver\driver_interface $db Database connection + * @param bool $return_statements True if only statements should be returned and no SQL being executed + */ + public function __construct(\phpbb\db\driver\driver_interface $db, $return_statements = false) + { + parent::__construct($db, $return_statements); + + // Determine mapping database type + switch ($this->db->get_sql_layer()) + { + case 'mssql': + case 'mssql_odbc': + $this->sql_layer = 'mssql'; + break; + + case 'mssqlnative': + $this->sql_layer = 'mssqlnative'; + break; + } + } + + /** + * {@inheritDoc} + */ + function sql_list_tables() + { + $sql = "SELECT name + FROM sysobjects + WHERE type='U'"; + $result = $this->db->sql_query($sql); + + $tables = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $name = current($row); + $tables[$name] = $name; + } + $this->db->sql_freeresult($result); + + return $tables; + } + + /** + * {@inheritDoc} + */ + function sql_create_table($table_name, $table_data) + { + // holds the DDL for a column + $columns = $statements = array(); + + if ($this->sql_table_exists($table_name)) + { + return $this->_sql_run_sql($statements); + } + + // Begin transaction + $statements[] = 'begin'; + + // Determine if we have created a PRIMARY KEY in the earliest + $primary_key_gen = false; + + // Determine if the table requires a sequence + $create_sequence = false; + + // Begin table sql statement + $table_sql = 'CREATE TABLE [' . $table_name . '] (' . "\n"; + + if (!isset($table_data['PRIMARY_KEY'])) + { + $table_data['COLUMNS']['mssqlindex'] = array('UINT', null, 'auto_increment'); + $table_data['PRIMARY_KEY'] = 'mssqlindex'; + } + + // Iterate through the columns to create a table + foreach ($table_data['COLUMNS'] as $column_name => $column_data) + { + // here lies an array, filled with information compiled on the column's data + $prepared_column = $this->sql_prepare_column_data($table_name, $column_name, $column_data); + + if (isset($prepared_column['auto_increment']) && $prepared_column['auto_increment'] && strlen($column_name) > 26) // "${column_name}_gen" + { + trigger_error("Index name '${column_name}_gen' on table '$table_name' is too long. The maximum auto increment column length is 26 characters.", E_USER_ERROR); + } + + // here we add the definition of the new column to the list of columns + $columns[] = "\t [{$column_name}] " . $prepared_column['column_type_sql_default']; + + // see if we have found a primary key set due to a column definition if we have found it, we can stop looking + if (!$primary_key_gen) + { + $primary_key_gen = isset($prepared_column['primary_key_set']) && $prepared_column['primary_key_set']; + } + + // create sequence DDL based off of the existance of auto incrementing columns + if (!$create_sequence && isset($prepared_column['auto_increment']) && $prepared_column['auto_increment']) + { + $create_sequence = $column_name; + } + } + + // this makes up all the columns in the create table statement + $table_sql .= implode(",\n", $columns); + + // Close the table for two DBMS and add to the statements + $table_sql .= "\n);"; + $statements[] = $table_sql; + + // we have yet to create a primary key for this table, + // this means that we can add the one we really wanted instead + if (!$primary_key_gen) + { + // Write primary key + if (isset($table_data['PRIMARY_KEY'])) + { + if (!is_array($table_data['PRIMARY_KEY'])) + { + $table_data['PRIMARY_KEY'] = array($table_data['PRIMARY_KEY']); + } + + // We need the data here + $old_return_statements = $this->return_statements; + $this->return_statements = true; + + $primary_key_stmts = $this->sql_create_primary_key($table_name, $table_data['PRIMARY_KEY']); + foreach ($primary_key_stmts as $pk_stmt) + { + $statements[] = $pk_stmt; + } + + $this->return_statements = $old_return_statements; + } + } + + // Write Keys + if (isset($table_data['KEYS'])) + { + foreach ($table_data['KEYS'] as $key_name => $key_data) + { + if (!is_array($key_data[1])) + { + $key_data[1] = array($key_data[1]); + } + + $old_return_statements = $this->return_statements; + $this->return_statements = true; + + $key_stmts = ($key_data[0] == 'UNIQUE') ? $this->sql_create_unique_index($table_name, $key_name, $key_data[1]) : $this->sql_create_index($table_name, $key_name, $key_data[1]); + + foreach ($key_stmts as $key_stmt) + { + $statements[] = $key_stmt; + } + + $this->return_statements = $old_return_statements; + } + } + + // Commit Transaction + $statements[] = 'commit'; + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_list_columns($table_name) + { + $columns = array(); + + $sql = "SELECT c.name + FROM syscolumns c + LEFT JOIN sysobjects o ON c.id = o.id + WHERE o.name = '{$table_name}'"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $column = strtolower(current($row)); + $columns[$column] = $column; + } + $this->db->sql_freeresult($result); + + return $columns; + } + + /** + * {@inheritDoc} + */ + function sql_index_exists($table_name, $index_name) + { + $sql = "EXEC sp_statistics '$table_name'"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + if ($row['TYPE'] == 3) + { + if (strtolower($row['INDEX_NAME']) == strtolower($index_name)) + { + $this->db->sql_freeresult($result); + return true; + } + } + } + $this->db->sql_freeresult($result); + + return false; + } + + /** + * {@inheritDoc} + */ + function sql_unique_index_exists($table_name, $index_name) + { + $sql = "EXEC sp_statistics '$table_name'"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + // Usually NON_UNIQUE is the column we want to check, but we allow for both + if ($row['TYPE'] == 3) + { + if (strtolower($row['INDEX_NAME']) == strtolower($index_name)) + { + $this->db->sql_freeresult($result); + return true; + } + } + } + $this->db->sql_freeresult($result); + + return false; + } + + /** + * {@inheritDoc} + */ + function sql_prepare_column_data($table_name, $column_name, $column_data) + { + if (strlen($column_name) > 30) + { + trigger_error("Column name '$column_name' on table '$table_name' is too long. The maximum is 30 characters.", E_USER_ERROR); + } + + // Get type + list($column_type, $orig_column_type) = $this->get_column_type($column_data[0]); + + // Adjust default value if db-dependent specified + if (is_array($column_data[1])) + { + $column_data[1] = (isset($column_data[1][$this->sql_layer])) ? $column_data[1][$this->sql_layer] : $column_data[1]['default']; + } + + $sql = ''; + + $return_array = array(); + + $sql .= " {$column_type} "; + $sql_default = " {$column_type} "; + + // For adding columns we need the default definition + if (!is_null($column_data[1])) + { + // For hexadecimal values do not use single quotes + if (strpos($column_data[1], '0x') === 0) + { + $return_array['default'] = 'DEFAULT (' . $column_data[1] . ') '; + $sql_default .= $return_array['default']; + } + else + { + $return_array['default'] = 'DEFAULT (' . ((is_numeric($column_data[1])) ? $column_data[1] : "'{$column_data[1]}'") . ') '; + $sql_default .= $return_array['default']; + } + } + + if (isset($column_data[2]) && $column_data[2] == 'auto_increment') + { + // $sql .= 'IDENTITY (1, 1) '; + $sql_default .= 'IDENTITY (1, 1) '; + } + + $return_array['textimage'] = $column_type === '[text]'; + + if (!is_null($column_data[1]) || (isset($column_data[2]) && $column_data[2] == 'auto_increment')) + { + $sql .= 'NOT NULL'; + $sql_default .= 'NOT NULL'; + } + else + { + $sql .= 'NULL'; + $sql_default .= 'NULL'; + } + + $return_array['column_type_sql_default'] = $sql_default; + + $return_array['column_type_sql'] = $sql; + + return $return_array; + } + + /** + * {@inheritDoc} + */ + function sql_column_add($table_name, $column_name, $column_data, $inline = false) + { + $column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data); + $statements = array(); + + // Does not support AFTER, only through temporary table + $statements[] = 'ALTER TABLE [' . $table_name . '] ADD [' . $column_name . '] ' . $column_data['column_type_sql_default']; + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_column_remove($table_name, $column_name, $inline = false) + { + $statements = array(); + + // We need the data here + $old_return_statements = $this->return_statements; + $this->return_statements = true; + + $indexes = $this->get_existing_indexes($table_name, $column_name); + $indexes = array_merge($indexes, $this->get_existing_indexes($table_name, $column_name, true)); + + // Drop any indexes + $recreate_indexes = array(); + if (!empty($indexes)) + { + foreach ($indexes as $index_name => $index_data) + { + $result = $this->sql_index_drop($table_name, $index_name); + $statements = array_merge($statements, $result); + if (sizeof($index_data) > 1) + { + // Remove this column from the index and recreate it + $recreate_indexes[$index_name] = array_diff($index_data, array($column_name)); + } + } + } + + // Drop default value constraint + $result = $this->mssql_get_drop_default_constraints_queries($table_name, $column_name); + $statements = array_merge($statements, $result); + + // Remove the column + $statements[] = 'ALTER TABLE [' . $table_name . '] DROP COLUMN [' . $column_name . ']'; + + if (!empty($recreate_indexes)) + { + // Recreate indexes after we removed the column + foreach ($recreate_indexes as $index_name => $index_data) + { + $result = $this->sql_create_index($table_name, $index_name, $index_data); + $statements = array_merge($statements, $result); + } + } + + $this->return_statements = $old_return_statements; + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_index_drop($table_name, $index_name) + { + $statements = array(); + + $statements[] = 'DROP INDEX ' . $table_name . '.' . $index_name; + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_table_drop($table_name) + { + $statements = array(); + + if (!$this->sql_table_exists($table_name)) + { + return $this->_sql_run_sql($statements); + } + + // the most basic operation, get rid of the table + $statements[] = 'DROP TABLE ' . $table_name; + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_create_primary_key($table_name, $column, $inline = false) + { + $statements = array(); + + $sql = "ALTER TABLE [{$table_name}] WITH NOCHECK ADD "; + $sql .= "CONSTRAINT [PK_{$table_name}] PRIMARY KEY CLUSTERED ("; + $sql .= '[' . implode("],\n\t\t[", $column) . ']'; + $sql .= ')'; + + $statements[] = $sql; + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_create_unique_index($table_name, $index_name, $column) + { + $statements = array(); + + $this->check_index_name_length($table_name, $index_name); + + $statements[] = 'CREATE UNIQUE INDEX [' . $index_name . '] ON [' . $table_name . ']([' . implode('], [', $column) . '])'; + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_create_index($table_name, $index_name, $column) + { + $statements = array(); + + $this->check_index_name_length($table_name, $index_name); + + // remove index length + $column = preg_replace('#:.*$#', '', $column); + + $statements[] = 'CREATE INDEX [' . $index_name . '] ON [' . $table_name . ']([' . implode('], [', $column) . '])'; + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_list_index($table_name) + { + $index_array = array(); + $sql = "EXEC sp_statistics '$table_name'"; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if ($row['TYPE'] == 3) + { + $index_array[] = strtolower($row['INDEX_NAME']); + } + } + $this->db->sql_freeresult($result); + + return $index_array; + } + + /** + * {@inheritDoc} + */ + function sql_column_change($table_name, $column_name, $column_data, $inline = false) + { + $column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data); + $statements = array(); + + // We need the data here + $old_return_statements = $this->return_statements; + $this->return_statements = true; + + $indexes = $this->get_existing_indexes($table_name, $column_name); + $unique_indexes = $this->get_existing_indexes($table_name, $column_name, true); + + // Drop any indexes + if (!empty($indexes) || !empty($unique_indexes)) + { + $drop_indexes = array_merge(array_keys($indexes), array_keys($unique_indexes)); + foreach ($drop_indexes as $index_name) + { + $result = $this->sql_index_drop($table_name, $index_name); + $statements = array_merge($statements, $result); + } + } + + // Drop default value constraint + $result = $this->mssql_get_drop_default_constraints_queries($table_name, $column_name); + $statements = array_merge($statements, $result); + + // Change the column + $statements[] = 'ALTER TABLE [' . $table_name . '] ALTER COLUMN [' . $column_name . '] ' . $column_data['column_type_sql']; + + if (!empty($column_data['default'])) + { + // Add new default value constraint + $statements[] = 'ALTER TABLE [' . $table_name . '] ADD CONSTRAINT [DF_' . $table_name . '_' . $column_name . '_1] ' . $this->db->sql_escape($column_data['default']) . ' FOR [' . $column_name . ']'; + } + + if (!empty($indexes)) + { + // Recreate indexes after we changed the column + foreach ($indexes as $index_name => $index_data) + { + $result = $this->sql_create_index($table_name, $index_name, $index_data); + $statements = array_merge($statements, $result); + } + } + + if (!empty($unique_indexes)) + { + // Recreate unique indexes after we changed the column + foreach ($unique_indexes as $index_name => $index_data) + { + $result = $this->sql_create_unique_index($table_name, $index_name, $index_data); + $statements = array_merge($statements, $result); + } + } + + $this->return_statements = $old_return_statements; + + return $this->_sql_run_sql($statements); + } + + /** + * Get queries to drop the default constraints of a column + * + * We need to drop the default constraints of a column, + * before being able to change their type or deleting them. + * + * @param string $table_name + * @param string $column_name + * @return array Array with SQL statements + */ + protected function mssql_get_drop_default_constraints_queries($table_name, $column_name) + { + $statements = array(); + if ($this->mssql_is_sql_server_2000()) + { + // http://msdn.microsoft.com/en-us/library/aa175912%28v=sql.80%29.aspx + // Deprecated in SQL Server 2005 + $sql = "SELECT so.name AS def_name + FROM sysobjects so + JOIN sysconstraints sc ON so.id = sc.constid + WHERE object_name(so.parent_obj) = '{$table_name}' + AND so.xtype = 'D' + AND sc.colid = (SELECT colid FROM syscolumns + WHERE id = object_id('{$table_name}') + AND name = '{$column_name}')"; + } + else + { + $sql = "SELECT dobj.name AS def_name + FROM sys.columns col + LEFT OUTER JOIN sys.objects dobj ON (dobj.object_id = col.default_object_id AND dobj.type = 'D') + WHERE col.object_id = object_id('{$table_name}') + AND col.name = '{$column_name}' + AND dobj.name IS NOT NULL"; + } + + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $statements[] = 'ALTER TABLE [' . $table_name . '] DROP CONSTRAINT [' . $row['def_name'] . ']'; + } + $this->db->sql_freeresult($result); + + return $statements; + } + + /** + * Get a list with existing indexes for the column + * + * @param string $table_name + * @param string $column_name + * @param bool $unique Should we get unique indexes or normal ones + * @return array Array with Index name => columns + */ + public function get_existing_indexes($table_name, $column_name, $unique = false) + { + $existing_indexes = array(); + if ($this->mssql_is_sql_server_2000()) + { + // http://msdn.microsoft.com/en-us/library/aa175912%28v=sql.80%29.aspx + // Deprecated in SQL Server 2005 + $sql = "SELECT DISTINCT ix.name AS phpbb_index_name + FROM sysindexes ix + INNER JOIN sysindexkeys ixc + ON ixc.id = ix.id + AND ixc.indid = ix.indid + INNER JOIN syscolumns cols + ON cols.colid = ixc.colid + AND cols.id = ix.id + WHERE ix.id = object_id('{$table_name}') + AND cols.name = '{$column_name}' + AND INDEXPROPERTY(ix.id, ix.name, 'IsUnique') = " . ($unique ? '1' : '0'); + } + else + { + $sql = "SELECT DISTINCT ix.name AS phpbb_index_name + FROM sys.indexes ix + INNER JOIN sys.index_columns ixc + ON ixc.object_id = ix.object_id + AND ixc.index_id = ix.index_id + INNER JOIN sys.columns cols + ON cols.column_id = ixc.column_id + AND cols.object_id = ix.object_id + WHERE ix.object_id = object_id('{$table_name}') + AND cols.name = '{$column_name}' + AND ix.is_unique = " . ($unique ? '1' : '0'); + } + + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if (!isset($row['is_unique']) || ($unique && $row['is_unique'] == 'UNIQUE') || (!$unique && $row['is_unique'] == 'NONUNIQUE')) + { + $existing_indexes[$row['phpbb_index_name']] = array(); + } + } + $this->db->sql_freeresult($result); + + if (empty($existing_indexes)) + { + return array(); + } + + if ($this->mssql_is_sql_server_2000()) + { + $sql = "SELECT DISTINCT ix.name AS phpbb_index_name, cols.name AS phpbb_column_name + FROM sysindexes ix + INNER JOIN sysindexkeys ixc + ON ixc.id = ix.id + AND ixc.indid = ix.indid + INNER JOIN syscolumns cols + ON cols.colid = ixc.colid + AND cols.id = ix.id + WHERE ix.id = object_id('{$table_name}') + AND " . $this->db->sql_in_set('ix.name', array_keys($existing_indexes)); + } + else + { + $sql = "SELECT DISTINCT ix.name AS phpbb_index_name, cols.name AS phpbb_column_name + FROM sys.indexes ix + INNER JOIN sys.index_columns ixc + ON ixc.object_id = ix.object_id + AND ixc.index_id = ix.index_id + INNER JOIN sys.columns cols + ON cols.column_id = ixc.column_id + AND cols.object_id = ix.object_id + WHERE ix.object_id = object_id('{$table_name}') + AND " . $this->db->sql_in_set('ix.name', array_keys($existing_indexes)); + } + + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $existing_indexes[$row['phpbb_index_name']][] = $row['phpbb_column_name']; + } + $this->db->sql_freeresult($result); + + return $existing_indexes; + } + + /** + * Is the used MS SQL Server a SQL Server 2000? + * + * @return bool + */ + protected function mssql_is_sql_server_2000() + { + if ($this->is_sql_server_2000 === null) + { + $sql = "SELECT CAST(SERVERPROPERTY('productversion') AS VARCHAR(25)) AS mssql_version"; + $result = $this->db->sql_query($sql); + $properties = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + $this->is_sql_server_2000 = $properties['mssql_version'][0] == '8'; + } + + return $this->is_sql_server_2000; + } + +} diff --git a/phpBB/phpbb/db/tools/tools.php b/phpBB/phpbb/db/tools/tools.php index 152ec6b00f..f05c690e5f 100644 --- a/phpBB/phpbb/db/tools/tools.php +++ b/phpBB/phpbb/db/tools/tools.php @@ -35,12 +35,6 @@ class tools implements tools_interface */ var $dbms_type_map = array(); - /** - * Is the used MS SQL Server a SQL Server 2000? - * @var bool - */ - protected $is_sql_server_2000; - /** * Get the column types for every database we support * @@ -109,66 +103,6 @@ class tools implements tools_interface 'VARBINARY' => 'varbinary(255)', ), - 'mssql' => array( - 'INT:' => '[int]', - 'BINT' => '[float]', - 'UINT' => '[int]', - 'UINT:' => '[int]', - 'TINT:' => '[int]', - 'USINT' => '[int]', - 'BOOL' => '[int]', - 'VCHAR' => '[varchar] (255)', - 'VCHAR:' => '[varchar] (%d)', - 'CHAR:' => '[char] (%d)', - 'XSTEXT' => '[varchar] (1000)', - 'STEXT' => '[varchar] (3000)', - 'TEXT' => '[varchar] (8000)', - 'MTEXT' => '[text]', - 'XSTEXT_UNI'=> '[varchar] (100)', - 'STEXT_UNI' => '[varchar] (255)', - 'TEXT_UNI' => '[varchar] (4000)', - 'MTEXT_UNI' => '[text]', - 'TIMESTAMP' => '[int]', - 'DECIMAL' => '[float]', - 'DECIMAL:' => '[float]', - 'PDECIMAL' => '[float]', - 'PDECIMAL:' => '[float]', - 'VCHAR_UNI' => '[varchar] (255)', - 'VCHAR_UNI:'=> '[varchar] (%d)', - 'VCHAR_CI' => '[varchar] (255)', - 'VARBINARY' => '[varchar] (255)', - ), - - 'mssqlnative' => array( - 'INT:' => '[int]', - 'BINT' => '[float]', - 'UINT' => '[int]', - 'UINT:' => '[int]', - 'TINT:' => '[int]', - 'USINT' => '[int]', - 'BOOL' => '[int]', - 'VCHAR' => '[varchar] (255)', - 'VCHAR:' => '[varchar] (%d)', - 'CHAR:' => '[char] (%d)', - 'XSTEXT' => '[varchar] (1000)', - 'STEXT' => '[varchar] (3000)', - 'TEXT' => '[varchar] (8000)', - 'MTEXT' => '[text]', - 'XSTEXT_UNI'=> '[varchar] (100)', - 'STEXT_UNI' => '[varchar] (255)', - 'TEXT_UNI' => '[varchar] (4000)', - 'MTEXT_UNI' => '[text]', - 'TIMESTAMP' => '[int]', - 'DECIMAL' => '[float]', - 'DECIMAL:' => '[float]', - 'PDECIMAL' => '[float]', - 'PDECIMAL:' => '[float]', - 'VCHAR_UNI' => '[varchar] (255)', - 'VCHAR_UNI:'=> '[varchar] (%d)', - 'VCHAR_CI' => '[varchar] (255)', - 'VARBINARY' => '[varchar] (255)', - ), - 'oracle' => array( 'INT:' => 'number(%d)', 'BINT' => 'number(20)', @@ -297,12 +231,6 @@ class tools implements tools_interface */ var $unsigned_types = array('UINT', 'UINT:', 'USINT', 'BOOL', 'TIMESTAMP'); - /** - * A list of supported DBMS. We change this class to support more DBMS, the DBMS itself only need to follow some rules. - * @var array - */ - var $supported_dbms = array('mssql', 'mssqlnative', 'mysql_40', 'mysql_41', 'oracle', 'postgres', 'sqlite', 'sqlite3'); - /** * This is set to true if user only wants to return the 'to-be-executed' SQL statement(s) (as an array). * This mode has no effect on some methods (inserting of data for example). This is expressed within the methods command. @@ -344,15 +272,6 @@ class tools implements tools_interface $this->sql_layer = 'mysql_41'; break; - case 'mssql': - case 'mssql_odbc': - $this->sql_layer = 'mssql'; - break; - - case 'mssqlnative': - $this->sql_layer = 'mssqlnative'; - break; - default: $this->sql_layer = $this->db->get_sql_layer(); break; @@ -396,14 +315,6 @@ class tools implements tools_interface AND name <> "sqlite_sequence"'; break; - case 'mssql': - case 'mssql_odbc': - case 'mssqlnative': - $sql = "SELECT name - FROM sysobjects - WHERE type='U'"; - break; - case 'postgres': $sql = 'SELECT relname FROM pg_stat_user_tables'; @@ -469,26 +380,7 @@ class tools implements tools_interface $create_sequence = false; // Begin table sql statement - switch ($this->sql_layer) - { - case 'mssql': - case 'mssqlnative': - $table_sql = 'CREATE TABLE [' . $table_name . '] (' . "\n"; - break; - - default: - $table_sql = 'CREATE TABLE ' . $table_name . ' (' . "\n"; - break; - } - - if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative') - { - if (!isset($table_data['PRIMARY_KEY'])) - { - $table_data['COLUMNS']['mssqlindex'] = array('UINT', null, 'auto_increment'); - $table_data['PRIMARY_KEY'] = 'mssqlindex'; - } - } + $table_sql = 'CREATE TABLE ' . $table_name . ' (' . "\n"; // Iterate through the columns to create a table foreach ($table_data['COLUMNS'] as $column_name => $column_data) @@ -502,17 +394,7 @@ class tools implements tools_interface } // here we add the definition of the new column to the list of columns - switch ($this->sql_layer) - { - case 'mssql': - case 'mssqlnative': - $columns[] = "\t [{$column_name}] " . $prepared_column['column_type_sql_default']; - break; - - default: - $columns[] = "\t {$column_name} " . $prepared_column['column_type_sql']; - break; - } + $columns[] = "\t {$column_name} " . $prepared_column['column_type_sql']; // see if we have found a primary key set due to a column definition if we have found it, we can stop looking if (!$primary_key_gen) @@ -530,16 +412,6 @@ class tools implements tools_interface // this makes up all the columns in the create table statement $table_sql .= implode(",\n", $columns); - // Close the table for two DBMS and add to the statements - switch ($this->sql_layer) - { - case 'mssql': - case 'mssqlnative': - $table_sql .= "\n);"; - $statements[] = $table_sql; - break; - } - // we have yet to create a primary key for this table, // this means that we can add the one we really wanted instead if (!$primary_key_gen) @@ -562,21 +434,6 @@ class tools implements tools_interface $table_sql .= ",\n\t PRIMARY KEY (" . implode(', ', $table_data['PRIMARY_KEY']) . ')'; break; - case 'mssql': - case 'mssqlnative': - // We need the data here - $old_return_statements = $this->return_statements; - $this->return_statements = true; - - $primary_key_stmts = $this->sql_create_primary_key($table_name, $table_data['PRIMARY_KEY']); - foreach ($primary_key_stmts as $pk_stmt) - { - $statements[] = $pk_stmt; - } - - $this->return_statements = $old_return_statements; - break; - case 'oracle': $table_sql .= ",\n\t CONSTRAINT pk_{$table_name} PRIMARY KEY (" . implode(', ', $table_data['PRIMARY_KEY']) . ')'; break; @@ -1073,16 +930,6 @@ class tools implements tools_interface AND a.attrelid = c.oid"; break; - // same deal with PostgreSQL, we must perform more complex operations than - // we technically could - case 'mssql': - case 'mssqlnative': - $sql = "SELECT c.name - FROM syscolumns c - LEFT JOIN sysobjects o ON c.id = o.id - WHERE o.name = '{$table_name}'"; - break; - case 'oracle': $sql = "SELECT column_name FROM user_tab_columns @@ -1154,27 +1001,6 @@ class tools implements tools_interface */ function sql_index_exists($table_name, $index_name) { - if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative') - { - $sql = "EXEC sp_statistics '$table_name'"; - $result = $this->db->sql_query($sql); - - while ($row = $this->db->sql_fetchrow($result)) - { - if ($row['TYPE'] == 3) - { - if (strtolower($row['INDEX_NAME']) == strtolower($index_name)) - { - $this->db->sql_freeresult($result); - return true; - } - } - } - $this->db->sql_freeresult($result); - - return false; - } - switch ($this->sql_layer) { case 'postgres': @@ -1246,27 +1072,6 @@ class tools implements tools_interface */ function sql_unique_index_exists($table_name, $index_name) { - if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative') - { - $sql = "EXEC sp_statistics '$table_name'"; - $result = $this->db->sql_query($sql); - - while ($row = $this->db->sql_fetchrow($result)) - { - // Usually NON_UNIQUE is the column we want to check, but we allow for both - if ($row['TYPE'] == 3) - { - if (strtolower($row['INDEX_NAME']) == strtolower($index_name)) - { - $this->db->sql_freeresult($result); - return true; - } - } - } - $this->db->sql_freeresult($result); - return false; - } - switch ($this->sql_layer) { case 'postgres': @@ -1410,50 +1215,6 @@ class tools implements tools_interface switch ($this->sql_layer) { - case 'mssql': - case 'mssqlnative': - $sql .= " {$column_type} "; - $sql_default = " {$column_type} "; - - // For adding columns we need the default definition - if (!is_null($column_data[1])) - { - // For hexadecimal values do not use single quotes - if (strpos($column_data[1], '0x') === 0) - { - $return_array['default'] = 'DEFAULT (' . $column_data[1] . ') '; - $sql_default .= $return_array['default']; - } - else - { - $return_array['default'] = 'DEFAULT (' . ((is_numeric($column_data[1])) ? $column_data[1] : "'{$column_data[1]}'") . ') '; - $sql_default .= $return_array['default']; - } - } - - if (isset($column_data[2]) && $column_data[2] == 'auto_increment') - { -// $sql .= 'IDENTITY (1, 1) '; - $sql_default .= 'IDENTITY (1, 1) '; - } - - $return_array['textimage'] = $column_type === '[text]'; - - if (!is_null($column_data[1]) || (isset($column_data[2]) && $column_data[2] == 'auto_increment')) - { - $sql .= 'NOT NULL'; - $sql_default .= 'NOT NULL'; - } - else - { - $sql .= 'NULL'; - $sql_default .= 'NULL'; - } - - $return_array['column_type_sql_default'] = $sql_default; - - break; - case 'mysql_40': case 'mysql_41': $sql .= " {$column_type} "; @@ -1653,12 +1414,6 @@ class tools implements tools_interface switch ($this->sql_layer) { - case 'mssql': - case 'mssqlnative': - // Does not support AFTER, only through temporary table - $statements[] = 'ALTER TABLE [' . $table_name . '] ADD [' . $column_name . '] ' . $column_data['column_type_sql_default']; - break; - case 'mysql_40': case 'mysql_41': $after = (!empty($column_data['after'])) ? ' AFTER ' . $column_data['after'] : ''; @@ -1770,51 +1525,6 @@ class tools implements tools_interface switch ($this->sql_layer) { - case 'mssql': - case 'mssqlnative': - // We need the data here - $old_return_statements = $this->return_statements; - $this->return_statements = true; - - $indexes = $this->get_existing_indexes($table_name, $column_name); - $indexes = array_merge($indexes, $this->get_existing_indexes($table_name, $column_name, true)); - - // Drop any indexes - $recreate_indexes = array(); - if (!empty($indexes)) - { - foreach ($indexes as $index_name => $index_data) - { - $result = $this->sql_index_drop($table_name, $index_name); - $statements = array_merge($statements, $result); - if (sizeof($index_data) > 1) - { - // Remove this column from the index and recreate it - $recreate_indexes[$index_name] = array_diff($index_data, array($column_name)); - } - } - } - - // Drop default value constraint - $result = $this->mssql_get_drop_default_constraints_queries($table_name, $column_name); - $statements = array_merge($statements, $result); - - // Remove the column - $statements[] = 'ALTER TABLE [' . $table_name . '] DROP COLUMN [' . $column_name . ']'; - - if (!empty($recreate_indexes)) - { - // Recreate indexes after we removed the column - foreach ($recreate_indexes as $index_name => $index_data) - { - $result = $this->sql_create_index($table_name, $index_name, $index_data); - $statements = array_merge($statements, $result); - } - } - - $this->return_statements = $old_return_statements; - break; - case 'mysql_40': case 'mysql_41': $statements[] = 'ALTER TABLE `' . $table_name . '` DROP COLUMN `' . $column_name . '`'; @@ -1899,11 +1609,6 @@ class tools implements tools_interface switch ($this->sql_layer) { - case 'mssql': - case 'mssqlnative': - $statements[] = 'DROP INDEX ' . $table_name . '.' . $index_name; - break; - case 'mysql_40': case 'mysql_41': $statements[] = 'DROP INDEX ' . $index_name . ' ON ' . $table_name; @@ -1988,16 +1693,6 @@ class tools implements tools_interface $statements[] = 'ALTER TABLE ' . $table_name . ' ADD PRIMARY KEY (' . implode(', ', $column) . ')'; break; - case 'mssql': - case 'mssqlnative': - $sql = "ALTER TABLE [{$table_name}] WITH NOCHECK ADD "; - $sql .= "CONSTRAINT [PK_{$table_name}] PRIMARY KEY CLUSTERED ("; - $sql .= '[' . implode("],\n\t\t[", $column) . ']'; - $sql .= ')'; - - $statements[] = $sql; - break; - case 'oracle': $statements[] = 'ALTER TABLE ' . $table_name . ' add CONSTRAINT pk_' . $table_name . ' PRIMARY KEY (' . implode(', ', $column) . ')'; break; @@ -2064,12 +1759,7 @@ class tools implements tools_interface { $statements = array(); - $table_prefix = substr(CONFIG_TABLE, 0, -6); // strlen(config) - if (strlen($table_name . '_' . $index_name) - strlen($table_prefix) > 24) - { - $max_length = strlen($table_prefix) + 24; - trigger_error("Index name '{$table_name}_$index_name' on table '$table_name' is too long. The maximum is $max_length characters.", E_USER_ERROR); - } + $this->check_index_name_length($table_name, $index_name); switch ($this->sql_layer) { @@ -2084,11 +1774,6 @@ class tools implements tools_interface case 'mysql_41': $statements[] = 'ALTER TABLE ' . $table_name . ' ADD UNIQUE INDEX ' . $index_name . '(' . implode(', ', $column) . ')'; break; - - case 'mssql': - case 'mssqlnative': - $statements[] = 'CREATE UNIQUE INDEX [' . $index_name . '] ON [' . $table_name . ']([' . implode('], [', $column) . '])'; - break; } return $this->_sql_run_sql($statements); @@ -2101,12 +1786,7 @@ class tools implements tools_interface { $statements = array(); - $table_prefix = substr(CONFIG_TABLE, 0, -6); // strlen(config) - if (strlen($table_name . $index_name) - strlen($table_prefix) > 24) - { - $max_length = strlen($table_prefix) + 24; - trigger_error("Index name '{$table_name}_$index_name' on table '$table_name' is too long. The maximum is $max_length characters.", E_USER_ERROR); - } + $this->check_index_name_length($table_name, $index_name); // remove index length unless MySQL4 if ('mysql_40' != $this->sql_layer) @@ -2137,16 +1817,27 @@ class tools implements tools_interface case 'mysql_41': $statements[] = 'ALTER TABLE ' . $table_name . ' ADD INDEX ' . $index_name . ' (' . implode(', ', $column) . ')'; break; - - case 'mssql': - case 'mssqlnative': - $statements[] = 'CREATE INDEX [' . $index_name . '] ON [' . $table_name . ']([' . implode('], [', $column) . '])'; - break; } return $this->_sql_run_sql($statements); } + /** + * Check whether the index name is too long + * + * @param string $table_name + * @param string $index_name + */ + protected function check_index_name_length($table_name, $index_name) + { + $table_prefix = substr(CONFIG_TABLE, 0, -6); // strlen(config) + if (strlen($table_name . $index_name) - strlen($table_prefix) > 24) + { + $max_length = strlen($table_prefix) + 24; + trigger_error("Index name '{$table_name}_$index_name' on table '$table_name' is too long. The maximum is $max_length characters.", E_USER_ERROR); + } + } + /** * {@inheritDoc} */ @@ -2154,79 +1845,63 @@ class tools implements tools_interface { $index_array = array(); - if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative') - { - $sql = "EXEC sp_statistics '$table_name'"; - $result = $this->db->sql_query($sql); - while ($row = $this->db->sql_fetchrow($result)) - { - if ($row['TYPE'] == 3) - { - $index_array[] = $row['INDEX_NAME']; - } - } - $this->db->sql_freeresult($result); - } - else + switch ($this->sql_layer) { - switch ($this->sql_layer) - { - case 'postgres': - $sql = "SELECT ic.relname as index_name - FROM pg_class bc, pg_class ic, pg_index i - WHERE (bc.oid = i.indrelid) - AND (ic.oid = i.indexrelid) - AND (bc.relname = '" . $table_name . "') - AND (i.indisunique != 't') - AND (i.indisprimary != 't')"; - $col = 'index_name'; + case 'postgres': + $sql = "SELECT ic.relname as index_name + FROM pg_class bc, pg_class ic, pg_index i + WHERE (bc.oid = i.indrelid) + AND (ic.oid = i.indexrelid) + AND (bc.relname = '" . $table_name . "') + AND (i.indisunique != 't') + AND (i.indisprimary != 't')"; + $col = 'index_name'; break; - case 'mysql_40': - case 'mysql_41': - $sql = 'SHOW KEYS - FROM ' . $table_name; - $col = 'Key_name'; + case 'mysql_40': + case 'mysql_41': + $sql = 'SHOW KEYS + FROM ' . $table_name; + $col = 'Key_name'; break; - case 'oracle': - $sql = "SELECT index_name - FROM user_indexes - WHERE table_name = '" . strtoupper($table_name) . "' - AND generated = 'N' - AND uniqueness = 'NONUNIQUE'"; - $col = 'index_name'; + case 'oracle': + $sql = "SELECT index_name + FROM user_indexes + WHERE table_name = '" . strtoupper($table_name) . "' + AND generated = 'N' + AND uniqueness = 'NONUNIQUE'"; + $col = 'index_name'; break; - case 'sqlite': - case 'sqlite3': - $sql = "PRAGMA index_info('" . $table_name . "');"; - $col = 'name'; + case 'sqlite': + case 'sqlite3': + $sql = "PRAGMA index_info('" . $table_name . "');"; + $col = 'name'; break; - } + } - $result = $this->db->sql_query($sql); - while ($row = $this->db->sql_fetchrow($result)) + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if (($this->sql_layer == 'mysql_40' || $this->sql_layer == 'mysql_41') && !$row['Non_unique']) { - if (($this->sql_layer == 'mysql_40' || $this->sql_layer == 'mysql_41') && !$row['Non_unique']) - { - continue; - } + continue; + } - switch ($this->sql_layer) - { - case 'oracle': - case 'postgres': - case 'sqlite': - case 'sqlite3': - $row[$col] = substr($row[$col], strlen($table_name) + 1); + switch ($this->sql_layer) + { + case 'oracle': + case 'postgres': + case 'sqlite': + case 'sqlite3': + $row[$col] = substr($row[$col], strlen($table_name) + 1); break; - } - - $index_array[] = $row[$col]; } - $this->db->sql_freeresult($result); + + $index_array[] = $row[$col]; } + $this->db->sql_freeresult($result); return array_map('strtolower', $index_array); } @@ -2254,62 +1929,6 @@ class tools implements tools_interface switch ($this->sql_layer) { - case 'mssql': - case 'mssqlnative': - // We need the data here - $old_return_statements = $this->return_statements; - $this->return_statements = true; - - $indexes = $this->get_existing_indexes($table_name, $column_name); - $unique_indexes = $this->get_existing_indexes($table_name, $column_name, true); - - // Drop any indexes - if (!empty($indexes) || !empty($unique_indexes)) - { - $drop_indexes = array_merge(array_keys($indexes), array_keys($unique_indexes)); - foreach ($drop_indexes as $index_name) - { - $result = $this->sql_index_drop($table_name, $index_name); - $statements = array_merge($statements, $result); - } - } - - // Drop default value constraint - $result = $this->mssql_get_drop_default_constraints_queries($table_name, $column_name); - $statements = array_merge($statements, $result); - - // Change the column - $statements[] = 'ALTER TABLE [' . $table_name . '] ALTER COLUMN [' . $column_name . '] ' . $column_data['column_type_sql']; - - if (!empty($column_data['default'])) - { - // Add new default value constraint - $statements[] = 'ALTER TABLE [' . $table_name . '] ADD CONSTRAINT [DF_' . $table_name . '_' . $column_name . '_1] ' . $this->db->sql_escape($column_data['default']) . ' FOR [' . $column_name . ']'; - } - - if (!empty($indexes)) - { - // Recreate indexes after we changed the column - foreach ($indexes as $index_name => $index_data) - { - $result = $this->sql_create_index($table_name, $index_name, $index_data); - $statements = array_merge($statements, $result); - } - } - - if (!empty($unique_indexes)) - { - // Recreate unique indexes after we changed the column - foreach ($unique_indexes as $index_name => $index_data) - { - $result = $this->sql_create_unique_index($table_name, $index_name, $index_data); - $statements = array_merge($statements, $result); - } - } - - $this->return_statements = $old_return_statements; - break; - case 'mysql_40': case 'mysql_41': $statements[] = 'ALTER TABLE `' . $table_name . '` CHANGE `' . $column_name . '` `' . $column_name . '` ' . $column_data['column_type_sql']; @@ -2511,52 +2130,6 @@ class tools implements tools_interface return $this->_sql_run_sql($statements); } - /** - * Get queries to drop the default constraints of a column - * - * We need to drop the default constraints of a column, - * before being able to change their type or deleting them. - * - * @param string $table_name - * @param string $column_name - * @return array Array with SQL statements - */ - protected function mssql_get_drop_default_constraints_queries($table_name, $column_name) - { - $statements = array(); - if ($this->mssql_is_sql_server_2000()) - { - // http://msdn.microsoft.com/en-us/library/aa175912%28v=sql.80%29.aspx - // Deprecated in SQL Server 2005 - $sql = "SELECT so.name AS def_name - FROM sysobjects so - JOIN sysconstraints sc ON so.id = sc.constid - WHERE object_name(so.parent_obj) = '{$table_name}' - AND so.xtype = 'D' - AND sc.colid = (SELECT colid FROM syscolumns - WHERE id = object_id('{$table_name}') - AND name = '{$column_name}')"; - } - else - { - $sql = "SELECT dobj.name AS def_name - FROM sys.columns col - LEFT OUTER JOIN sys.objects dobj ON (dobj.object_id = col.default_object_id AND dobj.type = 'D') - WHERE col.object_id = object_id('{$table_name}') - AND col.name = '{$column_name}' - AND dobj.name IS NOT NULL"; - } - - $result = $this->db->sql_query($sql); - while ($row = $this->db->sql_fetchrow($result)) - { - $statements[] = 'ALTER TABLE [' . $table_name . '] DROP CONSTRAINT [' . $row['def_name'] . ']'; - } - $this->db->sql_freeresult($result); - - return $statements; - } - /** * Get a list with existing indexes for the column * @@ -2584,40 +2157,6 @@ class tools implements tools_interface switch ($this->sql_layer) { - case 'mssql': - case 'mssqlnative': - if ($this->mssql_is_sql_server_2000()) - { - // http://msdn.microsoft.com/en-us/library/aa175912%28v=sql.80%29.aspx - // Deprecated in SQL Server 2005 - $sql = "SELECT DISTINCT ix.name AS phpbb_index_name - FROM sysindexes ix - INNER JOIN sysindexkeys ixc - ON ixc.id = ix.id - AND ixc.indid = ix.indid - INNER JOIN syscolumns cols - ON cols.colid = ixc.colid - AND cols.id = ix.id - WHERE ix.id = object_id('{$table_name}') - AND cols.name = '{$column_name}' - AND INDEXPROPERTY(ix.id, ix.name, 'IsUnique') = " . ($unique ? '1' : '0'); - } - else - { - $sql = "SELECT DISTINCT ix.name AS phpbb_index_name - FROM sys.indexes ix - INNER JOIN sys.index_columns ixc - ON ixc.object_id = ix.object_id - AND ixc.index_id = ix.index_id - INNER JOIN sys.columns cols - ON cols.column_id = ixc.column_id - AND cols.object_id = ix.object_id - WHERE ix.object_id = object_id('{$table_name}') - AND cols.name = '{$column_name}' - AND ix.is_unique = " . ($unique ? '1' : '0'); - } - break; - case 'oracle': $sql = "SELECT ix.index_name AS phpbb_index_name, ix.uniqueness AS is_unique FROM all_ind_columns ixc, all_indexes ix @@ -2644,36 +2183,6 @@ class tools implements tools_interface switch ($this->sql_layer) { - case 'mssql': - case 'mssqlnative': - if ($this->mssql_is_sql_server_2000()) - { - $sql = "SELECT DISTINCT ix.name AS phpbb_index_name, cols.name AS phpbb_column_name - FROM sysindexes ix - INNER JOIN sysindexkeys ixc - ON ixc.id = ix.id - AND ixc.indid = ix.indid - INNER JOIN syscolumns cols - ON cols.colid = ixc.colid - AND cols.id = ix.id - WHERE ix.id = object_id('{$table_name}') - AND " . $this->db->sql_in_set('ix.name', array_keys($existing_indexes)); - } - else - { - $sql = "SELECT DISTINCT ix.name AS phpbb_index_name, cols.name AS phpbb_column_name - FROM sys.indexes ix - INNER JOIN sys.index_columns ixc - ON ixc.object_id = ix.object_id - AND ixc.index_id = ix.index_id - INNER JOIN sys.columns cols - ON cols.column_id = ixc.column_id - AND cols.object_id = ix.object_id - WHERE ix.object_id = object_id('{$table_name}') - AND " . $this->db->sql_in_set('ix.name', array_keys($existing_indexes)); - } - break; - case 'oracle': $sql = "SELECT index_name AS phpbb_index_name, column_name AS phpbb_column_name FROM all_ind_columns @@ -2692,25 +2201,6 @@ class tools implements tools_interface return $existing_indexes; } - /** - * Is the used MS SQL Server a SQL Server 2000? - * - * @return bool - */ - protected function mssql_is_sql_server_2000() - { - if ($this->is_sql_server_2000 === null) - { - $sql = "SELECT CAST(SERVERPROPERTY('productversion') AS VARCHAR(25)) AS mssql_version"; - $result = $this->db->sql_query($sql); - $properties = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - $this->is_sql_server_2000 = $properties['mssql_version'][0] == '8'; - } - - return $this->is_sql_server_2000; - } - /** * Returns the Queries which are required to recreate a table including indexes * -- cgit v1.2.1 From 74d1b1812f512ae232a063a2574d62d3f5491f46 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Sat, 24 Jan 2015 21:35:17 +0100 Subject: [ticket/10748] Overwrite the type map correctly PHPBB3-10748 --- phpBB/phpbb/db/tools/mssql.php | 4 +++- phpBB/phpbb/db/tools/tools.php | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'phpBB/phpbb/db/tools') diff --git a/phpBB/phpbb/db/tools/mssql.php b/phpBB/phpbb/db/tools/mssql.php index b4a09143eb..3a7ac6185d 100644 --- a/phpBB/phpbb/db/tools/mssql.php +++ b/phpBB/phpbb/db/tools/mssql.php @@ -117,6 +117,8 @@ class mssql extends tools $this->sql_layer = 'mssqlnative'; break; } + + $this->dbms_type_map = self::get_dbms_type_map(); } /** @@ -344,7 +346,7 @@ class mssql extends tools } // Get type - list($column_type, $orig_column_type) = $this->get_column_type($column_data[0]); + list($column_type, ) = $this->get_column_type($column_data[0]); // Adjust default value if db-dependent specified if (is_array($column_data[1])) diff --git a/phpBB/phpbb/db/tools/tools.php b/phpBB/phpbb/db/tools/tools.php index f05c690e5f..d2bad21a3a 100644 --- a/phpBB/phpbb/db/tools/tools.php +++ b/phpBB/phpbb/db/tools/tools.php @@ -1354,6 +1354,7 @@ class tools implements tools_interface */ function get_column_type($column_map_type) { + $column_type = ''; if (strpos($column_map_type, ':') !== false) { list($orig_column_type, $column_length) = explode(':', $column_map_type); -- cgit v1.2.1 From ec1fb0423dbc0ee64f06e419657e62e4475fe2e9 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Fri, 13 Feb 2015 22:46:18 +0100 Subject: [ticket/10748] Split postgres DB tools into it's own class PHPBB3-10748 --- phpBB/phpbb/db/tools/factory.php | 4 + phpBB/phpbb/db/tools/mssql.php | 40 +-- phpBB/phpbb/db/tools/postgres.php | 613 ++++++++++++++++++++++++++++++++++++++ phpBB/phpbb/db/tools/tools.php | 257 ---------------- 4 files changed, 637 insertions(+), 277 deletions(-) create mode 100644 phpBB/phpbb/db/tools/postgres.php (limited to 'phpBB/phpbb/db/tools') diff --git a/phpBB/phpbb/db/tools/factory.php b/phpBB/phpbb/db/tools/factory.php index 24e2a4d875..d204451a63 100644 --- a/phpBB/phpbb/db/tools/factory.php +++ b/phpBB/phpbb/db/tools/factory.php @@ -29,6 +29,10 @@ class factory { return new \phpbb\db\tools\mssql($db_driver, $return_statements); } + else if ($db_driver instanceof \phpbb\db\driver\postgres) + { + return new \phpbb\db\tools\postgres($db_driver, $return_statements); + } else if ($db_driver instanceof \phpbb\db\driver\driver_interface) { return new \phpbb\db\tools\tools($db_driver, $return_statements); diff --git a/phpBB/phpbb/db/tools/mssql.php b/phpBB/phpbb/db/tools/mssql.php index 3a7ac6185d..6e58171040 100644 --- a/phpBB/phpbb/db/tools/mssql.php +++ b/phpBB/phpbb/db/tools/mssql.php @@ -1,35 +1,35 @@ -* @license GNU General Public License, version 2 (GPL-2.0) -* -* For full copyright and license information, please see -* the docs/CREDITS.txt file. -* -*/ + * + * This file is part of the phpBB Forum Software package. + * + * @copyright (c) phpBB Limited + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ namespace phpbb\db\tools; /** -* Database Tools for handling cross-db actions such as altering columns, etc. -* Currently not supported is returning SQL for creating tables. -*/ + * Database Tools for handling cross-db actions such as altering columns, etc. + * Currently not supported is returning SQL for creating tables. + */ class mssql extends tools { /** - * Is the used MS SQL Server a SQL Server 2000? - * @var bool - */ + * Is the used MS SQL Server a SQL Server 2000? + * @var bool + */ protected $is_sql_server_2000; /** - * Get the column types for every database we support - * - * @return array - */ + * Get the column types for mssql based databases + * + * @return array + */ public static function get_dbms_type_map() { return array( diff --git a/phpBB/phpbb/db/tools/postgres.php b/phpBB/phpbb/db/tools/postgres.php new file mode 100644 index 0000000000..8b61625c3c --- /dev/null +++ b/phpBB/phpbb/db/tools/postgres.php @@ -0,0 +1,613 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\tools; + +/** + * Database Tools for handling cross-db actions such as altering columns, etc. + * Currently not supported is returning SQL for creating tables. + */ +class postgres extends tools +{ + /** + * Get the column types for postgres only + * + * @return array + */ + public static function get_dbms_type_map() + { + return array( + 'postgres' => array( + 'INT:' => 'INT4', + 'BINT' => 'INT8', + 'UINT' => 'INT4', // unsigned + 'UINT:' => 'INT4', // unsigned + 'USINT' => 'INT2', // unsigned + 'BOOL' => 'INT2', // unsigned + 'TINT:' => 'INT2', + 'VCHAR' => 'varchar(255)', + 'VCHAR:' => 'varchar(%d)', + 'CHAR:' => 'char(%d)', + 'XSTEXT' => 'varchar(1000)', + 'STEXT' => 'varchar(3000)', + 'TEXT' => 'varchar(8000)', + 'MTEXT' => 'TEXT', + 'XSTEXT_UNI'=> 'varchar(100)', + 'STEXT_UNI' => 'varchar(255)', + 'TEXT_UNI' => 'varchar(4000)', + 'MTEXT_UNI' => 'TEXT', + 'TIMESTAMP' => 'INT4', // unsigned + 'DECIMAL' => 'decimal(5,2)', + 'DECIMAL:' => 'decimal(%d,2)', + 'PDECIMAL' => 'decimal(6,3)', + 'PDECIMAL:' => 'decimal(%d,3)', + 'VCHAR_UNI' => 'varchar(255)', + 'VCHAR_UNI:'=> 'varchar(%d)', + 'VCHAR_CI' => 'varchar_ci', + 'VARBINARY' => 'bytea', + ), + ); + } + + /** + * Constructor. Set DB Object and set {@link $return_statements return_statements}. + * + * @param \phpbb\db\driver\driver_interface $db Database connection + * @param bool $return_statements True if only statements should be returned and no SQL being executed + */ + public function __construct(\phpbb\db\driver\driver_interface $db, $return_statements = false) + { + parent::__construct($db, $return_statements); + + // Determine mapping database type + $this->sql_layer = 'postgres'; + + $this->dbms_type_map = self::get_dbms_type_map(); + } + + /** + * {@inheritDoc} + */ + function sql_list_tables() + { + $sql = 'SELECT relname + FROM pg_stat_user_tables'; + $result = $this->db->sql_query($sql); + + $tables = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $name = current($row); + $tables[$name] = $name; + } + $this->db->sql_freeresult($result); + + return $tables; + } + + /** + * {@inheritDoc} + */ + function sql_create_table($table_name, $table_data) + { + // holds the DDL for a column + $columns = $statements = array(); + + if ($this->sql_table_exists($table_name)) + { + return $this->_sql_run_sql($statements); + } + + // Begin transaction + $statements[] = 'begin'; + + // Determine if we have created a PRIMARY KEY in the earliest + $primary_key_gen = false; + + // Determine if the table requires a sequence + $create_sequence = false; + + // Begin table sql statement + $table_sql = 'CREATE TABLE ' . $table_name . ' (' . "\n"; + + // Iterate through the columns to create a table + foreach ($table_data['COLUMNS'] as $column_name => $column_data) + { + // here lies an array, filled with information compiled on the column's data + $prepared_column = $this->sql_prepare_column_data($table_name, $column_name, $column_data); + + if (isset($prepared_column['auto_increment']) && $prepared_column['auto_increment'] && strlen($column_name) > 26) // "${column_name}_gen" + { + trigger_error("Index name '${column_name}_gen' on table '$table_name' is too long. The maximum auto increment column length is 26 characters.", E_USER_ERROR); + } + + // here we add the definition of the new column to the list of columns + $columns[] = "\t {$column_name} " . $prepared_column['column_type_sql']; + + // see if we have found a primary key set due to a column definition if we have found it, we can stop looking + if (!$primary_key_gen) + { + $primary_key_gen = isset($prepared_column['primary_key_set']) && $prepared_column['primary_key_set']; + } + + // create sequence DDL based off of the existance of auto incrementing columns + if (!$create_sequence && isset($prepared_column['auto_increment']) && $prepared_column['auto_increment']) + { + $create_sequence = $column_name; + } + } + + // this makes up all the columns in the create table statement + $table_sql .= implode(",\n", $columns); + + // we have yet to create a primary key for this table, + // this means that we can add the one we really wanted instead + if (!$primary_key_gen) + { + // Write primary key + if (isset($table_data['PRIMARY_KEY'])) + { + if (!is_array($table_data['PRIMARY_KEY'])) + { + $table_data['PRIMARY_KEY'] = array($table_data['PRIMARY_KEY']); + } + + $table_sql .= ",\n\t PRIMARY KEY (" . implode(', ', $table_data['PRIMARY_KEY']) . ')'; + } + } + + // do we need to add a sequence for auto incrementing columns? + if ($create_sequence) + { + $statements[] = "CREATE SEQUENCE {$table_name}_seq;"; + } + + // close the table + $table_sql .= "\n);"; + $statements[] = $table_sql; + + // Write Keys + if (isset($table_data['KEYS'])) + { + foreach ($table_data['KEYS'] as $key_name => $key_data) + { + if (!is_array($key_data[1])) + { + $key_data[1] = array($key_data[1]); + } + + $old_return_statements = $this->return_statements; + $this->return_statements = true; + + $key_stmts = ($key_data[0] == 'UNIQUE') ? $this->sql_create_unique_index($table_name, $key_name, $key_data[1]) : $this->sql_create_index($table_name, $key_name, $key_data[1]); + + foreach ($key_stmts as $key_stmt) + { + $statements[] = $key_stmt; + } + + $this->return_statements = $old_return_statements; + } + } + + // Commit Transaction + $statements[] = 'commit'; + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_list_columns($table_name) + { + $columns = array(); + + $sql = "SELECT a.attname + FROM pg_class c, pg_attribute a + WHERE c.relname = '{$table_name}' + AND a.attnum > 0 + AND a.attrelid = c.oid"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $column = strtolower(current($row)); + $columns[$column] = $column; + } + $this->db->sql_freeresult($result); + + return $columns; + } + + /** + * {@inheritDoc} + */ + function sql_index_exists($table_name, $index_name) + { + $sql = "SELECT ic.relname as index_name + FROM pg_class bc, pg_class ic, pg_index i + WHERE (bc.oid = i.indrelid) + AND (ic.oid = i.indexrelid) + AND (bc.relname = '" . $table_name . "') + AND (i.indisunique != 't') + AND (i.indisprimary != 't')"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + // This DBMS prefixes index names with the table name + $row['index_name'] = $this->strip_table_name_from_index_name($table_name, $row['index_name']); + + if (strtolower($row['index_name']) == strtolower($index_name)) + { + $this->db->sql_freeresult($result); + return true; + } + } + $this->db->sql_freeresult($result); + + return false; + } + + /** + * {@inheritDoc} + */ + function sql_unique_index_exists($table_name, $index_name) + { + $sql = "SELECT ic.relname as index_name, i.indisunique + FROM pg_class bc, pg_class ic, pg_index i + WHERE (bc.oid = i.indrelid) + AND (ic.oid = i.indexrelid) + AND (bc.relname = '" . $table_name . "') + AND (i.indisprimary != 't')"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + if ($row['indisunique'] != 't') + { + continue; + } + + // This DBMS prefixes index names with the table name + $row['index_name'] = $this->strip_table_name_from_index_name($table_name, $row['index_name']); + + if (strtolower($row['index_name']) == strtolower($index_name)) + { + $this->db->sql_freeresult($result); + return true; + } + } + $this->db->sql_freeresult($result); + + return false; + } + + /** + * Function to prepare some column information for better usage + * @access private + */ + function sql_prepare_column_data($table_name, $column_name, $column_data) + { + if (strlen($column_name) > 30) + { + trigger_error("Column name '$column_name' on table '$table_name' is too long. The maximum is 30 characters.", E_USER_ERROR); + } + + // Get type + list($column_type, $orig_column_type) = $this->get_column_type($column_data[0]); + + // Adjust default value if db-dependent specified + if (is_array($column_data[1])) + { + $column_data[1] = (isset($column_data[1][$this->sql_layer])) ? $column_data[1][$this->sql_layer] : $column_data[1]['default']; + } + + $sql = " {$column_type} "; + + $return_array = array( + 'column_type' => $column_type, + 'auto_increment' => false, + ); + + if (isset($column_data[2]) && $column_data[2] == 'auto_increment') + { + $default_val = "nextval('{$table_name}_seq')"; + $return_array['auto_increment'] = true; + } + else if (!is_null($column_data[1])) + { + $default_val = "'" . $column_data[1] . "'"; + $return_array['null'] = 'NOT NULL'; + $sql .= 'NOT NULL '; + } + else + { + // Integers need to have 0 instead of empty string as default + if (strpos($column_type, 'INT') === 0) + { + $default_val = '0'; + } + else + { + $default_val = "'" . $column_data[1] . "'"; + } + $return_array['null'] = 'NULL'; + $sql .= 'NULL '; + } + + $return_array['default'] = $default_val; + + $sql .= "DEFAULT {$default_val}"; + + // Unsigned? Then add a CHECK contraint + if (in_array($orig_column_type, $this->unsigned_types)) + { + $return_array['constraint'] = "CHECK ({$column_name} >= 0)"; + $sql .= " CHECK ({$column_name} >= 0)"; + } + + $return_array['column_type_sql'] = $sql; + + return $return_array; + } + + /** + * {@inheritDoc} + */ + function sql_column_add($table_name, $column_name, $column_data, $inline = false) + { + $column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data); + $statements = array(); + + // Does not support AFTER, only through temporary table + if (version_compare($this->db->sql_server_info(true), '8.0', '>=')) + { + $statements[] = 'ALTER TABLE ' . $table_name . ' ADD COLUMN "' . $column_name . '" ' . $column_data['column_type_sql']; + } + else + { + // old versions cannot add columns with default and null information + $statements[] = 'ALTER TABLE ' . $table_name . ' ADD COLUMN "' . $column_name . '" ' . $column_data['column_type'] . ' ' . $column_data['constraint']; + + if (isset($column_data['null'])) + { + if ($column_data['null'] == 'NOT NULL') + { + $statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN ' . $column_name . ' SET NOT NULL'; + } + } + + if (isset($column_data['default'])) + { + $statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN ' . $column_name . ' SET DEFAULT ' . $column_data['default']; + } + } + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_column_remove($table_name, $column_name, $inline = false) + { + $statements = array(); + + $statements[] = 'ALTER TABLE ' . $table_name . ' DROP COLUMN "' . $column_name . '"'; + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_index_drop($table_name, $index_name) + { + $statements = array(); + + $statements[] = 'DROP INDEX ' . $table_name . '_' . $index_name; + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_table_drop($table_name) + { + $statements = array(); + + if (!$this->sql_table_exists($table_name)) + { + return $this->_sql_run_sql($statements); + } + + // the most basic operation, get rid of the table + $statements[] = 'DROP TABLE ' . $table_name; + + // PGSQL does not "tightly" bind sequences and tables, we must guess... + $sql = "SELECT relname + FROM pg_class + WHERE relkind = 'S' + AND relname = '{$table_name}_seq'"; + $result = $this->db->sql_query($sql); + + // We don't even care about storing the results. We already know the answer if we get rows back. + if ($this->db->sql_fetchrow($result)) + { + $statements[] = "DROP SEQUENCE {$table_name}_seq;\n"; + } + $this->db->sql_freeresult($result); + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_create_primary_key($table_name, $column, $inline = false) + { + $statements = array(); + + $statements[] = 'ALTER TABLE ' . $table_name . ' ADD PRIMARY KEY (' . implode(', ', $column) . ')'; + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_create_unique_index($table_name, $index_name, $column) + { + $statements = array(); + + $this->check_index_name_length($table_name, $index_name); + + $statements[] = 'CREATE UNIQUE INDEX ' . $table_name . '_' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')'; + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_create_index($table_name, $index_name, $column) + { + $statements = array(); + + $this->check_index_name_length($table_name, $index_name); + + // remove index length + $column = preg_replace('#:.*$#', '', $column); + + $statements[] = 'CREATE INDEX ' . $table_name . '_' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')'; + + return $this->_sql_run_sql($statements); + } + + + /** + * {@inheritDoc} + */ + function sql_list_index($table_name) + { + $index_array = array(); + + $sql = "SELECT ic.relname as index_name + FROM pg_class bc, pg_class ic, pg_index i + WHERE (bc.oid = i.indrelid) + AND (ic.oid = i.indexrelid) + AND (bc.relname = '" . $table_name . "') + AND (i.indisunique != 't') + AND (i.indisprimary != 't')"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $row['index_name'] = $this->strip_table_name_from_index_name($table_name, $row['index_name']); + + $index_array[] = $row['index_name']; + } + $this->db->sql_freeresult($result); + + return array_map('strtolower', $index_array); + } + + /** + * {@inheritDoc} + */ + function sql_column_change($table_name, $column_name, $column_data, $inline = false) + { + $column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data); + $statements = array(); + + $sql = 'ALTER TABLE ' . $table_name . ' '; + + $sql_array = array(); + $sql_array[] = 'ALTER COLUMN ' . $column_name . ' TYPE ' . $column_data['column_type']; + + if (isset($column_data['null'])) + { + if ($column_data['null'] == 'NOT NULL') + { + $sql_array[] = 'ALTER COLUMN ' . $column_name . ' SET NOT NULL'; + } + else if ($column_data['null'] == 'NULL') + { + $sql_array[] = 'ALTER COLUMN ' . $column_name . ' DROP NOT NULL'; + } + } + + if (isset($column_data['default'])) + { + $sql_array[] = 'ALTER COLUMN ' . $column_name . ' SET DEFAULT ' . $column_data['default']; + } + + // we don't want to double up on constraints if we change different number data types + if (isset($column_data['constraint'])) + { + $constraint_sql = "SELECT consrc as constraint_data + FROM pg_constraint, pg_class bc + WHERE conrelid = bc.oid + AND bc.relname = '{$table_name}' + AND NOT EXISTS ( + SELECT * + FROM pg_constraint as c, pg_inherits as i + WHERE i.inhrelid = pg_constraint.conrelid + AND c.conname = pg_constraint.conname + AND c.consrc = pg_constraint.consrc + AND c.conrelid = i.inhparent + )"; + + $constraint_exists = false; + + $result = $this->db->sql_query($constraint_sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if (trim($row['constraint_data']) == trim($column_data['constraint'])) + { + $constraint_exists = true; + break; + } + } + $this->db->sql_freeresult($result); + + if (!$constraint_exists) + { + $sql_array[] = 'ADD ' . $column_data['constraint']; + } + } + + $sql .= implode(', ', $sql_array); + + $statements[] = $sql; + + return $this->_sql_run_sql($statements); + } + + /** + * Get a list with existing indexes for the column + * + * @param string $table_name + * @param string $column_name + * @param bool $unique Should we get unique indexes or normal ones + * @return array Array with Index name => columns + */ + public function get_existing_indexes($table_name, $column_name, $unique = false) + { + // Not supported + throw new \Exception('DBMS is not supported'); + } +} diff --git a/phpBB/phpbb/db/tools/tools.php b/phpBB/phpbb/db/tools/tools.php index d2bad21a3a..0d1eb63c47 100644 --- a/phpBB/phpbb/db/tools/tools.php +++ b/phpBB/phpbb/db/tools/tools.php @@ -192,36 +192,6 @@ class tools implements tools_interface 'VCHAR_CI' => 'VARCHAR(255)', 'VARBINARY' => 'BLOB', ), - - 'postgres' => array( - 'INT:' => 'INT4', - 'BINT' => 'INT8', - 'UINT' => 'INT4', // unsigned - 'UINT:' => 'INT4', // unsigned - 'USINT' => 'INT2', // unsigned - 'BOOL' => 'INT2', // unsigned - 'TINT:' => 'INT2', - 'VCHAR' => 'varchar(255)', - 'VCHAR:' => 'varchar(%d)', - 'CHAR:' => 'char(%d)', - 'XSTEXT' => 'varchar(1000)', - 'STEXT' => 'varchar(3000)', - 'TEXT' => 'varchar(8000)', - 'MTEXT' => 'TEXT', - 'XSTEXT_UNI'=> 'varchar(100)', - 'STEXT_UNI' => 'varchar(255)', - 'TEXT_UNI' => 'varchar(4000)', - 'MTEXT_UNI' => 'TEXT', - 'TIMESTAMP' => 'INT4', // unsigned - 'DECIMAL' => 'decimal(5,2)', - 'DECIMAL:' => 'decimal(%d,2)', - 'PDECIMAL' => 'decimal(6,3)', - 'PDECIMAL:' => 'decimal(%d,3)', - 'VCHAR_UNI' => 'varchar(255)', - 'VCHAR_UNI:'=> 'varchar(%d)', - 'VCHAR_CI' => 'varchar_ci', - 'VARBINARY' => 'bytea', - ), ); } @@ -315,11 +285,6 @@ class tools implements tools_interface AND name <> "sqlite_sequence"'; break; - case 'postgres': - $sql = 'SELECT relname - FROM pg_stat_user_tables'; - break; - case 'oracle': $sql = 'SELECT table_name FROM USER_TABLES'; @@ -428,7 +393,6 @@ class tools implements tools_interface { case 'mysql_40': case 'mysql_41': - case 'postgres': case 'sqlite': case 'sqlite3': $table_sql .= ",\n\t PRIMARY KEY (" . implode(', ', $table_data['PRIMARY_KEY']) . ')'; @@ -457,17 +421,6 @@ class tools implements tools_interface $statements[] = $table_sql; break; - case 'postgres': - // do we need to add a sequence for auto incrementing columns? - if ($create_sequence) - { - $statements[] = "CREATE SEQUENCE {$table_name}_seq;"; - } - - $table_sql .= "\n);"; - $statements[] = $table_sql; - break; - case 'oracle': $table_sql .= "\n)"; $statements[] = $table_sql; @@ -920,16 +873,6 @@ class tools implements tools_interface $sql = "SHOW COLUMNS FROM $table_name"; break; - // PostgreSQL has a way of doing this in a much simpler way but would - // not allow us to support all versions of PostgreSQL - case 'postgres': - $sql = "SELECT a.attname - FROM pg_class c, pg_attribute a - WHERE c.relname = '{$table_name}' - AND a.attnum > 0 - AND a.attrelid = c.oid"; - break; - case 'oracle': $sql = "SELECT column_name FROM user_tab_columns @@ -1003,17 +946,6 @@ class tools implements tools_interface { switch ($this->sql_layer) { - case 'postgres': - $sql = "SELECT ic.relname as index_name - FROM pg_class bc, pg_class ic, pg_index i - WHERE (bc.oid = i.indrelid) - AND (ic.oid = i.indexrelid) - AND (bc.relname = '" . $table_name . "') - AND (i.indisunique != 't') - AND (i.indisprimary != 't')"; - $col = 'index_name'; - break; - case 'mysql_40': case 'mysql_41': $sql = 'SHOW KEYS @@ -1049,7 +981,6 @@ class tools implements tools_interface switch ($this->sql_layer) { case 'oracle': - case 'postgres': case 'sqlite': case 'sqlite3': $row[$col] = substr($row[$col], strlen($table_name) + 1); @@ -1074,16 +1005,6 @@ class tools implements tools_interface { switch ($this->sql_layer) { - case 'postgres': - $sql = "SELECT ic.relname as index_name, i.indisunique - FROM pg_class bc, pg_class ic, pg_index i - WHERE (bc.oid = i.indrelid) - AND (ic.oid = i.indexrelid) - AND (bc.relname = '" . $table_name . "') - AND (i.indisprimary != 't')"; - $col = 'index_name'; - break; - case 'mysql_40': case 'mysql_41': $sql = 'SHOW KEYS @@ -1120,11 +1041,6 @@ class tools implements tools_interface continue; } - if ($this->sql_layer == 'postgres' && $row['indisunique'] != 't') - { - continue; - } - // These DBMS prefix index name with the table name switch ($this->sql_layer) { @@ -1140,7 +1056,6 @@ class tools implements tools_interface } break; - case 'postgres': case 'sqlite': case 'sqlite3': $row[$col] = substr($row[$col], strlen($table_name) + 1); @@ -1268,51 +1183,6 @@ class tools implements tools_interface break; - case 'postgres': - $return_array['column_type'] = $column_type; - - $sql .= " {$column_type} "; - - $return_array['auto_increment'] = false; - if (isset($column_data[2]) && $column_data[2] == 'auto_increment') - { - $default_val = "nextval('{$table_name}_seq')"; - $return_array['auto_increment'] = true; - } - else if (!is_null($column_data[1])) - { - $default_val = "'" . $column_data[1] . "'"; - $return_array['null'] = 'NOT NULL'; - $sql .= 'NOT NULL '; - } - else - { - // Integers need to have 0 instead of empty string as default - if (strpos($column_type, 'INT') === 0) - { - $default_val = '0'; - } - else - { - $default_val = "'" . $column_data[1] . "'"; - } - $return_array['null'] = 'NULL'; - $sql .= 'NULL '; - } - - $return_array['default'] = $default_val; - - $sql .= "DEFAULT {$default_val}"; - - // Unsigned? Then add a CHECK contraint - if (in_array($orig_column_type, $this->unsigned_types)) - { - $return_array['constraint'] = "CHECK ({$column_name} >= 0)"; - $sql .= " CHECK ({$column_name} >= 0)"; - } - - break; - case 'sqlite': case 'sqlite3': $return_array['primary_key_set'] = false; @@ -1426,33 +1296,6 @@ class tools implements tools_interface $statements[] = 'ALTER TABLE ' . $table_name . ' ADD ' . $column_name . ' ' . $column_data['column_type_sql']; break; - case 'postgres': - // Does not support AFTER, only through temporary table - if (version_compare($this->db->sql_server_info(true), '8.0', '>=')) - { - $statements[] = 'ALTER TABLE ' . $table_name . ' ADD COLUMN "' . $column_name . '" ' . $column_data['column_type_sql']; - } - else - { - // old versions cannot add columns with default and null information - $statements[] = 'ALTER TABLE ' . $table_name . ' ADD COLUMN "' . $column_name . '" ' . $column_data['column_type'] . ' ' . $column_data['constraint']; - - if (isset($column_data['null'])) - { - if ($column_data['null'] == 'NOT NULL') - { - $statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN ' . $column_name . ' SET NOT NULL'; - } - } - - if (isset($column_data['default'])) - { - $statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN ' . $column_name . ' SET DEFAULT ' . $column_data['default']; - } - } - - break; - case 'sqlite': if ($inline && $this->return_statements) { @@ -1535,10 +1378,6 @@ class tools implements tools_interface $statements[] = 'ALTER TABLE ' . $table_name . ' DROP COLUMN ' . $column_name; break; - case 'postgres': - $statements[] = 'ALTER TABLE ' . $table_name . ' DROP COLUMN "' . $column_name . '"'; - break; - case 'sqlite': case 'sqlite3': @@ -1616,7 +1455,6 @@ class tools implements tools_interface break; case 'oracle': - case 'postgres': case 'sqlite': case 'sqlite3': $statements[] = 'DROP INDEX ' . $table_name . '_' . $index_name; @@ -1658,22 +1496,6 @@ class tools implements tools_interface } $this->db->sql_freeresult($result); break; - - case 'postgres': - // PGSQL does not "tightly" bind sequences and tables, we must guess... - $sql = "SELECT relname - FROM pg_class - WHERE relkind = 'S' - AND relname = '{$table_name}_seq'"; - $result = $this->db->sql_query($sql); - - // We don't even care about storing the results. We already know the answer if we get rows back. - if ($this->db->sql_fetchrow($result)) - { - $statements[] = "DROP SEQUENCE {$table_name}_seq;\n"; - } - $this->db->sql_freeresult($result); - break; } return $this->_sql_run_sql($statements); @@ -1688,7 +1510,6 @@ class tools implements tools_interface switch ($this->sql_layer) { - case 'postgres': case 'mysql_40': case 'mysql_41': $statements[] = 'ALTER TABLE ' . $table_name . ' ADD PRIMARY KEY (' . implode(', ', $column) . ')'; @@ -1764,7 +1585,6 @@ class tools implements tools_interface switch ($this->sql_layer) { - case 'postgres': case 'oracle': case 'sqlite': case 'sqlite3': @@ -1797,7 +1617,6 @@ class tools implements tools_interface switch ($this->sql_layer) { - case 'postgres': case 'oracle': case 'sqlite': case 'sqlite3': @@ -1848,17 +1667,6 @@ class tools implements tools_interface switch ($this->sql_layer) { - case 'postgres': - $sql = "SELECT ic.relname as index_name - FROM pg_class bc, pg_class ic, pg_index i - WHERE (bc.oid = i.indrelid) - AND (ic.oid = i.indexrelid) - AND (bc.relname = '" . $table_name . "') - AND (i.indisunique != 't') - AND (i.indisprimary != 't')"; - $col = 'index_name'; - break; - case 'mysql_40': case 'mysql_41': $sql = 'SHOW KEYS @@ -1893,7 +1701,6 @@ class tools implements tools_interface switch ($this->sql_layer) { case 'oracle': - case 'postgres': case 'sqlite': case 'sqlite3': $row[$col] = substr($row[$col], strlen($table_name) + 1); @@ -2001,69 +1808,6 @@ class tools implements tools_interface $this->return_statements = $old_return_statements; break; - case 'postgres': - $sql = 'ALTER TABLE ' . $table_name . ' '; - - $sql_array = array(); - $sql_array[] = 'ALTER COLUMN ' . $column_name . ' TYPE ' . $column_data['column_type']; - - if (isset($column_data['null'])) - { - if ($column_data['null'] == 'NOT NULL') - { - $sql_array[] = 'ALTER COLUMN ' . $column_name . ' SET NOT NULL'; - } - else if ($column_data['null'] == 'NULL') - { - $sql_array[] = 'ALTER COLUMN ' . $column_name . ' DROP NOT NULL'; - } - } - - if (isset($column_data['default'])) - { - $sql_array[] = 'ALTER COLUMN ' . $column_name . ' SET DEFAULT ' . $column_data['default']; - } - - // we don't want to double up on constraints if we change different number data types - if (isset($column_data['constraint'])) - { - $constraint_sql = "SELECT consrc as constraint_data - FROM pg_constraint, pg_class bc - WHERE conrelid = bc.oid - AND bc.relname = '{$table_name}' - AND NOT EXISTS ( - SELECT * - FROM pg_constraint as c, pg_inherits as i - WHERE i.inhrelid = pg_constraint.conrelid - AND c.conname = pg_constraint.conname - AND c.consrc = pg_constraint.consrc - AND c.conrelid = i.inhparent - )"; - - $constraint_exists = false; - - $result = $this->db->sql_query($constraint_sql); - while ($row = $this->db->sql_fetchrow($result)) - { - if (trim($row['constraint_data']) == trim($column_data['constraint'])) - { - $constraint_exists = true; - break; - } - } - $this->db->sql_freeresult($result); - - if (!$constraint_exists) - { - $sql_array[] = 'ADD ' . $column_data['constraint']; - } - } - - $sql .= implode(', ', $sql_array); - - $statements[] = $sql; - break; - case 'sqlite': case 'sqlite3': @@ -2145,7 +1889,6 @@ class tools implements tools_interface { case 'mysql_40': case 'mysql_41': - case 'postgres': case 'sqlite': case 'sqlite3': // Not supported -- cgit v1.2.1 From dbb538afbdce345dfc61b06ec6a84313c1141284 Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Fri, 27 Mar 2015 00:02:20 -0700 Subject: [ticket/13725] Coding guidelines: static public PHPBB3-13725 --- phpBB/phpbb/db/tools/tools.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB/phpbb/db/tools') diff --git a/phpBB/phpbb/db/tools/tools.php b/phpBB/phpbb/db/tools/tools.php index 0d1eb63c47..1d7b2ddfff 100644 --- a/phpBB/phpbb/db/tools/tools.php +++ b/phpBB/phpbb/db/tools/tools.php @@ -40,7 +40,7 @@ class tools implements tools_interface * * @return array */ - public static function get_dbms_type_map() + static public function get_dbms_type_map() { return array( 'mysql_41' => array( -- cgit v1.2.1 From 73900d1857a9a59eff82b224537a79110466ce7e Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 5 Dec 2015 16:07:14 +0100 Subject: [ticket/13454] Remove more unused variables This should be the last part. Off to checking if the changes were correct. PHPBB3-13454 --- phpBB/phpbb/db/tools/tools.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB/phpbb/db/tools') diff --git a/phpBB/phpbb/db/tools/tools.php b/phpBB/phpbb/db/tools/tools.php index 1d7b2ddfff..f06871a1d7 100644 --- a/phpBB/phpbb/db/tools/tools.php +++ b/phpBB/phpbb/db/tools/tools.php @@ -1116,7 +1116,7 @@ class tools implements tools_interface } // Get type - list($column_type, $orig_column_type) = $this->get_column_type($column_data[0]); + list($column_type) = $this->get_column_type($column_data[0]); // Adjust default value if db-dependent specified if (is_array($column_data[1])) -- cgit v1.2.1 From c64b8102b7c5dc5bd0be9d8085d01a66e90dde73 Mon Sep 17 00:00:00 2001 From: Jakub Senko Date: Tue, 29 Mar 2016 21:21:35 +0200 Subject: [ticket/10809] Remove MSSQL support PHPBB3-10809 --- phpBB/phpbb/db/tools/factory.php | 2 +- phpBB/phpbb/db/tools/mssql.php | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) (limited to 'phpBB/phpbb/db/tools') diff --git a/phpBB/phpbb/db/tools/factory.php b/phpBB/phpbb/db/tools/factory.php index d204451a63..96471c3408 100644 --- a/phpBB/phpbb/db/tools/factory.php +++ b/phpBB/phpbb/db/tools/factory.php @@ -25,7 +25,7 @@ class factory */ public function get($db_driver, $return_statements = false) { - if ($db_driver instanceof \phpbb\db\driver\mssql || $db_driver instanceof \phpbb\db\driver\mssql_base) + if ($db_driver instanceof \phpbb\db\driver\mssql_base) { return new \phpbb\db\tools\mssql($db_driver, $return_statements); } diff --git a/phpBB/phpbb/db/tools/mssql.php b/phpBB/phpbb/db/tools/mssql.php index 87719acd21..a132832005 100644 --- a/phpBB/phpbb/db/tools/mssql.php +++ b/phpBB/phpbb/db/tools/mssql.php @@ -110,7 +110,6 @@ class mssql extends tools // Determine mapping database type switch ($this->db->get_sql_layer()) { - case 'mssql': case 'mssql_odbc': $this->sql_layer = 'mssql'; break; -- cgit v1.2.1 From e974f338afb86c065e9b160363bc2e6156f8566d Mon Sep 17 00:00:00 2001 From: Jakub Senko Date: Sun, 13 Nov 2016 18:08:35 +0100 Subject: [ticket/14739] Remove SQLite 2.8.x database driver PHPBB3-14739 --- phpBB/phpbb/db/tools/tools.php | 116 ++--------------------------------------- 1 file changed, 3 insertions(+), 113 deletions(-) (limited to 'phpBB/phpbb/db/tools') diff --git a/phpBB/phpbb/db/tools/tools.php b/phpBB/phpbb/db/tools/tools.php index 37ac0d0468..9273d69cd6 100644 --- a/phpBB/phpbb/db/tools/tools.php +++ b/phpBB/phpbb/db/tools/tools.php @@ -136,37 +136,6 @@ class tools implements tools_interface 'VARBINARY' => 'raw(255)', ), - 'sqlite' => array( - 'INT:' => 'int(%d)', - 'BINT' => 'bigint(20)', - 'ULINT' => 'INTEGER UNSIGNED', // 'int(10) UNSIGNED', - 'UINT' => 'INTEGER UNSIGNED', // 'mediumint(8) UNSIGNED', - 'UINT:' => 'INTEGER UNSIGNED', // 'int(%d) UNSIGNED', - 'TINT:' => 'tinyint(%d)', - 'USINT' => 'INTEGER UNSIGNED', // 'mediumint(4) UNSIGNED', - 'BOOL' => 'INTEGER UNSIGNED', // 'tinyint(1) UNSIGNED', - 'VCHAR' => 'varchar(255)', - 'VCHAR:' => 'varchar(%d)', - 'CHAR:' => 'char(%d)', - 'XSTEXT' => 'text(65535)', - 'STEXT' => 'text(65535)', - 'TEXT' => 'text(65535)', - 'MTEXT' => 'mediumtext(16777215)', - 'XSTEXT_UNI'=> 'text(65535)', - 'STEXT_UNI' => 'text(65535)', - 'TEXT_UNI' => 'text(65535)', - 'MTEXT_UNI' => 'mediumtext(16777215)', - 'TIMESTAMP' => 'INTEGER UNSIGNED', // 'int(11) UNSIGNED', - 'DECIMAL' => 'decimal(5,2)', - 'DECIMAL:' => 'decimal(%d,2)', - 'PDECIMAL' => 'decimal(6,3)', - 'PDECIMAL:' => 'decimal(%d,3)', - 'VCHAR_UNI' => 'varchar(255)', - 'VCHAR_UNI:'=> 'varchar(%d)', - 'VCHAR_CI' => 'varchar(255)', - 'VARBINARY' => 'blob', - ), - 'sqlite3' => array( 'INT:' => 'INT(%d)', 'BINT' => 'BIGINT(20)', @@ -277,12 +246,6 @@ class tools implements tools_interface $sql = 'SHOW TABLES'; break; - case 'sqlite': - $sql = 'SELECT name - FROM sqlite_master - WHERE type = "table"'; - break; - case 'sqlite3': $sql = 'SELECT name FROM sqlite_master @@ -398,7 +361,6 @@ class tools implements tools_interface { case 'mysql_40': case 'mysql_41': - case 'sqlite': case 'sqlite3': $table_sql .= ",\n\t PRIMARY KEY (" . implode(', ', $table_data['PRIMARY_KEY']) . ')'; break; @@ -420,7 +382,6 @@ class tools implements tools_interface break; case 'mysql_40': - case 'sqlite': case 'sqlite3': $table_sql .= "\n);"; $statements[] = $table_sql; @@ -497,7 +458,7 @@ class tools implements tools_interface $sqlite = false; // For SQLite we need to perform the schema changes in a much more different way - if (($this->db->get_sql_layer() == 'sqlite' || $this->db->get_sql_layer() == 'sqlite3') && $this->return_statements) + if ($this->db->get_sql_layer() == 'sqlite3' && $this->return_statements) { $sqlite_data = array(); $sqlite = true; @@ -884,7 +845,6 @@ class tools implements tools_interface WHERE LOWER(table_name) = '" . strtolower($table_name) . "'"; break; - case 'sqlite': case 'sqlite3': $sql = "SELECT sql FROM sqlite_master @@ -967,7 +927,6 @@ class tools implements tools_interface $col = 'index_name'; break; - case 'sqlite': case 'sqlite3': $sql = "PRAGMA index_list('" . $table_name . "');"; $col = 'name'; @@ -986,7 +945,6 @@ class tools implements tools_interface switch ($this->sql_layer) { case 'oracle': - case 'sqlite': case 'sqlite3': $row[$col] = substr($row[$col], strlen($table_name) + 1); break; @@ -1026,7 +984,6 @@ class tools implements tools_interface $col = 'index_name'; break; - case 'sqlite': case 'sqlite3': $sql = "PRAGMA index_list('" . $table_name . "');"; $col = 'name'; @@ -1041,7 +998,7 @@ class tools implements tools_interface continue; } - if (($this->sql_layer == 'sqlite' || $this->sql_layer == 'sqlite3') && !$row['unique']) + if ($this->sql_layer == 'sqlite3' && !$row['unique']) { continue; } @@ -1061,7 +1018,6 @@ class tools implements tools_interface } break; - case 'sqlite': case 'sqlite3': $row[$col] = substr($row[$col], strlen($table_name) + 1); break; @@ -1193,18 +1149,12 @@ class tools implements tools_interface break; - case 'sqlite': case 'sqlite3': $return_array['primary_key_set'] = false; if (isset($column_data[2]) && $column_data[2] == 'auto_increment') { - $sql .= ' INTEGER PRIMARY KEY'; + $sql .= ' INTEGER PRIMARY KEY AUTOINCREMENT'; $return_array['primary_key_set'] = true; - - if ($this->sql_layer === 'sqlite3') - { - $sql .= ' AUTOINCREMENT'; - } } else { @@ -1306,57 +1256,6 @@ class tools implements tools_interface $statements[] = 'ALTER TABLE ' . $table_name . ' ADD ' . $column_name . ' ' . $column_data['column_type_sql']; break; - case 'sqlite': - if ($inline && $this->return_statements) - { - return $column_name . ' ' . $column_data['column_type_sql']; - } - - $recreate_queries = $this->sqlite_get_recreate_table_queries($table_name); - if (empty($recreate_queries)) - { - break; - } - - $statements[] = 'begin'; - - $sql_create_table = array_shift($recreate_queries); - - // Create a backup table and populate it, destroy the existing one - $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $sql_create_table); - $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name; - $statements[] = 'DROP TABLE ' . $table_name; - - preg_match('#\((.*)\)#s', $sql_create_table, $matches); - - $new_table_cols = trim($matches[1]); - $old_table_cols = preg_split('/,(?![\s\w]+\))/m', $new_table_cols); - $column_list = array(); - - foreach ($old_table_cols as $declaration) - { - $entities = preg_split('#\s+#', trim($declaration)); - if ($entities[0] == 'PRIMARY') - { - continue; - } - $column_list[] = $entities[0]; - } - - $columns = implode(',', $column_list); - - $new_table_cols = $column_name . ' ' . $column_data['column_type_sql'] . ',' . $new_table_cols; - - // create a new table and fill it up. destroy the temp one - $statements[] = 'CREATE TABLE ' . $table_name . ' (' . $new_table_cols . ');'; - $statements = array_merge($statements, $recreate_queries); - - $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;'; - $statements[] = 'DROP TABLE ' . $table_name . '_temp'; - - $statements[] = 'commit'; - break; - case 'sqlite3': if ($inline && $this->return_statements) { @@ -1388,7 +1287,6 @@ class tools implements tools_interface $statements[] = 'ALTER TABLE ' . $table_name . ' DROP COLUMN ' . $column_name; break; - case 'sqlite': case 'sqlite3': if ($inline && $this->return_statements) @@ -1465,7 +1363,6 @@ class tools implements tools_interface break; case 'oracle': - case 'sqlite': case 'sqlite3': $statements[] = 'DROP INDEX ' . $table_name . '_' . $index_name; break; @@ -1529,7 +1426,6 @@ class tools implements tools_interface $statements[] = 'ALTER TABLE ' . $table_name . ' add CONSTRAINT pk_' . $table_name . ' PRIMARY KEY (' . implode(', ', $column) . ')'; break; - case 'sqlite': case 'sqlite3': if ($inline && $this->return_statements) @@ -1596,7 +1492,6 @@ class tools implements tools_interface switch ($this->sql_layer) { case 'oracle': - case 'sqlite': case 'sqlite3': $statements[] = 'CREATE UNIQUE INDEX ' . $table_name . '_' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')'; break; @@ -1628,7 +1523,6 @@ class tools implements tools_interface switch ($this->sql_layer) { case 'oracle': - case 'sqlite': case 'sqlite3': $statements[] = 'CREATE INDEX ' . $table_name . '_' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')'; break; @@ -1693,7 +1587,6 @@ class tools implements tools_interface $col = 'index_name'; break; - case 'sqlite': case 'sqlite3': $sql = "PRAGMA index_info('" . $table_name . "');"; $col = 'name'; @@ -1711,7 +1604,6 @@ class tools implements tools_interface switch ($this->sql_layer) { case 'oracle': - case 'sqlite': case 'sqlite3': $row[$col] = substr($row[$col], strlen($table_name) + 1); break; @@ -1818,7 +1710,6 @@ class tools implements tools_interface $this->return_statements = $old_return_statements; break; - case 'sqlite': case 'sqlite3': if ($inline && $this->return_statements) @@ -1899,7 +1790,6 @@ class tools implements tools_interface { case 'mysql_40': case 'mysql_41': - case 'sqlite': case 'sqlite3': // Not supported throw new \Exception('DBMS is not supported'); -- cgit v1.2.1 From 66b0fe3b5d982220e593e945a71e95b3283ac4a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Col=C3=B3n?= Date: Wed, 25 Jan 2017 15:48:39 -0500 Subject: [ticket/15047] No index name length check for mssql --- phpBB/phpbb/db/tools/mssql.php | 4 ---- 1 file changed, 4 deletions(-) (limited to 'phpBB/phpbb/db/tools') diff --git a/phpBB/phpbb/db/tools/mssql.php b/phpBB/phpbb/db/tools/mssql.php index a132832005..254599444d 100644 --- a/phpBB/phpbb/db/tools/mssql.php +++ b/phpBB/phpbb/db/tools/mssql.php @@ -524,8 +524,6 @@ class mssql extends tools { $statements = array(); - $this->check_index_name_length($table_name, $index_name); - $statements[] = 'CREATE UNIQUE INDEX [' . $index_name . '] ON [' . $table_name . ']([' . implode('], [', $column) . '])'; return $this->_sql_run_sql($statements); @@ -538,8 +536,6 @@ class mssql extends tools { $statements = array(); - $this->check_index_name_length($table_name, $index_name); - // remove index length $column = preg_replace('#:.*$#', '', $column); -- cgit v1.2.1 From b52ee87df8add5666b6fbe7b30bbe9ede9a30371 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Col=C3=B3n?= Date: Wed, 25 Jan 2017 16:23:59 -0500 Subject: [ticket/15047] Don't attempt to drop primary keys --- phpBB/phpbb/db/tools/mssql.php | 1 + 1 file changed, 1 insertion(+) (limited to 'phpBB/phpbb/db/tools') diff --git a/phpBB/phpbb/db/tools/mssql.php b/phpBB/phpbb/db/tools/mssql.php index 254599444d..d4128f6bcd 100644 --- a/phpBB/phpbb/db/tools/mssql.php +++ b/phpBB/phpbb/db/tools/mssql.php @@ -713,6 +713,7 @@ class mssql extends tools AND cols.object_id = ix.object_id WHERE ix.object_id = object_id('{$table_name}') AND cols.name = '{$column_name}' + AND ix.is_primary_key = 0 AND ix.is_unique = " . ($unique ? '1' : '0'); } -- cgit v1.2.1 From c53054f2b7bb83a5b3ec39f310c8ea37bffa95ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Col=C3=B3n?= Date: Wed, 25 Jan 2017 16:27:59 -0500 Subject: [ticket/15047] Use brackets due to keyword usage --- phpBB/phpbb/db/tools/mssql.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB/phpbb/db/tools') diff --git a/phpBB/phpbb/db/tools/mssql.php b/phpBB/phpbb/db/tools/mssql.php index d4128f6bcd..3518fb416b 100644 --- a/phpBB/phpbb/db/tools/mssql.php +++ b/phpBB/phpbb/db/tools/mssql.php @@ -477,7 +477,7 @@ class mssql extends tools { $statements = array(); - $statements[] = 'DROP INDEX ' . $table_name . '.' . $index_name; + $statements[] = 'DROP INDEX [' . $table_name . '].[' . $index_name . ']'; return $this->_sql_run_sql($statements); } -- cgit v1.2.1 From fae78b4c011de598e4e05e26ff3129610ae087a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Col=C3=B3n?= Date: Wed, 25 Jan 2017 16:45:04 -0500 Subject: [ticket/15047] Do not set default for identity cols --- phpBB/phpbb/db/tools/mssql.php | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) (limited to 'phpBB/phpbb/db/tools') diff --git a/phpBB/phpbb/db/tools/mssql.php b/phpBB/phpbb/db/tools/mssql.php index 3518fb416b..81e7424136 100644 --- a/phpBB/phpbb/db/tools/mssql.php +++ b/phpBB/phpbb/db/tools/mssql.php @@ -597,7 +597,7 @@ class mssql extends tools // Change the column $statements[] = 'ALTER TABLE [' . $table_name . '] ALTER COLUMN [' . $column_name . '] ' . $column_data['column_type_sql']; - if (!empty($column_data['default'])) + if (!empty($column_data['default']) && !$this->mssql_is_column_identity($table_name, $column_name)) { // Add new default value constraint $statements[] = 'ALTER TABLE [' . $table_name . '] ADD CONSTRAINT [DF_' . $table_name . '_' . $column_name . '_1] ' . $column_data['default'] . ' FOR [' . $column_name . ']'; @@ -674,6 +674,37 @@ class mssql extends tools return $statements; } + /** + * Checks to see if column is an identity column + * + * Identity columns cannot have defaults set for them. + * + * @param string $table_name + * @param string $column_name + * @return bool true if identity, false if not + */ + protected function mssql_is_column_identity($table_name, $column_name) + { + if ($this->mssql_is_sql_server_2000()) + { + // http://msdn.microsoft.com/en-us/library/aa175912%28v=sql.80%29.aspx + // Deprecated in SQL Server 2005 + $sql = "SELECT COLUMNPROPERTY(object_id('{$table_name}'), '{$column_name}', 'IsIdentity') AS is_identity"; + } + else + { + $sql = "SELECT is_identity FROM sys.columns + WHERE object_id = object_id('{$table_name}') + AND name = '{$column_name}'"; + } + + $result = $this->db->sql_query($sql); + $is_identity = $this->db->sql_fetchfield('is_identity'); + $this->db->sql_freeresult($result); + + return (bool)$is_identity; + } + /** * Get a list with existing indexes for the column * -- cgit v1.2.1 From 2416a743f51dd5b995bb2bf1d6316f347fa0000e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Col=C3=B3n?= Date: Thu, 26 Jan 2017 18:44:18 -0500 Subject: [ticket/15047] Reinstate length check for MSSQL 2000 --- phpBB/phpbb/db/tools/mssql.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'phpBB/phpbb/db/tools') diff --git a/phpBB/phpbb/db/tools/mssql.php b/phpBB/phpbb/db/tools/mssql.php index 81e7424136..d31aa2ba0b 100644 --- a/phpBB/phpbb/db/tools/mssql.php +++ b/phpBB/phpbb/db/tools/mssql.php @@ -524,6 +524,11 @@ class mssql extends tools { $statements = array(); + if ($this->is_sql_server_2000()) + { + $this->check_index_name_length($table_name, $index_name); + } + $statements[] = 'CREATE UNIQUE INDEX [' . $index_name . '] ON [' . $table_name . ']([' . implode('], [', $column) . '])'; return $this->_sql_run_sql($statements); @@ -536,6 +541,11 @@ class mssql extends tools { $statements = array(); + if ($this->is_sql_server_2000()) + { + $this->check_index_name_length($table_name, $index_name); + } + // remove index length $column = preg_replace('#:.*$#', '', $column); @@ -702,7 +712,7 @@ class mssql extends tools $is_identity = $this->db->sql_fetchfield('is_identity'); $this->db->sql_freeresult($result); - return (bool)$is_identity; + return (bool) $is_identity; } /** -- cgit v1.2.1 From e5eb7025141238a3486a0395c42c55411dafb259 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Col=C3=B3n?= Date: Mon, 13 Feb 2017 14:03:54 -0500 Subject: [ticket/14992] Add indexes to user_notifications table [ticket/14992] Change how index length is checked [ticket/14992] Remove duplicates from user_notifications table [ticket/14992] Add unique index to user_notifications table [ticket/14992] Shorten unique index name [ticket/14992] Shorten another index for user notifications --- phpBB/phpbb/db/tools/tools.php | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) (limited to 'phpBB/phpbb/db/tools') diff --git a/phpBB/phpbb/db/tools/tools.php b/phpBB/phpbb/db/tools/tools.php index 9273d69cd6..4218addb8e 100644 --- a/phpBB/phpbb/db/tools/tools.php +++ b/phpBB/phpbb/db/tools/tools.php @@ -1487,17 +1487,17 @@ class tools implements tools_interface { $statements = array(); - $this->check_index_name_length($table_name, $index_name); - switch ($this->sql_layer) { case 'oracle': case 'sqlite3': + $this->check_index_name_length($table_name, $table_name . '_' . $index_name); $statements[] = 'CREATE UNIQUE INDEX ' . $table_name . '_' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')'; break; case 'mysql_40': case 'mysql_41': + $this->check_index_name_length($table_name, $index_name); $statements[] = 'ALTER TABLE ' . $table_name . ' ADD UNIQUE INDEX ' . $index_name . '(' . implode(', ', $column) . ')'; break; } @@ -1512,8 +1512,6 @@ class tools implements tools_interface { $statements = array(); - $this->check_index_name_length($table_name, $index_name); - // remove index length unless MySQL4 if ('mysql_40' != $this->sql_layer) { @@ -1524,6 +1522,7 @@ class tools implements tools_interface { case 'oracle': case 'sqlite3': + $this->check_index_name_length($table_name, $table_name . '_' . $index_name); $statements[] = 'CREATE INDEX ' . $table_name . '_' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')'; break; @@ -1539,6 +1538,7 @@ class tools implements tools_interface } // no break case 'mysql_41': + $this->check_index_name_length($table_name, $index_name); $statements[] = 'ALTER TABLE ' . $table_name . ' ADD INDEX ' . $index_name . ' (' . implode(', ', $column) . ')'; break; } @@ -1554,11 +1554,9 @@ class tools implements tools_interface */ protected function check_index_name_length($table_name, $index_name) { - $table_prefix = substr(CONFIG_TABLE, 0, -6); // strlen(config) - if (strlen($table_name . $index_name) - strlen($table_prefix) > 24) + if (strlen($index_name) > 30) { - $max_length = strlen($table_prefix) + 24; - trigger_error("Index name '{$table_name}_$index_name' on table '$table_name' is too long. The maximum is $max_length characters.", E_USER_ERROR); + trigger_error("Index name '$index_name' on table '$table_name' is too long. The maximum is 30 characters.", E_USER_ERROR); } } -- cgit v1.2.1 From 59ac4a71b6dc53f2edf8bb5c669b14df9ce5544a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Col=C3=B3n?= Date: Wed, 1 Mar 2017 22:55:13 -0500 Subject: [ticket/14992] Check index name length in a more proper way [ticket/14992] Add indexes to user_notifications table --- phpBB/phpbb/db/tools/tools.php | 45 ++++++++++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 10 deletions(-) (limited to 'phpBB/phpbb/db/tools') diff --git a/phpBB/phpbb/db/tools/tools.php b/phpBB/phpbb/db/tools/tools.php index 4218addb8e..112438fcdd 100644 --- a/phpBB/phpbb/db/tools/tools.php +++ b/phpBB/phpbb/db/tools/tools.php @@ -946,7 +946,8 @@ class tools implements tools_interface { case 'oracle': case 'sqlite3': - $row[$col] = substr($row[$col], strlen($table_name) + 1); + $index_name = $this->check_index_name_length($table_name, $table_name . '_' . $index_name, false); + $row[$col] = strpos($row[$col], $table_name) === 0 ? substr($row[$col], strlen($table_name) + 1) : $row[$col]; break; } @@ -1359,12 +1360,14 @@ class tools implements tools_interface { case 'mysql_40': case 'mysql_41': + $index_name = $this->check_index_name_length($table_name, $index_name, false); $statements[] = 'DROP INDEX ' . $index_name . ' ON ' . $table_name; break; case 'oracle': case 'sqlite3': - $statements[] = 'DROP INDEX ' . $table_name . '_' . $index_name; + $index_name = $this->check_index_name_length($table_name, $table_name . '_' . $index_name, false); + $statements[] = 'DROP INDEX ' . $index_name; break; } @@ -1491,13 +1494,13 @@ class tools implements tools_interface { case 'oracle': case 'sqlite3': - $this->check_index_name_length($table_name, $table_name . '_' . $index_name); - $statements[] = 'CREATE UNIQUE INDEX ' . $table_name . '_' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')'; + $index_name = $this->check_index_name_length($table_name, $table_name . '_' . $index_name); + $statements[] = 'CREATE UNIQUE INDEX ' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')'; break; case 'mysql_40': case 'mysql_41': - $this->check_index_name_length($table_name, $index_name); + $index_name = $this->check_index_name_length($table_name, $index_name); $statements[] = 'ALTER TABLE ' . $table_name . ' ADD UNIQUE INDEX ' . $index_name . '(' . implode(', ', $column) . ')'; break; } @@ -1522,8 +1525,8 @@ class tools implements tools_interface { case 'oracle': case 'sqlite3': - $this->check_index_name_length($table_name, $table_name . '_' . $index_name); - $statements[] = 'CREATE INDEX ' . $table_name . '_' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')'; + $index_name = $this->check_index_name_length($table_name, $table_name . '_' . $index_name); + $statements[] = 'CREATE INDEX ' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')'; break; case 'mysql_40': @@ -1538,7 +1541,7 @@ class tools implements tools_interface } // no break case 'mysql_41': - $this->check_index_name_length($table_name, $index_name); + $index_name = $this->check_index_name_length($table_name, $index_name); $statements[] = 'ALTER TABLE ' . $table_name . ' ADD INDEX ' . $index_name . ' (' . implode(', ', $column) . ')'; break; } @@ -1551,13 +1554,35 @@ class tools implements tools_interface * * @param string $table_name * @param string $index_name + * @param bool $throw_error + * @return string The index name, shortened if too long */ - protected function check_index_name_length($table_name, $index_name) + protected function check_index_name_length($table_name, $index_name, $throw_error = true) { if (strlen($index_name) > 30) { - trigger_error("Index name '$index_name' on table '$table_name' is too long. The maximum is 30 characters.", E_USER_ERROR); + // Try removing the table prefix if it's at the beginning + $table_prefix = substr(CONFIG_TABLE, 0, -6); // strlen(config) + if (strpos($index_name, $table_prefix) === 0) + { + $index_name = substr($index_name, strlen($table_prefix) + 1); + return $this->check_index_name_length($table_name, $index_name); + } + + // Try removing the table name then + if (strpos($index_name, $table_name) === 0) + { + $index_name = substr($index_name, strlen($table_name) + 1); + return $this->check_index_name_length($table_name, $index_name); + } + + if ($throw_error) + { + trigger_error("Index name '$index_name' on table '$table_name' is too long. The maximum is 30 characters.", E_USER_ERROR); + } } + + return $index_name; } /** -- cgit v1.2.1 From bcc85ab679f6f100d169597990a8ab4138dd3158 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 21 May 2017 15:59:59 +0200 Subject: [ticket/14992] Correctly remove table name and prefix from keys PHPBB3-14992 --- phpBB/phpbb/db/tools/tools.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'phpBB/phpbb/db/tools') diff --git a/phpBB/phpbb/db/tools/tools.php b/phpBB/phpbb/db/tools/tools.php index 112438fcdd..76036554d2 100644 --- a/phpBB/phpbb/db/tools/tools.php +++ b/phpBB/phpbb/db/tools/tools.php @@ -947,7 +947,19 @@ class tools implements tools_interface case 'oracle': case 'sqlite3': $index_name = $this->check_index_name_length($table_name, $table_name . '_' . $index_name, false); - $row[$col] = strpos($row[$col], $table_name) === 0 ? substr($row[$col], strlen($table_name) + 1) : $row[$col]; + $table_prefix = substr(CONFIG_TABLE, 0, -6); // strlen(config) + + if (strpos($index_name , $table_name) === false) + { + if (strpos($index_name, $table_prefix) !== false) + { + $row[$col] = substr($row[$col], strlen($table_prefix) + 1); + } + else + { + $row[$col] = substr($row[$col], strlen($table_name) + 1); + } + } break; } -- cgit v1.2.1 From b7ce395fbe6e2087a400487cb2431dc9ea66b8a9 Mon Sep 17 00:00:00 2001 From: lavigor Date: Sat, 1 Jul 2017 22:59:25 +0300 Subject: [ticket/15259] Fatal error on SQLite/Oracle database update PHPBB3-15259 --- phpBB/phpbb/db/tools/tools.php | 36 ++++++++++++++---------------------- 1 file changed, 14 insertions(+), 22 deletions(-) (limited to 'phpBB/phpbb/db/tools') diff --git a/phpBB/phpbb/db/tools/tools.php b/phpBB/phpbb/db/tools/tools.php index 76036554d2..2f891e43d5 100644 --- a/phpBB/phpbb/db/tools/tools.php +++ b/phpBB/phpbb/db/tools/tools.php @@ -941,29 +941,19 @@ class tools implements tools_interface continue; } - // These DBMS prefix index name with the table name switch ($this->sql_layer) { + // These DBMS prefix index name with the table name case 'oracle': case 'sqlite3': - $index_name = $this->check_index_name_length($table_name, $table_name . '_' . $index_name, false); - $table_prefix = substr(CONFIG_TABLE, 0, -6); // strlen(config) - - if (strpos($index_name , $table_name) === false) - { - if (strpos($index_name, $table_prefix) !== false) - { - $row[$col] = substr($row[$col], strlen($table_prefix) + 1); - } - else - { - $row[$col] = substr($row[$col], strlen($table_name) + 1); - } - } + $new_index_name = $this->check_index_name_length($table_name, $table_name . '_' . $index_name, false); + break; + default: + $new_index_name = $this->check_index_name_length($table_name, $index_name, false); break; } - if (strtolower($row[$col]) == strtolower($index_name)) + if (strtolower($row[$col]) == strtolower($new_index_name)) { $this->db->sql_freeresult($result); return true; @@ -1577,15 +1567,17 @@ class tools implements tools_interface $table_prefix = substr(CONFIG_TABLE, 0, -6); // strlen(config) if (strpos($index_name, $table_prefix) === 0) { - $index_name = substr($index_name, strlen($table_prefix) + 1); - return $this->check_index_name_length($table_name, $index_name); + $index_name = substr($index_name, strlen($table_prefix)); + return $this->check_index_name_length($table_name, $index_name, $throw_error); } - // Try removing the table name then - if (strpos($index_name, $table_name) === 0) + // Try removing the remaining suffix part of table name then + $table_suffix = substr($table_name, strlen($table_prefix)); + if (strpos($index_name, $table_suffix) === 0) { - $index_name = substr($index_name, strlen($table_name) + 1); - return $this->check_index_name_length($table_name, $index_name); + // Remove the suffix and underscore separator between table_name and index_name + $index_name = substr($index_name, strlen($table_suffix) + 1); + return $this->check_index_name_length($table_name, $index_name, $throw_error); } if ($throw_error) -- cgit v1.2.1 From 3aac61cfa74c0ca6816ddebed7e955519c11b9e2 Mon Sep 17 00:00:00 2001 From: Serge Skripchuk Date: Sun, 13 Aug 2017 15:33:04 +0300 Subject: [ticket/15319] Add IF EXISTS to Postgres DROP SEQUENCE query --- phpBB/phpbb/db/tools/postgres.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB/phpbb/db/tools') diff --git a/phpBB/phpbb/db/tools/postgres.php b/phpBB/phpbb/db/tools/postgres.php index e2a4e668a6..077d6e06f9 100644 --- a/phpBB/phpbb/db/tools/postgres.php +++ b/phpBB/phpbb/db/tools/postgres.php @@ -448,7 +448,7 @@ class postgres extends tools // We don't even care about storing the results. We already know the answer if we get rows back. if ($this->db->sql_fetchrow($result)) { - $statements[] = "DROP SEQUENCE {$table_name}_seq;\n"; + $statements[] = "DROP SEQUENCE IF EXISTS {$table_name}_seq;\n"; } $this->db->sql_freeresult($result); -- cgit v1.2.1