diff options
Diffstat (limited to 'phpBB/phpbb/db')
58 files changed, 5956 insertions, 1187 deletions
diff --git a/phpBB/phpbb/db/driver/driver.php b/phpBB/phpbb/db/driver/driver.php index 01dd66cd6e..214c5590e7 100644 --- a/phpBB/phpbb/db/driver/driver.php +++ b/phpBB/phpbb/db/driver/driver.php @@ -66,6 +66,15 @@ abstract class driver implements driver_interface */ var $sql_server_version = false; + const LOGICAL_OP = 0; + const STATEMENTS = 1; + const LEFT_STMT = 0; + const COMPARE_OP = 1; + const RIGHT_STMT = 2; + const SUBQUERY_OP = 3; + const SUBQUERY_SELECT_TYPE = 4; + const SUBQUERY_BUILD = 5; + /** * Constructor */ @@ -271,7 +280,7 @@ abstract class driver implements driver_interface $query_id = $this->query_result; } - if ($query_id !== false) + if ($query_id) { $result = array(); while ($row = $this->sql_fetchrow($query_id)) @@ -302,7 +311,7 @@ abstract class driver implements driver_interface return $cache->sql_rowseek($rownum, $query_id); } - if ($query_id === false) + if (!$query_id) { return false; } @@ -310,7 +319,7 @@ abstract class driver implements driver_interface $this->sql_freeresult($query_id); $query_id = $this->sql_query($this->last_query_text); - if ($query_id === false) + if (!$query_id) { return false; } @@ -339,7 +348,7 @@ abstract class driver implements driver_interface $query_id = $this->query_result; } - if ($query_id !== false) + if ($query_id) { if ($rownum !== false) { @@ -363,8 +372,8 @@ abstract class driver implements driver_interface */ function sql_like_expression($expression) { - $expression = utf8_str_replace(array('_', '%'), array("\_", "\%"), $expression); - $expression = utf8_str_replace(array(chr(0) . "\_", chr(0) . "\%"), array('_', '%'), $expression); + $expression = str_replace(array('_', '%'), array("\_", "\%"), $expression); + $expression = str_replace(array(chr(0) . "\_", chr(0) . "\%"), array('_', '%'), $expression); return $this->_sql_like_expression('LIKE \'' . $this->sql_escape($expression) . '\''); } @@ -374,8 +383,8 @@ abstract class driver implements driver_interface */ function sql_not_like_expression($expression) { - $expression = utf8_str_replace(array('_', '%'), array("\_", "\%"), $expression); - $expression = utf8_str_replace(array(chr(0) . "\_", chr(0) . "\%"), array('_', '%'), $expression); + $expression = str_replace(array('_', '%'), array("\_", "\%"), $expression); + $expression = str_replace(array(chr(0) . "\_", chr(0) . "\%"), array('_', '%'), $expression); return $this->_sql_not_like_expression('NOT LIKE \'' . $this->sql_escape($expression) . '\''); } @@ -774,7 +783,18 @@ abstract class driver implements driver_interface if (!empty($array['WHERE'])) { - $sql .= ' WHERE ' . $this->_sql_custom_build('WHERE', $array['WHERE']); + $sql .= ' WHERE '; + + if (is_array($array['WHERE'])) + { + $sql_where = $this->_process_boolean_tree_first($array['WHERE']); + } + else + { + $sql_where = $array['WHERE']; + } + + $sql .= $this->_sql_custom_build('WHERE', $sql_where); } if (!empty($array['GROUP_BY'])) @@ -793,6 +813,130 @@ abstract class driver implements driver_interface return $sql; } + + protected function _process_boolean_tree_first($operations_ary) + { + // In cases where an array exists but there is no head condition, + // it should be because there's only 1 WHERE clause. This seems the best way to deal with it. + if ($operations_ary[self::LOGICAL_OP] !== 'AND' && + $operations_ary[self::LOGICAL_OP] !== 'OR') + { + $operations_ary = array('AND', array($operations_ary)); + } + return $this->_process_boolean_tree($operations_ary) . "\n"; + } + + protected function _process_boolean_tree($operations_ary) + { + $operation = $operations_ary[self::LOGICAL_OP]; + + foreach ($operations_ary[self::STATEMENTS] as &$condition) + { + switch ($condition[self::LOGICAL_OP]) + { + case 'AND': + case 'OR': + + $condition = ' ( ' . $this->_process_boolean_tree($condition) . ') '; + + break; + case 'NOT': + + $condition = ' NOT (' . $this->_process_boolean_tree($condition) . ') '; + + break; + + default: + + switch (sizeof($condition)) + { + case 3: + + // Typical 3 element clause with {left hand} {operator} {right hand} + switch ($condition[self::COMPARE_OP]) + { + case 'IN': + case 'NOT_IN': + + // As this is used with an IN, assume it is a set of elements for sql_in_set() + $condition = $this->sql_in_set($condition[self::LEFT_STMT], $condition[self::RIGHT_STMT], $condition[self::COMPARE_OP] === 'NOT_IN', true); + + break; + + case 'LIKE': + + $condition = $condition[self::LEFT_STMT] . ' ' . $this->sql_like_expression($condition[self::RIGHT_STMT]) . ' '; + + break; + + case 'NOT_LIKE': + + $condition = $condition[self::LEFT_STMT] . ' ' . $this->sql_not_like_expression($condition[self::RIGHT_STMT]) . ' '; + + break; + + case 'IS_NOT': + + $condition[self::COMPARE_OP] = 'IS NOT'; + + // no break + case 'IS': + + // If the value is NULL, the string of it is the empty string ('') which is not the intended result. + // this should solve that + if ($condition[self::RIGHT_STMT] === null) + { + $condition[self::RIGHT_STMT] = 'NULL'; + } + + $condition = implode(' ', $condition); + + break; + + default: + + $condition = implode(' ', $condition); + + break; + } + + break; + + case 5: + + // Subquery with {left hand} {operator} {compare kind} {SELECT Kind } {Sub Query} + + $condition = $condition[self::LEFT_STMT] . ' ' . $condition[self::COMPARE_OP] . ' ' . $condition[self::SUBQUERY_OP] . ' ( '; + $condition .= $this->sql_build_query($condition[self::SUBQUERY_SELECT_TYPE], $condition[self::SUBQUERY_BUILD]); + $condition .= ' )'; + + break; + + default: + // This is an unpredicted clause setup. Just join all elements. + $condition = implode(' ', $condition); + + break; + } + + break; + } + + } + + if ($operation === 'NOT') + { + $operations_ary = implode("", $operations_ary[self::STATEMENTS]); + } + else + { + $operations_ary = implode(" \n $operation ", $operations_ary[self::STATEMENTS]); + } + + return $operations_ary; + } + + /** * {@inheritDoc} */ @@ -868,7 +1012,7 @@ abstract class driver implements driver_interface */ function sql_report($mode, $query = '') { - global $cache, $starttime, $phpbb_root_path, $phpbb_path_helper, $user; + global $cache, $starttime, $phpbb_root_path, $phpbb_path_helper; global $request; if (is_object($request) && !$request->variable('explain', false)) diff --git a/phpBB/phpbb/db/driver/mssql.php b/phpBB/phpbb/db/driver/mssql.php index f9ea884ce2..dfdbfe15e6 100644 --- a/phpBB/phpbb/db/driver/mssql.php +++ b/phpBB/phpbb/db/driver/mssql.php @@ -71,8 +71,8 @@ class mssql extends \phpbb\db\driver\driver $row = false; if ($result_id) { - $row = @mssql_fetch_assoc($result_id); - @mssql_free_result($result_id); + $row = mssql_fetch_assoc($result_id); + mssql_free_result($result_id); } $this->sql_server_version = ($row) ? trim(implode(' ', $row)) : 0; @@ -161,12 +161,17 @@ class mssql extends \phpbb\db\driver\driver $this->sql_time += microtime(true) - $this->curtime; } + if (!$this->query_result) + { + return false; + } + if ($cache && $cache_ttl) { $this->open_queries[(int) $this->query_result] = $this->query_result; $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); } - else if (strpos($query, 'SELECT') === 0 && $this->query_result) + else if (strpos($query, 'SELECT') === 0 && $this->query_result !== true) { $this->open_queries[(int) $this->query_result] = $this->query_result; } @@ -241,12 +246,12 @@ class mssql extends \phpbb\db\driver\driver return $cache->sql_fetchrow($query_id); } - if ($query_id === false) + if (!$query_id || $query_id === true) { return false; } - $row = @mssql_fetch_assoc($query_id); + $row = mssql_fetch_assoc($query_id); // I hope i am able to remove this later... hopefully only a PHP or MSSQL bug if ($row) @@ -272,12 +277,17 @@ class mssql extends \phpbb\db\driver\driver $query_id = $this->query_result; } + if ($query_id === true) + { + return false; + } + if ($cache && $cache->sql_exists($query_id)) { return $cache->sql_rowseek($rownum, $query_id); } - return ($query_id !== false) ? @mssql_data_seek($query_id, $rownum) : false; + return ($query_id) ? @mssql_data_seek($query_id, $rownum) : false; } /** @@ -288,12 +298,12 @@ class mssql extends \phpbb\db\driver\driver $result_id = @mssql_query('SELECT SCOPE_IDENTITY()', $this->db_connect_id); if ($result_id) { - if ($row = @mssql_fetch_assoc($result_id)) + if ($row = mssql_fetch_assoc($result_id)) { - @mssql_free_result($result_id); + mssql_free_result($result_id); return $row['computed']; } - @mssql_free_result($result_id); + mssql_free_result($result_id); } return false; @@ -311,6 +321,11 @@ class mssql extends \phpbb\db\driver\driver $query_id = $this->query_result; } + if ($query_id === true) + { + return false; + } + if ($cache && !is_object($query_id) && $cache->sql_exists($query_id)) { return $cache->sql_freeresult($query_id); @@ -319,7 +334,7 @@ class mssql extends \phpbb\db\driver\driver if (isset($this->open_queries[(int) $query_id])) { unset($this->open_queries[(int) $query_id]); - return @mssql_free_result($query_id); + return mssql_free_result($query_id); } return false; @@ -376,9 +391,9 @@ class mssql extends \phpbb\db\driver\driver $result_id = @mssql_query('SELECT @@ERROR as code', $this->db_connect_id); if ($result_id) { - $row = @mssql_fetch_assoc($result_id); + $row = mssql_fetch_assoc($result_id); $error['code'] = $row['code']; - @mssql_free_result($result_id); + mssql_free_result($result_id); } // Get full error message if possible @@ -389,12 +404,12 @@ class mssql extends \phpbb\db\driver\driver if ($result_id) { - $row = @mssql_fetch_assoc($result_id); + $row = mssql_fetch_assoc($result_id); if (!empty($row['message'])) { $error['message'] .= '<br />' . $row['message']; } - @mssql_free_result($result_id); + mssql_free_result($result_id); } } else @@ -440,13 +455,13 @@ class mssql extends \phpbb\db\driver\driver if ($result = @mssql_query($query, $this->db_connect_id)) { @mssql_next_result($result); - while ($row = @mssql_fetch_row($result)) + while ($row = mssql_fetch_row($result)) { $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); } } @mssql_query('SET SHOWPLAN_TEXT OFF;', $this->db_connect_id); - @mssql_free_result($result); + mssql_free_result($result); if ($html_table) { @@ -459,11 +474,14 @@ class mssql extends \phpbb\db\driver\driver $endtime = $endtime[0] + $endtime[1]; $result = @mssql_query($query, $this->db_connect_id); - while ($void = @mssql_fetch_assoc($result)) + if ($result) { - // Take the time spent on parsing rows into account + while ($void = mssql_fetch_assoc($result)) + { + // Take the time spent on parsing rows into account + } + mssql_free_result($result); } - @mssql_free_result($result); $splittime = explode(' ', microtime()); $splittime = $splittime[0] + $splittime[1]; diff --git a/phpBB/phpbb/db/driver/mssql_odbc.php b/phpBB/phpbb/db/driver/mssql_odbc.php index 8e5d4c7a4c..9d9ad603e0 100644 --- a/phpBB/phpbb/db/driver/mssql_odbc.php +++ b/phpBB/phpbb/db/driver/mssql_odbc.php @@ -98,8 +98,8 @@ class mssql_odbc extends \phpbb\db\driver\mssql_base $row = false; if ($result_id) { - $row = @odbc_fetch_array($result_id); - @odbc_free_result($result_id); + $row = odbc_fetch_array($result_id); + odbc_free_result($result_id); } $this->sql_server_version = ($row) ? trim(implode(' ', $row)) : 0; @@ -181,12 +181,17 @@ class mssql_odbc extends \phpbb\db\driver\mssql_base $this->sql_time += microtime(true) - $this->curtime; } + if (!$this->query_result) + { + return false; + } + if ($cache && $cache_ttl) { $this->open_queries[(int) $this->query_result] = $this->query_result; $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); } - else if (strpos($query, 'SELECT') === 0 && $this->query_result) + else if (strpos($query, 'SELECT') === 0) { $this->open_queries[(int) $this->query_result] = $this->query_result; } @@ -261,7 +266,7 @@ class mssql_odbc extends \phpbb\db\driver\mssql_base return $cache->sql_fetchrow($query_id); } - return ($query_id !== false) ? @odbc_fetch_array($query_id) : false; + return ($query_id) ? odbc_fetch_array($query_id) : false; } /** @@ -273,13 +278,13 @@ class mssql_odbc extends \phpbb\db\driver\mssql_base if ($result_id) { - if (@odbc_fetch_array($result_id)) + if (odbc_fetch_array($result_id)) { - $id = @odbc_result($result_id, 1); - @odbc_free_result($result_id); + $id = odbc_result($result_id, 1); + odbc_free_result($result_id); return $id; } - @odbc_free_result($result_id); + odbc_free_result($result_id); } return false; @@ -305,7 +310,7 @@ class mssql_odbc extends \phpbb\db\driver\mssql_base if (isset($this->open_queries[(int) $query_id])) { unset($this->open_queries[(int) $query_id]); - return @odbc_free_result($query_id); + return odbc_free_result($query_id); } return false; @@ -360,11 +365,14 @@ class mssql_odbc extends \phpbb\db\driver\mssql_base $endtime = $endtime[0] + $endtime[1]; $result = @odbc_exec($this->db_connect_id, $query); - while ($void = @odbc_fetch_array($result)) + if ($result) { - // Take the time spent on parsing rows into account + while ($void = odbc_fetch_array($result)) + { + // Take the time spent on parsing rows into account + } + odbc_free_result($result); } - @odbc_free_result($result); $splittime = explode(' ', microtime()); $splittime = $splittime[0] + $splittime[1]; diff --git a/phpBB/phpbb/db/driver/mssqlnative.php b/phpBB/phpbb/db/driver/mssqlnative.php index 46a9b3a477..50dce35baa 100644 --- a/phpBB/phpbb/db/driver/mssqlnative.php +++ b/phpBB/phpbb/db/driver/mssqlnative.php @@ -154,12 +154,17 @@ class mssqlnative extends \phpbb\db\driver\mssql_base $this->sql_time += microtime(true) - $this->curtime; } + if (!$this->query_result) + { + return false; + } + if ($cache && $cache_ttl) { $this->open_queries[(int) $this->query_result] = $this->query_result; $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); } - else if (strpos($query, 'SELECT') === 0 && $this->query_result) + else if (strpos($query, 'SELECT') === 0) { $this->open_queries[(int) $this->query_result] = $this->query_result; } @@ -242,12 +247,12 @@ class mssqlnative extends \phpbb\db\driver\mssql_base return $cache->sql_fetchrow($query_id); } - if ($query_id === false) + if (!$query_id) { return false; } - $row = @sqlsrv_fetch_array($query_id, SQLSRV_FETCH_ASSOC); + $row = sqlsrv_fetch_array($query_id, SQLSRV_FETCH_ASSOC); if ($row) { @@ -272,11 +277,11 @@ class mssqlnative extends \phpbb\db\driver\mssql_base { $result_id = @sqlsrv_query($this->db_connect_id, 'SELECT @@IDENTITY'); - if ($result_id !== false) + if ($result_id) { - $row = @sqlsrv_fetch_array($result_id); + $row = sqlsrv_fetch_array($result_id); $id = $row[0]; - @sqlsrv_free_stmt($result_id); + sqlsrv_free_stmt($result_id); return $id; } else @@ -305,7 +310,7 @@ class mssqlnative extends \phpbb\db\driver\mssql_base if (isset($this->open_queries[(int) $query_id])) { unset($this->open_queries[(int) $query_id]); - return @sqlsrv_free_stmt($query_id); + return sqlsrv_free_stmt($query_id); } return false; @@ -378,14 +383,14 @@ class mssqlnative extends \phpbb\db\driver\mssql_base @sqlsrv_query($this->db_connect_id, 'SET SHOWPLAN_TEXT ON;'); if ($result = @sqlsrv_query($this->db_connect_id, $query)) { - @sqlsrv_next_result($result); - while ($row = @sqlsrv_fetch_array($result)) + sqlsrv_next_result($result); + while ($row = sqlsrv_fetch_array($result)) { $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); } + sqlsrv_free_stmt($result); } @sqlsrv_query($this->db_connect_id, 'SET SHOWPLAN_TEXT OFF;'); - @sqlsrv_free_stmt($result); if ($html_table) { @@ -398,11 +403,14 @@ class mssqlnative extends \phpbb\db\driver\mssql_base $endtime = $endtime[0] + $endtime[1]; $result = @sqlsrv_query($this->db_connect_id, $query); - while ($void = @sqlsrv_fetch_array($result)) + if ($result) { - // Take the time spent on parsing rows into account + while ($void = sqlsrv_fetch_array($result)) + { + // Take the time spent on parsing rows into account + } + sqlsrv_free_stmt($result); } - @sqlsrv_free_stmt($result); $splittime = explode(' ', microtime()); $splittime = $splittime[0] + $splittime[1]; diff --git a/phpBB/phpbb/db/driver/mysql.php b/phpBB/phpbb/db/driver/mysql.php index e93c7239e8..a94e88b331 100644 --- a/phpBB/phpbb/db/driver/mysql.php +++ b/phpBB/phpbb/db/driver/mysql.php @@ -70,9 +70,16 @@ class mysql extends \phpbb\db\driver\mysql_base if (version_compare($this->sql_server_info(true), '5.0.2', '>=')) { $result = @mysql_query('SELECT @@session.sql_mode AS sql_mode', $this->db_connect_id); - $row = @mysql_fetch_assoc($result); - @mysql_free_result($result); - $modes = array_map('trim', explode(',', $row['sql_mode'])); + if ($result) + { + $row = mysql_fetch_assoc($result); + mysql_free_result($result); + $modes = array_map('trim', explode(',', $row['sql_mode'])); + } + else + { + $modes = array(); + } // TRADITIONAL includes STRICT_ALL_TABLES and STRICT_TRANS_TABLES if (!in_array('TRADITIONAL', $modes)) @@ -114,14 +121,17 @@ class mysql extends \phpbb\db\driver\mysql_base if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('mysql_version')) === false) { $result = @mysql_query('SELECT VERSION() AS version', $this->db_connect_id); - $row = @mysql_fetch_assoc($result); - @mysql_free_result($result); + if ($result) + { + $row = mysql_fetch_assoc($result); + mysql_free_result($result); - $this->sql_server_version = $row['version']; + $this->sql_server_version = $row['version']; - if (!empty($cache) && $use_cache) - { - $cache->put('mysql_version', $this->sql_server_version); + if (!empty($cache) && $use_cache) + { + $cache->put('mysql_version', $this->sql_server_version); + } } } @@ -190,12 +200,17 @@ class mysql extends \phpbb\db\driver\mysql_base $this->sql_time += microtime(true) - $this->curtime; } + if (!$this->query_result) + { + return false; + } + if ($cache && $cache_ttl) { $this->open_queries[(int) $this->query_result] = $this->query_result; $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); } - else if (strpos($query, 'SELECT') === 0 && $this->query_result) + else if (strpos($query, 'SELECT') === 0) { $this->open_queries[(int) $this->query_result] = $this->query_result; } @@ -257,7 +272,7 @@ class mysql extends \phpbb\db\driver\mysql_base return $cache->sql_fetchrow($query_id); } - return ($query_id !== false) ? @mysql_fetch_assoc($query_id) : false; + return ($query_id) ? mysql_fetch_assoc($query_id) : false; } /** @@ -308,7 +323,7 @@ class mysql extends \phpbb\db\driver\mysql_base if (isset($this->open_queries[(int) $query_id])) { unset($this->open_queries[(int) $query_id]); - return @mysql_free_result($query_id); + return mysql_free_result($query_id); } return false; @@ -411,12 +426,12 @@ class mysql extends \phpbb\db\driver\mysql_base if ($result = @mysql_query("EXPLAIN $explain_query", $this->db_connect_id)) { - while ($row = @mysql_fetch_assoc($result)) + while ($row = mysql_fetch_assoc($result)) { $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); } + mysql_free_result($result); } - @mysql_free_result($result); if ($html_table) { @@ -431,7 +446,7 @@ class mysql extends \phpbb\db\driver\mysql_base if ($result = @mysql_query('SHOW PROFILE ALL;', $this->db_connect_id)) { $this->html_hold .= '<br />'; - while ($row = @mysql_fetch_assoc($result)) + while ($row = mysql_fetch_assoc($result)) { // make <unknown> HTML safe if (!empty($row['Source_function'])) @@ -449,8 +464,8 @@ class mysql extends \phpbb\db\driver\mysql_base } $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); } + mysql_free_result($result); } - @mysql_free_result($result); if ($html_table) { @@ -468,11 +483,14 @@ class mysql extends \phpbb\db\driver\mysql_base $endtime = $endtime[0] + $endtime[1]; $result = @mysql_query($query, $this->db_connect_id); - while ($void = @mysql_fetch_assoc($result)) + if ($result) { - // Take the time spent on parsing rows into account + while ($void = mysql_fetch_assoc($result)) + { + // Take the time spent on parsing rows into account + } + mysql_free_result($result); } - @mysql_free_result($result); $splittime = explode(' ', microtime()); $splittime = $splittime[0] + $splittime[1]; diff --git a/phpBB/phpbb/db/driver/mysqli.php b/phpBB/phpbb/db/driver/mysqli.php index c0ddfbf76c..d43e201526 100644 --- a/phpBB/phpbb/db/driver/mysqli.php +++ b/phpBB/phpbb/db/driver/mysqli.php @@ -74,9 +74,10 @@ class mysqli extends \phpbb\db\driver\mysql_base if (version_compare($this->sql_server_info(true), '5.0.2', '>=')) { $result = @mysqli_query($this->db_connect_id, 'SELECT @@session.sql_mode AS sql_mode'); - if ($result !== null) + if ($result) { - $row = @mysqli_fetch_assoc($result); + $row = mysqli_fetch_assoc($result); + mysqli_free_result($result); $modes = array_map('trim', explode(',', $row['sql_mode'])); } @@ -84,7 +85,6 @@ class mysqli extends \phpbb\db\driver\mysql_base { $modes = array(); } - @mysqli_free_result($result); // TRADITIONAL includes STRICT_ALL_TABLES and STRICT_TRANS_TABLES if (!in_array('TRADITIONAL', $modes)) @@ -119,9 +119,10 @@ class mysqli extends \phpbb\db\driver\mysql_base if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('mysqli_version')) === false) { $result = @mysqli_query($this->db_connect_id, 'SELECT VERSION() AS version'); - if ($result !== null) + if ($result) { - $row = @mysqli_fetch_assoc($result); + $row = mysqli_fetch_assoc($result); + mysqli_free_result($result); $this->sql_server_version = $row['version']; @@ -130,7 +131,6 @@ class mysqli extends \phpbb\db\driver\mysql_base $cache->put('mysqli_version', $this->sql_server_version); } } - @mysqli_free_result($result); } return ($raw) ? $this->sql_server_version : 'MySQL(i) ' . $this->sql_server_version; @@ -202,6 +202,11 @@ class mysqli extends \phpbb\db\driver\mysql_base $this->sql_time += microtime(true) - $this->curtime; } + if (!$this->query_result) + { + return false; + } + if ($cache && $cache_ttl) { $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); @@ -245,9 +250,9 @@ class mysqli extends \phpbb\db\driver\mysql_base return $cache->sql_fetchrow($query_id); } - if ($query_id !== false && $query_id !== null) + if ($query_id) { - $result = @mysqli_fetch_assoc($query_id); + $result = mysqli_fetch_assoc($query_id); return $result !== null ? $result : false; } @@ -271,7 +276,7 @@ class mysqli extends \phpbb\db\driver\mysql_base return $cache->sql_rowseek($rownum, $query_id); } - return ($query_id !== false) ? @mysqli_data_seek($query_id, $rownum) : false; + return ($query_id) ? @mysqli_data_seek($query_id, $rownum) : false; } /** @@ -299,7 +304,17 @@ class mysqli extends \phpbb\db\driver\mysql_base return $cache->sql_freeresult($query_id); } - return @mysqli_free_result($query_id); + if (!$query_id) + { + return false; + } + + if ($query_id === true) + { + return true; + } + + return mysqli_free_result($query_id); } /** @@ -398,12 +413,12 @@ class mysqli extends \phpbb\db\driver\mysql_base if ($result = @mysqli_query($this->db_connect_id, "EXPLAIN $explain_query")) { - while ($row = @mysqli_fetch_assoc($result)) + while ($row = mysqli_fetch_assoc($result)) { $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); } + mysqli_free_result($result); } - @mysqli_free_result($result); if ($html_table) { @@ -418,7 +433,7 @@ class mysqli extends \phpbb\db\driver\mysql_base if ($result = @mysqli_query($this->db_connect_id, 'SHOW PROFILE ALL;')) { $this->html_hold .= '<br />'; - while ($row = @mysqli_fetch_assoc($result)) + while ($row = mysqli_fetch_assoc($result)) { // make <unknown> HTML safe if (!empty($row['Source_function'])) @@ -436,8 +451,8 @@ class mysqli extends \phpbb\db\driver\mysql_base } $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); } + mysqli_free_result($result); } - @mysqli_free_result($result); if ($html_table) { @@ -455,14 +470,14 @@ class mysqli extends \phpbb\db\driver\mysql_base $endtime = $endtime[0] + $endtime[1]; $result = @mysqli_query($this->db_connect_id, $query); - if ($result !== null) + if ($result) { - while ($void = @mysqli_fetch_assoc($result)) + while ($void = mysqli_fetch_assoc($result)) { // Take the time spent on parsing rows into account } + mysqli_free_result($result); } - @mysqli_free_result($result); $splittime = explode(' ', microtime()); $splittime = $splittime[0] + $splittime[1]; diff --git a/phpBB/phpbb/db/driver/oracle.php b/phpBB/phpbb/db/driver/oracle.php index 6dcab5dd7d..54238a15ef 100644 --- a/phpBB/phpbb/db/driver/oracle.php +++ b/phpBB/phpbb/db/driver/oracle.php @@ -84,8 +84,6 @@ class oracle extends \phpbb\db\driver\driver * but I assume its because the Oracle extension provides a direct method to access it * without a query. */ - - $use_cache = false; /* global $cache; @@ -439,12 +437,17 @@ class oracle extends \phpbb\db\driver\driver $this->sql_time += microtime(true) - $this->curtime; } + if (!$this->query_result) + { + return false; + } + if ($cache && $cache_ttl) { $this->open_queries[(int) $this->query_result] = $this->query_result; $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); } - else if (strpos($query, 'SELECT') === 0 && $this->query_result) + else if (strpos($query, 'SELECT') === 0) { $this->open_queries[(int) $this->query_result] = $this->query_result; } @@ -499,10 +502,10 @@ class oracle extends \phpbb\db\driver\driver return $cache->sql_fetchrow($query_id); } - if ($query_id !== false) + if ($query_id) { $row = array(); - $result = @ocifetchinto($query_id, $row, OCI_ASSOC + OCI_RETURN_NULLS); + $result = ocifetchinto($query_id, $row, OCI_ASSOC + OCI_RETURN_NULLS); if (!$result || !$row) { @@ -550,7 +553,7 @@ class oracle extends \phpbb\db\driver\driver return $cache->sql_rowseek($rownum, $query_id); } - if ($query_id === false) + if (!$query_id) { return false; } @@ -583,18 +586,24 @@ class oracle extends \phpbb\db\driver\driver { $query = 'SELECT ' . $tablename[1] . '_seq.currval FROM DUAL'; $stmt = @ociparse($this->db_connect_id, $query); - @ociexecute($stmt, OCI_DEFAULT); + if ($stmt) + { + $success = @ociexecute($stmt, OCI_DEFAULT); - $temp_result = @ocifetchinto($stmt, $temp_array, OCI_ASSOC + OCI_RETURN_NULLS); - @ocifreestatement($stmt); + if ($success) + { + $temp_result = ocifetchinto($stmt, $temp_array, OCI_ASSOC + OCI_RETURN_NULLS); + ocifreestatement($stmt); - if ($temp_result) - { - return $temp_array['CURRVAL']; - } - else - { - return false; + if ($temp_result) + { + return $temp_array['CURRVAL']; + } + else + { + return false; + } + } } } } @@ -622,7 +631,7 @@ class oracle extends \phpbb\db\driver\driver if (isset($this->open_queries[(int) $query_id])) { unset($this->open_queries[(int) $query_id]); - return @ocifreestatement($query_id); + return ocifreestatement($query_id); } return false; @@ -787,14 +796,20 @@ class oracle extends \phpbb\db\driver\driver $endtime = $endtime[0] + $endtime[1]; $result = @ociparse($this->db_connect_id, $query); - $success = @ociexecute($result, OCI_DEFAULT); - $row = array(); - - while (@ocifetchinto($result, $row, OCI_ASSOC + OCI_RETURN_NULLS)) + if ($result) { - // Take the time spent on parsing rows into account + $success = @ociexecute($result, OCI_DEFAULT); + if ($success) + { + $row = array(); + + while (ocifetchinto($result, $row, OCI_ASSOC + OCI_RETURN_NULLS)) + { + // Take the time spent on parsing rows into account + } + @ocifreestatement($result); + } } - @ocifreestatement($result); $splittime = explode(' ', microtime()); $splittime = $splittime[0] + $splittime[1]; diff --git a/phpBB/phpbb/db/driver/postgres.php b/phpBB/phpbb/db/driver/postgres.php index a3b9aa4c6b..44476612c3 100644 --- a/phpBB/phpbb/db/driver/postgres.php +++ b/phpBB/phpbb/db/driver/postgres.php @@ -123,14 +123,17 @@ class postgres extends \phpbb\db\driver\driver if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('pgsql_version')) === false) { $query_id = @pg_query($this->db_connect_id, 'SELECT VERSION() AS version'); - $row = @pg_fetch_assoc($query_id, null); - @pg_free_result($query_id); + if ($query_id) + { + $row = pg_fetch_assoc($query_id, null); + pg_free_result($query_id); - $this->sql_server_version = (!empty($row['version'])) ? trim(substr($row['version'], 10)) : 0; + $this->sql_server_version = (!empty($row['version'])) ? trim(substr($row['version'], 10)) : 0; - if (!empty($cache) && $use_cache) - { - $cache->put('pgsql_version', $this->sql_server_version); + if (!empty($cache) && $use_cache) + { + $cache->put('pgsql_version', $this->sql_server_version); + } } } @@ -200,12 +203,17 @@ class postgres extends \phpbb\db\driver\driver $this->sql_time += microtime(true) - $this->curtime; } + if (!$this->query_result) + { + return false; + } + if ($cache && $cache_ttl) { $this->open_queries[(int) $this->query_result] = $this->query_result; $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); } - else if (strpos($query, 'SELECT') === 0 && $this->query_result) + else if (strpos($query, 'SELECT') === 0) { $this->open_queries[(int) $this->query_result] = $this->query_result; } @@ -275,7 +283,7 @@ class postgres extends \phpbb\db\driver\driver return $cache->sql_fetchrow($query_id); } - return ($query_id !== false) ? @pg_fetch_assoc($query_id, null) : false; + return ($query_id) ? pg_fetch_assoc($query_id, null) : false; } /** @@ -295,7 +303,7 @@ class postgres extends \phpbb\db\driver\driver return $cache->sql_rowseek($rownum, $query_id); } - return ($query_id !== false) ? @pg_result_seek($query_id, $rownum) : false; + return ($query_id) ? @pg_result_seek($query_id, $rownum) : false; } /** @@ -317,8 +325,8 @@ class postgres extends \phpbb\db\driver\driver return false; } - $temp_result = @pg_fetch_assoc($temp_q_id, null); - @pg_free_result($query_id); + $temp_result = pg_fetch_assoc($temp_q_id, null); + pg_free_result($query_id); return ($temp_result) ? $temp_result['last_value'] : false; } @@ -347,7 +355,7 @@ class postgres extends \phpbb\db\driver\driver if (isset($this->open_queries[(int) $query_id])) { unset($this->open_queries[(int) $query_id]); - return @pg_free_result($query_id); + return pg_free_result($query_id); } return false; @@ -453,12 +461,12 @@ class postgres extends \phpbb\db\driver\driver if ($result = @pg_query($this->db_connect_id, "EXPLAIN $explain_query")) { - while ($row = @pg_fetch_assoc($result, null)) + while ($row = pg_fetch_assoc($result, null)) { $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); } + pg_free_result($result); } - @pg_free_result($result); if ($html_table) { @@ -473,11 +481,14 @@ class postgres extends \phpbb\db\driver\driver $endtime = $endtime[0] + $endtime[1]; $result = @pg_query($this->db_connect_id, $query); - while ($void = @pg_fetch_assoc($result, null)) + if ($result) { - // Take the time spent on parsing rows into account + while ($void = pg_fetch_assoc($result, null)) + { + // Take the time spent on parsing rows into account + } + pg_free_result($result); } - @pg_free_result($result); $splittime = explode(' ', microtime()); $splittime = $splittime[0] + $splittime[1]; diff --git a/phpBB/phpbb/db/driver/sqlite.php b/phpBB/phpbb/db/driver/sqlite.php index d5da0e2438..8e205ebb81 100644 --- a/phpBB/phpbb/db/driver/sqlite.php +++ b/phpBB/phpbb/db/driver/sqlite.php @@ -70,13 +70,16 @@ class sqlite extends \phpbb\db\driver\driver if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('sqlite_version')) === false) { $result = @sqlite_query('SELECT sqlite_version() AS version', $this->db_connect_id); - $row = @sqlite_fetch_array($result, SQLITE_ASSOC); + if ($result) + { + $row = sqlite_fetch_array($result, SQLITE_ASSOC); - $this->sql_server_version = (!empty($row['version'])) ? $row['version'] : 0; + $this->sql_server_version = (!empty($row['version'])) ? $row['version'] : 0; - if (!empty($cache) && $use_cache) - { - $cache->put('sqlite_version', $this->sql_server_version); + if (!empty($cache) && $use_cache) + { + $cache->put('sqlite_version', $this->sql_server_version); + } } } @@ -145,14 +148,14 @@ class sqlite extends \phpbb\db\driver\driver $this->sql_time += microtime(true) - $this->curtime; } - if ($cache && $cache_ttl) + if (!$this->query_result) { - $this->open_queries[(int) $this->query_result] = $this->query_result; - $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); + return false; } - else if (strpos($query, 'SELECT') === 0 && $this->query_result) + + if ($cache && $cache_ttl) { - $this->open_queries[(int) $this->query_result] = $this->query_result; + $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); } } else if (defined('DEBUG')) @@ -211,7 +214,7 @@ class sqlite extends \phpbb\db\driver\driver return $cache->sql_fetchrow($query_id); } - return ($query_id !== false) ? @sqlite_fetch_array($query_id, SQLITE_ASSOC) : false; + return ($query_id) ? sqlite_fetch_array($query_id, SQLITE_ASSOC) : false; } /** @@ -231,7 +234,7 @@ class sqlite extends \phpbb\db\driver\driver return $cache->sql_rowseek($rownum, $query_id); } - return ($query_id !== false) ? @sqlite_seek($query_id, $rownum) : false; + return ($query_id) ? @sqlite_seek($query_id, $rownum) : false; } /** @@ -362,9 +365,12 @@ class sqlite extends \phpbb\db\driver\driver $endtime = $endtime[0] + $endtime[1]; $result = @sqlite_query($query, $this->db_connect_id); - while ($void = @sqlite_fetch_array($result, SQLITE_ASSOC)) + if ($result) { - // Take the time spent on parsing rows into account + while ($void = sqlite_fetch_array($result, SQLITE_ASSOC)) + { + // Take the time spent on parsing rows into account + } } $splittime = explode(' ', microtime()); diff --git a/phpBB/phpbb/db/driver/sqlite3.php b/phpBB/phpbb/db/driver/sqlite3.php index cc3352af34..0508500c52 100644 --- a/phpBB/phpbb/db/driver/sqlite3.php +++ b/phpBB/phpbb/db/driver/sqlite3.php @@ -102,7 +102,7 @@ class sqlite3 extends \phpbb\db\driver\driver break; case 'rollback': - return $this->dbo->exec('ROLLBACK'); + return @$this->dbo->exec('ROLLBACK'); break; } @@ -134,9 +134,26 @@ class sqlite3 extends \phpbb\db\driver\driver if ($this->query_result === false) { + if ($this->transaction === true && strpos($query, 'INSERT') === 0) + { + $query = preg_replace('/^INSERT INTO/', 'INSERT OR ROLLBACK INTO', $query); + } + if (($this->query_result = @$this->dbo->query($query)) === false) { - $this->sql_error($query); + // Try to recover a lost database connection + if ($this->dbo && !@$this->dbo->lastErrorMsg()) + { + if ($this->sql_connect($this->server, $this->user, '', $this->dbname)) + { + $this->query_result = @$this->dbo->query($query); + } + } + + if ($this->query_result === false) + { + $this->sql_error($query); + } } if (defined('DEBUG')) @@ -148,6 +165,11 @@ class sqlite3 extends \phpbb\db\driver\driver $this->sql_time += microtime(true) - $this->curtime; } + if (!$this->query_result) + { + return false; + } + if ($cache && $cache_ttl) { $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); @@ -208,6 +230,7 @@ class sqlite3 extends \phpbb\db\driver\driver if ($query_id === false) { + /** @var \SQLite3Result $query_id */ $query_id = $this->query_result; } @@ -216,7 +239,7 @@ class sqlite3 extends \phpbb\db\driver\driver return $cache->sql_fetchrow($query_id); } - return is_object($query_id) ? $query_id->fetchArray(SQLITE3_ASSOC) : false; + return is_object($query_id) ? @$query_id->fetchArray(SQLITE3_ASSOC) : false; } /** @@ -389,9 +412,12 @@ class sqlite3 extends \phpbb\db\driver\driver $endtime = $endtime[0] + $endtime[1]; $result = $this->dbo->query($query); - while ($void = $result->fetchArray(SQLITE3_ASSOC)) + if ($result) { - // Take the time spent on parsing rows into account + while ($void = $result->fetchArray(SQLITE3_ASSOC)) + { + // Take the time spent on parsing rows into account + } } $splittime = explode(' ', microtime()); diff --git a/phpBB/phpbb/db/extractor/base_extractor.php b/phpBB/phpbb/db/extractor/base_extractor.php new file mode 100644 index 0000000000..547c85f066 --- /dev/null +++ b/phpBB/phpbb/db/extractor/base_extractor.php @@ -0,0 +1,252 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @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\extractor; + +use phpbb\db\extractor\exception\invalid_format_exception; +use phpbb\db\extractor\exception\extractor_not_initialized_exception; + +/** + * Abstract base class for database extraction + */ +abstract class base_extractor implements extractor_interface +{ + /** + * @var string phpBB root path + */ + protected $phpbb_root_path; + + /** + * @var \phpbb\request\request_interface + */ + protected $request; + + /** + * @var \phpbb\db\driver\driver_interface + */ + protected $db; + + /** + * @var bool + */ + protected $download; + + /** + * @var bool + */ + protected $store; + + /** + * @var int + */ + protected $time; + + /** + * @var string + */ + protected $format; + + /** + * @var resource + */ + protected $fp; + + /** + * @var string + */ + protected $write; + + /** + * @var string + */ + protected $close; + + /** + * @var bool + */ + protected $run_comp; + + /** + * @var bool + */ + protected $is_initialized; + + /** + * Constructor + * + * @param string $phpbb_root_path + * @param \phpbb\request\request_interface $request + * @param \phpbb\db\driver\driver_interface $db + */ + public function __construct($phpbb_root_path, \phpbb\request\request_interface $request, \phpbb\db\driver\driver_interface $db) + { + $this->phpbb_root_path = $phpbb_root_path; + $this->request = $request; + $this->db = $db; + $this->fp = null; + + $this->is_initialized = false; + } + + /** + * {@inheritdoc} + */ + public function init_extractor($format, $filename, $time, $download = false, $store = false) + { + $this->download = $download; + $this->store = $store; + $this->time = $time; + $this->format = $format; + + switch ($format) + { + case 'text': + $ext = '.sql'; + $open = 'fopen'; + $this->write = 'fwrite'; + $this->close = 'fclose'; + $mimetype = 'text/x-sql'; + break; + case 'bzip2': + $ext = '.sql.bz2'; + $open = 'bzopen'; + $this->write = 'bzwrite'; + $this->close = 'bzclose'; + $mimetype = 'application/x-bzip2'; + break; + case 'gzip': + $ext = '.sql.gz'; + $open = 'gzopen'; + $this->write = 'gzwrite'; + $this->close = 'gzclose'; + $mimetype = 'application/x-gzip'; + break; + default: + throw new invalid_format_exception(); + break; + } + + if ($download === true) + { + $name = $filename . $ext; + header('Cache-Control: private, no-cache'); + header("Content-Type: $mimetype; name=\"$name\""); + header("Content-disposition: attachment; filename=$name"); + + switch ($format) + { + case 'bzip2': + ob_start(); + break; + + case 'gzip': + if (strpos($this->request->header('Accept-Encoding'), 'gzip') !== false && strpos(strtolower($this->request->header('User-Agent')), 'msie') === false) + { + ob_start('ob_gzhandler'); + } + else + { + $this->run_comp = true; + } + break; + } + } + + if ($store === true) + { + $file = $this->phpbb_root_path . 'store/' . $filename . $ext; + + $this->fp = $open($file, 'w'); + + if (!$this->fp) + { + trigger_error('FILE_WRITE_FAIL', E_USER_ERROR); + } + } + + $this->is_initialized = true; + } + + /** + * {@inheritdoc} + */ + public function write_end() + { + static $close; + + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + if ($this->store) + { + if ($close === null) + { + $close = $this->close; + } + $close($this->fp); + } + + // bzip2 must be written all the way at the end + if ($this->download && $this->format === 'bzip2') + { + $c = ob_get_clean(); + echo bzcompress($c); + } + } + + /** + * {@inheritdoc} + */ + public function flush($data) + { + static $write; + + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + if ($this->store === true) + { + if ($write === null) + { + $write = $this->write; + } + $write($this->fp, $data); + } + + if ($this->download === true) + { + if ($this->format === 'bzip2' || $this->format === 'text' || ($this->format === 'gzip' && !$this->run_comp)) + { + echo $data; + } + + // we can write the gzip data as soon as we get it + if ($this->format === 'gzip') + { + if ($this->run_comp) + { + echo gzencode($data); + } + else + { + ob_flush(); + flush(); + } + } + } + } +} diff --git a/phpBB/phpbb/db/extractor/exception/extractor_not_initialized_exception.php b/phpBB/phpbb/db/extractor/exception/extractor_not_initialized_exception.php new file mode 100644 index 0000000000..62eb434be1 --- /dev/null +++ b/phpBB/phpbb/db/extractor/exception/extractor_not_initialized_exception.php @@ -0,0 +1,24 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @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\extractor\exception; + +use phpbb\exception\runtime_exception; + +/** +* This exception is thrown when invalid format is given to the extractor +*/ +class extractor_not_initialized_exception extends runtime_exception +{ + +} diff --git a/phpBB/phpbb/db/extractor/exception/invalid_format_exception.php b/phpBB/phpbb/db/extractor/exception/invalid_format_exception.php new file mode 100644 index 0000000000..6be24cb5dc --- /dev/null +++ b/phpBB/phpbb/db/extractor/exception/invalid_format_exception.php @@ -0,0 +1,22 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @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\extractor\exception; + +/** +* This exception is thrown when invalid format is given to the extractor +*/ +class invalid_format_exception extends \InvalidArgumentException +{ + +} diff --git a/phpBB/phpbb/db/extractor/extractor_interface.php b/phpBB/phpbb/db/extractor/extractor_interface.php new file mode 100644 index 0000000000..ff45df9bb7 --- /dev/null +++ b/phpBB/phpbb/db/extractor/extractor_interface.php @@ -0,0 +1,80 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @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\extractor; + +/** +* Database extractor interface +*/ +interface extractor_interface +{ + /** + * Start the extraction of the database + * + * This function initialize the database extraction. It is required to call this + * function before calling any other extractor functions. + * + * @param string $format + * @param string $filename + * @param int $time + * @param bool $download + * @param bool $store + * @return null + * @throws \phpbb\db\extractor\exception\invalid_format_exception when $format is invalid + */ + public function init_extractor($format, $filename, $time, $download = false, $store = false); + + /** + * Writes header comments to the database backup + * + * @param string $table_prefix prefix of phpBB database tables + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + public function write_start($table_prefix); + + /** + * Closes file and/or dumps download data + * + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + public function write_end(); + + /** + * Extracts database table structure + * + * @param string $table_name name of the database table + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + public function write_table($table_name); + + /** + * Extracts data from database table + * + * @param string $table_name name of the database table + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + public function write_data($table_name); + + /** + * Writes data to file/download content + * + * @param string $data + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + public function flush($data); +} diff --git a/phpBB/phpbb/db/extractor/factory.php b/phpBB/phpbb/db/extractor/factory.php new file mode 100644 index 0000000000..a1ffb65595 --- /dev/null +++ b/phpBB/phpbb/db/extractor/factory.php @@ -0,0 +1,79 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @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\extractor; + +/** +* A factory which serves the suitable extractor instance for the given dbal +*/ +class factory +{ + /** + * @var \phpbb\db\driver\driver_interface + */ + protected $db; + + /** + * @var \Symfony\Component\DependencyInjection\ContainerInterface + */ + protected $container; + + /** + * Extractor factory constructor + * + * @param \phpbb\db\driver\driver_interface $db + * @param \Symfony\Component\DependencyInjection\ContainerInterface $container + */ + public function __construct(\phpbb\db\driver\driver_interface $db, \Symfony\Component\DependencyInjection\ContainerInterface $container) + { + $this->db = $db; + $this->container = $container; + } + + /** + * DB extractor factory getter + * + * @return \phpbb\db\extractor\extractor_interface an appropriate instance of the database extractor for the used database driver + * @throws \InvalidArgumentException when the database driver is unknown + */ + public function get() + { + // Return the appropriate DB extractor + if ($this->db instanceof \phpbb\db\driver\mssql || $this->db instanceof \phpbb\db\driver\mssql_base) + { + return $this->container->get('dbal.extractor.extractors.mssql_extractor'); + } + else if ($this->db instanceof \phpbb\db\driver\mysql_base) + { + return $this->container->get('dbal.extractor.extractors.mysql_extractor'); + } + else if ($this->db instanceof \phpbb\db\driver\oracle) + { + return $this->container->get('dbal.extractor.extractors.oracle_extractor'); + } + else if ($this->db instanceof \phpbb\db\driver\postgres) + { + return $this->container->get('dbal.extractor.extractors.postgres_extractor'); + } + else if ($this->db instanceof \phpbb\db\driver\sqlite) + { + return $this->container->get('dbal.extractor.extractors.sqlite_extractor'); + } + else if ($this->db instanceof \phpbb\db\driver\sqlite3) + { + return $this->container->get('dbal.extractor.extractors.sqlite3_extractor'); + } + + throw new \InvalidArgumentException('Invalid database driver given'); + } +} diff --git a/phpBB/phpbb/db/extractor/mssql_extractor.php b/phpBB/phpbb/db/extractor/mssql_extractor.php new file mode 100644 index 0000000000..fc30f4789d --- /dev/null +++ b/phpBB/phpbb/db/extractor/mssql_extractor.php @@ -0,0 +1,524 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @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\extractor; + +use phpbb\db\extractor\exception\extractor_not_initialized_exception; + +class mssql_extractor extends base_extractor +{ + /** + * Writes closing line(s) to database backup + * + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + public function write_end() + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $this->flush("COMMIT\nGO\n"); + parent::write_end(); + } + + /** + * {@inheritdoc} + */ + public function write_start($table_prefix) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql_data = "--\n"; + $sql_data .= "-- phpBB Backup Script\n"; + $sql_data .= "-- Dump of tables for $table_prefix\n"; + $sql_data .= "-- DATE : " . gmdate("d-m-Y H:i:s", $this->time) . " GMT\n"; + $sql_data .= "--\n"; + $sql_data .= "BEGIN TRANSACTION\n"; + $sql_data .= "GO\n"; + $this->flush($sql_data); + } + + /** + * {@inheritdoc} + */ + public function write_table($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql_data = '-- Table: ' . $table_name . "\n"; + $sql_data .= "IF OBJECT_ID(N'$table_name', N'U') IS NOT NULL\n"; + $sql_data .= "DROP TABLE $table_name;\n"; + $sql_data .= "GO\n"; + $sql_data .= "\nCREATE TABLE [$table_name] (\n"; + $rows = array(); + + $text_flag = false; + + $sql = "SELECT COLUMN_NAME, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, COLUMNPROPERTY(object_id(TABLE_NAME), COLUMN_NAME, 'IsIdentity') as IS_IDENTITY + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_NAME = '$table_name'"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $line = "\t[{$row['COLUMN_NAME']}] [{$row['DATA_TYPE']}]"; + + if ($row['DATA_TYPE'] == 'text') + { + $text_flag = true; + } + + if ($row['IS_IDENTITY']) + { + $line .= ' IDENTITY (1 , 1)'; + } + + if ($row['CHARACTER_MAXIMUM_LENGTH'] && $row['DATA_TYPE'] !== 'text') + { + $line .= ' (' . $row['CHARACTER_MAXIMUM_LENGTH'] . ')'; + } + + if ($row['IS_NULLABLE'] == 'YES') + { + $line .= ' NULL'; + } + else + { + $line .= ' NOT NULL'; + } + + if ($row['COLUMN_DEFAULT']) + { + $line .= ' DEFAULT ' . $row['COLUMN_DEFAULT']; + } + + $rows[] = $line; + } + $this->db->sql_freeresult($result); + + $sql_data .= implode(",\n", $rows); + $sql_data .= "\n) ON [PRIMARY]"; + + if ($text_flag) + { + $sql_data .= " TEXTIMAGE_ON [PRIMARY]"; + } + + $sql_data .= "\nGO\n\n"; + $rows = array(); + + $sql = "SELECT CONSTRAINT_NAME, COLUMN_NAME + FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE + WHERE TABLE_NAME = '$table_name'"; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if (!sizeof($rows)) + { + $sql_data .= "ALTER TABLE [$table_name] WITH NOCHECK ADD\n"; + $sql_data .= "\tCONSTRAINT [{$row['CONSTRAINT_NAME']}] PRIMARY KEY CLUSTERED \n\t(\n"; + } + $rows[] = "\t\t[{$row['COLUMN_NAME']}]"; + } + if (sizeof($rows)) + { + $sql_data .= implode(",\n", $rows); + $sql_data .= "\n\t) ON [PRIMARY] \nGO\n"; + } + $this->db->sql_freeresult($result); + + $index = 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[$row['INDEX_NAME']][] = '[' . $row['COLUMN_NAME'] . ']'; + } + } + $this->db->sql_freeresult($result); + + foreach ($index as $index_name => $column_name) + { + $index[$index_name] = implode(', ', $column_name); + } + + foreach ($index as $index_name => $columns) + { + $sql_data .= "\nCREATE INDEX [$index_name] ON [$table_name]($columns) ON [PRIMARY]\nGO\n"; + } + $this->flush($sql_data); + } + + /** + * {@inheritdoc} + */ + public function write_data($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + if ($this->db->get_sql_layer() === 'mssql') + { + $this->write_data_mssql($table_name); + } + else if ($this->db->get_sql_layer() === 'mssqlnative') + { + $this->write_data_mssqlnative($table_name); + } + else + { + $this->write_data_odbc($table_name); + } + } + + /** + * Extracts data from database table (for MSSQL driver) + * + * @param string $table_name name of the database table + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + protected function write_data_mssql($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $ary_type = $ary_name = array(); + $ident_set = false; + $sql_data = ''; + + // Grab all of the data from current table. + $sql = "SELECT * + FROM $table_name"; + $result = $this->db->sql_query($sql); + + $retrieved_data = mssql_num_rows($result); + + $i_num_fields = mssql_num_fields($result); + + for ($i = 0; $i < $i_num_fields; $i++) + { + $ary_type[$i] = mssql_field_type($result, $i); + $ary_name[$i] = mssql_field_name($result, $i); + } + + if ($retrieved_data) + { + $sql = "SELECT 1 as has_identity + FROM INFORMATION_SCHEMA.COLUMNS + WHERE COLUMNPROPERTY(object_id('$table_name'), COLUMN_NAME, 'IsIdentity') = 1"; + $result2 = $this->db->sql_query($sql); + $row2 = $this->db->sql_fetchrow($result2); + if (!empty($row2['has_identity'])) + { + $sql_data .= "\nSET IDENTITY_INSERT $table_name ON\nGO\n"; + $ident_set = true; + } + $this->db->sql_freeresult($result2); + } + + while ($row = $this->db->sql_fetchrow($result)) + { + $schema_vals = $schema_fields = array(); + + // Build the SQL statement to recreate the data. + for ($i = 0; $i < $i_num_fields; $i++) + { + $str_val = $row[$ary_name[$i]]; + + if (preg_match('#char|text|bool|varbinary#i', $ary_type[$i])) + { + $str_quote = ''; + $str_empty = "''"; + $str_val = sanitize_data_mssql(str_replace("'", "''", $str_val)); + } + else if (preg_match('#date|timestamp#i', $ary_type[$i])) + { + if (empty($str_val)) + { + $str_quote = ''; + } + else + { + $str_quote = "'"; + } + } + else + { + $str_quote = ''; + $str_empty = 'NULL'; + } + + if (empty($str_val) && $str_val !== '0' && !(is_int($str_val) || is_float($str_val))) + { + $str_val = $str_empty; + } + + $schema_vals[$i] = $str_quote . $str_val . $str_quote; + $schema_fields[$i] = $ary_name[$i]; + } + + // Take the ordered fields and their associated data and build it + // into a valid sql statement to recreate that field in the data. + $sql_data .= "INSERT INTO $table_name (" . implode(', ', $schema_fields) . ') VALUES (' . implode(', ', $schema_vals) . ");\nGO\n"; + + $this->flush($sql_data); + $sql_data = ''; + } + $this->db->sql_freeresult($result); + + if ($retrieved_data && $ident_set) + { + $sql_data .= "\nSET IDENTITY_INSERT $table_name OFF\nGO\n"; + } + $this->flush($sql_data); + } + + /** + * Extracts data from database table (for MSSQL Native driver) + * + * @param string $table_name name of the database table + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + protected function write_data_mssqlnative($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $ary_type = $ary_name = array(); + $ident_set = false; + $sql_data = ''; + + // Grab all of the data from current table. + $sql = "SELECT * FROM $table_name"; + $this->db->mssqlnative_set_query_options(array('Scrollable' => SQLSRV_CURSOR_STATIC)); + $result = $this->db->sql_query($sql); + + $retrieved_data = $this->db->mssqlnative_num_rows($result); + + if (!$retrieved_data) + { + $this->db->sql_freeresult($result); + return; + } + + $sql = "SELECT COLUMN_NAME, DATA_TYPE + FROM INFORMATION_SCHEMA.COLUMNS + WHERE INFORMATION_SCHEMA.COLUMNS.TABLE_NAME = '" . $this->db->sql_escape($table_name) . "'"; + $result_fields = $this->db->sql_query($sql); + + $i_num_fields = 0; + while ($row = $this->db->sql_fetchrow($result_fields)) + { + $ary_type[$i_num_fields] = $row['DATA_TYPE']; + $ary_name[$i_num_fields] = $row['COLUMN_NAME']; + $i_num_fields++; + } + $this->db->sql_freeresult($result_fields); + + $sql = "SELECT 1 as has_identity + FROM INFORMATION_SCHEMA.COLUMNS + WHERE COLUMNPROPERTY(object_id('$table_name'), COLUMN_NAME, 'IsIdentity') = 1"; + $result2 = $this->db->sql_query($sql); + $row2 = $this->db->sql_fetchrow($result2); + + if (!empty($row2['has_identity'])) + { + $sql_data .= "\nSET IDENTITY_INSERT $table_name ON\nGO\n"; + $ident_set = true; + } + $this->db->sql_freeresult($result2); + + while ($row = $this->db->sql_fetchrow($result)) + { + $schema_vals = $schema_fields = array(); + + // Build the SQL statement to recreate the data. + for ($i = 0; $i < $i_num_fields; $i++) + { + $str_val = $row[$ary_name[$i]]; + + // defaults to type number - better quote just to be safe, so check for is_int too + if (is_int($ary_type[$i]) || preg_match('#char|text|bool|varbinary#i', $ary_type[$i])) + { + $str_quote = ''; + $str_empty = "''"; + $str_val = sanitize_data_mssql(str_replace("'", "''", $str_val)); + } + else if (preg_match('#date|timestamp#i', $ary_type[$i])) + { + if (empty($str_val)) + { + $str_quote = ''; + } + else + { + $str_quote = "'"; + } + } + else + { + $str_quote = ''; + $str_empty = 'NULL'; + } + + if (empty($str_val) && $str_val !== '0' && !(is_int($str_val) || is_float($str_val))) + { + $str_val = $str_empty; + } + + $schema_vals[$i] = $str_quote . $str_val . $str_quote; + $schema_fields[$i] = $ary_name[$i]; + } + + // Take the ordered fields and their associated data and build it + // into a valid sql statement to recreate that field in the data. + $sql_data .= "INSERT INTO $table_name (" . implode(', ', $schema_fields) . ') VALUES (' . implode(', ', $schema_vals) . ");\nGO\n"; + + $this->flush($sql_data); + $sql_data = ''; + } + $this->db->sql_freeresult($result); + + if ($ident_set) + { + $sql_data .= "\nSET IDENTITY_INSERT $table_name OFF\nGO\n"; + } + $this->flush($sql_data); + } + + /** + * Extracts data from database table (for ODBC driver) + * + * @param string $table_name name of the database table + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + protected function write_data_odbc($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $ary_type = $ary_name = array(); + $ident_set = false; + $sql_data = ''; + + // Grab all of the data from current table. + $sql = "SELECT * + FROM $table_name"; + $result = $this->db->sql_query($sql); + + $retrieved_data = odbc_num_rows($result); + + if ($retrieved_data) + { + $sql = "SELECT 1 as has_identity + FROM INFORMATION_SCHEMA.COLUMNS + WHERE COLUMNPROPERTY(object_id('$table_name'), COLUMN_NAME, 'IsIdentity') = 1"; + $result2 = $this->db->sql_query($sql); + $row2 = $this->db->sql_fetchrow($result2); + if (!empty($row2['has_identity'])) + { + $sql_data .= "\nSET IDENTITY_INSERT $table_name ON\nGO\n"; + $ident_set = true; + } + $this->db->sql_freeresult($result2); + } + + $i_num_fields = odbc_num_fields($result); + + for ($i = 0; $i < $i_num_fields; $i++) + { + $ary_type[$i] = odbc_field_type($result, $i + 1); + $ary_name[$i] = odbc_field_name($result, $i + 1); + } + + while ($row = $this->db->sql_fetchrow($result)) + { + $schema_vals = $schema_fields = array(); + + // Build the SQL statement to recreate the data. + for ($i = 0; $i < $i_num_fields; $i++) + { + $str_val = $row[$ary_name[$i]]; + + if (preg_match('#char|text|bool|varbinary#i', $ary_type[$i])) + { + $str_quote = ''; + $str_empty = "''"; + $str_val = sanitize_data_mssql(str_replace("'", "''", $str_val)); + } + else if (preg_match('#date|timestamp#i', $ary_type[$i])) + { + if (empty($str_val)) + { + $str_quote = ''; + } + else + { + $str_quote = "'"; + } + } + else + { + $str_quote = ''; + $str_empty = 'NULL'; + } + + if (empty($str_val) && $str_val !== '0' && !(is_int($str_val) || is_float($str_val))) + { + $str_val = $str_empty; + } + + $schema_vals[$i] = $str_quote . $str_val . $str_quote; + $schema_fields[$i] = $ary_name[$i]; + } + + // Take the ordered fields and their associated data and build it + // into a valid sql statement to recreate that field in the data. + $sql_data .= "INSERT INTO $table_name (" . implode(', ', $schema_fields) . ') VALUES (' . implode(', ', $schema_vals) . ");\nGO\n"; + + $this->flush($sql_data); + + $sql_data = ''; + + } + $this->db->sql_freeresult($result); + + if ($retrieved_data && $ident_set) + { + $sql_data .= "\nSET IDENTITY_INSERT $table_name OFF\nGO\n"; + } + $this->flush($sql_data); + } +} diff --git a/phpBB/phpbb/db/extractor/mysql_extractor.php b/phpBB/phpbb/db/extractor/mysql_extractor.php new file mode 100644 index 0000000000..34e309c19e --- /dev/null +++ b/phpBB/phpbb/db/extractor/mysql_extractor.php @@ -0,0 +1,403 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @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\extractor; + +use phpbb\db\extractor\exception\extractor_not_initialized_exception; + +class mysql_extractor extends base_extractor +{ + /** + * {@inheritdoc} + */ + public function write_start($table_prefix) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql_data = "#\n"; + $sql_data .= "# phpBB Backup Script\n"; + $sql_data .= "# Dump of tables for $table_prefix\n"; + $sql_data .= "# DATE : " . gmdate("d-m-Y H:i:s", $this->time) . " GMT\n"; + $sql_data .= "#\n"; + $this->flush($sql_data); + } + + /** + * {@inheritdoc} + */ + public function write_table($table_name) + { + static $new_extract; + + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + if ($new_extract === null) + { + if ($this->db->get_sql_layer() === 'mysqli' || version_compare($this->db->sql_server_info(true), '3.23.20', '>=')) + { + $new_extract = true; + } + else + { + $new_extract = false; + } + } + + if ($new_extract) + { + $this->new_write_table($table_name); + } + else + { + $this->old_write_table($table_name); + } + } + + /** + * {@inheritdoc} + */ + public function write_data($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + if ($this->db->get_sql_layer() === 'mysqli') + { + $this->write_data_mysqli($table_name); + } + else + { + $this->write_data_mysql($table_name); + } + } + + /** + * Extracts data from database table (for MySQLi driver) + * + * @param string $table_name name of the database table + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + protected function write_data_mysqli($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql = "SELECT * + FROM $table_name"; + $result = mysqli_query($this->db->get_db_connect_id(), $sql, MYSQLI_USE_RESULT); + if ($result != false) + { + $fields_cnt = mysqli_num_fields($result); + + // Get field information + $field = mysqli_fetch_fields($result); + $field_set = array(); + + for ($j = 0; $j < $fields_cnt; $j++) + { + $field_set[] = $field[$j]->name; + } + + $search = array("\\", "'", "\x00", "\x0a", "\x0d", "\x1a", '"'); + $replace = array("\\\\", "\\'", '\0', '\n', '\r', '\Z', '\\"'); + $fields = implode(', ', $field_set); + $sql_data = 'INSERT INTO ' . $table_name . ' (' . $fields . ') VALUES '; + $first_set = true; + $query_len = 0; + $max_len = get_usable_memory(); + + while ($row = mysqli_fetch_row($result)) + { + $values = array(); + if ($first_set) + { + $query = $sql_data . '('; + } + else + { + $query .= ',('; + } + + for ($j = 0; $j < $fields_cnt; $j++) + { + if (!isset($row[$j]) || is_null($row[$j])) + { + $values[$j] = 'NULL'; + } + else if (($field[$j]->flags & 32768) && !($field[$j]->flags & 1024)) + { + $values[$j] = $row[$j]; + } + else + { + $values[$j] = "'" . str_replace($search, $replace, $row[$j]) . "'"; + } + } + $query .= implode(', ', $values) . ')'; + + $query_len += strlen($query); + if ($query_len > $max_len) + { + $this->flush($query . ";\n\n"); + $query = ''; + $query_len = 0; + $first_set = true; + } + else + { + $first_set = false; + } + } + mysqli_free_result($result); + + // check to make sure we have nothing left to flush + if (!$first_set && $query) + { + $this->flush($query . ";\n\n"); + } + } + } + + /** + * Extracts data from database table (for MySQL driver) + * + * @param string $table_name name of the database table + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + protected function write_data_mysql($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql = "SELECT * + FROM $table_name"; + $result = mysql_unbuffered_query($sql, $this->db->get_db_connect_id()); + + if ($result != false) + { + $fields_cnt = mysql_num_fields($result); + + // Get field information + $field = array(); + for ($i = 0; $i < $fields_cnt; $i++) + { + $field[] = mysql_fetch_field($result, $i); + } + $field_set = array(); + + for ($j = 0; $j < $fields_cnt; $j++) + { + $field_set[] = $field[$j]->name; + } + + $search = array("\\", "'", "\x00", "\x0a", "\x0d", "\x1a", '"'); + $replace = array("\\\\", "\\'", '\0', '\n', '\r', '\Z', '\\"'); + $fields = implode(', ', $field_set); + $sql_data = 'INSERT INTO ' . $table_name . ' (' . $fields . ') VALUES '; + $first_set = true; + $query_len = 0; + $max_len = get_usable_memory(); + + while ($row = mysql_fetch_row($result)) + { + $values = array(); + if ($first_set) + { + $query = $sql_data . '('; + } + else + { + $query .= ',('; + } + + for ($j = 0; $j < $fields_cnt; $j++) + { + if (!isset($row[$j]) || is_null($row[$j])) + { + $values[$j] = 'NULL'; + } + else if ($field[$j]->numeric && ($field[$j]->type !== 'timestamp')) + { + $values[$j] = $row[$j]; + } + else + { + $values[$j] = "'" . str_replace($search, $replace, $row[$j]) . "'"; + } + } + $query .= implode(', ', $values) . ')'; + + $query_len += strlen($query); + if ($query_len > $max_len) + { + $this->flush($query . ";\n\n"); + $query = ''; + $query_len = 0; + $first_set = true; + } + else + { + $first_set = false; + } + } + mysql_free_result($result); + + // check to make sure we have nothing left to flush + if (!$first_set && $query) + { + $this->flush($query . ";\n\n"); + } + } + } + + /** + * Extracts database table structure (for MySQLi or MySQL 3.23.20+) + * + * @param string $table_name name of the database table + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + protected function new_write_table($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql = 'SHOW CREATE TABLE ' . $table_name; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + + $sql_data = '# Table: ' . $table_name . "\n"; + $sql_data .= "DROP TABLE IF EXISTS $table_name;\n"; + $this->flush($sql_data . $row['Create Table'] . ";\n\n"); + + $this->db->sql_freeresult($result); + } + + /** + * Extracts database table structure (for MySQL verisons older than 3.23.20) + * + * @param string $table_name name of the database table + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + protected function old_write_table($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql_data = '# Table: ' . $table_name . "\n"; + $sql_data .= "DROP TABLE IF EXISTS $table_name;\n"; + $sql_data .= "CREATE TABLE $table_name(\n"; + $rows = array(); + + $sql = "SHOW FIELDS + FROM $table_name"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $line = ' ' . $row['Field'] . ' ' . $row['Type']; + + if (!is_null($row['Default'])) + { + $line .= " DEFAULT '{$row['Default']}'"; + } + + if ($row['Null'] != 'YES') + { + $line .= ' NOT NULL'; + } + + if ($row['Extra'] != '') + { + $line .= ' ' . $row['Extra']; + } + + $rows[] = $line; + } + $this->db->sql_freeresult($result); + + $sql = "SHOW KEYS + FROM $table_name"; + + $result = $this->db->sql_query($sql); + + $index = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $kname = $row['Key_name']; + + if ($kname != 'PRIMARY') + { + if ($row['Non_unique'] == 0) + { + $kname = "UNIQUE|$kname"; + } + } + + if ($row['Sub_part']) + { + $row['Column_name'] .= '(' . $row['Sub_part'] . ')'; + } + $index[$kname][] = $row['Column_name']; + } + $this->db->sql_freeresult($result); + + foreach ($index as $key => $columns) + { + $line = ' '; + + if ($key == 'PRIMARY') + { + $line .= 'PRIMARY KEY (' . implode(', ', $columns) . ')'; + } + else if (strpos($key, 'UNIQUE') === 0) + { + $line .= 'UNIQUE ' . substr($key, 7) . ' (' . implode(', ', $columns) . ')'; + } + else if (strpos($key, 'FULLTEXT') === 0) + { + $line .= 'FULLTEXT ' . substr($key, 9) . ' (' . implode(', ', $columns) . ')'; + } + else + { + $line .= "KEY $key (" . implode(', ', $columns) . ')'; + } + + $rows[] = $line; + } + + $sql_data .= implode(",\n", $rows); + $sql_data .= "\n);\n\n"; + + $this->flush($sql_data); + } +} diff --git a/phpBB/phpbb/db/extractor/oracle_extractor.php b/phpBB/phpbb/db/extractor/oracle_extractor.php new file mode 100644 index 0000000000..79a991889b --- /dev/null +++ b/phpBB/phpbb/db/extractor/oracle_extractor.php @@ -0,0 +1,263 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @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\extractor; + +use phpbb\db\extractor\exception\extractor_not_initialized_exception; + +class oracle_extractor extends base_extractor +{ + /** + * {@inheritdoc} + */ + public function write_table($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql_data = '-- Table: ' . $table_name . "\n"; + $sql_data .= "DROP TABLE $table_name\n/\n"; + $sql_data .= "\nCREATE TABLE $table_name (\n"; + + $sql = "SELECT COLUMN_NAME, DATA_TYPE, DATA_PRECISION, DATA_LENGTH, NULLABLE, DATA_DEFAULT + FROM ALL_TAB_COLS + WHERE table_name = '{$table_name}'"; + $result = $this->db->sql_query($sql); + + $rows = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $line = ' "' . $row['column_name'] . '" ' . $row['data_type']; + + if ($row['data_type'] !== 'CLOB') + { + if ($row['data_type'] !== 'VARCHAR2' && $row['data_type'] !== 'CHAR') + { + $line .= '(' . $row['data_precision'] . ')'; + } + else + { + $line .= '(' . $row['data_length'] . ')'; + } + } + + if (!empty($row['data_default'])) + { + $line .= ' DEFAULT ' . $row['data_default']; + } + + if ($row['nullable'] == 'N') + { + $line .= ' NOT NULL'; + } + $rows[] = $line; + } + $this->db->sql_freeresult($result); + + $sql = "SELECT A.CONSTRAINT_NAME, A.COLUMN_NAME + FROM USER_CONS_COLUMNS A, USER_CONSTRAINTS B + WHERE A.CONSTRAINT_NAME = B.CONSTRAINT_NAME + AND B.CONSTRAINT_TYPE = 'P' + AND A.TABLE_NAME = '{$table_name}'"; + $result = $this->db->sql_query($sql); + + $primary_key = array(); + $constraint_name = ''; + while ($row = $this->db->sql_fetchrow($result)) + { + $constraint_name = '"' . $row['constraint_name'] . '"'; + $primary_key[] = '"' . $row['column_name'] . '"'; + } + $this->db->sql_freeresult($result); + + if (sizeof($primary_key)) + { + $rows[] = " CONSTRAINT {$constraint_name} PRIMARY KEY (" . implode(', ', $primary_key) . ')'; + } + + $sql = "SELECT A.CONSTRAINT_NAME, A.COLUMN_NAME + FROM USER_CONS_COLUMNS A, USER_CONSTRAINTS B + WHERE A.CONSTRAINT_NAME = B.CONSTRAINT_NAME + AND B.CONSTRAINT_TYPE = 'U' + AND A.TABLE_NAME = '{$table_name}'"; + $result = $this->db->sql_query($sql); + + $unique = array(); + $constraint_name = ''; + while ($row = $this->db->sql_fetchrow($result)) + { + $constraint_name = '"' . $row['constraint_name'] . '"'; + $unique[] = '"' . $row['column_name'] . '"'; + } + $this->db->sql_freeresult($result); + + if (sizeof($unique)) + { + $rows[] = " CONSTRAINT {$constraint_name} UNIQUE (" . implode(', ', $unique) . ')'; + } + + $sql_data .= implode(",\n", $rows); + $sql_data .= "\n)\n/\n"; + + $sql = "SELECT A.REFERENCED_NAME, C.* + FROM USER_DEPENDENCIES A, USER_TRIGGERS B, USER_SEQUENCES C + WHERE A.REFERENCED_TYPE = 'SEQUENCE' + AND A.NAME = B.TRIGGER_NAME + AND B.TABLE_NAME = '{$table_name}' + AND C.SEQUENCE_NAME = A.REFERENCED_NAME"; + $result = $this->db->sql_query($sql); + + $type = $this->request->variable('type', ''); + + while ($row = $this->db->sql_fetchrow($result)) + { + $sql_data .= "\nDROP SEQUENCE \"{$row['referenced_name']}\"\n/\n"; + $sql_data .= "\nCREATE SEQUENCE \"{$row['referenced_name']}\""; + + if ($type == 'full') + { + $sql_data .= ' START WITH ' . $row['last_number']; + } + + $sql_data .= "\n/\n"; + } + $this->db->sql_freeresult($result); + + $sql = "SELECT DESCRIPTION, WHEN_CLAUSE, TRIGGER_BODY + FROM USER_TRIGGERS + WHERE TABLE_NAME = '{$table_name}'"; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $sql_data .= "\nCREATE OR REPLACE TRIGGER {$row['description']}WHEN ({$row['when_clause']})\n{$row['trigger_body']}\n/\n"; + } + $this->db->sql_freeresult($result); + + $sql = "SELECT A.INDEX_NAME, B.COLUMN_NAME + FROM USER_INDEXES A, USER_IND_COLUMNS B + WHERE A.UNIQUENESS = 'NONUNIQUE' + AND A.INDEX_NAME = B.INDEX_NAME + AND B.TABLE_NAME = '{$table_name}'"; + $result = $this->db->sql_query($sql); + + $index = array(); + + while ($row = $this->db->sql_fetchrow($result)) + { + $index[$row['index_name']][] = $row['column_name']; + } + + foreach ($index as $index_name => $column_names) + { + $sql_data .= "\nCREATE INDEX $index_name ON $table_name(" . implode(', ', $column_names) . ")\n/\n"; + } + $this->db->sql_freeresult($result); + $this->flush($sql_data); + } + + /** + * {@inheritdoc} + */ + public function write_data($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $ary_type = $ary_name = array(); + + // Grab all of the data from current table. + $sql = "SELECT * + FROM $table_name"; + $result = $this->db->sql_query($sql); + + $i_num_fields = ocinumcols($result); + + for ($i = 0; $i < $i_num_fields; $i++) + { + $ary_type[$i] = ocicolumntype($result, $i + 1); + $ary_name[$i] = ocicolumnname($result, $i + 1); + } + + while ($row = $this->db->sql_fetchrow($result)) + { + $schema_vals = $schema_fields = array(); + + // Build the SQL statement to recreate the data. + for ($i = 0; $i < $i_num_fields; $i++) + { + // Oracle uses uppercase - we use lowercase + $str_val = $row[strtolower($ary_name[$i])]; + + if (preg_match('#char|text|bool|raw|clob#i', $ary_type[$i])) + { + $str_quote = ''; + $str_empty = "''"; + $str_val = sanitize_data_oracle($str_val); + } + else if (preg_match('#date|timestamp#i', $ary_type[$i])) + { + if (empty($str_val)) + { + $str_quote = ''; + } + else + { + $str_quote = "'"; + } + } + else + { + $str_quote = ''; + $str_empty = 'NULL'; + } + + if (empty($str_val) && $str_val !== '0') + { + $str_val = $str_empty; + } + + $schema_vals[$i] = $str_quote . $str_val . $str_quote; + $schema_fields[$i] = '"' . $ary_name[$i] . '"'; + } + + // Take the ordered fields and their associated data and build it + // into a valid sql statement to recreate that field in the data. + $sql_data = "INSERT INTO $table_name (" . implode(', ', $schema_fields) . ') VALUES (' . implode(', ', $schema_vals) . ")\n/\n"; + + $this->flush($sql_data); + } + $this->db->sql_freeresult($result); + } + + /** + * {@inheritdoc} + */ + public function write_start($table_prefix) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql_data = "--\n"; + $sql_data .= "-- phpBB Backup Script\n"; + $sql_data .= "-- Dump of tables for $table_prefix\n"; + $sql_data .= "-- DATE : " . gmdate("d-m-Y H:i:s", $this->time) . " GMT\n"; + $sql_data .= "--\n"; + $this->flush($sql_data); + } +} diff --git a/phpBB/phpbb/db/extractor/postgres_extractor.php b/phpBB/phpbb/db/extractor/postgres_extractor.php new file mode 100644 index 0000000000..a98e39621c --- /dev/null +++ b/phpBB/phpbb/db/extractor/postgres_extractor.php @@ -0,0 +1,339 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @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\extractor; + +use phpbb\db\extractor\exception\extractor_not_initialized_exception; + +class postgres_extractor extends base_extractor +{ + /** + * {@inheritdoc} + */ + public function write_start($table_prefix) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql_data = "--\n"; + $sql_data .= "-- phpBB Backup Script\n"; + $sql_data .= "-- Dump of tables for $table_prefix\n"; + $sql_data .= "-- DATE : " . gmdate("d-m-Y H:i:s", $this->time) . " GMT\n"; + $sql_data .= "--\n"; + $sql_data .= "BEGIN TRANSACTION;\n"; + $this->flush($sql_data); + } + + /** + * {@inheritdoc} + */ + public function write_table($table_name) + { + static $domains_created = array(); + + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql = "SELECT a.domain_name, a.data_type, a.character_maximum_length, a.domain_default + FROM INFORMATION_SCHEMA.domains a, INFORMATION_SCHEMA.column_domain_usage b + WHERE a.domain_name = b.domain_name + AND b.table_name = '{$table_name}'"; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if (empty($domains_created[$row['domain_name']])) + { + $domains_created[$row['domain_name']] = true; + //$sql_data = "DROP DOMAIN {$row['domain_name']};\n"; + $sql_data = "CREATE DOMAIN {$row['domain_name']} as {$row['data_type']}"; + if (!empty($row['character_maximum_length'])) + { + $sql_data .= '(' . $row['character_maximum_length'] . ')'; + } + $sql_data .= ' NOT NULL'; + if (!empty($row['domain_default'])) + { + $sql_data .= ' DEFAULT ' . $row['domain_default']; + } + $this->flush($sql_data . ";\n"); + } + } + $this->db->sql_freeresult($result); + + $sql_data = '-- Table: ' . $table_name . "\n"; + $sql_data .= "DROP TABLE $table_name;\n"; + // 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)) + { + $sql_data .= "DROP SEQUENCE {$table_name}_seq;\n"; + $sql_data .= "CREATE SEQUENCE {$table_name}_seq;\n"; + } + $this->db->sql_freeresult($result); + + $field_query = "SELECT a.attnum, a.attname as field, t.typname as type, a.attlen as length, a.atttypmod as lengthvar, a.attnotnull as notnull + FROM pg_class c, pg_attribute a, pg_type t + WHERE c.relname = '" . $this->db->sql_escape($table_name) . "' + AND a.attnum > 0 + AND a.attrelid = c.oid + AND a.atttypid = t.oid + ORDER BY a.attnum"; + $result = $this->db->sql_query($field_query); + + $sql_data .= "CREATE TABLE $table_name(\n"; + $lines = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + // Get the data from the table + $sql_get_default = "SELECT pg_get_expr(d.adbin, d.adrelid) as rowdefault + FROM pg_attrdef d, pg_class c + WHERE (c.relname = '" . $this->db->sql_escape($table_name) . "') + AND (c.oid = d.adrelid) + AND d.adnum = " . $row['attnum']; + $def_res = $this->db->sql_query($sql_get_default); + $def_row = $this->db->sql_fetchrow($def_res); + $this->db->sql_freeresult($def_res); + + if (empty($def_row)) + { + unset($row['rowdefault']); + } + else + { + $row['rowdefault'] = $def_row['rowdefault']; + } + + if ($row['type'] == 'bpchar') + { + // Internally stored as bpchar, but isn't accepted in a CREATE TABLE statement. + $row['type'] = 'char'; + } + + $line = ' ' . $row['field'] . ' ' . $row['type']; + + if (strpos($row['type'], 'char') !== false) + { + if ($row['lengthvar'] > 0) + { + $line .= '(' . ($row['lengthvar'] - 4) . ')'; + } + } + + if (strpos($row['type'], 'numeric') !== false) + { + $line .= '('; + $line .= sprintf("%s,%s", (($row['lengthvar'] >> 16) & 0xffff), (($row['lengthvar'] - 4) & 0xffff)); + $line .= ')'; + } + + if (isset($row['rowdefault'])) + { + $line .= ' DEFAULT ' . $row['rowdefault']; + } + + if ($row['notnull'] == 't') + { + $line .= ' NOT NULL'; + } + + $lines[] = $line; + } + $this->db->sql_freeresult($result); + + // Get the listing of primary keys. + $sql_pri_keys = "SELECT ic.relname as index_name, bc.relname as tab_name, ta.attname as column_name, i.indisunique as unique_key, i.indisprimary as primary_key + FROM pg_class bc, pg_class ic, pg_index i, pg_attribute ta, pg_attribute ia + WHERE (bc.oid = i.indrelid) + AND (ic.oid = i.indexrelid) + AND (ia.attrelid = i.indexrelid) + AND (ta.attrelid = bc.oid) + AND (bc.relname = '" . $this->db->sql_escape($table_name) . "') + AND (ta.attrelid = i.indrelid) + AND (ta.attnum = i.indkey[ia.attnum-1]) + ORDER BY index_name, tab_name, column_name"; + + $result = $this->db->sql_query($sql_pri_keys); + + $index_create = $index_rows = $primary_key = array(); + + // We do this in two steps. It makes placing the comma easier + while ($row = $this->db->sql_fetchrow($result)) + { + if ($row['primary_key'] == 't') + { + $primary_key[] = $row['column_name']; + $primary_key_name = $row['index_name']; + } + else + { + // We have to store this all this info because it is possible to have a multi-column key... + // we can loop through it again and build the statement + $index_rows[$row['index_name']]['table'] = $table_name; + $index_rows[$row['index_name']]['unique'] = ($row['unique_key'] == 't') ? true : false; + $index_rows[$row['index_name']]['column_names'][] = $row['column_name']; + } + } + $this->db->sql_freeresult($result); + + if (!empty($index_rows)) + { + foreach ($index_rows as $idx_name => $props) + { + $index_create[] = 'CREATE ' . ($props['unique'] ? 'UNIQUE ' : '') . "INDEX $idx_name ON $table_name (" . implode(', ', $props['column_names']) . ");"; + } + } + + if (!empty($primary_key)) + { + $lines[] = " CONSTRAINT $primary_key_name PRIMARY KEY (" . implode(', ', $primary_key) . ")"; + } + + // Generate constraint clauses for CHECK constraints + $sql_checks = "SELECT conname as index_name, consrc + FROM pg_constraint, 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 = pg_constraint.conrelid + AND c.conname = pg_constraint.conname + AND c.consrc = pg_constraint.consrc + AND c.conrelid = i.inhparent + )"; + $result = $this->db->sql_query($sql_checks); + + // Add the constraints to the sql file. + while ($row = $this->db->sql_fetchrow($result)) + { + if (!is_null($row['consrc'])) + { + $lines[] = ' CONSTRAINT ' . $row['index_name'] . ' CHECK ' . $row['consrc']; + } + } + $this->db->sql_freeresult($result); + + $sql_data .= implode(", \n", $lines); + $sql_data .= "\n);\n"; + + if (!empty($index_create)) + { + $sql_data .= implode("\n", $index_create) . "\n\n"; + } + $this->flush($sql_data); + } + + /** + * {@inheritdoc} + */ + public function write_data($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + // Grab all of the data from current table. + $sql = "SELECT * + FROM $table_name"; + $result = $this->db->sql_query($sql); + + $i_num_fields = pg_num_fields($result); + $seq = ''; + + for ($i = 0; $i < $i_num_fields; $i++) + { + $ary_type[] = pg_field_type($result, $i); + $ary_name[] = pg_field_name($result, $i); + + $sql = "SELECT pg_get_expr(d.adbin, d.adrelid) as rowdefault + FROM pg_attrdef d, pg_class c + WHERE (c.relname = '{$table_name}') + AND (c.oid = d.adrelid) + AND d.adnum = " . strval($i + 1); + $result2 = $this->db->sql_query($sql); + if ($row = $this->db->sql_fetchrow($result2)) + { + // Determine if we must reset the sequences + if (strpos($row['rowdefault'], "nextval('") === 0) + { + $seq .= "SELECT SETVAL('{$table_name}_seq',(select case when max({$ary_name[$i]})>0 then max({$ary_name[$i]})+1 else 1 end FROM {$table_name}));\n"; + } + } + } + + $this->flush("COPY $table_name (" . implode(', ', $ary_name) . ') FROM stdin;' . "\n"); + while ($row = $this->db->sql_fetchrow($result)) + { + $schema_vals = array(); + + // Build the SQL statement to recreate the data. + for ($i = 0; $i < $i_num_fields; $i++) + { + $str_val = $row[$ary_name[$i]]; + + if (preg_match('#char|text|bool|bytea#i', $ary_type[$i])) + { + $str_val = str_replace(array("\n", "\t", "\r", "\b", "\f", "\v"), array('\n', '\t', '\r', '\b', '\f', '\v'), addslashes($str_val)); + $str_empty = ''; + } + else + { + $str_empty = '\N'; + } + + if (empty($str_val) && $str_val !== '0') + { + $str_val = $str_empty; + } + + $schema_vals[] = $str_val; + } + + // Take the ordered fields and their associated data and build it + // into a valid sql statement to recreate that field in the data. + $this->flush(implode("\t", $schema_vals) . "\n"); + } + $this->db->sql_freeresult($result); + $this->flush("\\.\n"); + + // Write out the sequence statements + $this->flush($seq); + } + + /** + * Writes closing line(s) to database backup + * + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + public function write_end() + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $this->flush("COMMIT;\n"); + parent::write_end(); + } +} diff --git a/phpBB/phpbb/db/extractor/sqlite3_extractor.php b/phpBB/phpbb/db/extractor/sqlite3_extractor.php new file mode 100644 index 0000000000..ce8da6a652 --- /dev/null +++ b/phpBB/phpbb/db/extractor/sqlite3_extractor.php @@ -0,0 +1,151 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @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\extractor; + +use phpbb\db\extractor\exception\extractor_not_initialized_exception; + +class sqlite3_extractor extends base_extractor +{ + /** + * {@inheritdoc} + */ + public function write_start($table_prefix) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql_data = "--\n"; + $sql_data .= "-- phpBB Backup Script\n"; + $sql_data .= "-- Dump of tables for $table_prefix\n"; + $sql_data .= "-- DATE : " . gmdate("d-m-Y H:i:s", $this->time) . " GMT\n"; + $sql_data .= "--\n"; + $sql_data .= "BEGIN TRANSACTION;\n"; + $this->flush($sql_data); + } + + /** + * {@inheritdoc} + */ + public function write_table($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql_data = '-- Table: ' . $table_name . "\n"; + $sql_data .= "DROP TABLE $table_name;\n"; + + $sql = "SELECT sql + FROM sqlite_master + WHERE type = 'table' + AND name = '" . $this->db->sql_escape($table_name) . "' + ORDER BY name ASC;"; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + // Create Table + $sql_data .= $row['sql'] . ";\n"; + + $result = $this->db->sql_query("PRAGMA index_list('" . $this->db->sql_escape($table_name) . "');"); + + while ($row = $this->db->sql_fetchrow($result)) + { + if (strpos($row['name'], 'autoindex') !== false) + { + continue; + } + + $result2 = $this->db->sql_query("PRAGMA index_info('" . $this->db->sql_escape($row['name']) . "');"); + + $fields = array(); + while ($row2 = $this->db->sql_fetchrow($result2)) + { + $fields[] = $row2['name']; + } + $this->db->sql_freeresult($result2); + + $sql_data .= 'CREATE ' . ($row['unique'] ? 'UNIQUE ' : '') . 'INDEX ' . $row['name'] . ' ON ' . $table_name . ' (' . implode(', ', $fields) . ");\n"; + } + $this->db->sql_freeresult($result); + + $this->flush($sql_data . "\n"); + } + + /** + * {@inheritdoc} + */ + public function write_data($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $result = $this->db->sql_query("PRAGMA table_info('" . $this->db->sql_escape($table_name) . "');"); + + $col_types = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $col_types[$row['name']] = $row['type']; + } + $this->db->sql_freeresult($result); + + $sql_insert = 'INSERT INTO ' . $table_name . ' (' . implode(', ', array_keys($col_types)) . ') VALUES ('; + + $sql = "SELECT * + FROM $table_name"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + foreach ($row as $column_name => $column_data) + { + if (is_null($column_data)) + { + $row[$column_name] = 'NULL'; + } + else if ($column_data === '') + { + $row[$column_name] = "''"; + } + else if (stripos($col_types[$column_name], 'text') !== false || stripos($col_types[$column_name], 'char') !== false || stripos($col_types[$column_name], 'blob') !== false) + { + $row[$column_name] = sanitize_data_generic(str_replace("'", "''", $column_data)); + } + } + $this->flush($sql_insert . implode(', ', $row) . ");\n"); + } + } + + /** + * Writes closing line(s) to database backup + * + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + public function write_end() + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $this->flush("COMMIT;\n"); + parent::write_end(); + } +} diff --git a/phpBB/phpbb/db/extractor/sqlite_extractor.php b/phpBB/phpbb/db/extractor/sqlite_extractor.php new file mode 100644 index 0000000000..2734e23235 --- /dev/null +++ b/phpBB/phpbb/db/extractor/sqlite_extractor.php @@ -0,0 +1,149 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @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\extractor; + +use phpbb\db\extractor\exception\extractor_not_initialized_exception; + +class sqlite_extractor extends base_extractor +{ + /** + * {@inheritdoc} + */ + public function write_start($table_prefix) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql_data = "--\n"; + $sql_data .= "-- phpBB Backup Script\n"; + $sql_data .= "-- Dump of tables for $table_prefix\n"; + $sql_data .= "-- DATE : " . gmdate("d-m-Y H:i:s", $this->time) . " GMT\n"; + $sql_data .= "--\n"; + $sql_data .= "BEGIN TRANSACTION;\n"; + $this->flush($sql_data); + } + + /** + * {@inheritdoc} + */ + public function write_table($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql_data = '-- Table: ' . $table_name . "\n"; + $sql_data .= "DROP TABLE $table_name;\n"; + + $sql = "SELECT sql + FROM sqlite_master + WHERE type = 'table' + AND name = '" . $this->db->sql_escape($table_name) . "' + ORDER BY type DESC, name;"; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + // Create Table + $sql_data .= $row['sql'] . ";\n"; + + $result = $this->db->sql_query("PRAGMA index_list('" . $this->db->sql_escape($table_name) . "');"); + + $ar = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $ar[] = $row; + } + $this->db->sql_freeresult($result); + + foreach ($ar as $value) + { + if (strpos($value['name'], 'autoindex') !== false) + { + continue; + } + + $result = $this->db->sql_query("PRAGMA index_info('" . $this->db->sql_escape($value['name']) . "');"); + + $fields = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $fields[] = $row['name']; + } + $this->db->sql_freeresult($result); + + $sql_data .= 'CREATE ' . ($value['unique'] ? 'UNIQUE ' : '') . 'INDEX ' . $value['name'] . ' on ' . $table_name . ' (' . implode(', ', $fields) . ");\n"; + } + + $this->flush($sql_data . "\n"); + } + + /** + * {@inheritdoc} + */ + public function write_data($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $col_types = sqlite_fetch_column_types($this->db->get_db_connect_id(), $table_name); + + $sql = "SELECT * + FROM $table_name"; + $result = sqlite_unbuffered_query($this->db->get_db_connect_id(), $sql); + $rows = sqlite_fetch_all($result, SQLITE_ASSOC); + $sql_insert = 'INSERT INTO ' . $table_name . ' (' . implode(', ', array_keys($col_types)) . ') VALUES ('; + foreach ($rows as $row) + { + foreach ($row as $column_name => $column_data) + { + if (is_null($column_data)) + { + $row[$column_name] = 'NULL'; + } + else if ($column_data == '') + { + $row[$column_name] = "''"; + } + else if (strpos($col_types[$column_name], 'text') !== false || strpos($col_types[$column_name], 'char') !== false || strpos($col_types[$column_name], 'blob') !== false) + { + $row[$column_name] = sanitize_data_generic(str_replace("'", "''", $column_data)); + } + } + $this->flush($sql_insert . implode(', ', $row) . ");\n"); + } + } + + /** + * Writes closing line(s) to database backup + * + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + public function write_end() + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $this->flush("COMMIT;\n"); + parent::write_end(); + } +} diff --git a/phpBB/phpbb/db/migration/data/v30x/release_3_0_5_rc1.php b/phpBB/phpbb/db/migration/data/v30x/release_3_0_5_rc1.php index 003ccf8f18..084d00a13a 100644 --- a/phpBB/phpbb/db/migration/data/v30x/release_3_0_5_rc1.php +++ b/phpBB/phpbb/db/migration/data/v30x/release_3_0_5_rc1.php @@ -57,7 +57,9 @@ class release_3_0_5_rc1 extends container_aware_migration public function hash_old_passwords() { + /* @var $passwords_manager \phpbb\passwords\manager */ $passwords_manager = $this->container->get('passwords.manager'); + $sql = 'SELECT user_id, user_password FROM ' . $this->table_prefix . 'users WHERE user_pass_convert = 1'; @@ -116,7 +118,7 @@ class release_3_0_5_rc1 extends container_aware_migration $result = $this->db->sql_query($sql); // Skip first row, this is our original auth option we want to preserve - $row = $this->db->sql_fetchrow($result); + $this->db->sql_fetchrow($result); while ($row = $this->db->sql_fetchrow($result)) { diff --git a/phpBB/phpbb/db/migration/data/v30x/release_3_0_9_rc1.php b/phpBB/phpbb/db/migration/data/v30x/release_3_0_9_rc1.php index 06e46d522f..5f928df47c 100644 --- a/phpBB/phpbb/db/migration/data/v30x/release_3_0_9_rc1.php +++ b/phpBB/phpbb/db/migration/data/v30x/release_3_0_9_rc1.php @@ -34,7 +34,7 @@ class release_3_0_9_rc1 extends \phpbb\db\migration\migration // this column was removed from the database updater // after 3.0.9-RC3 was released. It might still exist // in 3.0.9-RCX installations and has to be dropped as - // soon as the db_tools class is capable of properly + // soon as the \phpbb\db\tools\tools class is capable of properly // removing a primary key. // 'attempt_id' => array('UINT', NULL, 'auto_increment'), 'attempt_ip' => array('VCHAR:40', ''), diff --git a/phpBB/phpbb/db/migration/data/v310/acp_prune_users_module.php b/phpBB/phpbb/db/migration/data/v310/acp_prune_users_module.php index 0ca4f2f19c..725c57ca86 100644 --- a/phpBB/phpbb/db/migration/data/v310/acp_prune_users_module.php +++ b/phpBB/phpbb/db/migration/data/v310/acp_prune_users_module.php @@ -13,7 +13,7 @@ namespace phpbb\db\migration\data\v310; -class acp_prune_users_module extends \phpbb\db\migration\migration +class acp_prune_users_module extends \phpbb\db\migration\container_aware_migration { public function effectively_installed() { @@ -70,12 +70,7 @@ class acp_prune_users_module extends \phpbb\db\migration\migration $acp_cat_users_id = (int) $this->db->sql_fetchfield('module_id'); $this->db->sql_freeresult($result); - if (!class_exists('\acp_modules')) - { - include($this->phpbb_root_path . 'includes/acp/acp_modules.' . $this->php_ext); - } - $module_manager = new \acp_modules(); - $module_manager->module_class = 'acp'; - $module_manager->move_module($acp_prune_users_id, $acp_cat_users_id); + $module_manager = $this->container->get('module.manager'); + $module_manager->move_module($acp_prune_users_id, $acp_cat_users_id, 'acp'); } } diff --git a/phpBB/phpbb/db/migration/data/v310/dev.php b/phpBB/phpbb/db/migration/data/v310/dev.php index f037191c2a..250258eea7 100644 --- a/phpBB/phpbb/db/migration/data/v310/dev.php +++ b/phpBB/phpbb/db/migration/data/v310/dev.php @@ -13,7 +13,7 @@ namespace phpbb\db\migration\data\v310; -class dev extends \phpbb\db\migration\migration +class dev extends \phpbb\db\migration\container_aware_migration { public function effectively_installed() { @@ -204,18 +204,13 @@ class dev extends \phpbb\db\migration\migration $language_management_module_id = $this->db->sql_fetchfield('module_id'); $this->db->sql_freeresult($result); - if (!class_exists('acp_modules')) - { - include($this->phpbb_root_path . 'includes/acp/acp_modules.' . $this->php_ext); - } // acp_modules calls adm_back_link, which is undefined at this point if (!function_exists('adm_back_link')) { include($this->phpbb_root_path . 'includes/functions_acp.' . $this->php_ext); } - $module_manager = new \acp_modules(); - $module_manager->module_class = 'acp'; - $module_manager->move_module($language_module_id, $language_management_module_id); + $module_manager = $this->container->get('module.manager'); + $module_manager->move_module($language_module_id, $language_management_module_id, 'acp'); } public function update_ucp_pm_basename() diff --git a/phpBB/phpbb/db/migration/data/v31x/update_custom_bbcodes_with_idn.php b/phpBB/phpbb/db/migration/data/v31x/update_custom_bbcodes_with_idn.php index 854ed1f568..14b7b7b0f6 100644 --- a/phpBB/phpbb/db/migration/data/v31x/update_custom_bbcodes_with_idn.php +++ b/phpBB/phpbb/db/migration/data/v31x/update_custom_bbcodes_with_idn.php @@ -45,7 +45,6 @@ class update_custom_bbcodes_with_idn extends \phpbb\db\migration\migration $sql_ary = array(); while ($row = $this->db->sql_fetchrow($result)) { - $data = array(); if (preg_match('/(URL|LOCAL_URL|RELATIVE_URL)/', $row['bbcode_match'])) { $data = $bbcodes->build_regexp($row['bbcode_match'], $row['bbcode_tpl']); diff --git a/phpBB/phpbb/db/migration/data/v320/allowed_schemes_links.php b/phpBB/phpbb/db/migration/data/v320/allowed_schemes_links.php new file mode 100644 index 0000000000..726822bc71 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/allowed_schemes_links.php @@ -0,0 +1,31 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @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\migration\data\v320; + +class allowed_schemes_links extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v320\dev', + ); + } + + public function update_data() + { + return array( + array('config.add', array('allowed_schemes_links', 'http,https,ftp')), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/v320/announce_global_permission.php b/phpBB/phpbb/db/migration/data/v320/announce_global_permission.php new file mode 100644 index 0000000000..7afecb884b --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/announce_global_permission.php @@ -0,0 +1,43 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @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\migration\data\v320; + +class announce_global_permission extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + $sql = 'SELECT auth_option_id + FROM ' . ACL_OPTIONS_TABLE . " + WHERE auth_option = 'f_announce_global'"; + $result = $this->db->sql_query($sql); + $auth_option_id = $this->db->sql_fetchfield('auth_option_id'); + $this->db->sql_freeresult($result); + + return $auth_option_id !== false; + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v320\dev', + ); + } + + public function update_data() + { + return array( + array('permission.add', array('f_announce_global', false, 'f_announce')), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/v320/default_data_type_ids.php b/phpBB/phpbb/db/migration/data/v320/default_data_type_ids.php new file mode 100644 index 0000000000..65e5b3fa73 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/default_data_type_ids.php @@ -0,0 +1,361 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @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\migration\data\v320; + +class default_data_type_ids extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v320\v320a2', + '\phpbb\db\migration\data\v320\oauth_states', + ); + } + + public function update_schema() + { + return array( + 'change_columns' => array( + $this->table_prefix . 'acl_users' => array( + 'user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'attachments' => array( + 'attach_id' => array('ULINT', null, 'auto_increment'), + 'post_msg_id' => array('ULINT', 0), + 'poster_id' => array('ULINT', 0), + 'topic_id' => array('ULINT', 0), + ), + $this->table_prefix . 'banlist' => array( + 'ban_id' => array('ULINT', null, 'auto_increment'), + 'ban_userid' => array('ULINT', 0), + ), + $this->table_prefix . 'bookmarks' => array( + 'topic_id' => array('ULINT', 0), + 'user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'bots' => array( + 'bot_id' => array('ULINT', null, 'auto_increment'), + 'user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'drafts' => array( + 'draft_id' => array('ULINT', null, 'auto_increment'), + 'user_id' => array('ULINT', 0), + 'topic_id' => array('ULINT', 0), + ), + $this->table_prefix . 'forums' => array( + 'forum_last_post_id' => array('ULINT', 0), + 'forum_last_poster_id' => array('ULINT', 0), + ), + $this->table_prefix . 'forums_access' => array( + 'user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'forums_track' => array( + 'user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'forums_watch' => array( + 'user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'log' => array( + 'log_id' => array('ULINT', null, 'auto_increment'), + 'post_id' => array('ULINT', 0), + 'reportee_id' => array('ULINT', 0), + 'user_id' => array('ULINT', 0), + 'topic_id' => array('ULINT', 0), + ), + $this->table_prefix . 'login_attempts' => array( + 'user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'moderator_cache' => array( + 'user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'notifications' => array( + 'user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'oauth_accounts' => array( + 'user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'oauth_states' => array( + 'user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'oauth_tokens' => array( + 'user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'poll_options' => array( + 'topic_id' => array('ULINT', 0), + ), + $this->table_prefix . 'poll_votes' => array( + 'topic_id' => array('ULINT', 0), + 'vote_user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'posts' => array( + 'post_id' => array('ULINT', null, 'auto_increment'), + 'poster_id' => array('ULINT', 0), + 'post_delete_user' => array('ULINT', 0), + 'post_edit_user' => array('ULINT', 0), + 'topic_id' => array('ULINT', 0), + ), + $this->table_prefix . 'privmsgs' => array( + 'author_id' => array('ULINT', 0), + 'message_edit_user' => array('ULINT', 0), + 'msg_id' => array('ULINT', null, 'auto_increment'), + ), + $this->table_prefix . 'privmsgs_folder' => array( + 'user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'privmsgs_rules' => array( + 'rule_user_id' => array('ULINT', 0), + 'user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'privmsgs_to' => array( + 'author_id' => array('ULINT', 0), + 'msg_id' => array('ULINT', 0), + 'user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'profile_fields_data' => array( + 'user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'reports' => array( + 'report_id' => array('ULINT', 0), + 'pm_id' => array('ULINT', 0), + 'post_id' => array('ULINT', 0), + 'user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'search_wordlist' => array( + 'word_id' => array('ULINT', null, 'auto_increment'), + ), + $this->table_prefix . 'search_wordmatch' => array( + 'post_id' => array('ULINT', 0), + 'word_id' => array('ULINT', 0), + ), + $this->table_prefix . 'sessions' => array( + 'session_user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'sessions_keys' => array( + 'user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'topics' => array( + 'topic_id' => array('ULINT', null, 'auto_increment'), + 'topic_poster' => array('ULINT', 0), + 'topic_first_post_id' => array('ULINT', 0), + 'topic_last_post_id' => array('ULINT', 0), + 'topic_last_poster_id' => array('ULINT', 0), + 'topic_moved_id' => array('ULINT', 0), + 'topic_delete_user' => array('ULINT', 0), + ), + $this->table_prefix . 'topics_track' => array( + 'user_id' => array('ULINT', 0), + 'topic_id' => array('ULINT', 0), + ), + $this->table_prefix . 'topics_posted' => array( + 'user_id' => array('ULINT', 0), + 'topic_id' => array('ULINT', 0), + ), + $this->table_prefix . 'topics_watch' => array( + 'user_id' => array('ULINT', 0), + 'topic_id' => array('ULINT', 0), + ), + $this->table_prefix . 'user_notifications' => array( + 'item_id' => array('ULINT', 0), + 'user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'user_group' => array( + 'user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'users' => array( + 'user_id' => array('ULINT', null, 'auto_increment'), + ), + $this->table_prefix . 'warnings' => array( + 'log_id' => array('ULINT', 0), + 'user_id' => array('ULINT', 0), + 'post_id' => array('ULINT', 0), + ), + $this->table_prefix . 'words' => array( + 'word_id' => array('ULINT', null, 'auto_increment'), + ), + $this->table_prefix . 'zebra' => array( + 'user_id' => array('ULINT', 0), + 'zebra_id' => array('ULINT', 0), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'change_columns' => array( + $this->table_prefix . 'acl_users' => array( + 'user_id' => array('UINT', 0), + ), + $this->table_prefix . 'attachments' => array( + 'attach_id' => array('UINT', null, 'auto_increment'), + 'post_msg_id' => array('UINT', 0), + 'poster_id' => array('UINT', 0), + 'topic_id' => array('UINT', 0), + ), + $this->table_prefix . 'banlist' => array( + 'ban_id' => array('UINT', null, 'auto_increment'), + 'ban_userid' => array('UINT', 0), + ), + $this->table_prefix . 'bookmarks' => array( + 'topic_id' => array('UINT', 0), + 'user_id' => array('UINT', 0), + ), + $this->table_prefix . 'bots' => array( + 'bot_id' => array('UINT', null, 'auto_increment'), + 'user_id' => array('UINT', 0), + ), + $this->table_prefix . 'drafts' => array( + 'draft_id' => array('UINT', null, 'auto_increment'), + 'user_id' => array('UINT', 0), + 'topic_id' => array('UINT', 0), + ), + $this->table_prefix . 'forums' => array( + 'forum_last_post_id' => array('UINT', 0), + 'forum_last_poster_id' => array('UINT', 0), + ), + $this->table_prefix . 'forums_access' => array( + 'user_id' => array('UINT', 0), + ), + $this->table_prefix . 'forums_track' => array( + 'user_id' => array('UINT', 0), + ), + $this->table_prefix . 'forums_watch' => array( + 'user_id' => array('UINT', 0), + ), + $this->table_prefix . 'log' => array( + 'log_id' => array('UINT', null, 'auto_increment'), + 'post_id' => array('UINT', 0), + 'reportee_id' => array('UINT', 0), + 'user_id' => array('UINT', 0), + 'topic_id' => array('UINT', 0), + ), + $this->table_prefix . 'login_attempts' => array( + 'user_id' => array('UINT', 0), + ), + $this->table_prefix . 'moderator_cache' => array( + 'user_id' => array('UINT', 0), + ), + $this->table_prefix . 'notifications' => array( + 'user_id' => array('UINT', 0), + ), + $this->table_prefix . 'oauth_accounts' => array( + 'user_id' => array('UINT', 0), + ), + $this->table_prefix . 'oauth_states' => array( + 'user_id' => array('UINT', 0), + ), + $this->table_prefix . 'oauth_tokens' => array( + 'user_id' => array('UINT', 0), + ), + $this->table_prefix . 'poll_options' => array( + 'topic_id' => array('UINT', 0), + ), + $this->table_prefix . 'poll_votes' => array( + 'topic_id' => array('UINT', 0), + 'vote_user_id' => array('UINT', 0), + ), + $this->table_prefix . 'posts' => array( + 'post_id' => array('UINT', null, 'auto_increment'), + 'poster_id' => array('UINT', 0), + 'post_delete_user' => array('UINT', 0), + 'post_edit_user' => array('UINT', 0), + 'topic_id' => array('UINT', 0), + ), + $this->table_prefix . 'privmsgs' => array( + 'author_id' => array('UINT', 0), + 'message_edit_user' => array('UINT', 0), + 'msg_id' => array('UINT', null, 'auto_increment'), + ), + $this->table_prefix . 'privmsgs_folder' => array( + 'user_id' => array('UINT', 0), + ), + $this->table_prefix . 'privmsgs_rules' => array( + 'rule_user_id' => array('UINT', 0), + 'user_id' => array('UINT', 0), + ), + $this->table_prefix . 'privmsgs_to' => array( + 'author_id' => array('UINT', 0), + 'msg_id' => array('UINT', 0), + 'user_id' => array('UINT', 0), + ), + $this->table_prefix . 'profile_fields_data' => array( + 'user_id' => array('UINT', 0), + ), + $this->table_prefix . 'reports' => array( + 'report_id' => array('UINT', 0), + 'pm_id' => array('UINT', 0), + 'post_id' => array('UINT', 0), + 'user_id' => array('UINT', 0), + ), + $this->table_prefix . 'search_wordlist' => array( + 'word_id' => array('UINT', null, 'auto_increment'), + ), + $this->table_prefix . 'search_wordmatch' => array( + 'post_id' => array('UINT', 0), + 'word_id' => array('UINT', 0), + ), + $this->table_prefix . 'sessions' => array( + 'session_user_id' => array('UINT', 0), + ), + $this->table_prefix . 'sessions_keys' => array( + 'user_id' => array('UINT', 0), + ), + $this->table_prefix . 'topics' => array( + 'topic_id' => array('UINT', null, 'auto_increment'), + 'topic_poster' => array('UINT', 0), + 'topic_first_post_id' => array('UINT', 0), + 'topic_last_post_id' => array('UINT', 0), + 'topic_last_poster_id' => array('UINT', 0), + 'topic_moved_id' => array('UINT', 0), + 'topic_delete_user' => array('UINT', 0), + ), + $this->table_prefix . 'topics_track' => array( + 'user_id' => array('UINT', 0), + 'topic_id' => array('UINT', 0), + ), + $this->table_prefix . 'topics_posted' => array( + 'user_id' => array('UINT', 0), + 'topic_id' => array('UINT', 0), + ), + $this->table_prefix . 'topics_watch' => array( + 'user_id' => array('UINT', 0), + 'topic_id' => array('UINT', 0), + ), + $this->table_prefix . 'user_notifications' => array( + 'item_id' => array('UINT', 0), + 'user_id' => array('UINT', 0), + ), + $this->table_prefix . 'user_group' => array( + 'user_id' => array('UINT', 0), + ), + $this->table_prefix . 'users' => array( + 'user_id' => array('UINT', null, 'auto_increment'), + ), + $this->table_prefix . 'warnings' => array( + 'log_id' => array('UINT', 0), + 'user_id' => array('UINT', 0), + 'post_id' => array('UINT', 0), + ), + $this->table_prefix . 'words' => array( + 'word_id' => array('UINT', null, 'auto_increment'), + ), + $this->table_prefix . 'zebra' => array( + 'user_id' => array('UINT', 0), + 'zebra_id' => array('UINT', 0), + ), + ), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/v320/dev.php b/phpBB/phpbb/db/migration/data/v320/dev.php new file mode 100644 index 0000000000..ad2da3c1f4 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/dev.php @@ -0,0 +1,36 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @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\migration\data\v320; + +class dev extends \phpbb\db\migration\container_aware_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.2.0-dev', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v31x\v316', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.2.0-dev')), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/v320/font_awesome_update.php b/phpBB/phpbb/db/migration/data/v320/font_awesome_update.php new file mode 100644 index 0000000000..817b638037 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/font_awesome_update.php @@ -0,0 +1,36 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @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\migration\data\v320; + +class font_awesome_update extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v320\dev', + ); + } + + public function effectively_installed() + { + return isset($this->config['load_font_awesome_url']); + } + + public function update_data() + { + return array( + array('config.add', array('load_font_awesome_url', 'https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css')), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/v320/icons_alt.php b/phpBB/phpbb/db/migration/data/v320/icons_alt.php new file mode 100644 index 0000000000..80132e579e --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/icons_alt.php @@ -0,0 +1,46 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @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\migration\data\v320; + +class icons_alt extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v320\dev', + ); + } + + public function update_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'icons' => array( + 'icons_alt' => array('VCHAR', '', 'after' => 'icons_height'), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'icons' => array( + 'icons_alt', + ), + ), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/v320/log_post_id.php b/phpBB/phpbb/db/migration/data/v320/log_post_id.php new file mode 100644 index 0000000000..ead53c8138 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/log_post_id.php @@ -0,0 +1,46 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @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\migration\data\v320; + +class log_post_id extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v320\dev', + ); + } + + public function update_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'log' => array( + 'post_id' => array('UINT', 0, 'after' => 'topic_id'), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'log' => array( + 'post_id', + ), + ), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/v320/notifications_board.php b/phpBB/phpbb/db/migration/data/v320/notifications_board.php new file mode 100644 index 0000000000..ac1b3a0f2c --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/notifications_board.php @@ -0,0 +1,75 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @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\migration\data\v320; + +class notifications_board extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v320\dev', + ); + } + + public function update_data() + { + return array( + array('config.add', array('allow_board_notifications', 1)), + array('custom', array(array($this, 'update_user_subscriptions'))), + array('custom', array(array($this, 'update_module'))), + ); + } + + public function update_module() + { + $sql = 'UPDATE ' . MODULES_TABLE . " + SET module_auth = 'cfg_allow_board_notifications' + WHERE module_basename = 'ucp_notifications' + AND module_mode = 'notification_list'"; + $this->sql_query($sql); + } + + public function update_user_subscriptions() + { + $sql = 'UPDATE ' . USER_NOTIFICATIONS_TABLE . " + SET method = 'notification.method.board' + WHERE method = ''"; + $this->sql_query($sql); + } + + public function revert_data() + { + return array( + array('custom', array(array($this, 'revert_user_subscriptions'))), + array('custom', array(array($this, 'revert_module'))), + ); + } + + public function revert_user_subscriptions() + { + $sql = 'UPDATE ' . USER_NOTIFICATIONS_TABLE . " + SET method = '' + WHERE method = 'notification.method.board'"; + $this->sql_query($sql); + } + + public function revert_module() + { + $sql = 'UPDATE ' . MODULES_TABLE . " + SET auth = '' + WHERE module_basename = 'ucp_notifications' + AND module_mode = 'notification_list'"; + $this->sql_query($sql); + } +} diff --git a/phpBB/phpbb/db/migration/data/v320/oauth_states.php b/phpBB/phpbb/db/migration/data/v320/oauth_states.php new file mode 100644 index 0000000000..22ab2dabb3 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/oauth_states.php @@ -0,0 +1,56 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @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\migration\data\v320; + +class oauth_states extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array('\phpbb\db\migration\data\v310\auth_provider_oauth'); + } + + public function effectively_installed() + { + return $this->db_tools->sql_table_exists($this->table_prefix . 'oauth_states'); + } + + public function update_schema() + { + return array( + 'add_tables' => array( + $this->table_prefix . 'oauth_states' => array( + 'COLUMNS' => array( + 'user_id' => array('UINT', 0), + 'session_id' => array('CHAR:32', ''), + 'provider' => array('VCHAR', ''), + 'oauth_state' => array('VCHAR', ''), + ), + 'KEYS' => array( + 'user_id' => array('INDEX', 'user_id'), + 'provider' => array('INDEX', 'provider'), + ), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_tables' => array( + $this->table_prefix . 'oauth_states', + ), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/v320/remote_upload_validation.php b/phpBB/phpbb/db/migration/data/v320/remote_upload_validation.php new file mode 100644 index 0000000000..d61f6b96fd --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/remote_upload_validation.php @@ -0,0 +1,31 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @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\migration\data\v320; + +class remote_upload_validation extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v320\v320a2', + ); + } + + public function update_data() + { + return array( + array('config.add', array('remote_upload_verify', '0')), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/v320/remove_outdated_media.php b/phpBB/phpbb/db/migration/data/v320/remove_outdated_media.php new file mode 100644 index 0000000000..c14d31f1c0 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/remove_outdated_media.php @@ -0,0 +1,90 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @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\migration\data\v320; + +class remove_outdated_media extends \phpbb\db\migration\migration +{ + protected $cat_id = array( + ATTACHMENT_CATEGORY_WM, + ATTACHMENT_CATEGORY_RM, + ATTACHMENT_CATEGORY_QUICKTIME, + ); + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v320\dev', + ); + } + + public function update_data() + { + return array( + array('custom', array(array($this, 'change_extension_group'))), + ); + } + + public function change_extension_group() + { + // select group ids of outdated media + $sql = 'SELECT group_id + FROM ' . EXTENSION_GROUPS_TABLE . ' + WHERE ' . $this->db->sql_in_set('cat_id', $this->cat_id); + $result = $this->db->sql_query($sql); + + $group_ids = array(); + while ($group_id = (int) $this->db->sql_fetchfield('group_id')) + { + $group_ids[] = $group_id; + } + $this->db->sql_freeresult($result); + + // nothing to do, admin has removed all the outdated media extension groups + if (empty($group_ids)) + { + return true; + } + + // get the group id of downloadable files + $sql = 'SELECT group_id + FROM ' . EXTENSION_GROUPS_TABLE . " + WHERE group_name = 'DOWNLOADABLE_FILES'"; + $result = $this->db->sql_query($sql); + $download_id = (int) $this->db->sql_fetchfield('group_id'); + $this->db->sql_freeresult($result); + + if (empty($download_id)) + { + $sql = 'UPDATE ' . EXTENSIONS_TABLE . ' + SET group_id = 0 + WHERE ' . $this->db->sql_in_set('group_id', $group_ids); + } + else + { + // move outdated media extensions to downloadable files + $sql = 'UPDATE ' . EXTENSIONS_TABLE . " + SET group_id = $download_id" . ' + WHERE ' . $this->db->sql_in_set('group_id', $group_ids); + } + + $result = $this->db->sql_query($sql); + $this->db->sql_freeresult($result); + + // delete the now empty, outdated media extension groups + $sql = 'DELETE FROM ' . EXTENSION_GROUPS_TABLE . ' + WHERE ' . $this->db->sql_in_set('group_id', $group_ids); + $result = $this->db->sql_query($sql); + $this->db->sql_freeresult($result); + } +} diff --git a/phpBB/phpbb/db/migration/data/v320/remove_profilefield_wlm.php b/phpBB/phpbb/db/migration/data/v320/remove_profilefield_wlm.php new file mode 100644 index 0000000000..1cb9070bf9 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/remove_profilefield_wlm.php @@ -0,0 +1,152 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @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\migration\data\v320; + +class remove_profilefield_wlm extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v320\dev', + ); + } + + public function update_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'profile_fields_data' => array( + 'pf_phpbb_wlm', + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'profile_fields_data' => array( + 'pf_phpbb_wlm' => array('VCHAR', ''), + ), + ), + ); + } + + public function update_data() + { + return array( + array('custom', array(array($this, 'delete_custom_profile_field_data'))), + ); + } + + public function revert_data() + { + return array( + array('custom', array(array($this, 'create_custom_field'))), + ); + } + + public function delete_custom_profile_field_data() + { + $field_id = $this->get_custom_profile_field_id(); + + $sql = 'DELETE FROM ' . PROFILE_FIELDS_TABLE . ' + WHERE field_id = ' . (int) $field_id; + $this->db->sql_query($sql); + + $sql = 'DELETE FROM ' . PROFILE_LANG_TABLE . ' + WHERE field_id = ' . (int) $field_id; + $this->db->sql_query($sql); + + $sql = 'DELETE FROM ' . PROFILE_FIELDS_LANG_TABLE . ' + WHERE field_id = ' . (int) $field_id; + $this->db->sql_query($sql); + } + + /** + * Get custom profile field id + * @return int custom profile filed id + */ + public function get_custom_profile_field_id() + { + $sql = 'SELECT field_id + FROM ' . PROFILE_FIELDS_TABLE . " + WHERE field_name = 'phpbb_wlm'"; + $result = $this->db->sql_query($sql); + $field_id = (int) $this->db->sql_fetchfield('field_id'); + $this->db->sql_freeresult($result); + + return $field_id; + } + + public function create_custom_field() + { + $sql = 'SELECT MAX(field_order) as max_field_order + FROM ' . PROFILE_FIELDS_TABLE; + $result = $this->db->sql_query($sql); + $max_field_order = (int) $this->db->sql_fetchfield('max_field_order'); + $this->db->sql_freeresult($result); + + $sql_ary = array( + 'field_name' => 'phpbb_wlm', + 'field_type' => 'profilefields.type.string', + 'field_ident' => 'phpbb_wlm', + 'field_length' => '40', + 'field_minlen' => '5', + 'field_maxlen' => '255', + 'field_novalue' => '', + 'field_default_value' => '', + 'field_validation' => '.*', + 'field_required' => 0, + 'field_show_novalue' => 0, + 'field_show_on_reg' => 0, + 'field_show_on_pm' => 1, + 'field_show_on_vt' => 1, + 'field_show_on_ml' => 0, + 'field_show_profile' => 1, + 'field_hide' => 0, + 'field_no_view' => 0, + 'field_active' => 1, + 'field_is_contact' => 1, + 'field_contact_desc' => '', + 'field_contact_url' => '', + 'field_order' => $max_field_order + 1, + ); + + $sql = 'INSERT INTO ' . PROFILE_FIELDS_TABLE . ' ' . $this->db->sql_build_array('INSERT', $sql_ary); + $this->db->sql_query($sql); + $field_id = (int) $this->db->sql_nextid(); + + $insert_buffer = new \phpbb\db\sql_insert_buffer($this->db, PROFILE_LANG_TABLE); + + $sql = 'SELECT lang_id + FROM ' . LANG_TABLE; + $result = $this->db->sql_query($sql); + $lang_name = 'WLM'; + while ($lang_id = (int) $this->db->sql_fetchfield('lang_id')) + { + $insert_buffer->insert(array( + 'field_id' => (int) $field_id, + 'lang_id' => (int) $lang_id, + 'lang_name' => $lang_name, + 'lang_explain' => '', + 'lang_default_value' => '', + )); + } + $this->db->sql_freeresult($result); + + $insert_buffer->flush(); + } +} diff --git a/phpBB/phpbb/db/migration/data/v320/text_reparser.php b/phpBB/phpbb/db/migration/data/v320/text_reparser.php new file mode 100644 index 0000000000..ea614feb40 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/text_reparser.php @@ -0,0 +1,104 @@ +<?php +/** + * + * This file is part of the phpBB Forum Software package. + * + * @copyright (c) phpBB Limited <https://www.phpbb.com> + * @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\migration\data\v320; + +class text_reparser extends \phpbb\db\migration\container_aware_migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\contact_admin_form', + '\phpbb\db\migration\data\v320\allowed_schemes_links', + ); + } + + public function effectively_installed() + { + return isset($this->config['reparse_lock']); + } + + public function update_data() + { + return array( + array('config.add', array('reparse_lock', 0, true)), + array('config.add', array('text_reparser.pm_text_cron_interval', 10)), + array('config.add', array('text_reparser.pm_text_last_cron', 0)), + array('config.add', array('text_reparser.poll_option_cron_interval', 10)), + array('config.add', array('text_reparser.poll_option_last_cron', 0)), + array('config.add', array('text_reparser.poll_title_cron_interval', 10)), + array('config.add', array('text_reparser.poll_title_last_cron', 0)), + array('config.add', array('text_reparser.post_text_cron_interval', 10)), + array('config.add', array('text_reparser.post_text_last_cron', 0)), + array('config.add', array('text_reparser.user_signature_cron_interval', 10)), + array('config.add', array('text_reparser.user_signature_last_cron', 0)), + array('custom', array(array($this, 'reparse'))), + ); + } + + public function reparse($resume_data) + { + // Somtimes a cron job is too much + $limit = 100; + $fast_reparsers = array( + 'text_reparser.contact_admin_info', + 'text_reparser.forum_description', + 'text_reparser.forum_rules', + 'text_reparser.group_description', + ); + + if (!is_array($resume_data)) + { + $resume_data = array( + 'reparser' => 0, + 'current' => $this->container->get($fast_reparsers[0])->get_max_id(), + ); + } + + $fast_reparsers_size = sizeof($fast_reparsers); + $processed_records = 0; + while ($processed_records < $limit && $resume_data['reparser'] < $fast_reparsers_size) + { + $reparser = $this->container->get($fast_reparsers[$resume_data['reparser']]); + + // New reparser + if ($resume_data['current'] === 0) + { + $resume_data['current'] = $reparser->get_max_id(); + } + + $start = max(1, $resume_data['current'] + 1 - ($limit - $processed_records)); + $end = max(1, $resume_data['current']); + $reparser->reparse_range($start, $end); + + $processed_records += $end - $start + 1; + $resume_data['current'] = $start - 1; + + if ($start === 1) + { + // Prevent CLI command from running these reparsers again + $reparser_manager = $this->container->get('text_reparser.manager'); + $reparser_manager->update_resume_data($fast_reparsers[$resume_data['reparser']], 1, 0, $limit); + + $resume_data['reparser']++; + } + } + + if ($resume_data['reparser'] === $fast_reparsers_size) + { + return true; + } + + return $resume_data; + } +} diff --git a/phpBB/phpbb/db/migration/data/v320/v320a1.php b/phpBB/phpbb/db/migration/data/v320/v320a1.php new file mode 100644 index 0000000000..d7ecb36f90 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/v320a1.php @@ -0,0 +1,44 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @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\migration\data\v320; + +class v320a1 extends \phpbb\db\migration\container_aware_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.2.0-a1', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v320\dev', + '\phpbb\db\migration\data\v320\allowed_schemes_links', + '\phpbb\db\migration\data\v320\announce_global_permission', + '\phpbb\db\migration\data\v320\remove_profilefield_wlm', + '\phpbb\db\migration\data\v320\font_awesome_update', + '\phpbb\db\migration\data\v320\icons_alt', + '\phpbb\db\migration\data\v320\log_post_id', + '\phpbb\db\migration\data\v320\remove_outdated_media', + '\phpbb\db\migration\data\v320\notifications_board', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.2.0-dev')), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/v320/v320a2.php b/phpBB/phpbb/db/migration/data/v320/v320a2.php new file mode 100644 index 0000000000..ae53a73210 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/v320a2.php @@ -0,0 +1,38 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @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\migration\data\v320; + +class v320a2 extends \phpbb\db\migration\container_aware_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.2.0-a2', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v31x\v317rc1', + '\phpbb\db\migration\data\v320\text_reparser', + '\phpbb\db\migration\data\v320\v320a1', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.2.0-a2')), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/v320/v320b1.php b/phpBB/phpbb/db/migration/data/v320/v320b1.php new file mode 100644 index 0000000000..5c3a3797cd --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/v320b1.php @@ -0,0 +1,39 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @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\migration\data\v320; + +class v320b1 extends \phpbb\db\migration\container_aware_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.2.0-b1', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v31x\v317pl1', + '\phpbb\db\migration\data\v320\v320a2', + '\phpbb\db\migration\data\v31x\increase_size_of_dateformat', + '\phpbb\db\migration\data\v320\default_data_type_ids', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.2.0-b1')), + ); + } +} diff --git a/phpBB/phpbb/db/migration/migration.php b/phpBB/phpbb/db/migration/migration.php index 5f120333e1..4e218344f4 100644 --- a/phpBB/phpbb/db/migration/migration.php +++ b/phpBB/phpbb/db/migration/migration.php @@ -20,7 +20,7 @@ namespace phpbb\db\migration; * in a subclass. This class provides various utility methods to simplify editing * a phpBB. */ -abstract class migration +abstract class migration implements migration_interface { /** @var \phpbb\config\config */ protected $config; @@ -28,7 +28,7 @@ abstract class migration /** @var \phpbb\db\driver\driver_interface */ protected $db; - /** @var \phpbb\db\tools */ + /** @var \phpbb\db\tools\tools_interface */ protected $db_tools; /** @var string */ @@ -51,12 +51,12 @@ abstract class migration * * @param \phpbb\config\config $config * @param \phpbb\db\driver\driver_interface $db - * @param \phpbb\db\tools $db_tools + * @param \phpbb\db\tools\tools_interface $db_tools * @param string $phpbb_root_path * @param string $php_ext * @param string $table_prefix */ - public function __construct(\phpbb\config\config $config, \phpbb\db\driver\driver_interface $db, \phpbb\db\tools $db_tools, $phpbb_root_path, $php_ext, $table_prefix) + public function __construct(\phpbb\config\config $config, \phpbb\db\driver\driver_interface $db, \phpbb\db\tools\tools_interface $db_tools, $phpbb_root_path, $php_ext, $table_prefix) { $this->config = $config; $this->db = $db; @@ -70,9 +70,7 @@ abstract class migration } /** - * Defines other migrations to be applied first - * - * @return array An array of migration class names + * {@inheritdoc} */ static public function depends_on() { @@ -80,14 +78,7 @@ abstract class migration } /** - * Allows you to check if the migration is effectively installed (entirely optional) - * - * This is checked when a migration is installed. If true is returned, the migration will be set as - * installed without performing the database changes. - * This function is intended to help moving to migrations from a previous database updater, where some - * migrations may have been installed already even though they are not yet listed in the migrations table. - * - * @return bool True if this migration is installed, False if this migration is not installed (checked on install) + * {@inheritdoc} */ public function effectively_installed() { @@ -95,9 +86,7 @@ abstract class migration } /** - * Updates the database schema by providing a set of change instructions - * - * @return array Array of schema changes (compatible with db_tools->perform_schema_changes()) + * {@inheritdoc} */ public function update_schema() { @@ -105,9 +94,7 @@ abstract class migration } /** - * Reverts the database schema by providing a set of change instructions - * - * @return array Array of schema changes (compatible with db_tools->perform_schema_changes()) + * {@inheritdoc} */ public function revert_schema() { @@ -115,9 +102,7 @@ abstract class migration } /** - * Updates data by returning a list of instructions to be executed - * - * @return array Array of data update instructions + * {@inheritdoc} */ public function update_data() { @@ -125,12 +110,7 @@ abstract class migration } /** - * Reverts data by returning a list of instructions to be executed - * - * @return array Array of data instructions that will be performed on revert - * NOTE: calls to tools (such as config.add) are automatically reverted when - * possible, so you should not attempt to revert those, this is mostly for - * otherwise unrevertable calls (custom functions for example) + * {@inheritdoc} */ public function revert_data() { diff --git a/phpBB/phpbb/db/migration/migration_interface.php b/phpBB/phpbb/db/migration/migration_interface.php new file mode 100644 index 0000000000..2aba5ec608 --- /dev/null +++ b/phpBB/phpbb/db/migration/migration_interface.php @@ -0,0 +1,70 @@ +<?php +/** + * + * This file is part of the phpBB Forum Software package. + * + * @copyright (c) phpBB Limited <https://www.phpbb.com> + * @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\migration; + +/** + * Base class interface for database migrations + */ +interface migration_interface +{ + /** + * Defines other migrations to be applied first + * + * @return array An array of migration class names + */ + static public function depends_on(); + + /** + * Allows you to check if the migration is effectively installed (entirely optional) + * + * This is checked when a migration is installed. If true is returned, the migration will be set as + * installed without performing the database changes. + * This function is intended to help moving to migrations from a previous database updater, where some + * migrations may have been installed already even though they are not yet listed in the migrations table. + * + * @return bool True if this migration is installed, False if this migration is not installed (checked on install) + */ + public function effectively_installed(); + + /** + * Updates the database schema by providing a set of change instructions + * + * @return array Array of schema changes (compatible with db_tools->perform_schema_changes()) + */ + public function update_schema(); + + /** + * Reverts the database schema by providing a set of change instructions + * + * @return array Array of schema changes (compatible with db_tools->perform_schema_changes()) + */ + public function revert_schema(); + + /** + * Updates data by returning a list of instructions to be executed + * + * @return array Array of data update instructions + */ + public function update_data(); + + /** + * Reverts data by returning a list of instructions to be executed + * + * @return array Array of data instructions that will be performed on revert + * NOTE: calls to tools (such as config.add) are automatically reverted when + * possible, so you should not attempt to revert those, this is mostly for + * otherwise unrevertable calls (custom functions for example) + */ + public function revert_data(); +} diff --git a/phpBB/phpbb/db/migration/profilefield_base_migration.php b/phpBB/phpbb/db/migration/profilefield_base_migration.php index da1a38e2fa..3f26a4998c 100644 --- a/phpBB/phpbb/db/migration/profilefield_base_migration.php +++ b/phpBB/phpbb/db/migration/profilefield_base_migration.php @@ -237,6 +237,7 @@ abstract class profilefield_base_migration extends container_aware_migration if ($profile_row === null) { + /* @var $manager \phpbb\profilefields\manager */ $manager = $this->container->get('profilefields.manager'); $profile_row = $manager->build_insert_sql_array(array()); } diff --git a/phpBB/phpbb/db/migration/schema_generator.php b/phpBB/phpbb/db/migration/schema_generator.php index 91d8307d91..c579e25824 100644 --- a/phpBB/phpbb/db/migration/schema_generator.php +++ b/phpBB/phpbb/db/migration/schema_generator.php @@ -24,7 +24,7 @@ class schema_generator /** @var \phpbb\db\driver\driver_interface */ protected $db; - /** @var \phpbb\db\tools */ + /** @var \phpbb\db\tools\tools_interface */ protected $db_tools; /** @var array */ @@ -48,7 +48,7 @@ class schema_generator /** * Constructor */ - public function __construct(array $class_names, \phpbb\config\config $config, \phpbb\db\driver\driver_interface $db, \phpbb\db\tools $db_tools, $phpbb_root_path, $php_ext, $table_prefix) + public function __construct(array $class_names, \phpbb\config\config $config, \phpbb\db\driver\driver_interface $db, \phpbb\db\tools\tools_interface $db_tools, $phpbb_root_path, $php_ext, $table_prefix) { $this->config = $config; $this->db = $db; @@ -77,8 +77,15 @@ class schema_generator $check_dependencies = true; while (!empty($migrations)) { - foreach ($migrations as $migration_class) + foreach ($migrations as $key => $migration_class) { + // Unset classes that are not a valid migration + if (\phpbb\db\migrator::is_migration($migration_class) === false) + { + unset($migrations[$key]); + continue; + } + $open_dependencies = array_diff($migration_class::depends_on(), $tree); if (empty($open_dependencies)) diff --git a/phpBB/phpbb/db/migration/tool/module.php b/phpBB/phpbb/db/migration/tool/module.php index 035625b095..a5ed62fd65 100644 --- a/phpBB/phpbb/db/migration/tool/module.php +++ b/phpBB/phpbb/db/migration/tool/module.php @@ -13,6 +13,8 @@ namespace phpbb\db\migration\tool; +use phpbb\module\exception\module_exception; + /** * Migration module management tool */ @@ -27,6 +29,9 @@ class module implements \phpbb\db\migration\tool\tool_interface /** @var \phpbb\user */ protected $user; + /** @var \phpbb\module\module_manager */ + protected $module_manager; + /** @var string */ protected $phpbb_root_path; @@ -42,15 +47,17 @@ class module implements \phpbb\db\migration\tool\tool_interface * @param \phpbb\db\driver\driver_interface $db * @param \phpbb\cache\service $cache * @param \phpbb\user $user + * @param \phpbb\module\module_manager $module_manager * @param string $phpbb_root_path * @param string $php_ext * @param string $modules_table */ - public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\cache\service $cache, \phpbb\user $user, $phpbb_root_path, $php_ext, $modules_table) + public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\cache\service $cache, \phpbb\user $user, \phpbb\module\module_manager $module_manager, $phpbb_root_path, $php_ext, $modules_table) { $this->db = $db; $this->cache = $cache; $this->user = $user; + $this->module_manager = $module_manager; $this->phpbb_root_path = $phpbb_root_path; $this->php_ext = $php_ext; $this->modules_table = $modules_table; @@ -171,6 +178,8 @@ class module implements \phpbb\db\migration\tool\tool_interface */ public function add($class, $parent = 0, $data = array()) { + global $user, $phpbb_log; + // Allows '' to be sent as 0 $parent = $parent ?: 0; @@ -186,7 +195,6 @@ class module implements \phpbb\db\migration\tool\tool_interface $basename = (isset($data['module_basename'])) ? $data['module_basename'] : ''; $module = $this->get_module_info($class, $basename); - $result = ''; foreach ($module['modes'] as $mode => $module_info) { if (!isset($data['modes']) || in_array($mode, $data['modes'])) @@ -237,13 +245,6 @@ class module implements \phpbb\db\migration\tool\tool_interface return; } - if (!class_exists('acp_modules')) - { - include($this->phpbb_root_path . 'includes/acp/acp_modules.' . $this->php_ext); - $this->user->add_lang('acp/modules'); - } - $acp_modules = new \acp_modules(); - $module_data = array( 'module_enabled' => (isset($data['module_enabled'])) ? $data['module_enabled'] : 1, 'module_display' => (isset($data['module_display'])) ? $data['module_display'] : 1, @@ -254,19 +255,14 @@ class module implements \phpbb\db\migration\tool\tool_interface 'module_mode' => (isset($data['module_mode'])) ? $data['module_mode'] : '', 'module_auth' => (isset($data['module_auth'])) ? $data['module_auth'] : '', ); - $result = $acp_modules->update_module_data($module_data, true); - // update_module_data can either return a string or an empty array... - if (is_string($result)) - { - // Error - throw new \phpbb\db\migration\exception('MODULE_ERROR', $result); - } - else + try { + $this->module_manager->update_module_data($module_data); + // Success $module_log_name = ((isset($this->user->lang[$data['module_langname']])) ? $this->user->lang[$data['module_langname']] : $data['module_langname']); - add_log('admin', 'LOG_MODULE_ADD', $module_log_name); + $phpbb_log->add('admin', (isset($user->data['user_id'])) ? $user->data['user_id'] : ANONYMOUS, $user->ip, 'LOG_MODULE_ADD', false, array($module_log_name)); // Move the module if requested above/below an existing one if (isset($data['before']) && $data['before']) @@ -316,6 +312,11 @@ class module implements \phpbb\db\migration\tool\tool_interface $this->db->sql_query($sql); } } + catch (module_exception $e) + { + // Error + throw new \phpbb\db\migration\exception('MODULE_ERROR', $e->getMessage()); + } // Clear the Modules Cache $this->cache->destroy("_modules_$class"); @@ -415,21 +416,9 @@ class module implements \phpbb\db\migration\tool\tool_interface $module_ids[] = (int) $module; } - if (!class_exists('acp_modules')) - { - include($this->phpbb_root_path . 'includes/acp/acp_modules.' . $this->php_ext); - $this->user->add_lang('acp/modules'); - } - $acp_modules = new \acp_modules(); - $acp_modules->module_class = $class; - foreach ($module_ids as $module_id) { - $result = $acp_modules->delete_module($module_id); - if (!empty($result)) - { - return; - } + $this->module_manager->delete_module($module_id, $class); } $this->cache->destroy("_modules_$class"); @@ -472,13 +461,7 @@ class module implements \phpbb\db\migration\tool\tool_interface */ protected function get_module_info($class, $basename) { - if (!class_exists('acp_modules')) - { - include($this->phpbb_root_path . 'includes/acp/acp_modules.' . $this->php_ext); - $this->user->add_lang('acp/modules'); - } - $acp_modules = new \acp_modules(); - $module = $acp_modules->get_module_infos($basename, $class, true); + $module = $this->module_manager->get_module_infos($class, $basename, true); if (empty($module)) { diff --git a/phpBB/phpbb/db/migrator.php b/phpBB/phpbb/db/migrator.php index 7fc3e787e2..a1e93942cd 100644 --- a/phpBB/phpbb/db/migrator.php +++ b/phpBB/phpbb/db/migrator.php @@ -13,6 +13,8 @@ namespace phpbb\db; +use phpbb\db\output_handler\migrator_output_handler_interface; +use phpbb\db\output_handler\null_migrator_output_handler; use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -32,7 +34,7 @@ class migrator /** @var \phpbb\db\driver\driver_interface */ protected $db; - /** @var \phpbb\db\tools */ + /** @var \phpbb\db\tools\tools_interface */ protected $db_tools; /** @var \phpbb\db\migration\helper */ @@ -92,7 +94,7 @@ class migrator /** * Constructor of the database migrator */ - public function __construct(ContainerInterface $container, \phpbb\config\config $config, \phpbb\db\driver\driver_interface $db, \phpbb\db\tools $db_tools, $migrations_table, $phpbb_root_path, $php_ext, $table_prefix, $tools, \phpbb\db\migration\helper $helper) + public function __construct(ContainerInterface $container, \phpbb\config\config $config, \phpbb\db\driver\driver_interface $db, \phpbb\db\tools\tools_interface $db_tools, $migrations_table, $phpbb_root_path, $php_ext, $table_prefix, $tools, \phpbb\db\migration\helper $helper) { $this->container = $container; $this->config = $config; @@ -122,7 +124,7 @@ class migrator /** * Set the output handler. * - * @param migrator_output_handler $handler The output handler + * @param migrator_output_handler_interface $handler The output handler */ public function set_output_handler(migrator_output_handler_interface $handler) { @@ -168,10 +170,28 @@ class migrator */ public function set_migrations($class_names) { + foreach ($class_names as $key => $class) + { + if (!self::is_migration($class)) + { + unset($class_names[$key]); + } + } + $this->migrations = $class_names; } /** + * Get the list of available migration class names + * + * @return array Array of all migrations available to be run + */ + public function get_migrations() + { + return $this->migrations; + } + + /** * Runs a single update step from the next migration to be applied. * * The update step can either be a schema or a (partial) data update. To @@ -416,6 +436,9 @@ class migrator if ($state['migration_data_done']) { + $this->output_handler->write(array('MIGRATION_REVERT_DATA_RUNNING', $name), migrator_output_handler_interface::VERBOSITY_VERBOSE); + $elapsed_time = microtime(true); + if ($state['migration_data_state'] !== 'revert_data') { $result = $this->process_data_step($migration->update_data(), $state['migration_data_state'], true); @@ -431,9 +454,22 @@ class migrator } $this->set_migration_state($name, $state); + + $elapsed_time = microtime(true) - $elapsed_time; + if ($state['migration_data_done']) + { + $this->output_handler->write(array('MIGRATION_REVERT_DATA_DONE', $name, $elapsed_time), migrator_output_handler_interface::VERBOSITY_NORMAL); + } + else + { + $this->output_handler->write(array('MIGRATION_REVERT_DATA_IN_PROGRESS', $name, $elapsed_time), migrator_output_handler_interface::VERBOSITY_VERY_VERBOSE); + } } else if ($state['migration_schema_done']) { + $this->output_handler->write(array('MIGRATION_REVERT_SCHEMA_RUNNING', $name), migrator_output_handler_interface::VERBOSITY_VERBOSE); + $elapsed_time = microtime(true); + $steps = $this->helper->get_schema_steps($migration->revert_schema()); $result = $this->process_data_step($steps, $state['migration_data_state']); @@ -448,6 +484,9 @@ class migrator unset($this->migration_state[$name]); } + + $elapsed_time = microtime(true) - $elapsed_time; + $this->output_handler->write(array('MIGRATION_REVERT_SCHEMA_DONE', $name, $elapsed_time), migrator_output_handler_interface::VERBOSITY_NORMAL); } return true; @@ -514,7 +553,7 @@ class migrator } // Reverse the step that was run - $result = $this->run_step($reverse_step, false, !$revert); + $this->run_step($reverse_step, false, !$revert); } // rethrow the exception @@ -836,4 +875,27 @@ class migrator )); } } + + /** + * Check if a class is a migration. + * + * @param string $migration A migration class name + * @return bool Return true if class is a migration, false otherwise + */ + static public function is_migration($migration) + { + if (class_exists($migration)) + { + // Migration classes should extend the abstract class + // phpbb\db\migration\migration (which implements the + // migration_interface) and be instantiable. + $reflector = new \ReflectionClass($migration); + if ($reflector->implementsInterface('\phpbb\db\migration\migration_interface') && $reflector->isInstantiable()) + { + return true; + } + } + + return false; + } } diff --git a/phpBB/phpbb/db/html_migrator_output_handler.php b/phpBB/phpbb/db/output_handler/html_migrator_output_handler.php index e37c667463..67309649c9 100644 --- a/phpBB/phpbb/db/html_migrator_output_handler.php +++ b/phpBB/phpbb/db/output_handler/html_migrator_output_handler.php @@ -11,27 +11,25 @@ * */ -namespace phpbb\db; - -use phpbb\user; +namespace phpbb\db\output_handler; class html_migrator_output_handler implements migrator_output_handler_interface { /** - * User object. + * Language object. * - * @var user + * @var \phpbb\language\language */ - private $user; + private $language; /** * Constructor * - * @param user $user User object + * @param \phpbb\language\language $language Language object */ - public function __construct(user $user) + public function __construct(\phpbb\language\language $language) { - $this->user = $user; + $this->language = $language; } /** @@ -41,7 +39,7 @@ class html_migrator_output_handler implements migrator_output_handler_interface { if ($verbosity <= migrator_output_handler_interface::VERBOSITY_VERBOSE) { - $final_message = call_user_func_array(array($this->user, 'lang'), $message); + $final_message = $this->language->lang_array(array_shift($message), $message); echo $final_message . "<br />\n"; } } diff --git a/phpBB/phpbb/db/output_handler/installer_migrator_output_handler.php b/phpBB/phpbb/db/output_handler/installer_migrator_output_handler.php new file mode 100644 index 0000000000..56d5cf49a1 --- /dev/null +++ b/phpBB/phpbb/db/output_handler/installer_migrator_output_handler.php @@ -0,0 +1,46 @@ +<?php +/** + * + * This file is part of the phpBB Forum Software package. + * + * @copyright (c) phpBB Limited <https://www.phpbb.com> + * @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\output_handler; + +use phpbb\install\helper\iohandler\iohandler_interface; + +class installer_migrator_output_handler implements migrator_output_handler_interface +{ + /** + * @var iohandler_interface + */ + protected $iohandler; + + /** + * Constructor + * + * @param iohandler_interface $iohandler Installer's IO-handler + */ + public function __construct(iohandler_interface $iohandler) + { + $this->iohandler = $iohandler; + } + + /** + * {@inheritdoc} + */ + public function write($message, $verbosity) + { + if ($verbosity <= migrator_output_handler_interface::VERBOSITY_VERBOSE) + { + $this->iohandler->add_log_message($message); + $this->iohandler->send_response(); + } + } +} diff --git a/phpBB/phpbb/db/log_wrapper_migrator_output_handler.php b/phpBB/phpbb/db/output_handler/log_wrapper_migrator_output_handler.php index 94c293dc45..20991746ac 100644 --- a/phpBB/phpbb/db/log_wrapper_migrator_output_handler.php +++ b/phpBB/phpbb/db/output_handler/log_wrapper_migrator_output_handler.php @@ -11,18 +11,16 @@ * */ -namespace phpbb\db; - -use phpbb\user; +namespace phpbb\db\output_handler; class log_wrapper_migrator_output_handler implements migrator_output_handler_interface { /** - * User object. + * Language object. * - * @var user + * @var \phpbb\language\language */ - protected $user; + protected $language; /** * A migrator output handler @@ -38,16 +36,23 @@ class log_wrapper_migrator_output_handler implements migrator_output_handler_int protected $file_handle = false; /** + * @var \phpbb\filesystem\filesystem_interface + */ + protected $filesystem; + + /** * Constructor * - * @param user $user User object - * @param migrator_output_handler_interface $migrator Migrator output handler - * @param string $log_file File to log to + * @param \phpbb\language\language $language Language object + * @param migrator_output_handler_interface $migrator Migrator output handler + * @param string $log_file File to log to + * @param \phpbb\filesystem\filesystem_interface $filesystem phpBB filesystem object */ - public function __construct(user $user, migrator_output_handler_interface $migrator, $log_file) + public function __construct(\phpbb\language\language $language, migrator_output_handler_interface $migrator, $log_file, \phpbb\filesystem\filesystem_interface $filesystem) { - $this->user = $user; + $this->language = $language; $this->migrator = $migrator; + $this->filesystem = $filesystem; $this->file_open($log_file); } @@ -58,7 +63,7 @@ class log_wrapper_migrator_output_handler implements migrator_output_handler_int */ protected function file_open($file) { - if (phpbb_is_writable(dirname($file))) + if ($this->filesystem->is_writable(dirname($file))) { $this->file_handle = fopen($file, 'w'); } @@ -77,7 +82,8 @@ class log_wrapper_migrator_output_handler implements migrator_output_handler_int if ($this->file_handle !== false) { - $translated_message = call_user_func_array(array($this->user, 'lang'), $message) . "\n"; + + $translated_message = $this->language->lang_array(array_shift($message), $message); if ($verbosity <= migrator_output_handler_interface::VERBOSITY_NORMAL) { diff --git a/phpBB/phpbb/db/migrator_output_handler_interface.php b/phpBB/phpbb/db/output_handler/migrator_output_handler_interface.php index a923af99f6..7bb5c73fec 100644 --- a/phpBB/phpbb/db/migrator_output_handler_interface.php +++ b/phpBB/phpbb/db/output_handler/migrator_output_handler_interface.php @@ -11,7 +11,7 @@ * */ -namespace phpbb\db; +namespace phpbb\db\output_handler; interface migrator_output_handler_interface { diff --git a/phpBB/phpbb/db/null_migrator_output_handler.php b/phpBB/phpbb/db/output_handler/null_migrator_output_handler.php index 0e8cfbb049..5fc2a52577 100644 --- a/phpBB/phpbb/db/null_migrator_output_handler.php +++ b/phpBB/phpbb/db/output_handler/null_migrator_output_handler.php @@ -11,7 +11,7 @@ * */ -namespace phpbb\db; +namespace phpbb\db\output_handler; class null_migrator_output_handler implements migrator_output_handler_interface { diff --git a/phpBB/phpbb/db/tools/factory.php b/phpBB/phpbb/db/tools/factory.php new file mode 100644 index 0000000000..d204451a63 --- /dev/null +++ b/phpBB/phpbb/db/tools/factory.php @@ -0,0 +1,43 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\tools; + +/** + * A factory which serves the suitable tools instance for the given dbal + */ +class factory +{ + /** + * @param mixed $db_driver + * @param bool $return_statements + * @return \phpbb\db\tools\tools_interface + */ + public function get($db_driver, $return_statements = false) + { + if ($db_driver instanceof \phpbb\db\driver\mssql || $db_driver instanceof \phpbb\db\driver\mssql_base) + { + return new \phpbb\db\tools\mssql($db_driver, $return_statements); + } + else if ($db_driver instanceof \phpbb\db\driver\postgres) + { + return new \phpbb\db\tools\postgres($db_driver, $return_statements); + } + else if ($db_driver instanceof \phpbb\db\driver\driver_interface) + { + return new \phpbb\db\tools\tools($db_driver, $return_statements); + } + + throw new \InvalidArgumentException('Invalid database driver given'); + } +} diff --git a/phpBB/phpbb/db/tools/mssql.php b/phpBB/phpbb/db/tools/mssql.php new file mode 100644 index 0000000000..87719acd21 --- /dev/null +++ b/phpBB/phpbb/db/tools/mssql.php @@ -0,0 +1,795 @@ +<?php +/** + * + * This file is part of the phpBB Forum Software package. + * + * @copyright (c) phpBB Limited <https://www.phpbb.com> + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\tools; + +/** + * Database Tools for handling cross-db actions such as altering columns, etc. + * Currently not supported is returning SQL for creating tables. + */ +class mssql extends tools +{ + /** + * Is the used MS SQL Server a SQL Server 2000? + * @var bool + */ + protected $is_sql_server_2000; + + /** + * Get the column types for 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'=> '[varchar] (100)', + 'STEXT_UNI' => '[varchar] (255)', + 'TEXT_UNI' => '[varchar] (4000)', + 'MTEXT_UNI' => '[text]', + 'TIMESTAMP' => '[int]', + 'DECIMAL' => '[float]', + 'DECIMAL:' => '[float]', + 'PDECIMAL' => '[float]', + 'PDECIMAL:' => '[float]', + 'VCHAR_UNI' => '[varchar] (255)', + 'VCHAR_UNI:'=> '[varchar] (%d)', + 'VCHAR_CI' => '[varchar] (255)', + 'VARBINARY' => '[varchar] (255)', + ), + + 'mssqlnative' => array( + 'INT:' => '[int]', + 'BINT' => '[float]', + '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'=> '[varchar] (100)', + 'STEXT_UNI' => '[varchar] (255)', + 'TEXT_UNI' => '[varchar] (4000)', + 'MTEXT_UNI' => '[text]', + 'TIMESTAMP' => '[int]', + 'DECIMAL' => '[float]', + 'DECIMAL:' => '[float]', + 'PDECIMAL' => '[float]', + 'PDECIMAL:' => '[float]', + 'VCHAR_UNI' => '[varchar] (255)', + 'VCHAR_UNI:'=> '[varchar] (%d)', + 'VCHAR_CI' => '[varchar] (255)', + 'VARBINARY' => '[varchar] (255)', + ), + ); + } + + /** + * Constructor. Set DB Object and set {@link $return_statements return_statements}. + * + * @param \phpbb\db\driver\driver_interface $db Database connection + * @param bool $return_statements True if only statements should be returned and no SQL being executed + */ + public function __construct(\phpbb\db\driver\driver_interface $db, $return_statements = false) + { + parent::__construct($db, $return_statements); + + // Determine mapping database type + switch ($this->db->get_sql_layer()) + { + case 'mssql': + case 'mssql_odbc': + $this->sql_layer = 'mssql'; + break; + + case 'mssqlnative': + $this->sql_layer = 'mssqlnative'; + break; + } + + $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 existance of auto incrementing columns + if (!$create_sequence && isset($prepared_column['auto_increment']) && $prepared_column['auto_increment']) + { + $create_sequence = $column_name; + } + } + + // this makes up all the columns in the create table statement + $table_sql .= implode(",\n", $columns); + + // Close the table for two DBMS and add to the statements + $table_sql .= "\n);"; + $statements[] = $table_sql; + + // we have yet to create a primary key for this table, + // this means that we can add the one we really wanted instead + if (!$primary_key_gen) + { + // Write primary key + if (isset($table_data['PRIMARY_KEY'])) + { + if (!is_array($table_data['PRIMARY_KEY'])) + { + $table_data['PRIMARY_KEY'] = array($table_data['PRIMARY_KEY']); + } + + // We need the data here + $old_return_statements = $this->return_statements; + $this->return_statements = true; + + $primary_key_stmts = $this->sql_create_primary_key($table_name, $table_data['PRIMARY_KEY']); + foreach ($primary_key_stmts as $pk_stmt) + { + $statements[] = $pk_stmt; + } + + $this->return_statements = $old_return_statements; + } + } + + // Write Keys + if (isset($table_data['KEYS'])) + { + foreach ($table_data['KEYS'] as $key_name => $key_data) + { + if (!is_array($key_data[1])) + { + $key_data[1] = array($key_data[1]); + } + + $old_return_statements = $this->return_statements; + $this->return_statements = true; + + $key_stmts = ($key_data[0] == 'UNIQUE') ? $this->sql_create_unique_index($table_name, $key_name, $key_data[1]) : $this->sql_create_index($table_name, $key_name, $key_data[1]); + + foreach ($key_stmts as $key_stmt) + { + $statements[] = $key_stmt; + } + + $this->return_statements = $old_return_statements; + } + } + + // Commit Transaction + $statements[] = 'commit'; + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_list_columns($table_name) + { + $columns = array(); + + $sql = "SELECT c.name + FROM syscolumns c + LEFT JOIN sysobjects o ON c.id = o.id + WHERE o.name = '{$table_name}'"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $column = strtolower(current($row)); + $columns[$column] = $column; + } + $this->db->sql_freeresult($result); + + return $columns; + } + + /** + * {@inheritDoc} + */ + function sql_index_exists($table_name, $index_name) + { + $sql = "EXEC sp_statistics '$table_name'"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + if ($row['TYPE'] == 3) + { + if (strtolower($row['INDEX_NAME']) == strtolower($index_name)) + { + $this->db->sql_freeresult($result); + return true; + } + } + } + $this->db->sql_freeresult($result); + + return false; + } + + /** + * {@inheritDoc} + */ + function sql_unique_index_exists($table_name, $index_name) + { + $sql = "EXEC sp_statistics '$table_name'"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + // Usually NON_UNIQUE is the column we want to check, but we allow for both + if ($row['TYPE'] == 3) + { + if (strtolower($row['INDEX_NAME']) == strtolower($index_name)) + { + $this->db->sql_freeresult($result); + return true; + } + } + } + $this->db->sql_freeresult($result); + + return false; + } + + /** + * {@inheritDoc} + */ + function sql_prepare_column_data($table_name, $column_name, $column_data) + { + if (strlen($column_name) > 30) + { + trigger_error("Column name '$column_name' on table '$table_name' is too long. The maximum is 30 characters.", E_USER_ERROR); + } + + // Get type + list($column_type, ) = $this->get_column_type($column_data[0]); + + // Adjust default value if db-dependent specified + if (is_array($column_data[1])) + { + $column_data[1] = (isset($column_data[1][$this->sql_layer])) ? $column_data[1][$this->sql_layer] : $column_data[1]['default']; + } + + $sql = ''; + + $return_array = array(); + + $sql .= " {$column_type} "; + $sql_default = " {$column_type} "; + + // For adding columns we need the default definition + if (!is_null($column_data[1])) + { + // For hexadecimal values do not use single quotes + if (strpos($column_data[1], '0x') === 0) + { + $return_array['default'] = 'DEFAULT (' . $column_data[1] . ') '; + $sql_default .= $return_array['default']; + } + else + { + $return_array['default'] = 'DEFAULT (' . ((is_numeric($column_data[1])) ? $column_data[1] : "'{$column_data[1]}'") . ') '; + $sql_default .= $return_array['default']; + } + } + + if (isset($column_data[2]) && $column_data[2] == 'auto_increment') + { + // $sql .= 'IDENTITY (1, 1) '; + $sql_default .= 'IDENTITY (1, 1) '; + } + + $return_array['textimage'] = $column_type === '[text]'; + + if (!is_null($column_data[1]) || (isset($column_data[2]) && $column_data[2] == 'auto_increment')) + { + $sql .= 'NOT NULL'; + $sql_default .= 'NOT NULL'; + } + else + { + $sql .= 'NULL'; + $sql_default .= 'NULL'; + } + + $return_array['column_type_sql_default'] = $sql_default; + + $return_array['column_type_sql'] = $sql; + + return $return_array; + } + + /** + * {@inheritDoc} + */ + function sql_column_add($table_name, $column_name, $column_data, $inline = false) + { + $column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data); + $statements = array(); + + // Does not support AFTER, only through temporary table + $statements[] = 'ALTER TABLE [' . $table_name . '] ADD [' . $column_name . '] ' . $column_data['column_type_sql_default']; + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_column_remove($table_name, $column_name, $inline = false) + { + $statements = array(); + + // We need the data here + $old_return_statements = $this->return_statements; + $this->return_statements = true; + + $indexes = $this->get_existing_indexes($table_name, $column_name); + $indexes = array_merge($indexes, $this->get_existing_indexes($table_name, $column_name, true)); + + // Drop any indexes + $recreate_indexes = array(); + if (!empty($indexes)) + { + foreach ($indexes as $index_name => $index_data) + { + $result = $this->sql_index_drop($table_name, $index_name); + $statements = array_merge($statements, $result); + if (sizeof($index_data) > 1) + { + // Remove this column from the index and recreate it + $recreate_indexes[$index_name] = array_diff($index_data, array($column_name)); + } + } + } + + // Drop default value constraint + $result = $this->mssql_get_drop_default_constraints_queries($table_name, $column_name); + $statements = array_merge($statements, $result); + + // Remove the column + $statements[] = 'ALTER TABLE [' . $table_name . '] DROP COLUMN [' . $column_name . ']'; + + if (!empty($recreate_indexes)) + { + // Recreate indexes after we removed the column + foreach ($recreate_indexes as $index_name => $index_data) + { + $result = $this->sql_create_index($table_name, $index_name, $index_data); + $statements = array_merge($statements, $result); + } + } + + $this->return_statements = $old_return_statements; + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_index_drop($table_name, $index_name) + { + $statements = array(); + + $statements[] = 'DROP INDEX ' . $table_name . '.' . $index_name; + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_table_drop($table_name) + { + $statements = array(); + + if (!$this->sql_table_exists($table_name)) + { + return $this->_sql_run_sql($statements); + } + + // the most basic operation, get rid of the table + $statements[] = 'DROP TABLE ' . $table_name; + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_create_primary_key($table_name, $column, $inline = false) + { + $statements = array(); + + $sql = "ALTER TABLE [{$table_name}] WITH NOCHECK ADD "; + $sql .= "CONSTRAINT [PK_{$table_name}] PRIMARY KEY CLUSTERED ("; + $sql .= '[' . implode("],\n\t\t[", $column) . ']'; + $sql .= ')'; + + $statements[] = $sql; + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_create_unique_index($table_name, $index_name, $column) + { + $statements = array(); + + $this->check_index_name_length($table_name, $index_name); + + $statements[] = 'CREATE UNIQUE INDEX [' . $index_name . '] ON [' . $table_name . ']([' . implode('], [', $column) . '])'; + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_create_index($table_name, $index_name, $column) + { + $statements = array(); + + $this->check_index_name_length($table_name, $index_name); + + // remove index length + $column = preg_replace('#:.*$#', '', $column); + + $statements[] = 'CREATE INDEX [' . $index_name . '] ON [' . $table_name . ']([' . implode('], [', $column) . '])'; + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_list_index($table_name) + { + $index_array = array(); + $sql = "EXEC sp_statistics '$table_name'"; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if ($row['TYPE'] == 3) + { + $index_array[] = strtolower($row['INDEX_NAME']); + } + } + $this->db->sql_freeresult($result); + + return $index_array; + } + + /** + * {@inheritDoc} + */ + function sql_column_change($table_name, $column_name, $column_data, $inline = false) + { + $column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data); + $statements = array(); + + // We need the data here + $old_return_statements = $this->return_statements; + $this->return_statements = true; + + $indexes = $this->get_existing_indexes($table_name, $column_name); + $unique_indexes = $this->get_existing_indexes($table_name, $column_name, true); + + // Drop any indexes + if (!empty($indexes) || !empty($unique_indexes)) + { + $drop_indexes = array_merge(array_keys($indexes), array_keys($unique_indexes)); + foreach ($drop_indexes as $index_name) + { + $result = $this->sql_index_drop($table_name, $index_name); + $statements = array_merge($statements, $result); + } + } + + // Drop default value constraint + $result = $this->mssql_get_drop_default_constraints_queries($table_name, $column_name); + $statements = array_merge($statements, $result); + + // Change the column + $statements[] = 'ALTER TABLE [' . $table_name . '] ALTER COLUMN [' . $column_name . '] ' . $column_data['column_type_sql']; + + if (!empty($column_data['default'])) + { + // Add new default value constraint + $statements[] = 'ALTER TABLE [' . $table_name . '] ADD CONSTRAINT [DF_' . $table_name . '_' . $column_name . '_1] ' . $column_data['default'] . ' FOR [' . $column_name . ']'; + } + + if (!empty($indexes)) + { + // Recreate indexes after we changed the column + foreach ($indexes as $index_name => $index_data) + { + $result = $this->sql_create_index($table_name, $index_name, $index_data); + $statements = array_merge($statements, $result); + } + } + + if (!empty($unique_indexes)) + { + // Recreate unique indexes after we changed the column + foreach ($unique_indexes as $index_name => $index_data) + { + $result = $this->sql_create_unique_index($table_name, $index_name, $index_data); + $statements = array_merge($statements, $result); + } + } + + $this->return_statements = $old_return_statements; + + return $this->_sql_run_sql($statements); + } + + /** + * Get queries to drop the default constraints of a column + * + * We need to drop the default constraints of a column, + * before being able to change their type or deleting them. + * + * @param string $table_name + * @param string $column_name + * @return array Array with SQL statements + */ + protected function mssql_get_drop_default_constraints_queries($table_name, $column_name) + { + $statements = array(); + if ($this->mssql_is_sql_server_2000()) + { + // http://msdn.microsoft.com/en-us/library/aa175912%28v=sql.80%29.aspx + // Deprecated in SQL Server 2005 + $sql = "SELECT so.name AS def_name + FROM sysobjects so + JOIN sysconstraints sc ON so.id = sc.constid + WHERE object_name(so.parent_obj) = '{$table_name}' + AND so.xtype = 'D' + AND sc.colid = (SELECT colid FROM syscolumns + WHERE id = object_id('{$table_name}') + AND name = '{$column_name}')"; + } + else + { + $sql = "SELECT dobj.name AS def_name + FROM sys.columns col + LEFT OUTER JOIN sys.objects dobj ON (dobj.object_id = col.default_object_id AND dobj.type = 'D') + WHERE col.object_id = object_id('{$table_name}') + AND col.name = '{$column_name}' + AND dobj.name IS NOT NULL"; + } + + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $statements[] = 'ALTER TABLE [' . $table_name . '] DROP CONSTRAINT [' . $row['def_name'] . ']'; + } + $this->db->sql_freeresult($result); + + return $statements; + } + + /** + * Get a list with existing indexes for the column + * + * @param string $table_name + * @param string $column_name + * @param bool $unique Should we get unique indexes or normal ones + * @return array Array with Index name => columns + */ + public function get_existing_indexes($table_name, $column_name, $unique = false) + { + $existing_indexes = array(); + if ($this->mssql_is_sql_server_2000()) + { + // http://msdn.microsoft.com/en-us/library/aa175912%28v=sql.80%29.aspx + // Deprecated in SQL Server 2005 + $sql = "SELECT DISTINCT ix.name AS phpbb_index_name + FROM sysindexes ix + INNER JOIN sysindexkeys ixc + ON ixc.id = ix.id + AND ixc.indid = ix.indid + INNER JOIN syscolumns cols + ON cols.colid = ixc.colid + AND cols.id = ix.id + WHERE ix.id = object_id('{$table_name}') + AND cols.name = '{$column_name}' + AND INDEXPROPERTY(ix.id, ix.name, 'IsUnique') = " . ($unique ? '1' : '0'); + } + else + { + $sql = "SELECT DISTINCT ix.name AS phpbb_index_name + FROM sys.indexes ix + INNER JOIN sys.index_columns ixc + ON ixc.object_id = ix.object_id + AND ixc.index_id = ix.index_id + INNER JOIN sys.columns cols + ON cols.column_id = ixc.column_id + AND cols.object_id = ix.object_id + WHERE ix.object_id = object_id('{$table_name}') + AND cols.name = '{$column_name}' + AND ix.is_unique = " . ($unique ? '1' : '0'); + } + + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if (!isset($row['is_unique']) || ($unique && $row['is_unique'] == 'UNIQUE') || (!$unique && $row['is_unique'] == 'NONUNIQUE')) + { + $existing_indexes[$row['phpbb_index_name']] = array(); + } + } + $this->db->sql_freeresult($result); + + if (empty($existing_indexes)) + { + return array(); + } + + if ($this->mssql_is_sql_server_2000()) + { + $sql = "SELECT DISTINCT ix.name AS phpbb_index_name, cols.name AS phpbb_column_name + FROM sysindexes ix + INNER JOIN sysindexkeys ixc + ON ixc.id = ix.id + AND ixc.indid = ix.indid + INNER JOIN syscolumns cols + ON cols.colid = ixc.colid + AND cols.id = ix.id + WHERE ix.id = object_id('{$table_name}') + AND " . $this->db->sql_in_set('ix.name', array_keys($existing_indexes)); + } + else + { + $sql = "SELECT DISTINCT ix.name AS phpbb_index_name, cols.name AS phpbb_column_name + FROM sys.indexes ix + INNER JOIN sys.index_columns ixc + ON ixc.object_id = ix.object_id + AND ixc.index_id = ix.index_id + INNER JOIN sys.columns cols + ON cols.column_id = ixc.column_id + AND cols.object_id = ix.object_id + WHERE ix.object_id = object_id('{$table_name}') + AND " . $this->db->sql_in_set('ix.name', array_keys($existing_indexes)); + } + + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $existing_indexes[$row['phpbb_index_name']][] = $row['phpbb_column_name']; + } + $this->db->sql_freeresult($result); + + return $existing_indexes; + } + + /** + * Is the used MS SQL Server a SQL Server 2000? + * + * @return bool + */ + protected function mssql_is_sql_server_2000() + { + if ($this->is_sql_server_2000 === null) + { + $sql = "SELECT CAST(SERVERPROPERTY('productversion') AS VARCHAR(25)) AS mssql_version"; + $result = $this->db->sql_query($sql); + $properties = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + $this->is_sql_server_2000 = $properties['mssql_version'][0] == '8'; + } + + return $this->is_sql_server_2000; + } + +} diff --git a/phpBB/phpbb/db/tools/postgres.php b/phpBB/phpbb/db/tools/postgres.php new file mode 100644 index 0000000000..e2a4e668a6 --- /dev/null +++ b/phpBB/phpbb/db/tools/postgres.php @@ -0,0 +1,614 @@ +<?php +/** + * + * This file is part of the phpBB Forum Software package. + * + * @copyright (c) phpBB Limited <https://www.phpbb.com> + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\tools; + +/** + * Database Tools for handling cross-db actions such as altering columns, etc. + * Currently not supported is returning SQL for creating tables. + */ +class postgres extends tools +{ + /** + * Get the column types for postgres only + * + * @return array + */ + public static function get_dbms_type_map() + { + return array( + 'postgres' => array( + 'INT:' => 'INT4', + 'BINT' => 'INT8', + '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_create_table($table_name, $table_data) + { + // holds the DDL for a column + $columns = $statements = array(); + + if ($this->sql_table_exists($table_name)) + { + return $this->_sql_run_sql($statements); + } + + // Begin transaction + $statements[] = 'begin'; + + // Determine if we have created a PRIMARY KEY in the earliest + $primary_key_gen = false; + + // Determine if the table requires a sequence + $create_sequence = false; + + // Begin table sql statement + $table_sql = 'CREATE TABLE ' . $table_name . ' (' . "\n"; + + // Iterate through the columns to create a table + foreach ($table_data['COLUMNS'] as $column_name => $column_data) + { + // here lies an array, filled with information compiled on the column's data + $prepared_column = $this->sql_prepare_column_data($table_name, $column_name, $column_data); + + if (isset($prepared_column['auto_increment']) && $prepared_column['auto_increment'] && strlen($column_name) > 26) // "${column_name}_gen" + { + trigger_error("Index name '${column_name}_gen' on table '$table_name' is too long. The maximum auto increment column length is 26 characters.", E_USER_ERROR); + } + + // here we add the definition of the new column to the list of columns + $columns[] = "\t {$column_name} " . $prepared_column['column_type_sql']; + + // see if we have found a primary key set due to a column definition if we have found it, we can stop looking + if (!$primary_key_gen) + { + $primary_key_gen = isset($prepared_column['primary_key_set']) && $prepared_column['primary_key_set']; + } + + // create sequence DDL based off of the existance of auto incrementing columns + if (!$create_sequence && isset($prepared_column['auto_increment']) && $prepared_column['auto_increment']) + { + $create_sequence = $column_name; + } + } + + // this makes up all the columns in the create table statement + $table_sql .= implode(",\n", $columns); + + // we have yet to create a primary key for this table, + // this means that we can add the one we really wanted instead + if (!$primary_key_gen) + { + // Write primary key + if (isset($table_data['PRIMARY_KEY'])) + { + if (!is_array($table_data['PRIMARY_KEY'])) + { + $table_data['PRIMARY_KEY'] = array($table_data['PRIMARY_KEY']); + } + + $table_sql .= ",\n\t PRIMARY KEY (" . implode(', ', $table_data['PRIMARY_KEY']) . ')'; + } + } + + // do we need to add a sequence for auto incrementing columns? + if ($create_sequence) + { + $statements[] = "CREATE SEQUENCE {$table_name}_seq;"; + } + + // close the table + $table_sql .= "\n);"; + $statements[] = $table_sql; + + // Write Keys + if (isset($table_data['KEYS'])) + { + foreach ($table_data['KEYS'] as $key_name => $key_data) + { + if (!is_array($key_data[1])) + { + $key_data[1] = array($key_data[1]); + } + + $old_return_statements = $this->return_statements; + $this->return_statements = true; + + $key_stmts = ($key_data[0] == 'UNIQUE') ? $this->sql_create_unique_index($table_name, $key_name, $key_data[1]) : $this->sql_create_index($table_name, $key_name, $key_data[1]); + + foreach ($key_stmts as $key_stmt) + { + $statements[] = $key_stmt; + } + + $this->return_statements = $old_return_statements; + } + } + + // Commit Transaction + $statements[] = 'commit'; + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_list_columns($table_name) + { + $columns = array(); + + $sql = "SELECT a.attname + FROM pg_class c, pg_attribute a + WHERE c.relname = '{$table_name}' + AND a.attnum > 0 + AND a.attrelid = c.oid"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $column = strtolower(current($row)); + $columns[$column] = $column; + } + $this->db->sql_freeresult($result); + + return $columns; + } + + /** + * {@inheritDoc} + */ + function sql_index_exists($table_name, $index_name) + { + $sql = "SELECT ic.relname as index_name + FROM pg_class bc, pg_class ic, pg_index i + WHERE (bc.oid = i.indrelid) + AND (ic.oid = i.indexrelid) + AND (bc.relname = '" . $table_name . "') + AND (i.indisunique != 't') + AND (i.indisprimary != 't')"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + // This DBMS prefixes index names with the table name + $row['index_name'] = $this->strip_table_name_from_index_name($table_name, $row['index_name']); + + if (strtolower($row['index_name']) == strtolower($index_name)) + { + $this->db->sql_freeresult($result); + return true; + } + } + $this->db->sql_freeresult($result); + + return false; + } + + /** + * {@inheritDoc} + */ + function sql_unique_index_exists($table_name, $index_name) + { + $sql = "SELECT ic.relname as index_name, i.indisunique + FROM pg_class bc, pg_class ic, pg_index i + WHERE (bc.oid = i.indrelid) + AND (ic.oid = i.indexrelid) + AND (bc.relname = '" . $table_name . "') + AND (i.indisprimary != 't')"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + if ($row['indisunique'] != 't') + { + continue; + } + + // This DBMS prefixes index names with the table name + $row['index_name'] = $this->strip_table_name_from_index_name($table_name, $row['index_name']); + + if (strtolower($row['index_name']) == strtolower($index_name)) + { + $this->db->sql_freeresult($result); + return true; + } + } + $this->db->sql_freeresult($result); + + return false; + } + + /** + * Function to prepare some column information for better usage + * @access private + */ + function sql_prepare_column_data($table_name, $column_name, $column_data) + { + if (strlen($column_name) > 30) + { + trigger_error("Column name '$column_name' on table '$table_name' is too long. The maximum is 30 characters.", E_USER_ERROR); + } + + // Get type + list($column_type, $orig_column_type) = $this->get_column_type($column_data[0]); + + // Adjust default value if db-dependent specified + if (is_array($column_data[1])) + { + $column_data[1] = (isset($column_data[1][$this->sql_layer])) ? $column_data[1][$this->sql_layer] : $column_data[1]['default']; + } + + $sql = " {$column_type} "; + + $return_array = array( + 'column_type' => $column_type, + 'auto_increment' => false, + ); + + if (isset($column_data[2]) && $column_data[2] == 'auto_increment') + { + $default_val = "nextval('{$table_name}_seq')"; + $return_array['auto_increment'] = true; + } + else if (!is_null($column_data[1])) + { + $default_val = "'" . $column_data[1] . "'"; + $return_array['null'] = 'NOT NULL'; + $sql .= 'NOT NULL '; + } + else + { + // Integers need to have 0 instead of empty string as default + if (strpos($column_type, 'INT') === 0) + { + $default_val = '0'; + } + else + { + $default_val = "'" . $column_data[1] . "'"; + } + $return_array['null'] = 'NULL'; + $sql .= 'NULL '; + } + + $return_array['default'] = $default_val; + + $sql .= "DEFAULT {$default_val}"; + + // Unsigned? Then add a CHECK contraint + if (in_array($orig_column_type, $this->unsigned_types)) + { + $return_array['constraint'] = "CHECK ({$column_name} >= 0)"; + $sql .= " CHECK ({$column_name} >= 0)"; + } + + $return_array['column_type_sql'] = $sql; + + return $return_array; + } + + /** + * {@inheritDoc} + */ + function sql_column_add($table_name, $column_name, $column_data, $inline = false) + { + $column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data); + $statements = array(); + + // Does not support AFTER, only through temporary table + if (version_compare($this->db->sql_server_info(true), '8.0', '>=')) + { + $statements[] = 'ALTER TABLE ' . $table_name . ' ADD COLUMN "' . $column_name . '" ' . $column_data['column_type_sql']; + } + else + { + // old versions cannot add columns with default and null information + $statements[] = 'ALTER TABLE ' . $table_name . ' ADD COLUMN "' . $column_name . '" ' . $column_data['column_type'] . ' ' . $column_data['constraint']; + + if (isset($column_data['null'])) + { + if ($column_data['null'] == 'NOT NULL') + { + $statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN ' . $column_name . ' SET NOT NULL'; + } + } + + if (isset($column_data['default'])) + { + $statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN ' . $column_name . ' SET DEFAULT ' . $column_data['default']; + } + } + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_column_remove($table_name, $column_name, $inline = false) + { + $statements = array(); + + $statements[] = 'ALTER TABLE ' . $table_name . ' DROP COLUMN "' . $column_name . '"'; + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_index_drop($table_name, $index_name) + { + $statements = array(); + + $statements[] = 'DROP INDEX ' . $table_name . '_' . $index_name; + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_table_drop($table_name) + { + $statements = array(); + + if (!$this->sql_table_exists($table_name)) + { + return $this->_sql_run_sql($statements); + } + + // the most basic operation, get rid of the table + $statements[] = 'DROP TABLE ' . $table_name; + + // PGSQL does not "tightly" bind sequences and tables, we must guess... + $sql = "SELECT relname + FROM pg_class + WHERE relkind = 'S' + AND relname = '{$table_name}_seq'"; + $result = $this->db->sql_query($sql); + + // We don't even care about storing the results. We already know the answer if we get rows back. + if ($this->db->sql_fetchrow($result)) + { + $statements[] = "DROP SEQUENCE {$table_name}_seq;\n"; + } + $this->db->sql_freeresult($result); + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_create_primary_key($table_name, $column, $inline = false) + { + $statements = array(); + + $statements[] = 'ALTER TABLE ' . $table_name . ' ADD PRIMARY KEY (' . implode(', ', $column) . ')'; + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_create_unique_index($table_name, $index_name, $column) + { + $statements = array(); + + $this->check_index_name_length($table_name, $index_name); + + $statements[] = 'CREATE UNIQUE INDEX ' . $table_name . '_' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')'; + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_create_index($table_name, $index_name, $column) + { + $statements = array(); + + $this->check_index_name_length($table_name, $index_name); + + // remove index length + $column = preg_replace('#:.*$#', '', $column); + + $statements[] = 'CREATE INDEX ' . $table_name . '_' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')'; + + return $this->_sql_run_sql($statements); + } + + + /** + * {@inheritDoc} + */ + function sql_list_index($table_name) + { + $index_array = array(); + + $sql = "SELECT ic.relname as index_name + FROM pg_class bc, pg_class ic, pg_index i + WHERE (bc.oid = i.indrelid) + AND (ic.oid = i.indexrelid) + AND (bc.relname = '" . $table_name . "') + AND (i.indisunique != 't') + AND (i.indisprimary != 't')"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $row['index_name'] = $this->strip_table_name_from_index_name($table_name, $row['index_name']); + + $index_array[] = $row['index_name']; + } + $this->db->sql_freeresult($result); + + return array_map('strtolower', $index_array); + } + + /** + * {@inheritDoc} + */ + function sql_column_change($table_name, $column_name, $column_data, $inline = false) + { + $column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data); + $statements = array(); + + $sql = 'ALTER TABLE ' . $table_name . ' '; + + $sql_array = array(); + $sql_array[] = 'ALTER COLUMN ' . $column_name . ' TYPE ' . $column_data['column_type']; + + if (isset($column_data['null'])) + { + if ($column_data['null'] == 'NOT NULL') + { + $sql_array[] = 'ALTER COLUMN ' . $column_name . ' SET NOT NULL'; + } + else if ($column_data['null'] == 'NULL') + { + $sql_array[] = 'ALTER COLUMN ' . $column_name . ' DROP NOT NULL'; + } + } + + if (isset($column_data['default'])) + { + $sql_array[] = 'ALTER COLUMN ' . $column_name . ' SET DEFAULT ' . $column_data['default']; + } + + // we don't want to double up on constraints if we change different number data types + if (isset($column_data['constraint'])) + { + $constraint_sql = "SELECT consrc as constraint_data + FROM pg_constraint, pg_class bc + WHERE conrelid = bc.oid + AND bc.relname = '{$table_name}' + AND NOT EXISTS ( + SELECT * + FROM pg_constraint as c, pg_inherits as i + WHERE i.inhrelid = pg_constraint.conrelid + AND c.conname = pg_constraint.conname + AND c.consrc = pg_constraint.consrc + AND c.conrelid = i.inhparent + )"; + + $constraint_exists = false; + + $result = $this->db->sql_query($constraint_sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if (trim($row['constraint_data']) == trim($column_data['constraint'])) + { + $constraint_exists = true; + break; + } + } + $this->db->sql_freeresult($result); + + if (!$constraint_exists) + { + $sql_array[] = 'ADD ' . $column_data['constraint']; + } + } + + $sql .= implode(', ', $sql_array); + + $statements[] = $sql; + + return $this->_sql_run_sql($statements); + } + + /** + * Get a list with existing indexes for the column + * + * @param string $table_name + * @param string $column_name + * @param bool $unique Should we get unique indexes or normal ones + * @return array Array with Index name => columns + */ + public function get_existing_indexes($table_name, $column_name, $unique = false) + { + // Not supported + throw new \Exception('DBMS is not supported'); + } +} diff --git a/phpBB/phpbb/db/tools.php b/phpBB/phpbb/db/tools/tools.php index 832a0c510c..37ac0d0468 100644 --- a/phpBB/phpbb/db/tools.php +++ b/phpBB/phpbb/db/tools/tools.php @@ -11,13 +11,13 @@ * */ -namespace phpbb\db; +namespace phpbb\db\tools; /** * Database Tools for handling cross-db actions such as altering columns, etc. * Currently not supported is returning SQL for creating tables. */ -class tools +class tools implements tools_interface { /** * Current sql layer @@ -36,22 +36,17 @@ class tools var $dbms_type_map = array(); /** - * Is the used MS SQL Server a SQL Server 2000? - * @var bool - */ - protected $is_sql_server_2000; - - /** * Get the column types for every database we support * * @return array */ - public static function get_dbms_type_map() + static public 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)', @@ -82,6 +77,7 @@ class tools 'mysql_40' => array( 'INT:' => 'int(%d)', 'BINT' => 'bigint(20)', + 'ULINT' => 'INT(10) UNSIGNED', 'UINT' => 'mediumint(8) UNSIGNED', 'UINT:' => 'int(%d) UNSIGNED', 'TINT:' => 'tinyint(%d)', @@ -109,69 +105,10 @@ class tools 'VARBINARY' => 'varbinary(255)', ), - 'mssql' => array( - 'INT:' => '[int]', - 'BINT' => '[float]', - 'UINT' => '[int]', - 'UINT:' => '[int]', - 'TINT:' => '[int]', - 'USINT' => '[int]', - 'BOOL' => '[int]', - 'VCHAR' => '[varchar] (255)', - 'VCHAR:' => '[varchar] (%d)', - 'CHAR:' => '[char] (%d)', - 'XSTEXT' => '[varchar] (1000)', - 'STEXT' => '[varchar] (3000)', - 'TEXT' => '[varchar] (8000)', - 'MTEXT' => '[text]', - 'XSTEXT_UNI'=> '[varchar] (100)', - 'STEXT_UNI' => '[varchar] (255)', - 'TEXT_UNI' => '[varchar] (4000)', - 'MTEXT_UNI' => '[text]', - 'TIMESTAMP' => '[int]', - 'DECIMAL' => '[float]', - 'DECIMAL:' => '[float]', - 'PDECIMAL' => '[float]', - 'PDECIMAL:' => '[float]', - 'VCHAR_UNI' => '[varchar] (255)', - 'VCHAR_UNI:'=> '[varchar] (%d)', - 'VCHAR_CI' => '[varchar] (255)', - 'VARBINARY' => '[varchar] (255)', - ), - - 'mssqlnative' => array( - 'INT:' => '[int]', - 'BINT' => '[float]', - 'UINT' => '[int]', - 'UINT:' => '[int]', - 'TINT:' => '[int]', - 'USINT' => '[int]', - 'BOOL' => '[int]', - 'VCHAR' => '[varchar] (255)', - 'VCHAR:' => '[varchar] (%d)', - 'CHAR:' => '[char] (%d)', - 'XSTEXT' => '[varchar] (1000)', - 'STEXT' => '[varchar] (3000)', - 'TEXT' => '[varchar] (8000)', - 'MTEXT' => '[text]', - 'XSTEXT_UNI'=> '[varchar] (100)', - 'STEXT_UNI' => '[varchar] (255)', - 'TEXT_UNI' => '[varchar] (4000)', - 'MTEXT_UNI' => '[text]', - 'TIMESTAMP' => '[int]', - 'DECIMAL' => '[float]', - 'DECIMAL:' => '[float]', - 'PDECIMAL' => '[float]', - 'PDECIMAL:' => '[float]', - 'VCHAR_UNI' => '[varchar] (255)', - 'VCHAR_UNI:'=> '[varchar] (%d)', - 'VCHAR_CI' => '[varchar] (255)', - 'VARBINARY' => '[varchar] (255)', - ), - 'oracle' => array( 'INT:' => 'number(%d)', 'BINT' => 'number(20)', + 'ULINT' => 'number(10)', 'UINT' => 'number(8)', 'UINT:' => 'number(%d)', 'TINT:' => 'number(%d)', @@ -202,11 +139,12 @@ class tools 'sqlite' => array( 'INT:' => 'int(%d)', 'BINT' => 'bigint(20)', - 'UINT' => 'INTEGER UNSIGNED', //'mediumint(8) UNSIGNED', + 'ULINT' => 'INTEGER UNSIGNED', // 'int(10) UNSIGNED', + 'UINT' => 'INTEGER UNSIGNED', // 'mediumint(8) UNSIGNED', 'UINT:' => 'INTEGER UNSIGNED', // 'int(%d) UNSIGNED', 'TINT:' => 'tinyint(%d)', - 'USINT' => 'INTEGER UNSIGNED', //'mediumint(4) UNSIGNED', - 'BOOL' => 'INTEGER UNSIGNED', //'tinyint(1) UNSIGNED', + 'USINT' => 'INTEGER UNSIGNED', // 'mediumint(4) UNSIGNED', + 'BOOL' => 'INTEGER UNSIGNED', // 'tinyint(1) UNSIGNED', 'VCHAR' => 'varchar(255)', 'VCHAR:' => 'varchar(%d)', 'CHAR:' => 'char(%d)', @@ -218,7 +156,7 @@ class tools 'STEXT_UNI' => 'text(65535)', 'TEXT_UNI' => 'text(65535)', 'MTEXT_UNI' => 'mediumtext(16777215)', - 'TIMESTAMP' => 'INTEGER UNSIGNED', //'int(11) UNSIGNED', + 'TIMESTAMP' => 'INTEGER UNSIGNED', // 'int(11) UNSIGNED', 'DECIMAL' => 'decimal(5,2)', 'DECIMAL:' => 'decimal(%d,2)', 'PDECIMAL' => 'decimal(6,3)', @@ -232,6 +170,7 @@ class tools 'sqlite3' => array( 'INT:' => 'INT(%d)', 'BINT' => 'BIGINT(20)', + 'ULINT' => 'INTEGER UNSIGNED', 'UINT' => 'INTEGER UNSIGNED', 'UINT:' => 'INTEGER UNSIGNED', 'TINT:' => 'TINYINT(%d)', @@ -258,36 +197,6 @@ class tools 'VCHAR_CI' => 'VARCHAR(255)', 'VARBINARY' => 'BLOB', ), - - 'postgres' => array( - 'INT:' => 'INT4', - 'BINT' => 'INT8', - 'UINT' => 'INT4', // unsigned - 'UINT:' => 'INT4', // unsigned - 'USINT' => 'INT2', // unsigned - 'BOOL' => 'INT2', // unsigned - 'TINT:' => 'INT2', - 'VCHAR' => 'varchar(255)', - 'VCHAR:' => 'varchar(%d)', - 'CHAR:' => 'char(%d)', - 'XSTEXT' => 'varchar(1000)', - 'STEXT' => 'varchar(3000)', - 'TEXT' => 'varchar(8000)', - 'MTEXT' => 'TEXT', - 'XSTEXT_UNI'=> 'varchar(100)', - 'STEXT_UNI' => 'varchar(255)', - 'TEXT_UNI' => 'varchar(4000)', - 'MTEXT_UNI' => 'TEXT', - 'TIMESTAMP' => 'INT4', // unsigned - 'DECIMAL' => 'decimal(5,2)', - 'DECIMAL:' => 'decimal(%d,2)', - 'PDECIMAL' => 'decimal(6,3)', - 'PDECIMAL:' => 'decimal(%d,3)', - 'VCHAR_UNI' => 'varchar(255)', - 'VCHAR_UNI:'=> 'varchar(%d)', - 'VCHAR_CI' => 'varchar_ci', - 'VARBINARY' => 'bytea', - ), ); } @@ -295,13 +204,7 @@ class tools * A list of types being unsigned for better reference in some db's * @var array */ - var $unsigned_types = array('UINT', 'UINT:', 'USINT', 'BOOL', 'TIMESTAMP'); - - /** - * A list of supported DBMS. We change this class to support more DBMS, the DBMS itself only need to follow some rules. - * @var array - */ - var $supported_dbms = array('mssql', 'mssqlnative', 'mysql_40', 'mysql_41', 'oracle', 'postgres', 'sqlite', 'sqlite3'); + 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). @@ -344,15 +247,6 @@ class tools $this->sql_layer = 'mysql_41'; break; - case 'mssql': - case 'mssql_odbc': - $this->sql_layer = 'mssql'; - break; - - case 'mssqlnative': - $this->sql_layer = 'mssqlnative'; - break; - default: $this->sql_layer = $this->db->get_sql_layer(); break; @@ -371,10 +265,8 @@ class tools } /** - * Gets a list of tables in the database. - * - * @return array Array of table names (all lower case) - */ + * {@inheritDoc} + */ function sql_list_tables() { switch ($this->db->get_sql_layer()) @@ -398,19 +290,6 @@ class tools AND name <> "sqlite_sequence"'; break; - case 'mssql': - case 'mssql_odbc': - case 'mssqlnative': - $sql = "SELECT name - FROM sysobjects - WHERE type='U'"; - break; - - case 'postgres': - $sql = 'SELECT relname - FROM pg_stat_user_tables'; - break; - case 'oracle': $sql = 'SELECT table_name FROM USER_TABLES'; @@ -431,12 +310,8 @@ class tools } /** - * Check if table exists - * - * - * @param string $table_name The table name to check for - * @return bool true if table exists, else false - */ + * {@inheritDoc} + */ function sql_table_exists($table_name) { $this->db->sql_return_on_error(true); @@ -453,12 +328,8 @@ class tools } /** - * Create SQL Table - * - * @param string $table_name The table name to create - * @param array $table_data Array containing table data. - * @return array Statements if $return_statements is true. - */ + * {@inheritDoc} + */ function sql_create_table($table_name, $table_data) { // holds the DDL for a column @@ -479,26 +350,7 @@ class tools $create_sequence = false; // Begin table sql statement - switch ($this->sql_layer) - { - case 'mssql': - case 'mssqlnative': - $table_sql = 'CREATE TABLE [' . $table_name . '] (' . "\n"; - break; - - default: - $table_sql = 'CREATE TABLE ' . $table_name . ' (' . "\n"; - break; - } - - if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative') - { - if (!isset($table_data['PRIMARY_KEY'])) - { - $table_data['COLUMNS']['mssqlindex'] = array('UINT', null, 'auto_increment'); - $table_data['PRIMARY_KEY'] = 'mssqlindex'; - } - } + $table_sql = 'CREATE TABLE ' . $table_name . ' (' . "\n"; // Iterate through the columns to create a table foreach ($table_data['COLUMNS'] as $column_name => $column_data) @@ -512,17 +364,7 @@ class tools } // here we add the definition of the new column to the list of columns - switch ($this->sql_layer) - { - case 'mssql': - case 'mssqlnative': - $columns[] = "\t [{$column_name}] " . $prepared_column['column_type_sql_default']; - break; - - default: - $columns[] = "\t {$column_name} " . $prepared_column['column_type_sql']; - break; - } + $columns[] = "\t {$column_name} " . $prepared_column['column_type_sql']; // see if we have found a primary key set due to a column definition if we have found it, we can stop looking if (!$primary_key_gen) @@ -540,16 +382,6 @@ class tools // this makes up all the columns in the create table statement $table_sql .= implode(",\n", $columns); - // Close the table for two DBMS and add to the statements - switch ($this->sql_layer) - { - case 'mssql': - case 'mssqlnative': - $table_sql .= "\n);"; - $statements[] = $table_sql; - break; - } - // we have yet to create a primary key for this table, // this means that we can add the one we really wanted instead if (!$primary_key_gen) @@ -566,27 +398,11 @@ class tools { case 'mysql_40': case 'mysql_41': - case 'postgres': case 'sqlite': case 'sqlite3': $table_sql .= ",\n\t PRIMARY KEY (" . implode(', ', $table_data['PRIMARY_KEY']) . ')'; break; - case 'mssql': - case 'mssqlnative': - // We need the data here - $old_return_statements = $this->return_statements; - $this->return_statements = true; - - $primary_key_stmts = $this->sql_create_primary_key($table_name, $table_data['PRIMARY_KEY']); - foreach ($primary_key_stmts as $pk_stmt) - { - $statements[] = $pk_stmt; - } - - $this->return_statements = $old_return_statements; - break; - case 'oracle': $table_sql .= ",\n\t CONSTRAINT pk_{$table_name} PRIMARY KEY (" . implode(', ', $table_data['PRIMARY_KEY']) . ')'; break; @@ -610,17 +426,6 @@ class tools $statements[] = $table_sql; break; - case 'postgres': - // do we need to add a sequence for auto incrementing columns? - if ($create_sequence) - { - $statements[] = "CREATE SEQUENCE {$table_name}_seq;"; - } - - $table_sql .= "\n);"; - $statements[] = $table_sql; - break; - case 'oracle': $table_sql .= "\n)"; $statements[] = $table_sql; @@ -679,27 +484,8 @@ class tools } /** - * Handle passed database update array. - * Expected structure... - * Key being one of the following - * drop_tables: Drop tables - * add_tables: Add tables - * change_columns: Column changes (only type, not name) - * add_columns: Add columns to a table - * drop_keys: Dropping keys - * drop_columns: Removing/Dropping columns - * add_primary_keys: adding primary keys - * add_unique_index: adding an unique index - * add_index: adding an index (can be column:index_size if you need to provide size) - * - * The values are in this format: - * {TABLE NAME} => array( - * {COLUMN NAME} => array({COLUMN TYPE}, {DEFAULT VALUE}, {OPTIONAL VARIABLES}), - * {KEY/INDEX NAME} => array({COLUMN NAMES}), - * ) - * - * For more information have a look at /develop/create_schema_files.php (only available through SVN) - */ + * {@inheritDoc} + */ function perform_schema_changes($schema_changes) { if (empty($schema_changes)) @@ -1079,13 +865,9 @@ class tools } /** - * Gets a list of columns of a table. - * - * @param string $table Table name - * - * @return array Array of column names (all lower case) - */ - function sql_list_columns($table) + * {@inheritDoc} + */ + function sql_list_columns($table_name) { $columns = array(); @@ -1093,33 +875,13 @@ class tools { case 'mysql_40': case 'mysql_41': - $sql = "SHOW COLUMNS FROM $table"; - break; - - // PostgreSQL has a way of doing this in a much simpler way but would - // not allow us to support all versions of PostgreSQL - case 'postgres': - $sql = "SELECT a.attname - FROM pg_class c, pg_attribute a - WHERE c.relname = '{$table}' - AND a.attnum > 0 - AND a.attrelid = c.oid"; - break; - - // same deal with PostgreSQL, we must perform more complex operations than - // we technically could - case 'mssql': - case 'mssqlnative': - $sql = "SELECT c.name - FROM syscolumns c - LEFT JOIN sysobjects o ON c.id = o.id - WHERE o.name = '{$table}'"; + $sql = "SHOW COLUMNS FROM $table_name"; break; case 'oracle': $sql = "SELECT column_name FROM user_tab_columns - WHERE LOWER(table_name) = '" . strtolower($table) . "'"; + WHERE LOWER(table_name) = '" . strtolower($table_name) . "'"; break; case 'sqlite': @@ -1127,7 +889,7 @@ class tools $sql = "SELECT sql FROM sqlite_master WHERE type = 'table' - AND name = '{$table}'"; + AND name = '{$table_name}'"; $result = $this->db->sql_query($sql); @@ -1173,64 +935,22 @@ class tools } /** - * Check whether a specified column exist in a table - * - * @param string $table Table to check - * @param string $column_name Column to check - * - * @return bool True if column exists, false otherwise - */ - function sql_column_exists($table, $column_name) + * {@inheritDoc} + */ + function sql_column_exists($table_name, $column_name) { - $columns = $this->sql_list_columns($table); + $columns = $this->sql_list_columns($table_name); return isset($columns[$column_name]); } /** - * Check if a specified index exists in table. Does not return PRIMARY KEY and UNIQUE indexes. - * - * @param string $table_name Table to check the index at - * @param string $index_name The index name to check - * - * @return bool True if index exists, else false - */ + * {@inheritDoc} + */ function sql_index_exists($table_name, $index_name) { - if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative') - { - $sql = "EXEC sp_statistics '$table_name'"; - $result = $this->db->sql_query($sql); - - while ($row = $this->db->sql_fetchrow($result)) - { - if ($row['TYPE'] == 3) - { - if (strtolower($row['INDEX_NAME']) == strtolower($index_name)) - { - $this->db->sql_freeresult($result); - return true; - } - } - } - $this->db->sql_freeresult($result); - - return false; - } - switch ($this->sql_layer) { - case 'postgres': - $sql = "SELECT ic.relname as index_name - FROM pg_class bc, pg_class ic, pg_index i - WHERE (bc.oid = i.indrelid) - AND (ic.oid = i.indexrelid) - AND (bc.relname = '" . $table_name . "') - AND (i.indisunique != 't') - AND (i.indisprimary != 't')"; - $col = 'index_name'; - break; - case 'mysql_40': case 'mysql_41': $sql = 'SHOW KEYS @@ -1266,7 +986,6 @@ class tools switch ($this->sql_layer) { case 'oracle': - case 'postgres': case 'sqlite': case 'sqlite3': $row[$col] = substr($row[$col], strlen($table_name) + 1); @@ -1285,48 +1004,12 @@ class tools } /** - * Check if a specified index exists in table. Does not return PRIMARY KEY indexes. - * - * @param string $table_name Table to check the index at - * @param string $index_name The index name to check - * - * @return bool True if index exists, else false - */ + * {@inheritDoc} + */ function sql_unique_index_exists($table_name, $index_name) { - if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative') - { - $sql = "EXEC sp_statistics '$table_name'"; - $result = $this->db->sql_query($sql); - - while ($row = $this->db->sql_fetchrow($result)) - { - // Usually NON_UNIQUE is the column we want to check, but we allow for both - if ($row['TYPE'] == 3) - { - if (strtolower($row['INDEX_NAME']) == strtolower($index_name)) - { - $this->db->sql_freeresult($result); - return true; - } - } - } - $this->db->sql_freeresult($result); - return false; - } - switch ($this->sql_layer) { - case 'postgres': - $sql = "SELECT ic.relname as index_name, i.indisunique - FROM pg_class bc, pg_class ic, pg_index i - WHERE (bc.oid = i.indrelid) - AND (ic.oid = i.indexrelid) - AND (bc.relname = '" . $table_name . "') - AND (i.indisprimary != 't')"; - $col = 'index_name'; - break; - case 'mysql_40': case 'mysql_41': $sql = 'SHOW KEYS @@ -1363,11 +1046,6 @@ class tools continue; } - if ($this->sql_layer == 'postgres' && $row['indisunique'] != 't') - { - continue; - } - // These DBMS prefix index name with the table name switch ($this->sql_layer) { @@ -1383,7 +1061,6 @@ class tools } break; - case 'postgres': case 'sqlite': case 'sqlite3': $row[$col] = substr($row[$col], strlen($table_name) + 1); @@ -1444,7 +1121,7 @@ class tools } // Get type - list($column_type, $orig_column_type) = $this->get_column_type($column_data[0]); + list($column_type) = $this->get_column_type($column_data[0]); // Adjust default value if db-dependent specified if (is_array($column_data[1])) @@ -1458,50 +1135,6 @@ class tools switch ($this->sql_layer) { - case 'mssql': - case 'mssqlnative': - $sql .= " {$column_type} "; - $sql_default = " {$column_type} "; - - // For adding columns we need the default definition - if (!is_null($column_data[1])) - { - // For hexadecimal values do not use single quotes - if (strpos($column_data[1], '0x') === 0) - { - $return_array['default'] = 'DEFAULT (' . $column_data[1] . ') '; - $sql_default .= $return_array['default']; - } - else - { - $return_array['default'] = 'DEFAULT (' . ((is_numeric($column_data[1])) ? $column_data[1] : "'{$column_data[1]}'") . ') '; - $sql_default .= $return_array['default']; - } - } - - if (isset($column_data[2]) && $column_data[2] == 'auto_increment') - { -// $sql .= 'IDENTITY (1, 1) '; - $sql_default .= 'IDENTITY (1, 1) '; - } - - $return_array['textimage'] = $column_type === '[text]'; - - if (!is_null($column_data[1]) || (isset($column_data[2]) && $column_data[2] == 'auto_increment')) - { - $sql .= 'NOT NULL'; - $sql_default .= 'NOT NULL'; - } - else - { - $sql .= 'NULL'; - $sql_default .= 'NULL'; - } - - $return_array['column_type_sql_default'] = $sql_default; - - break; - case 'mysql_40': case 'mysql_41': $sql .= " {$column_type} "; @@ -1560,51 +1193,6 @@ class tools break; - case 'postgres': - $return_array['column_type'] = $column_type; - - $sql .= " {$column_type} "; - - $return_array['auto_increment'] = false; - if (isset($column_data[2]) && $column_data[2] == 'auto_increment') - { - $default_val = "nextval('{$table_name}_seq')"; - $return_array['auto_increment'] = true; - } - else if (!is_null($column_data[1])) - { - $default_val = "'" . $column_data[1] . "'"; - $return_array['null'] = 'NOT NULL'; - $sql .= 'NOT NULL '; - } - else - { - // Integers need to have 0 instead of empty string as default - if (strpos($column_type, 'INT') === 0) - { - $default_val = '0'; - } - else - { - $default_val = "'" . $column_data[1] . "'"; - } - $return_array['null'] = 'NULL'; - $sql .= 'NULL '; - } - - $return_array['default'] = $default_val; - - $sql .= "DEFAULT {$default_val}"; - - // Unsigned? Then add a CHECK contraint - if (in_array($orig_column_type, $this->unsigned_types)) - { - $return_array['constraint'] = "CHECK ({$column_name} >= 0)"; - $sql .= " CHECK ({$column_name} >= 0)"; - } - - break; - case 'sqlite': case 'sqlite3': $return_array['primary_key_set'] = false; @@ -1646,6 +1234,7 @@ class tools */ 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); @@ -1697,8 +1286,8 @@ class tools } /** - * Add new column - */ + * {@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); @@ -1706,12 +1295,6 @@ class tools switch ($this->sql_layer) { - case 'mssql': - case 'mssqlnative': - // Does not support AFTER, only through temporary table - $statements[] = 'ALTER TABLE [' . $table_name . '] ADD [' . $column_name . '] ' . $column_data['column_type_sql_default']; - break; - case 'mysql_40': case 'mysql_41': $after = (!empty($column_data['after'])) ? ' AFTER ' . $column_data['after'] : ''; @@ -1723,33 +1306,6 @@ class tools $statements[] = 'ALTER TABLE ' . $table_name . ' ADD ' . $column_name . ' ' . $column_data['column_type_sql']; break; - case 'postgres': - // Does not support AFTER, only through temporary table - if (version_compare($this->db->sql_server_info(true), '8.0', '>=')) - { - $statements[] = 'ALTER TABLE ' . $table_name . ' ADD COLUMN "' . $column_name . '" ' . $column_data['column_type_sql']; - } - else - { - // old versions cannot add columns with default and null information - $statements[] = 'ALTER TABLE ' . $table_name . ' ADD COLUMN "' . $column_name . '" ' . $column_data['column_type'] . ' ' . $column_data['constraint']; - - if (isset($column_data['null'])) - { - if ($column_data['null'] == 'NOT NULL') - { - $statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN ' . $column_name . ' SET NOT NULL'; - } - } - - if (isset($column_data['default'])) - { - $statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN ' . $column_name . ' SET DEFAULT ' . $column_data['default']; - } - } - - break; - case 'sqlite': if ($inline && $this->return_statements) { @@ -1815,59 +1371,14 @@ class tools } /** - * Drop column - */ + * {@inheritDoc} + */ function sql_column_remove($table_name, $column_name, $inline = false) { $statements = array(); switch ($this->sql_layer) { - case 'mssql': - case 'mssqlnative': - // We need the data here - $old_return_statements = $this->return_statements; - $this->return_statements = true; - - $indexes = $this->get_existing_indexes($table_name, $column_name); - $indexes = array_merge($indexes, $this->get_existing_indexes($table_name, $column_name, true)); - - // Drop any indexes - $recreate_indexes = array(); - if (!empty($indexes)) - { - foreach ($indexes as $index_name => $index_data) - { - $result = $this->sql_index_drop($table_name, $index_name); - $statements = array_merge($statements, $result); - if (sizeof($index_data) > 1) - { - // Remove this column from the index and recreate it - $recreate_indexes[$index_name] = array_diff($index_data, array($column_name)); - } - } - } - - // Drop default value constraint - $result = $this->mssql_get_drop_default_constraints_queries($table_name, $column_name); - $statements = array_merge($statements, $result); - - // Remove the column - $statements[] = 'ALTER TABLE [' . $table_name . '] DROP COLUMN [' . $column_name . ']'; - - if (!empty($recreate_indexes)) - { - // Recreate indexes after we removed the column - foreach ($recreate_indexes as $index_name => $index_data) - { - $result = $this->sql_create_index($table_name, $index_name, $index_data); - $statements = array_merge($statements, $result); - } - } - - $this->return_statements = $old_return_statements; - break; - case 'mysql_40': case 'mysql_41': $statements[] = 'ALTER TABLE `' . $table_name . '` DROP COLUMN `' . $column_name . '`'; @@ -1877,10 +1388,6 @@ class tools $statements[] = 'ALTER TABLE ' . $table_name . ' DROP COLUMN ' . $column_name; break; - case 'postgres': - $statements[] = 'ALTER TABLE ' . $table_name . ' DROP COLUMN "' . $column_name . '"'; - break; - case 'sqlite': case 'sqlite3': @@ -1944,26 +1451,20 @@ class tools } /** - * Drop Index - */ + * {@inheritDoc} + */ function sql_index_drop($table_name, $index_name) { $statements = array(); switch ($this->sql_layer) { - case 'mssql': - case 'mssqlnative': - $statements[] = 'DROP INDEX ' . $table_name . '.' . $index_name; - break; - case 'mysql_40': case 'mysql_41': $statements[] = 'DROP INDEX ' . $index_name . ' ON ' . $table_name; break; case 'oracle': - case 'postgres': case 'sqlite': case 'sqlite3': $statements[] = 'DROP INDEX ' . $table_name . '_' . $index_name; @@ -1974,8 +1475,8 @@ class tools } /** - * Drop Table - */ + * {@inheritDoc} + */ function sql_table_drop($table_name) { $statements = array(); @@ -2005,52 +1506,25 @@ class tools } $this->db->sql_freeresult($result); break; - - case 'postgres': - // PGSQL does not "tightly" bind sequences and tables, we must guess... - $sql = "SELECT relname - FROM pg_class - WHERE relkind = 'S' - AND relname = '{$table_name}_seq'"; - $result = $this->db->sql_query($sql); - - // We don't even care about storing the results. We already know the answer if we get rows back. - if ($this->db->sql_fetchrow($result)) - { - $statements[] = "DROP SEQUENCE {$table_name}_seq;\n"; - } - $this->db->sql_freeresult($result); - break; } return $this->_sql_run_sql($statements); } /** - * Add primary key - */ + * {@inheritDoc} + */ function sql_create_primary_key($table_name, $column, $inline = false) { $statements = array(); switch ($this->sql_layer) { - case 'postgres': case 'mysql_40': case 'mysql_41': $statements[] = 'ALTER TABLE ' . $table_name . ' ADD PRIMARY KEY (' . implode(', ', $column) . ')'; break; - case 'mssql': - case 'mssqlnative': - $sql = "ALTER TABLE [{$table_name}] WITH NOCHECK ADD "; - $sql .= "CONSTRAINT [PK_{$table_name}] PRIMARY KEY CLUSTERED ("; - $sql .= '[' . implode("],\n\t\t[", $column) . ']'; - $sql .= ')'; - - $statements[] = $sql; - break; - case 'oracle': $statements[] = 'ALTER TABLE ' . $table_name . ' add CONSTRAINT pk_' . $table_name . ' PRIMARY KEY (' . implode(', ', $column) . ')'; break; @@ -2111,22 +1585,16 @@ class tools } /** - * Add unique index - */ + * {@inheritDoc} + */ function sql_create_unique_index($table_name, $index_name, $column) { $statements = array(); - $table_prefix = substr(CONFIG_TABLE, 0, -6); // strlen(config) - if (strlen($table_name . '_' . $index_name) - strlen($table_prefix) > 24) - { - $max_length = strlen($table_prefix) + 24; - trigger_error("Index name '{$table_name}_$index_name' on table '$table_name' is too long. The maximum is $max_length characters.", E_USER_ERROR); - } + $this->check_index_name_length($table_name, $index_name); switch ($this->sql_layer) { - case 'postgres': case 'oracle': case 'sqlite': case 'sqlite3': @@ -2137,29 +1605,19 @@ class tools case 'mysql_41': $statements[] = 'ALTER TABLE ' . $table_name . ' ADD UNIQUE INDEX ' . $index_name . '(' . implode(', ', $column) . ')'; break; - - case 'mssql': - case 'mssqlnative': - $statements[] = 'CREATE UNIQUE INDEX [' . $index_name . '] ON [' . $table_name . ']([' . implode('], [', $column) . '])'; - break; } return $this->_sql_run_sql($statements); } /** - * Add index - */ + * {@inheritDoc} + */ function sql_create_index($table_name, $index_name, $column) { $statements = array(); - $table_prefix = substr(CONFIG_TABLE, 0, -6); // strlen(config) - if (strlen($table_name . $index_name) - strlen($table_prefix) > 24) - { - $max_length = strlen($table_prefix) + 24; - trigger_error("Index name '{$table_name}_$index_name' on table '$table_name' is too long. The maximum is $max_length characters.", E_USER_ERROR); - } + $this->check_index_name_length($table_name, $index_name); // remove index length unless MySQL4 if ('mysql_40' != $this->sql_layer) @@ -2169,7 +1627,6 @@ class tools switch ($this->sql_layer) { - case 'postgres': case 'oracle': case 'sqlite': case 'sqlite3': @@ -2190,99 +1647,79 @@ class tools case 'mysql_41': $statements[] = 'ALTER TABLE ' . $table_name . ' ADD INDEX ' . $index_name . ' (' . implode(', ', $column) . ')'; break; - - case 'mssql': - case 'mssqlnative': - $statements[] = 'CREATE INDEX [' . $index_name . '] ON [' . $table_name . ']([' . implode('], [', $column) . '])'; - break; } return $this->_sql_run_sql($statements); } /** - * List all of the indices that belong to a table, - * does not count: - * * UNIQUE indices - * * PRIMARY keys - */ + * Check whether the index name is too long + * + * @param string $table_name + * @param string $index_name + */ + protected function check_index_name_length($table_name, $index_name) + { + $table_prefix = substr(CONFIG_TABLE, 0, -6); // strlen(config) + if (strlen($table_name . $index_name) - strlen($table_prefix) > 24) + { + $max_length = strlen($table_prefix) + 24; + trigger_error("Index name '{$table_name}_$index_name' on table '$table_name' is too long. The maximum is $max_length characters.", E_USER_ERROR); + } + } + + /** + * {@inheritDoc} + */ function sql_list_index($table_name) { $index_array = array(); - if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative') - { - $sql = "EXEC sp_statistics '$table_name'"; - $result = $this->db->sql_query($sql); - while ($row = $this->db->sql_fetchrow($result)) - { - if ($row['TYPE'] == 3) - { - $index_array[] = $row['INDEX_NAME']; - } - } - $this->db->sql_freeresult($result); - } - else + switch ($this->sql_layer) { - switch ($this->sql_layer) - { - case 'postgres': - $sql = "SELECT ic.relname as index_name - FROM pg_class bc, pg_class ic, pg_index i - WHERE (bc.oid = i.indrelid) - AND (ic.oid = i.indexrelid) - AND (bc.relname = '" . $table_name . "') - AND (i.indisunique != 't') - AND (i.indisprimary != 't')"; - $col = 'index_name'; + case 'mysql_40': + case 'mysql_41': + $sql = 'SHOW KEYS + FROM ' . $table_name; + $col = 'Key_name'; break; - case 'mysql_40': - case 'mysql_41': - $sql = 'SHOW KEYS - FROM ' . $table_name; - $col = 'Key_name'; + case 'oracle': + $sql = "SELECT index_name + FROM user_indexes + WHERE table_name = '" . strtoupper($table_name) . "' + AND generated = 'N' + AND uniqueness = 'NONUNIQUE'"; + $col = 'index_name'; break; - case 'oracle': - $sql = "SELECT index_name - FROM user_indexes - WHERE table_name = '" . strtoupper($table_name) . "' - AND generated = 'N' - AND uniqueness = 'NONUNIQUE'"; - $col = 'index_name'; + case 'sqlite': + case 'sqlite3': + $sql = "PRAGMA index_info('" . $table_name . "');"; + $col = 'name'; break; + } - case 'sqlite': - case 'sqlite3': - $sql = "PRAGMA index_info('" . $table_name . "');"; - $col = 'name'; - break; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if (($this->sql_layer == 'mysql_40' || $this->sql_layer == 'mysql_41') && !$row['Non_unique']) + { + continue; } - $result = $this->db->sql_query($sql); - while ($row = $this->db->sql_fetchrow($result)) + switch ($this->sql_layer) { - if (($this->sql_layer == 'mysql_40' || $this->sql_layer == 'mysql_41') && !$row['Non_unique']) - { - continue; - } - - switch ($this->sql_layer) - { - case 'oracle': - case 'postgres': - case 'sqlite': - case 'sqlite3': - $row[$col] = substr($row[$col], strlen($table_name) + 1); + case 'oracle': + case 'sqlite': + case 'sqlite3': + $row[$col] = substr($row[$col], strlen($table_name) + 1); break; - } - - $index_array[] = $row[$col]; } - $this->db->sql_freeresult($result); + + $index_array[] = $row[$col]; } + $this->db->sql_freeresult($result); return array_map('strtolower', $index_array); } @@ -2300,8 +1737,8 @@ class tools } /** - * Change column type (not name!) - */ + * {@inheritDoc} + */ function sql_column_change($table_name, $column_name, $column_data, $inline = false) { $original_column_data = $column_data; @@ -2310,62 +1747,6 @@ class tools switch ($this->sql_layer) { - case 'mssql': - case 'mssqlnative': - // We need the data here - $old_return_statements = $this->return_statements; - $this->return_statements = true; - - $indexes = $this->get_existing_indexes($table_name, $column_name); - $unique_indexes = $this->get_existing_indexes($table_name, $column_name, true); - - // Drop any indexes - if (!empty($indexes) || !empty($unique_indexes)) - { - $drop_indexes = array_merge(array_keys($indexes), array_keys($unique_indexes)); - foreach ($drop_indexes as $index_name) - { - $result = $this->sql_index_drop($table_name, $index_name); - $statements = array_merge($statements, $result); - } - } - - // Drop default value constraint - $result = $this->mssql_get_drop_default_constraints_queries($table_name, $column_name); - $statements = array_merge($statements, $result); - - // Change the column - $statements[] = 'ALTER TABLE [' . $table_name . '] ALTER COLUMN [' . $column_name . '] ' . $column_data['column_type_sql']; - - if (!empty($column_data['default'])) - { - // Add new default value constraint - $statements[] = 'ALTER TABLE [' . $table_name . '] ADD CONSTRAINT [DF_' . $table_name . '_' . $column_name . '_1] ' . $column_data['default'] . ' FOR [' . $column_name . ']'; - } - - if (!empty($indexes)) - { - // Recreate indexes after we changed the column - foreach ($indexes as $index_name => $index_data) - { - $result = $this->sql_create_index($table_name, $index_name, $index_data); - $statements = array_merge($statements, $result); - } - } - - if (!empty($unique_indexes)) - { - // Recreate unique indexes after we changed the column - foreach ($unique_indexes as $index_name => $index_data) - { - $result = $this->sql_create_unique_index($table_name, $index_name, $index_data); - $statements = array_merge($statements, $result); - } - } - - $this->return_statements = $old_return_statements; - break; - case 'mysql_40': case 'mysql_41': $statements[] = 'ALTER TABLE `' . $table_name . '` CHANGE `' . $column_name . '` `' . $column_name . '` ' . $column_data['column_type_sql']; @@ -2437,69 +1818,6 @@ class tools $this->return_statements = $old_return_statements; break; - case 'postgres': - $sql = 'ALTER TABLE ' . $table_name . ' '; - - $sql_array = array(); - $sql_array[] = 'ALTER COLUMN ' . $column_name . ' TYPE ' . $column_data['column_type']; - - if (isset($column_data['null'])) - { - if ($column_data['null'] == 'NOT NULL') - { - $sql_array[] = 'ALTER COLUMN ' . $column_name . ' SET NOT NULL'; - } - else if ($column_data['null'] == 'NULL') - { - $sql_array[] = 'ALTER COLUMN ' . $column_name . ' DROP NOT NULL'; - } - } - - if (isset($column_data['default'])) - { - $sql_array[] = 'ALTER COLUMN ' . $column_name . ' SET DEFAULT ' . $column_data['default']; - } - - // we don't want to double up on constraints if we change different number data types - if (isset($column_data['constraint'])) - { - $constraint_sql = "SELECT consrc as constraint_data - FROM pg_constraint, pg_class bc - WHERE conrelid = bc.oid - AND bc.relname = '{$table_name}' - AND NOT EXISTS ( - SELECT * - FROM pg_constraint as c, pg_inherits as i - WHERE i.inhrelid = pg_constraint.conrelid - AND c.conname = pg_constraint.conname - AND c.consrc = pg_constraint.consrc - AND c.conrelid = i.inhparent - )"; - - $constraint_exists = false; - - $result = $this->db->sql_query($constraint_sql); - while ($row = $this->db->sql_fetchrow($result)) - { - if (trim($row['constraint_data']) == trim($column_data['constraint'])) - { - $constraint_exists = true; - break; - } - } - $this->db->sql_freeresult($result); - - if (!$constraint_exists) - { - $sql_array[] = 'ADD ' . $column_data['constraint']; - } - } - - $sql .= implode(', ', $sql_array); - - $statements[] = $sql; - break; - case 'sqlite': case 'sqlite3': @@ -2568,52 +1886,6 @@ class tools } /** - * Get queries to drop the default constraints of a column - * - * We need to drop the default constraints of a column, - * before being able to change their type or deleting them. - * - * @param string $table_name - * @param string $column_name - * @return array Array with SQL statements - */ - protected function mssql_get_drop_default_constraints_queries($table_name, $column_name) - { - $statements = array(); - if ($this->mssql_is_sql_server_2000()) - { - // http://msdn.microsoft.com/en-us/library/aa175912%28v=sql.80%29.aspx - // Deprecated in SQL Server 2005 - $sql = "SELECT so.name AS def_name - FROM sysobjects so - JOIN sysconstraints sc ON so.id = sc.constid - WHERE object_name(so.parent_obj) = '{$table_name}' - AND so.xtype = 'D' - AND sc.colid = (SELECT colid FROM syscolumns - WHERE id = object_id('{$table_name}') - AND name = '{$column_name}')"; - } - else - { - $sql = "SELECT dobj.name AS def_name - FROM sys.columns col - LEFT OUTER JOIN sys.objects dobj ON (dobj.object_id = col.default_object_id AND dobj.type = 'D') - WHERE col.object_id = object_id('{$table_name}') - AND col.name = '{$column_name}' - AND dobj.name IS NOT NULL"; - } - - $result = $this->db->sql_query($sql); - while ($row = $this->db->sql_fetchrow($result)) - { - $statements[] = 'ALTER TABLE [' . $table_name . '] DROP CONSTRAINT [' . $row['def_name'] . ']'; - } - $this->db->sql_freeresult($result); - - return $statements; - } - - /** * Get a list with existing indexes for the column * * @param string $table_name @@ -2627,7 +1899,6 @@ class tools { case 'mysql_40': case 'mysql_41': - case 'postgres': case 'sqlite': case 'sqlite3': // Not supported @@ -2640,40 +1911,6 @@ class tools switch ($this->sql_layer) { - case 'mssql': - case 'mssqlnative': - if ($this->mssql_is_sql_server_2000()) - { - // http://msdn.microsoft.com/en-us/library/aa175912%28v=sql.80%29.aspx - // Deprecated in SQL Server 2005 - $sql = "SELECT DISTINCT ix.name AS phpbb_index_name - FROM sysindexes ix - INNER JOIN sysindexkeys ixc - ON ixc.id = ix.id - AND ixc.indid = ix.indid - INNER JOIN syscolumns cols - ON cols.colid = ixc.colid - AND cols.id = ix.id - WHERE ix.id = object_id('{$table_name}') - AND cols.name = '{$column_name}' - AND INDEXPROPERTY(ix.id, ix.name, 'IsUnique') = " . ($unique ? '1' : '0'); - } - else - { - $sql = "SELECT DISTINCT ix.name AS phpbb_index_name - FROM sys.indexes ix - INNER JOIN sys.index_columns ixc - ON ixc.object_id = ix.object_id - AND ixc.index_id = ix.index_id - INNER JOIN sys.columns cols - ON cols.column_id = ixc.column_id - AND cols.object_id = ix.object_id - WHERE ix.object_id = object_id('{$table_name}') - AND cols.name = '{$column_name}' - AND ix.is_unique = " . ($unique ? '1' : '0'); - } - break; - case 'oracle': $sql = "SELECT ix.index_name AS phpbb_index_name, ix.uniqueness AS is_unique FROM all_ind_columns ixc, all_indexes ix @@ -2700,36 +1937,6 @@ class tools switch ($this->sql_layer) { - case 'mssql': - case 'mssqlnative': - if ($this->mssql_is_sql_server_2000()) - { - $sql = "SELECT DISTINCT ix.name AS phpbb_index_name, cols.name AS phpbb_column_name - FROM sysindexes ix - INNER JOIN sysindexkeys ixc - ON ixc.id = ix.id - AND ixc.indid = ix.indid - INNER JOIN syscolumns cols - ON cols.colid = ixc.colid - AND cols.id = ix.id - WHERE ix.id = object_id('{$table_name}') - AND " . $this->db->sql_in_set('ix.name', array_keys($existing_indexes)); - } - else - { - $sql = "SELECT DISTINCT ix.name AS phpbb_index_name, cols.name AS phpbb_column_name - FROM sys.indexes ix - INNER JOIN sys.index_columns ixc - ON ixc.object_id = ix.object_id - AND ixc.index_id = ix.index_id - INNER JOIN sys.columns cols - ON cols.column_id = ixc.column_id - AND cols.object_id = ix.object_id - WHERE ix.object_id = object_id('{$table_name}') - AND " . $this->db->sql_in_set('ix.name', array_keys($existing_indexes)); - } - break; - case 'oracle': $sql = "SELECT index_name AS phpbb_index_name, column_name AS phpbb_column_name FROM all_ind_columns @@ -2749,25 +1956,6 @@ class tools } /** - * Is the used MS SQL Server a SQL Server 2000? - * - * @return bool - */ - protected function mssql_is_sql_server_2000() - { - if ($this->is_sql_server_2000 === null) - { - $sql = "SELECT CAST(SERVERPROPERTY('productversion') AS VARCHAR(25)) AS mssql_version"; - $result = $this->db->sql_query($sql); - $properties = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - $this->is_sql_server_2000 = $properties['mssql_version'][0] == '8'; - } - - return $this->is_sql_server_2000; - } - - /** * Returns the Queries which are required to recreate a table including indexes * * @param string $table_name diff --git a/phpBB/phpbb/db/tools/tools_interface.php b/phpBB/phpbb/db/tools/tools_interface.php new file mode 100644 index 0000000000..f153f73a54 --- /dev/null +++ b/phpBB/phpbb/db/tools/tools_interface.php @@ -0,0 +1,202 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\tools; + +/** + * Interface for a Database Tools for handling cross-db actions such as altering columns, etc. + */ +interface tools_interface +{ + /** + * Handle passed database update array. + * Expected structure... + * Key being one of the following + * drop_tables: Drop tables + * add_tables: Add tables + * change_columns: Column changes (only type, not name) + * add_columns: Add columns to a table + * drop_keys: Dropping keys + * drop_columns: Removing/Dropping columns + * add_primary_keys: adding primary keys + * add_unique_index: adding an unique index + * add_index: adding an index (can be column:index_size if you need to provide size) + * + * The values are in this format: + * {TABLE NAME} => array( + * {COLUMN NAME} => array({COLUMN TYPE}, {DEFAULT VALUE}, {OPTIONAL VARIABLES}), + * {KEY/INDEX NAME} => array({COLUMN NAMES}), + * ) + * + * + * @param array $schema_changes + * @return null + */ + public function perform_schema_changes($schema_changes); + + /** + * Gets a list of tables in the database. + * + * @return array Array of table names (all lower case) + */ + public function sql_list_tables(); + + /** + * Check if table exists + * + * @param string $table_name The table name to check for + * @return bool true if table exists, else false + */ + public function sql_table_exists($table_name); + + /** + * Create SQL Table + * + * @param string $table_name The table name to create + * @param array $table_data Array containing table data. + * @return array|true Statements to run, or true if the statements have been executed + */ + public function sql_create_table($table_name, $table_data); + + /** + * Drop Table + * + * @param string $table_name The table name to drop + * @return array|true Statements to run, or true if the statements have been executed + */ + public function sql_table_drop($table_name); + + /** + * Gets a list of columns of a table. + * + * @param string $table_name Table name + * @return array Array of column names (all lower case) + */ + public function sql_list_columns($table_name); + + /** + * Check whether a specified column exist in a table + * + * @param string $table_name Table to check + * @param string $column_name Column to check + * @return bool True if column exists, false otherwise + */ + public function sql_column_exists($table_name, $column_name); + + /** + * Add new column + * + * @param string $table_name Table to modify + * @param string $column_name Name of the column to add + * @param array $column_data Column data + * @param bool $inline Whether the query should actually be run, + * or return a string for adding the column + * @return array|true Statements to run, or true if the statements have been executed + */ + public function sql_column_add($table_name, $column_name, $column_data, $inline = false); + + /** + * Change column type (not name!) + * + * @param string $table_name Table to modify + * @param string $column_name Name of the column to modify + * @param array $column_data Column data + * @param bool $inline Whether the query should actually be run, + * or return a string for modifying the column + * @return array|true Statements to run, or true if the statements have been executed + */ + public function sql_column_change($table_name, $column_name, $column_data, $inline = false); + + /** + * Drop column + * + * @param string $table_name Table to modify + * @param string $column_name Name of the column to drop + * @param bool $inline Whether the query should actually be run, + * or return a string for deleting the column + * @return array|true Statements to run, or true if the statements have been executed + */ + public function sql_column_remove($table_name, $column_name, $inline = false); + + /** + * List all of the indices that belong to a table + * + * NOTE: does not list + * - UNIQUE indices + * - PRIMARY keys + * + * @param string $table_name Table to check + * @return array Array with index names + */ + public function sql_list_index($table_name); + + /** + * Check if a specified index exists in table. Does not return PRIMARY KEY and UNIQUE indexes. + * + * @param string $table_name Table to check the index at + * @param string $index_name The index name to check + * @return bool True if index exists, else false + */ + public function sql_index_exists($table_name, $index_name); + + /** + * Add index + * + * @param string $table_name Table to modify + * @param string $index_name Name of the index to create + * @param string|array $column Either a string with a column name, or an array with columns + * @return array|true Statements to run, or true if the statements have been executed + */ + public function sql_create_index($table_name, $index_name, $column); + + /** + * Drop Index + * + * @param string $table_name Table to modify + * @param string $index_name Name of the index to delete + * @return array|true Statements to run, or true if the statements have been executed + */ + public function sql_index_drop($table_name, $index_name); + + /** + * Check if a specified index exists in table. + * + * NOTE: Does not return normal and PRIMARY KEY indexes + * + * @param string $table_name Table to check the index at + * @param string $index_name The index name to check + * @return bool True if index exists, else false + */ + public function sql_unique_index_exists($table_name, $index_name); + + /** + * Add unique index + * + * @param string $table_name Table to modify + * @param string $index_name Name of the unique index to create + * @param string|array $column Either a string with a column name, or an array with columns + * @return array|true Statements to run, or true if the statements have been executed + */ + public function sql_create_unique_index($table_name, $index_name, $column); + + /** + * Add primary key + * + * @param string $table_name Table to modify + * @param string|array $column Either a string with a column name, or an array with columns + * @param bool $inline Whether the query should actually be run, + * or return a string for creating the key + * @return array|true Statements to run, or true if the statements have been executed + */ + public function sql_create_primary_key($table_name, $column, $inline = false); +} |