From 98134abe206de61b40ba8e64bd759cbfe4a48c9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Bartus?= Date: Sat, 31 Jul 2021 12:06:08 +0200 Subject: [PATCH] [ticket/16741] Database tools to use Doctrine PHPBB3-16741 --- phpBB/config/default/container/services.yml | 1 + .../default/container/services_doctrine.yml | 5 + .../db/doctrine/case_insensitive_string.php | 55 + .../phpbb/db/doctrine/connection_factory.php | 58 +- .../doctrine/connection_parameter_factory.php | 1 + phpBB/phpbb/db/doctrine/driver_convertor.php | 68 - phpBB/phpbb/db/doctrine/oracle_platform.php | 43 + phpBB/phpbb/db/doctrine/table_helper.php | 122 ++ phpBB/phpbb/db/doctrine/type_converter.php | 120 ++ phpBB/phpbb/db/tools/doctrine.php | 439 ++++ phpBB/phpbb/db/tools/factory.php | 1 + phpBB/phpbb/db/tools/mssql.php | 860 +------- phpBB/phpbb/db/tools/postgres.php | 612 +----- phpBB/phpbb/db/tools/tools.php | 1861 +---------------- phpBB/phpbb/db/tools/tools_interface.php | 66 +- 15 files changed, 877 insertions(+), 3435 deletions(-) create mode 100644 phpBB/config/default/container/services_doctrine.yml create mode 100644 phpBB/phpbb/db/doctrine/case_insensitive_string.php delete mode 100644 phpBB/phpbb/db/doctrine/driver_convertor.php create mode 100644 phpBB/phpbb/db/doctrine/oracle_platform.php create mode 100644 phpBB/phpbb/db/doctrine/table_helper.php create mode 100644 phpBB/phpbb/db/doctrine/type_converter.php create mode 100644 phpBB/phpbb/db/tools/doctrine.php diff --git a/phpBB/config/default/container/services.yml b/phpBB/config/default/container/services.yml index ba78673211..3402d78dea 100644 --- a/phpBB/config/default/container/services.yml +++ b/phpBB/config/default/container/services.yml @@ -7,6 +7,7 @@ imports: - { resource: services_content.yml } - { resource: services_cron.yml } - { resource: services_db.yml } + - { resource: services_doctrine.yml } - { resource: services_event.yml } - { resource: services_extensions.yml } - { resource: services_feed.yml } diff --git a/phpBB/config/default/container/services_doctrine.yml b/phpBB/config/default/container/services_doctrine.yml new file mode 100644 index 0000000000..1511c830fd --- /dev/null +++ b/phpBB/config/default/container/services_doctrine.yml @@ -0,0 +1,5 @@ +services: + doctrine.connection: + class: Doctrine\DBAL\Connection + factory: ['phpbb\db\doctrine\connection_factory', 'get_connection'] + arguments: ['@config.php'] diff --git a/phpBB/phpbb/db/doctrine/case_insensitive_string.php b/phpBB/phpbb/db/doctrine/case_insensitive_string.php new file mode 100644 index 0000000000..357776971d --- /dev/null +++ b/phpBB/phpbb/db/doctrine/case_insensitive_string.php @@ -0,0 +1,55 @@ + + * @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\doctrine; + +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Types\Type; + +/** + * Case-insensitive string type (only supported by Postgres). + */ +class case_insensitive_string extends Type +{ + public const CASE_INSENSITIVE_STRING = 'string_ci'; + + /** + * {@inheritdoc} + */ + public function getSQLDeclaration(array $column, AbstractPlatform $platform): string + { + if ($platform->getName() === 'postgresql') + { + return 'varchar_ci'; + } + + // This relies on our own oracle_platform implementation, and the fact that + // we used 3 times larger capacity for strings on oracle for unicode strings + // as on other platforms. This is not the case with varchar_ci, which uses + // the same length as other platforms. + if ($platform->getName() === 'oracle') + { + return $platform->getAsciiStringTypeDeclarationSQL($column); + } + + return $platform->getVarcharTypeDeclarationSQL($column); + } + + /** + * {@inheritdoc} + */ + public function getName(): string + { + return self::CASE_INSENSITIVE_STRING; + } +} diff --git a/phpBB/phpbb/db/doctrine/connection_factory.php b/phpBB/phpbb/db/doctrine/connection_factory.php index 93c5b8c03e..b88006a1d5 100644 --- a/phpBB/phpbb/db/doctrine/connection_factory.php +++ b/phpBB/phpbb/db/doctrine/connection_factory.php @@ -16,6 +16,7 @@ namespace phpbb\db\doctrine; use Doctrine\DBAL\Connection; use Doctrine\DBAL\DriverManager; use Doctrine\DBAL\Exception; +use Doctrine\DBAL\Types\Type; use InvalidArgumentException; use phpbb\config_php_file; use phpbb\exception\runtime_exception; @@ -25,8 +26,6 @@ use phpbb\exception\runtime_exception; */ class connection_factory { - use driver_convertor; - /** * Creates a Doctrine DBAL connection from phpBB configuration. * @@ -37,7 +36,7 @@ class connection_factory * @throws runtime_exception If the database connection could not be established. * @throws InvalidArgumentException If the provided driver name is not a valid phpBB database driver. */ - public static function get_connection(config_php_file $config) : Connection + public static function get_connection(config_php_file $config): Connection { $driver = $config->get('dbms'); $host = $config->get('dbhost'); @@ -77,7 +76,7 @@ class connection_factory ?string $user = null, ?string $password = null, ?string $name = null, - ?string $port = null) : Connection + ?string $port = null): Connection { $available_drivers = DriverManager::getAvailableDrivers(); if (!in_array($driver, $available_drivers)) @@ -97,7 +96,10 @@ class connection_factory try { - return DriverManager::getConnection($params); + $connection = DriverManager::getConnection($params); + Type::addType(case_insensitive_string::CASE_INSENSITIVE_STRING, case_insensitive_string::class); + $connection->getDatabasePlatform()->registerDoctrineTypeMapping('varchar_ci', case_insensitive_string::CASE_INSENSITIVE_STRING); + return $connection; } catch (Exception $e) { @@ -105,6 +107,52 @@ class connection_factory } } + /** + * Converts phpBB driver names to Doctrine's equivalent. + * + * @param string $driver_name phpBB database driver name. + * + * @return string Doctrine DBAL's driver name. + * + * @throws InvalidArgumentException If $driver_name is not a valid phpBB database driver. + */ + private static function to_doctrine_driver(string $driver_name): string + { + // Normalize driver name. + $name = str_replace('phpbb\db\driver', '', $driver_name); + $name = preg_replace('/mysql$/i', 'mysqli', $name); + $name = trim($name, '\\'); + + switch ($name) + { + case 'mssql_odbc': + case 'mssqlnative': + $name = 'pdo_sqlsrv'; + break; + + case 'mysqli': + $name = 'pdo_mysql'; + break; + + case 'oracle': + $name = 'oci8'; + break; + + case 'postgres': + $name = 'pdo_pgsql'; + break; + + case 'sqlite3': + $name = 'pdo_sqlite'; + break; + + default: + throw new InvalidArgumentException('Invalid phpBB database driver provided: ' . $driver_name); + } + + return $name; + } + /* * Disable constructor. */ diff --git a/phpBB/phpbb/db/doctrine/connection_parameter_factory.php b/phpBB/phpbb/db/doctrine/connection_parameter_factory.php index 203ab37b89..a87c6360ce 100644 --- a/phpBB/phpbb/db/doctrine/connection_parameter_factory.php +++ b/phpBB/phpbb/db/doctrine/connection_parameter_factory.php @@ -151,6 +151,7 @@ class connection_parameter_factory ], 'oci8' => [ 'charset' => 'UTF8', + 'platform' => new oracle_platform(), ], 'pdo_pgsql' => [ 'charset' => 'UTF8', diff --git a/phpBB/phpbb/db/doctrine/driver_convertor.php b/phpBB/phpbb/db/doctrine/driver_convertor.php deleted file mode 100644 index b729b1065e..0000000000 --- a/phpBB/phpbb/db/doctrine/driver_convertor.php +++ /dev/null @@ -1,68 +0,0 @@ - - * @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\doctrine; - -use InvalidArgumentException; - -/** - * Driver convertor utility for Doctrine DBAL. - */ -trait driver_convertor -{ - /** - * Converts phpBB driver names to Doctrine's equivalent. - * - * @param string $driver_name phpBB database driver name. - * - * @return string Doctrine DBAL's driver name. - * - * @throws InvalidArgumentException If $driver_name is not a valid phpBB database driver. - */ - public static function to_doctrine_driver(string $driver_name) : string - { - // Normalize driver name. - $name = str_replace('phpbb\db\driver', '', $driver_name); - $name = preg_replace('/mysql$/i', 'mysqli', $name); - $name = trim($name, '\\'); - - switch ($name) - { - case 'mssql_odbc': - case 'mssqlnative': - $name = 'pdo_sqlsrv'; - break; - - case 'mysqli': - $name = 'pdo_mysql'; - break; - - case 'oracle': - $name = 'oci8'; - break; - - case 'postgres': - $name = 'pdo_pgsql'; - break; - - case 'sqlite3': - $name = 'pdo_sqlite'; - break; - - default: - throw new InvalidArgumentException('Invalid phpBB database driver provided: ' . $driver_name); - } - - return $name; - } -} diff --git a/phpBB/phpbb/db/doctrine/oracle_platform.php b/phpBB/phpbb/db/doctrine/oracle_platform.php new file mode 100644 index 0000000000..6c8de23e8a --- /dev/null +++ b/phpBB/phpbb/db/doctrine/oracle_platform.php @@ -0,0 +1,43 @@ + + * @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\doctrine; + +use Doctrine\DBAL\Platforms\OraclePlatform; + +/** + * Oracle specific schema restrictions for BC. + */ +class oracle_platform extends OraclePlatform +{ + /** + * {@inheritDoc} + */ + public function getVarcharTypeDeclarationSQL(array $column): string + { + if (array_key_exists('length', $column) && is_int($column['length'])) + { + $column['length'] *= 3; + } + + return parent::getVarcharTypeDeclarationSQL($column); + } + + /** + * {@inheritDoc} + */ + public function getAsciiStringTypeDeclarationSQL(array $column): string + { + return parent::getVarcharTypeDeclarationSQL($column); + } +} diff --git a/phpBB/phpbb/db/doctrine/table_helper.php b/phpBB/phpbb/db/doctrine/table_helper.php new file mode 100644 index 0000000000..949931916b --- /dev/null +++ b/phpBB/phpbb/db/doctrine/table_helper.php @@ -0,0 +1,122 @@ + + * @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\doctrine; + +use InvalidArgumentException; + +class table_helper +{ + /** + * Converts phpBB's column representation to Doctrine's representation. + * + * @param array $column_data Column data. + * + * @return array A pair of type and array of column options. + */ + public static function convert_column_data(array $column_data, string $dbms_layer): array + { + $options = self::resolve_dbms_specific_options($column_data, $dbms_layer); + list($type, $opts) = type_converter::convert($column_data[0]); + $options = array_merge($options, $opts); + return [$type, $options]; + } + + /** + * Resolve DBMS specific options in column data. + * + * @param array $column_data Original column data. + * @param string $dbms_layer DBMS layer name. + * + * @return array Doctrine column options. + */ + private static function resolve_dbms_specific_options(array $column_data, string $dbms_layer): array + { + $doctrine_options = []; + + if (is_array($column_data[1])) + { + $column_data[1] = self::get_default_column_option($column_data[1], $dbms_layer); + } + + if (!is_null($column_data[1])) + { + $doctrine_options['default'] = $column_data[1]; + $doctrine_options['notnull'] = true; + } + + $non_string_pattern = '/^[a-z]*(?:int|decimal|bool|timestamp)(?::[0-9]+)?$/'; + if ($dbms_layer === 'oracle' && !preg_match($non_string_pattern, strtolower($column_data[0])) + && array_key_exists('default', $doctrine_options[0]) && $doctrine_options[0]['default'] === '') + { + unset($doctrine_options['notnull']); + } + + if (isset($column_data[2])) + { + if ($column_data[2] === 'auto_increment') + { + $doctrine_options['autoincrement'] = true; + } + elseif ($dbms_layer === 'mysql' && $column_data[2] === 'true_sort') + { + $doctrine_options['platformoptions']['collation'] = 'utf8_unicode_ci'; + } + } + + return $doctrine_options; + } + + /** + * Returns the DBMS specific default value for a column definition. + * + * @param array $default_options Database specific default value options. + * @param string $dbms_layer Name of the DBMS layer. + * + * @return mixed Default value for the current DBMS. + * + * @throws InvalidArgumentException When `$schema_name` contains an invalid legacy DBMS name. + */ + private static function get_default_column_option(array $default_options, string $dbms_layer) + { + switch ($dbms_layer) + { + case 'mysql': + return array_key_exists('mysql_41', $default_options) + ? $default_options['mysql_41'] + : $default_options['default']; + case 'oracle': + return array_key_exists('oracle', $default_options) + ? $default_options['oracle'] + : $default_options['default']; + case 'postgresql': + return array_key_exists('postgres', $default_options) + ? $default_options['postgres'] + : $default_options['default']; + case 'mssql': + return array_key_exists('mssqlnative', $default_options) + ? $default_options['mssqlnative'] + : (array_key_exists('mssql', $default_options) ? $default_options['mssql'] : $default_options['default']); + case 'sqlite': + return array_key_exists('sqlite3', $default_options) + ? $default_options['sqlite3'] + : $default_options['default']; + default: + throw new InvalidArgumentException('Invalid schema name.'); + } + } + + private function __construct() + { + } +} diff --git a/phpBB/phpbb/db/doctrine/type_converter.php b/phpBB/phpbb/db/doctrine/type_converter.php new file mode 100644 index 0000000000..c339cb7c9e --- /dev/null +++ b/phpBB/phpbb/db/doctrine/type_converter.php @@ -0,0 +1,120 @@ + + * @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\doctrine; + +/** + * Map phpBB's database types to Doctrine's portable types. + */ +class type_converter +{ + /** + * Type map. + * + * @var array + */ + private const TYPE_MAP = [ + 'BINT' => ['bigint', []], + 'ULINT' => ['integer', ['unsigned' => true]], + 'UINT' => ['integer', ['unsigned' => true]], + 'TINT' => ['smallint', []], + 'USINT' => ['smallint', ['unsigned' => true]], + 'BOOL' => ['boolean', ['unsigned' => true]], + 'VCHAR' => ['string', ['length' => 255]], + 'CHAR' => ['ascii_string', []], + 'XSTEXT' => ['ascii_string', ['length' => 1000]], + 'XSTEXT_UNI'=> ['string', ['length' => 100]], + 'STEXT' => ['ascii_string', ['length' => 3000]], + 'STEXT_UNI' => ['string', ['length' => 255]], + 'TEXT' => ['text', ['length' => ((1 << 16) - 1)]], + 'TEXT_UNI' => ['text', ['length' => ((1 << 16) - 1)]], + 'MTEXT' => ['text', ['length' => ((1 << 24) - 1)]], + 'MTEXT_UNI' => ['text', ['length' => ((1 << 24) - 1)]], + 'TIMESTAMP' => ['integer', ['unsigned' => true]], + 'DECIMAL' => ['integer', ['precision' => 5, 'scale' => 2]], + 'PDECIMAL' => ['integer', ['precision' => 6, 'scale' => 3]], + 'VCHAR_UNI' => ['string', ['length' => 255]], + 'VCHAR_CI' => ['string_ci', ['length' => 255]], + 'VARBINARY' => ['binary', ['length' => 255]], + ]; + + /** + * Convert legacy type to Doctrine's type system. + * + * @param string $type Legacy type name + * + * @return array Pair of type name and options. + */ + public static function convert(string $type): array + { + if (strpos($type, ':') !== false) + { + list($typename, $length) = explode(':', $type); + return self::mapWithLength($typename, (int) $length); + } + + return self::mapType($type); + } + + /** + * Map legacy types with length attribute. + * + * @param string $type Legacy type name. + * @param int $length Type length. + * + * @return array Pair of type name and options. + */ + private static function mapWithLength(string $type, int $length): array + { + switch ($type) + { + case 'UINT': + case 'INT': + case 'TINT': + return self::TYPE_MAP[$type]; + + case 'DECIMAL': + case 'PDECIMAL': + $pair = self::TYPE_MAP[$type]; + $pair[1]['precision'] = $length; + return $pair; + + case 'VCHAR': + case 'CHAR': + case 'VCHAR_UNI': + $pair = self::TYPE_MAP[$type]; + $pair[1]['length'] = $length; + return $pair; + + default: + throw new \InvalidArgumentException("Database type is undefined."); + } + } + + /** + * Map phpBB's legacy database types to Doctrine types. + * + * @param string $type Type name. + * + * @return array Pair of type name and an array of options. + */ + private static function mapType(string $type): array + { + if (!in_array($type, self::TYPE_MAP, true)) + { + throw new \InvalidArgumentException("Database type is undefined."); + } + + return self::TYPE_MAP[$type]; + } +} diff --git a/phpBB/phpbb/db/tools/doctrine.php b/phpBB/phpbb/db/tools/doctrine.php new file mode 100644 index 0000000000..279e07018d --- /dev/null +++ b/phpBB/phpbb/db/tools/doctrine.php @@ -0,0 +1,439 @@ + + * @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; + +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Exception; +use Doctrine\DBAL\Schema\AbstractAsset; +use Doctrine\DBAL\Schema\Comparator; +use Doctrine\DBAL\Schema\Index; +use Doctrine\DBAL\Schema\Table; +use phpbb\db\doctrine\table_helper; + +/** + * BC layer for database tools. + * + * In general, it is recommended to use Doctrine directly instead of this class as this + * implementation is only a BC layer. + * + * In the 3.3.x version branch this class could return SQL statements instead of + * performing changes. This functionality has been removed. + */ +class doctrine implements tools_interface +{ + /** + * @var Comparator + */ + private $comparator; + + /** + * @var \Doctrine\DBAL\Schema\AbstractSchemaManager + */ + private $schema_manager; + + /** + * Database tools constructors. + * + * @param Connection $connection + * + * @throws Exception If the schema manager cannot be created. + */ + public function __construct(Connection $connection) + { + $this->comparator = new Comparator(); + $this->schema_manager = $connection->createSchemaManager(); + } + + /** + * {@inheritDoc} + */ + public function perform_schema_changes(array $schema_changes): void + { + // @todo + } + + /** + * {@inheritDoc} + */ + public function sql_list_tables(): array + { + try + { + return array_map('strtolower', $this->schema_manager->listTableNames()); + } + catch (Exception $e) + { + return []; + } + } + + /** + * {@inheritDoc} + */ + public function sql_table_exists(string $table_name): bool + { + try + { + return $this->schema_manager->tablesExist([$table_name]); + } + catch (Exception $e) + { + return false; + } + } + + /** + * {@inheritDoc} + */ + public function sql_create_table(string $table_name, array $table_data): bool + { + if ($this->sql_table_exists($table_name)) + { + return false; + } + + try + { + $table = new Table($table_name); + $dbms_name = $this->schema_manager->getDatabasePlatform()->getName(); + + foreach ($table_data['COLUMNS'] as $column_name => $column_data) + { + list($type, $options) = table_helper::convert_column_data( + $column_data, + $dbms_name + ); + $table->addColumn($column_name, $type, $options); + } + + $table_data['PRIMARY_KEY'] = (!is_array($table_data['PRIMARY_KEY'])) + ? [$table_data['PRIMARY_KEY']] + : $table_data['PRIMARY_KEY']; + + $table->setPrimaryKey($table_data['PRIMARY_KEY']); + + if (array_key_exists('KEYS', $table_data)) + { + foreach ($table_data['KEYS'] as $key_name => $key_data) + { + $columns = (is_array($key_data[1])) ? $key_data[1] : [$key_data[1]]; + if ($key_data[0] === 'UNIQUE') + { + $table->addUniqueIndex($columns, $key_name); + } + else + { + $table->addIndex($columns, $key_name); + } + } + } + + switch ($dbms_name) + { + case 'mysql': + $table->addOption('collate', 'utf8_bin'); + break; + } + + $this->schema_manager->createTable($table); + } + catch (Exception $e) + { + return false; + } + + return true; + } + + /** + * {@inheritDoc} + */ + public function sql_table_drop(string $table_name): bool + { + try + { + $this->schema_manager->dropTable($table_name); + return true; + } + catch (Exception $e) + { + return false; + } + } + + /** + * {@inheritDoc} + */ + public function sql_list_columns(string $table_name): array + { + try + { + return $this->get_asset_names($this->schema_manager->listTableColumns($table_name)); + } + catch (Exception $e) + { + return []; + } + } + + /** + * {@inheritDoc} + */ + public function sql_column_exists(string $table_name, string $column_name): bool + { + try + { + return $this->asset_exists($column_name, $this->schema_manager->listTableColumns($table_name)); + } + catch (Exception $e) + { + return false; + } + } + + /** + * {@inheritDoc} + */ + public function sql_column_add(string $table_name, string $column_name, array $column_data): bool + { + $dbms_name = $this->schema_manager->getDatabasePlatform()->getName(); + return $this->alter_table( + $table_name, + function (Table $table) use ($column_name, $column_data, $dbms_name) { + list($type, $options) = table_helper::convert_column_data($column_data, $dbms_name); + return $table->addColumn($column_name, $type, $options); + } + ); + } + + /** + * {@inheritDoc} + */ + public function sql_column_change(string $table_name, string $column_name, array $column_data): bool + { + // @todo: index handling. + return $this->alter_table( + $table_name, + function (Table $table) use ($column_name, $column_data) { + // @todo type maps to options['type'] + //$table->dropColumn($column_name); + //list($type, $options) = table_helper::convert_column_data($column_data); + //return $table->addColumn($column_name, $type, $options); + } + ); + } + + /** + * {@inheritDoc} + */ + public function sql_column_remove(string $table_name, string $column_name): bool + { + // @todo: index handling. + return $this->alter_table( + $table_name, + function (Table $table) use ($column_name) { + return $table->dropColumn($column_name); + } + ); + } + + /** + * {@inheritDoc} + */ + public function sql_list_index(string $table_name): array + { + return $this->get_asset_names($this->get_filtered_index_list($table_name, true)); + } + + /** + * {@inheritDoc} + */ + public function sql_index_exists(string $table_name, string $index_name): bool + { + return $this->asset_exists($index_name, $this->get_filtered_index_list($table_name, true)); + } + + /** + * {@inheritDoc} + */ + public function sql_create_index(string $table_name, string $index_name, $column): bool + { + $column = (is_array($column)) ? $column : [$column]; + $index = new Index($index_name, $column); + try + { + $this->schema_manager->createIndex($index, $table_name); + } + catch (Exception $e) + { + return false; + } + + return true; + } + + /** + * {@inheritDoc} + */ + public function sql_index_drop(string $table_name, string $index_name): bool + { + try + { + $this->schema_manager->dropIndex($index_name, $table_name); + return true; + } + catch (Exception $e) + { + return false; + } + } + + /** + * {@inheritDoc} + */ + public function sql_unique_index_exists(string $table_name, string $index_name): bool + { + return $this->asset_exists($index_name, $this->get_filtered_index_list($table_name, false)); + } + + /** + * {@inheritDoc} + */ + public function sql_create_unique_index(string $table_name, string $index_name, $column): bool + { + $column = (is_array($column)) ? $column : [$column]; + $index = new Index($index_name, $column, true); + try + { + $this->schema_manager->createIndex($index, $table_name); + } + catch (Exception $e) + { + return false; + } + + return true; + } + + /** + * {@inheritDoc} + */ + public function sql_create_primary_key(string $table_name, $column): bool + { + $column = (is_array($column)) ? $column : [$column]; + $index = new Index('primary', $column, true, true); + try + { + $this->schema_manager->createIndex($index, $table_name); + } + catch (Exception $e) + { + return false; + } + + return true; + } + + /** + * Returns an array of indices for either unique and primary keys, or simple indices. + * + * @param string $table_name The name of the table. + * @param bool $is_non_unique Whether to return simple indices or primary and unique ones. + * + * @return array The filtered index array. + */ + private function get_filtered_index_list(string $table_name, bool $is_non_unique): array + { + try + { + $indices = $this->schema_manager->listTableIndexes($table_name); + } + catch (Exception $e) + { + return []; + } + + if ($is_non_unique) + { + return array_filter($indices, function(Index $index) { + return $index->isSimpleIndex(); + }); + } + + return array_filter($indices, function(Index $index) { + return !$index->isSimpleIndex(); + }); + } + + /** + * Returns an array of lowercase asset names. + * + * @param array $assets Array of assets. + * + * @return array An array of lowercase asset names. + */ + private function get_asset_names(array $assets): array + { + return array_map( + function(AbstractAsset $asset) { + return strtolower($asset->getName()); + }, + $assets + ); + } + + /** + * Returns whether an asset name exists in a list of assets (case insensitive). + * + * @param string $needle The asset name to search for. + * @param array $assets The array of assets. + * + * @return bool Whether the asset name exists in a list of assets. + */ + private function asset_exists(string $needle, array $assets): bool + { + return in_array(strtolower($needle), $this->get_asset_names($assets), true); + } + + /** + * Alter table. + * + * @param string $table_name Table name. + * @param callable $callback Callback function to modify the table. + * + * @return bool True if the changes were applied successfully, false otherwise. + */ + private function alter_table(string $table_name, callable $callback): bool + { + try + { + $table = $this->schema_manager->listTableDetails($table_name); + $altered_table = clone $table; + $altered_table = call_user_func($callback, $altered_table); + $diff = $this->comparator->diffTable($table, $altered_table); + if ($diff === false) + { + return true; + } + + $this->schema_manager->alterTable($diff); + } + catch (Exception $e) + { + return false; + } + + return true; + } +} diff --git a/phpBB/phpbb/db/tools/factory.php b/phpBB/phpbb/db/tools/factory.php index 96471c3408..44671a764a 100644 --- a/phpBB/phpbb/db/tools/factory.php +++ b/phpBB/phpbb/db/tools/factory.php @@ -25,6 +25,7 @@ class factory */ public function get($db_driver, $return_statements = false) { + // @todo: only create the doctrine tools object. 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 29f816a869..b638e9faaf 100644 --- a/phpBB/phpbb/db/tools/mssql.php +++ b/phpBB/phpbb/db/tools/mssql.php @@ -16,865 +16,9 @@ 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. + * + * @deprecated 4.0.0-a1 */ 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 mssql based databases - * - * @return array - */ - public static function get_dbms_type_map() - { - return array( - 'mssql' => array( - 'INT:' => '[int]', - 'BINT' => '[float]', - 'ULINT' => '[int]', - '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'=> '[nvarchar] (100)', - 'STEXT_UNI' => '[nvarchar] (255)', - 'TEXT_UNI' => '[nvarchar] (4000)', - 'MTEXT_UNI' => '[ntext]', - 'TIMESTAMP' => '[int]', - 'DECIMAL' => '[float]', - 'DECIMAL:' => '[float]', - 'PDECIMAL' => '[float]', - 'PDECIMAL:' => '[float]', - 'VCHAR_UNI' => '[nvarchar] (255)', - 'VCHAR_UNI:'=> '[nvarchar] (%d)', - 'VCHAR_CI' => '[nvarchar] (255)', - 'VARBINARY' => '[varchar] (255)', - ), - - 'mssqlnative' => array( - 'INT:' => '[int]', - 'BINT' => '[float]', - 'ULINT' => '[int]', - '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'=> '[nvarchar] (100)', - 'STEXT_UNI' => '[nvarchar] (255)', - 'TEXT_UNI' => '[nvarchar] (4000)', - 'MTEXT_UNI' => '[ntext]', - 'TIMESTAMP' => '[int]', - 'DECIMAL' => '[float]', - 'DECIMAL:' => '[float]', - 'PDECIMAL' => '[float]', - 'PDECIMAL:' => '[float]', - 'VCHAR_UNI' => '[nvarchar] (255)', - 'VCHAR_UNI:'=> '[nvarchar] (%d)', - 'VCHAR_CI' => '[nvarchar] (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_odbc': - $this->sql_layer = 'mssql'; - break; - - case 'mssqlnative': - $this->sql_layer = 'mssqlnative'; - break; - } - - $this->dbms_type_map = self::get_dbms_type_map(); - } - - /** - * {@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 existence 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, ) = $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 (count($index_data) > 1) - { - // Remove this column from the index and recreate it - $recreate_indexes[$index_name] = array_diff($index_data, array($column_name)); - } - } - } - - // Drop primary keys depending on this column - $result = $this->mssql_get_drop_default_primary_key_queries($table_name, $column_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); - - // 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(); - - if ($this->mssql_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); - } - - /** - * {@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} - */ - protected function get_max_index_name_length() - { - if ($this->mssql_is_sql_server_2000()) - { - return parent::get_max_index_name_length(); - } - else - { - return 128; - } - } - - /** - * {@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']) && !$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 . ']'; - } - - 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 queries to drop the primary keys depending on the specified column - * - * We need to drop primary keys depending on this column before being able - * to delete them. - * - * @param string $table_name - * @param string $column_name - * @return array Array with SQL statements - */ - protected function mssql_get_drop_default_primary_key_queries($table_name, $column_name) - { - $statements = array(); - - $sql = "SELECT ccu.CONSTRAINT_NAME, ccu.COLUMN_NAME - FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc - JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE ccu ON tc.CONSTRAINT_NAME = ccu.Constraint_name - WHERE tc.TABLE_NAME = '{$table_name}' - AND tc.CONSTRAINT_TYPE = 'Primary Key' - AND ccu.COLUMN_NAME = '{$column_name}'"; - - $result = $this->db->sql_query($sql); - - while ($primary_key = $this->db->sql_fetchrow($result)) - { - $statements[] = 'ALTER TABLE [' . $table_name . '] DROP CONSTRAINT [' . $primary_key['CONSTRAINT_NAME'] . ']'; - } - $this->db->sql_freeresult($result); - - 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 - * - * @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_primary_key = 0 - 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/postgres.php b/phpBB/phpbb/db/tools/postgres.php index 573b40fe5d..1beac0bb6b 100644 --- a/phpBB/phpbb/db/tools/postgres.php +++ b/phpBB/phpbb/db/tools/postgres.php @@ -16,617 +16,9 @@ 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. + * + * @deprecated 4.0.0-a1 */ 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', - 'ULINT' => 'INT4', // unsigned - '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_table_exists($table_name) - { - $sql = "SELECT CAST(EXISTS( - SELECT FROM information_schema.tables - WHERE table_schema = 'public' - AND table_name = '" . $this->db->sql_escape($table_name) . "' - ) AS INTEGER)"; - $result = $this->db->sql_query_limit($sql, 1); - $row = $this->db->sql_fetchrow($result); - $table_exists = (booL) $row['exists']; - $this->db->sql_freeresult($result); - - return $table_exists; - } - - /** - * {@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 existence 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 IF EXISTS {$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 pg_get_constraintdef(pc.oid) AS constraint_data - FROM pg_constraint pc, pg_class bc - WHERE conrelid = bc.oid - AND bc.relname = '" . $this->db->sql_escape($table_name) . "' - AND NOT EXISTS ( - SELECT * - FROM pg_constraint AS c, pg_inherits AS i - WHERE i.inhrelid = pc.conrelid - AND c.conname = pc.conname - AND pg_get_constraintdef(c.oid) = pg_get_constraintdef(pc.oid) - 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 61bf8b7ce2..5036f78dc4 100644 --- a/phpBB/phpbb/db/tools/tools.php +++ b/phpBB/phpbb/db/tools/tools.php @@ -16,1864 +16,9 @@ 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. +* +* @deprecated 4.0.0-a1 */ -class tools implements tools_interface +class tools extends doctrine { - /** - * 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(); - - /** - * 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)', - 'ULINT' => 'INT(10) UNSIGNED', - '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)', - ), - - 'oracle' => array( - 'INT:' => 'number(%d)', - 'BINT' => 'number(20)', - 'ULINT' => 'number(10)', - '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)', - ), - - 'sqlite3' => array( - 'INT:' => 'INT(%d)', - 'BINT' => 'BIGINT(20)', - 'ULINT' => 'INTEGER UNSIGNED', - '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', - ), - ); - } - - /** - * A list of types being unsigned for better reference in some db's - * @var array - */ - var $unsigned_types = array('ULINT', 'UINT', 'UINT:', 'USINT', 'BOOL', 'TIMESTAMP'); - - /** - * 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 'mysqli': - $this->sql_layer = 'mysql_41'; - 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 'mysqli': - $sql = 'SHOW TABLES'; - break; - - case 'sqlite3': - $sql = 'SELECT name - FROM sqlite_master - WHERE type = "table" - AND name <> "sqlite_sequence"'; - 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 - $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 existence 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']); - } - - switch ($this->sql_layer) - { - case 'mysql_41': - case 'sqlite3': - $table_sql .= ",\n\t PRIMARY KEY (" . implode(', ', $table_data['PRIMARY_KEY']) . ')'; - 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 'sqlite3': - $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() == '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) && !$this->sql_unique_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_41': - $sql = "SHOW COLUMNS FROM $table_name"; - break; - - case 'oracle': - $sql = "SELECT column_name - FROM user_tab_columns - WHERE LOWER(table_name) = '" . strtolower($table_name) . "'"; - break; - - 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) - { - switch ($this->sql_layer) - { - 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 '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_41' && !$row['Non_unique']) - { - continue; - } - - switch ($this->sql_layer) - { - // These DBMS prefix index name with the table name - case 'oracle': - case 'sqlite3': - $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($new_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) - { - switch ($this->sql_layer) - { - 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 '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_41' && ($row['Non_unique'] || $row[$col] == 'PRIMARY')) - { - continue; - } - - if ($this->sql_layer == 'sqlite3' && !$row['unique']) - { - 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 '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) = $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 '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'; - } - } - - if (isset($column_data['after'])) - { - $return_array['after'] = $column_data['after']; - } - - 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 'sqlite3': - $return_array['primary_key_set'] = false; - if (isset($column_data[2]) && $column_data[2] == 'auto_increment') - { - $sql .= ' INTEGER PRIMARY KEY AUTOINCREMENT'; - $return_array['primary_key_set'] = true; - } - 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) - { - $column_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 '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 '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 '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 '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 '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': - $index_name = $this->check_index_name_length($table_name, $table_name . '_' . $index_name, false); - $statements[] = 'DROP INDEX ' . $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; - } - - return $this->_sql_run_sql($statements); - } - - /** - * {@inheritDoc} - */ - function sql_create_primary_key($table_name, $column, $inline = false) - { - $statements = array(); - - switch ($this->sql_layer) - { - case 'mysql_41': - $statements[] = 'ALTER TABLE ' . $table_name . ' ADD PRIMARY KEY (' . implode(', ', $column) . ')'; - break; - - case 'oracle': - $statements[] = 'ALTER TABLE ' . $table_name . ' add CONSTRAINT pk_' . $table_name . ' PRIMARY KEY (' . implode(', ', $column) . ')'; - break; - - 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(); - - switch ($this->sql_layer) - { - case 'oracle': - case 'sqlite3': - $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_41': - $index_name = $this->check_index_name_length($table_name, $index_name); - $statements[] = 'ALTER TABLE ' . $table_name . ' ADD UNIQUE INDEX ' . $index_name . '(' . implode(', ', $column) . ')'; - break; - } - - return $this->_sql_run_sql($statements); - } - - /** - * {@inheritDoc} - */ - function sql_create_index($table_name, $index_name, $column) - { - $statements = array(); - - $column = preg_replace('#:.*$#', '', $column); - - switch ($this->sql_layer) - { - case 'oracle': - case 'sqlite3': - $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_41': - $index_name = $this->check_index_name_length($table_name, $index_name); - $statements[] = 'ALTER TABLE ' . $table_name . ' ADD INDEX ' . $index_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 - * @param bool $throw_error - * @return string The index name, shortened if too long - */ - protected function check_index_name_length($table_name, $index_name, $throw_error = true) - { - $max_index_name_length = $this->get_max_index_name_length(); - if (strlen($index_name) > $max_index_name_length) - { - // 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)); - return $this->check_index_name_length($table_name, $index_name, $throw_error); - } - - // 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) - { - // 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) - { - trigger_error("Index name '$index_name' on table '$table_name' is too long. The maximum is $max_index_name_length characters.", E_USER_ERROR); - } - } - - return $index_name; - } - - /** - * Get maximum index name length. Might vary depending on db type - * - * @return int Maximum index name length - */ - protected function get_max_index_name_length() - { - return 30; - } - - /** - * {@inheritDoc} - */ - function sql_list_index($table_name) - { - $index_array = array(); - - switch ($this->sql_layer) - { - 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 '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_41' && !$row['Non_unique']) - { - continue; - } - - switch ($this->sql_layer) - { - case 'oracle': - 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 '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 '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 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_41': - case 'sqlite3': - // Not supported - throw new \Exception('DBMS is not supported'); - break; - } - - $sql = ''; - $existing_indexes = array(); - - switch ($this->sql_layer) - { - 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 '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; - } - - /** - * 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 index f153f73a54..259b6497b2 100644 --- a/phpBB/phpbb/db/tools/tools_interface.php +++ b/phpBB/phpbb/db/tools/tools_interface.php @@ -40,41 +40,40 @@ interface tools_interface * * * @param array $schema_changes - * @return null */ - public function perform_schema_changes($schema_changes); + public function perform_schema_changes(array $schema_changes): void; /** * Gets a list of tables in the database. * * @return array Array of table names (all lower case) */ - public function sql_list_tables(); + public function sql_list_tables(): array; /** * Check if table exists * * @param string $table_name The table name to check for - * @return bool true if table exists, else false + * @return bool True if table exists, else false */ - public function sql_table_exists($table_name); + public function sql_table_exists(string $table_name): bool; /** * 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 + * @return bool True if the statements have been executed */ - public function sql_create_table($table_name, $table_data); + public function sql_create_table(string $table_name, array $table_data): bool; /** * 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 + * @return bool True if the statements have been executed */ - public function sql_table_drop($table_name); + public function sql_table_drop(string $table_name): bool; /** * Gets a list of columns of a table. @@ -82,7 +81,7 @@ interface tools_interface * @param string $table_name Table name * @return array Array of column names (all lower case) */ - public function sql_list_columns($table_name); + public function sql_list_columns(string $table_name): array; /** * Check whether a specified column exist in a table @@ -91,7 +90,7 @@ interface tools_interface * @param string $column_name Column to check * @return bool True if column exists, false otherwise */ - public function sql_column_exists($table_name, $column_name); + public function sql_column_exists(string $table_name, string $column_name): bool; /** * Add new column @@ -99,11 +98,10 @@ interface tools_interface * @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 + * + * @return bool True if the statements have been executed */ - public function sql_column_add($table_name, $column_name, $column_data, $inline = false); + public function sql_column_add(string $table_name, string $column_name, array $column_data): bool; /** * Change column type (not name!) @@ -111,22 +109,20 @@ interface tools_interface * @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 + * + * @return bool True if the statements have been executed */ - public function sql_column_change($table_name, $column_name, $column_data, $inline = false); + public function sql_column_change(string $table_name, string $column_name, array $column_data): bool; /** * 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 + * + * @return bool True if the statements have been executed */ - public function sql_column_remove($table_name, $column_name, $inline = false); + public function sql_column_remove(string $table_name, string $column_name): bool; /** * List all of the indices that belong to a table @@ -138,7 +134,7 @@ interface tools_interface * @param string $table_name Table to check * @return array Array with index names */ - public function sql_list_index($table_name); + public function sql_list_index(string $table_name): array; /** * Check if a specified index exists in table. Does not return PRIMARY KEY and UNIQUE indexes. @@ -147,7 +143,7 @@ interface tools_interface * @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); + public function sql_index_exists(string $table_name, string $index_name): bool; /** * Add index @@ -155,18 +151,18 @@ interface tools_interface * @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 + * @return bool True if the statements have been executed */ - public function sql_create_index($table_name, $index_name, $column); + public function sql_create_index(string $table_name, string $index_name, $column): bool; /** * 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 + * @return bool True if the statements have been executed */ - public function sql_index_drop($table_name, $index_name); + public function sql_index_drop(string $table_name, string $index_name): bool; /** * Check if a specified index exists in table. @@ -177,7 +173,7 @@ interface tools_interface * @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); + public function sql_unique_index_exists(string $table_name, string $index_name): bool; /** * Add unique index @@ -185,18 +181,16 @@ interface tools_interface * @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 + * @return bool True if the statements have been executed */ - public function sql_create_unique_index($table_name, $index_name, $column); + public function sql_create_unique_index(string $table_name, string $index_name, $column): bool; /** * 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 + * @return bool True if the statements have been executed */ - public function sql_create_primary_key($table_name, $column, $inline = false); + public function sql_create_primary_key(string $table_name, $column): bool; }