diff options
7 files changed, 516 insertions, 13 deletions
diff --git a/phpBB/includes/functions_database_helper.php b/phpBB/includes/functions_database_helper.php new file mode 100644 index 0000000000..664c246888 --- /dev/null +++ b/phpBB/includes/functions_database_helper.php @@ -0,0 +1,206 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** +* Updates rows in given table from a set of values to a new value. +* If this results in rows violating uniqueness constraints, the duplicate +* rows are eliminated. +* +* The only supported table is bookmarks. +* +* @param dbal $db Database object +* @param string $table Table on which to perform the update +* @param string $column Column whose values to change +* @param array $from_values An array of values that should be changed +* @param int $to_value The new value +* @return null +*/ +function phpbb_update_rows_avoiding_duplicates($db, $table, $column, $from_values, $to_value) +{ +	$sql = "SELECT $column, user_id +		FROM $table +		WHERE " . $db->sql_in_set($column, $from_values); +	$result = $db->sql_query($sql); + +	$old_user_ids = array(); +	while ($row = $db->sql_fetchrow($result)) +	{ +		$old_user_ids[$row[$column]][] = (int) $row['user_id']; +	} +	$db->sql_freeresult($result); + +	$sql = "SELECT $column, user_id +		FROM $table +		WHERE $column = " . (int) $to_value; +	$result = $db->sql_query($sql); + +	$new_user_ids = array(); +	while ($row = $db->sql_fetchrow($result)) +	{ +		$new_user_ids[$row[$column]][] = (int) $row['user_id']; +	} +	$db->sql_freeresult($result); + +	$queries = array(); +	foreach ($from_values as $from_value) +	{ +		if (!isset($old_user_ids[$from_value])) +		{ +			continue; +		} +		if (empty($new_user_ids)) +		{ +			$sql = "UPDATE $table +				SET $column = " . (int) $to_value . " +				WHERE $column = '" . $db->sql_escape($from_value) . "'"; +			$queries[] = $sql; +		} +		else +		{ +			$different_user_ids = array_diff($old_user_ids[$from_value], $new_user_ids[$to_value]); +			if (!empty($different_user_ids)) +			{ +				$sql = "UPDATE $table +					SET $column = " . (int) $to_value . " +					WHERE $column = '" . $db->sql_escape($from_value) . "' +					AND " . $db->sql_in_set('user_id', $different_user_ids); +				$queries[] = $sql; +			} +		} +	} + +	if (!empty($queries)) +	{ +		$db->sql_transaction('begin'); + +		foreach ($queries as $sql) +		{ +			$db->sql_query($sql); +		} + +		$sql = "DELETE FROM $table +			WHERE " . $db->sql_in_set($column, $from_values); +		$db->sql_query($sql); + +		$db->sql_transaction('commit'); +	} +} + +/** +* Updates rows in given table from a set of values to a new value. +* If this results in rows violating uniqueness constraints, the duplicate +* rows are merged respecting notify_status (0 takes precedence over 1). +* +* The only supported table is topics_watch. +* +* @param dbal $db Database object +* @param string $table Table on which to perform the update +* @param string $column Column whose values to change +* @param array $from_values An array of values that should be changed +* @param int $to_value The new value +* @return null +*/ +function phpbb_update_rows_avoiding_duplicates_notify_status($db, $table, $column, $from_values, $to_value) +{ +	$sql = "SELECT $column, user_id, notify_status +		FROM $table +		WHERE " . $db->sql_in_set($column, $from_values); +	$result = $db->sql_query($sql); + +	$old_user_ids = array(); +	while ($row = $db->sql_fetchrow($result)) +	{ +		$old_user_ids[(int) $row['notify_status']][$row[$column]][] = (int) $row['user_id']; +	} +	$db->sql_freeresult($result); + +	$sql = "SELECT $column, user_id +		FROM $table +		WHERE $column = " . (int) $to_value; +	$result = $db->sql_query($sql); + +	$new_user_ids = array(); +	while ($row = $db->sql_fetchrow($result)) +	{ +		$new_user_ids[$row[$column]][] = (int) $row['user_id']; +	} +	$db->sql_freeresult($result); + +	$queries = array(); +	$extra_updates = array( +		0 => 'notify_status = 0', +		1 => '', +	); +	foreach ($from_values as $from_value) +	{ +		foreach ($extra_updates as $notify_status => $extra_update) +		{ +			if (!isset($old_user_ids[$notify_status][$from_value])) +			{ +				continue; +			} +			if (empty($new_user_ids)) +			{ +				$sql = "UPDATE $table +					SET $column = " . (int) $to_value . " +					WHERE $column = '" . $db->sql_escape($from_value) . "'"; +				$queries[] = $sql; +			} +			else +			{ +				$different_user_ids = array_diff($old_user_ids[$notify_status][$from_value], $new_user_ids[$to_value]); +				if (!empty($different_user_ids)) +				{ +					$sql = "UPDATE $table +						SET $column = " . (int) $to_value . " +						WHERE $column = '" . $db->sql_escape($from_value) . "' +						AND " . $db->sql_in_set('user_id', $different_user_ids); +					$queries[] = $sql; +				} + +				if ($extra_update) +				{ +					$same_user_ids = array_diff($old_user_ids[$notify_status][$from_value], $different_user_ids); +					if (!empty($same_user_ids)) +					{ +						$sql = "UPDATE $table +							SET $extra_update +							WHERE $column = '" . (int) $to_value . "' +							AND " . $db->sql_in_set('user_id', $same_user_ids); +						$queries[] = $sql; +					} +				} +			} +		} +	} + +	if (!empty($queries)) +	{ +		$db->sql_transaction('begin'); + +		foreach ($queries as $sql) +		{ +			$db->sql_query($sql); +		} + +		$sql = "DELETE FROM $table +			WHERE " . $db->sql_in_set($column, $from_values); +		$db->sql_query($sql); + +		$db->sql_transaction('commit'); +	} +} diff --git a/phpBB/includes/mcp/mcp_forum.php b/phpBB/includes/mcp/mcp_forum.php index b70601b479..db9fbd90bd 100644 --- a/phpBB/includes/mcp/mcp_forum.php +++ b/phpBB/includes/mcp/mcp_forum.php @@ -414,13 +414,12 @@ function merge_topics($forum_id, $topic_ids, $to_topic_id)  		// Message and return links  		$success_msg = 'POSTS_MERGED_SUCCESS'; -		// If the topic no longer exist, we will update the topic watch table. -		// To not let it error out on users watching both topics, we just return on an error... -		$db->sql_return_on_error(true); -		$db->sql_query('UPDATE ' . TOPICS_WATCH_TABLE . ' SET topic_id = ' . (int) $to_topic_id . ' WHERE ' . $db->sql_in_set('topic_id', $topic_ids)); -		$db->sql_return_on_error(false); - -		$db->sql_query('DELETE FROM ' . TOPICS_WATCH_TABLE . ' WHERE ' . $db->sql_in_set('topic_id', $topic_ids)); +		// Update the topic watch table. +		if (!function_exists('phpbb_update_rows_avoiding_duplicates_notify_status')) +		{ +			include($phpbb_root_path . 'includes/functions_database_helper.' . $phpEx); +		} +		phpbb_update_rows_avoiding_duplicates_notify_status($db, TOPICS_WATCH_TABLE, 'topic_id', $topic_ids, $to_topic_id);  		// Link to the new topic  		$return_link .= (($return_link) ? '<br /><br />' : '') . sprintf($user->lang['RETURN_NEW_TOPIC'], '<a href="' . append_sid("{$phpbb_root_path}viewtopic.$phpEx", 'f=' . $to_forum_id . '&t=' . $to_topic_id) . '">', '</a>'); diff --git a/phpBB/includes/mcp/mcp_topic.php b/phpBB/includes/mcp/mcp_topic.php index 7d4edaf362..66d0c7a47e 100644 --- a/phpBB/includes/mcp/mcp_topic.php +++ b/phpBB/includes/mcp/mcp_topic.php @@ -620,12 +620,11 @@ function merge_posts($topic_id, $to_topic_id)  		else  		{  			// If the topic no longer exist, we will update the topic watch table. -			// To not let it error out on users watching both topics, we just return on an error... -			$db->sql_return_on_error(true); -			$db->sql_query('UPDATE ' . TOPICS_WATCH_TABLE . ' SET topic_id = ' . (int) $to_topic_id . ' WHERE topic_id = ' . (int) $topic_id); -			$db->sql_return_on_error(false); - -			$db->sql_query('DELETE FROM ' . TOPICS_WATCH_TABLE . ' WHERE topic_id = ' . (int) $topic_id); +			if (!function_exists('phpbb_update_rows_avoiding_duplicates_notify_status')) +			{ +				include($phpbb_root_path . 'includes/functions_database_helper.' . $phpEx); +			} +			phpbb_update_rows_avoiding_duplicates_notify_status($db, TOPICS_WATCH_TABLE, 'topic_id', $topic_ids, $to_topic_id);  		}  		// Link to the new topic diff --git a/tests/functions_database_helper/fixtures/bookmarks_duplicates.xml b/tests/functions_database_helper/fixtures/bookmarks_duplicates.xml new file mode 100644 index 0000000000..d49f76b073 --- /dev/null +++ b/tests/functions_database_helper/fixtures/bookmarks_duplicates.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<dataset> +	<table name="phpbb_bookmarks"> +		<column>user_id</column> +		<column>topic_id</column> + +		<!-- one entry for this topic --> +		<row> +			<value>1</value> +			<value>1</value> +		</row> + +		<!-- non-conflicting entries --> +		<row> +			<value>2</value> +			<value>2</value> +		</row> +		<row> +			<value>3</value> +			<value>3</value> +		</row> + +		<!-- conflicting entries --> +		<row> +			<value>1</value> +			<value>4</value> +		</row> +		<row> +			<value>1</value> +			<value>5</value> +		</row> + +		<!-- conflicting and non-conflicting entries --> +		<row> +			<value>1</value> +			<value>6</value> +		</row> +		<row> +			<value>1</value> +			<value>7</value> +		</row> +		<row> +			<value>2</value> +			<value>6</value> +		</row> +	</table> +</dataset> diff --git a/tests/functions_database_helper/fixtures/topics_watch_duplicates.xml b/tests/functions_database_helper/fixtures/topics_watch_duplicates.xml new file mode 100644 index 0000000000..c387bb737a --- /dev/null +++ b/tests/functions_database_helper/fixtures/topics_watch_duplicates.xml @@ -0,0 +1,80 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<dataset> +	<table name="phpbb_topics_watch"> +		<column>user_id</column> +		<column>topic_id</column> +		<column>notify_status</column> + +		<!-- one entry for this topic --> +		<row> +			<value>1</value> +			<value>1</value> +			<value>1</value> +		</row> + +		<!-- non-conflicting entries --> +		<row> +			<value>1</value> +			<value>2</value> +			<value>1</value> +		</row> +		<row> +			<value>2</value> +			<value>3</value> +			<value>0</value> +		</row> + +		<!-- conflicting entries, same notify status --> +		<row> +			<value>1</value> +			<value>4</value> +			<value>1</value> +		</row> +		<row> +			<value>1</value> +			<value>5</value> +			<value>1</value> +		</row> + +		<!-- conflicting entries, notify status 0 into 1 --> +		<row> +			<value>1</value> +			<value>6</value> +			<value>0</value> +		</row> +		<row> +			<value>1</value> +			<value>7</value> +			<value>1</value> +		</row> + +		<!-- conflicting entries, notify status 1 into 0 --> +		<row> +			<value>1</value> +			<value>8</value> +			<value>1</value> +		</row> +		<row> +			<value>1</value> +			<value>9</value> +			<value>0</value> +		</row> + +		<!-- conflicting and non-conflicting entries --> +		<row> +			<value>1</value> +			<value>10</value> +			<value>0</value> +		</row> +		<row> +			<value>1</value> +			<value>11</value> +			<value>1</value> +		</row> +		<row> +			<value>2</value> +			<value>10</value> +			<value>1</value> +		</row> +	</table> +</dataset> diff --git a/tests/functions_database_helper/update_rows_avoiding_duplicates_notify_status_test.php b/tests/functions_database_helper/update_rows_avoiding_duplicates_notify_status_test.php new file mode 100644 index 0000000000..d4881daf7e --- /dev/null +++ b/tests/functions_database_helper/update_rows_avoiding_duplicates_notify_status_test.php @@ -0,0 +1,101 @@ +<?php +/** +* +* @package testing +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +require_once dirname(__FILE__) . '/../../phpBB/includes/functions_database_helper.php'; + +class phpbb_update_rows_avoiding_duplicates_notify_status_test extends phpbb_database_test_case +{ +	public function getDataSet() +	{ +		return $this->createXMLDataSet(dirname(__FILE__).'/fixtures/topics_watch_duplicates.xml'); +	} + +	public static function fixture_data() +	{ +		return array( +			// description +			// from array +			// to value +			// expected count with to value post update +			// expected notify_status values +			array( +				'trivial', +				array(1), +				1000, +				1, +				1, +			), +			array( +				'no conflict', +				array(2), +				3, +				2, +				1, +			), +			array( +				'conflict, same notify status', +				array(4), +				5, +				1, +				1, +			), +			array( +				'conflict, notify status 0 into 1', +				array(6), +				7, +				1, +				0, +			), +			array( +				'conflict, notify status 1 into 0', +				array(8), +				9, +				1, +				0, +			), +			array( +				'conflict and no conflict', +				array(10), +				11, +				2, +				0, +			), +		); +	} + +	/** +	* @dataProvider fixture_data +	*/ +	public function test_update($description, $from, $to, $expected_result_count, $expected_notify_status) +	{ +		$db = $this->new_dbal(); + +		phpbb_update_rows_avoiding_duplicates_notify_status($db, TOPICS_WATCH_TABLE, 'topic_id', $from, $to); + +		$sql = 'SELECT COUNT(*) AS remaining_rows +			FROM ' . TOPICS_WATCH_TABLE . ' +			WHERE topic_id = ' . (int) $to; +		$result = $db->sql_query($sql); +		$result_count = $db->sql_fetchfield('remaining_rows'); +		$db->sql_freeresult($result); + +		$this->assertEquals($expected_result_count, $result_count); + +		// user id of 1 is the user being updated +		$sql = 'SELECT notify_status +			FROM ' . TOPICS_WATCH_TABLE . ' +			WHERE topic_id = ' . (int) $to . ' +			AND user_id = 1'; +		$result = $db->sql_query($sql); +		$notify_status = $db->sql_fetchfield('notify_status'); +		$db->sql_freeresult($result); + +		$this->assertEquals($expected_notify_status, $notify_status); +	} +} diff --git a/tests/functions_database_helper/update_rows_avoiding_duplicates_test.php b/tests/functions_database_helper/update_rows_avoiding_duplicates_test.php new file mode 100644 index 0000000000..2f01d29d15 --- /dev/null +++ b/tests/functions_database_helper/update_rows_avoiding_duplicates_test.php @@ -0,0 +1,71 @@ +<?php +/** +* +* @package testing +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +require_once dirname(__FILE__) . '/../../phpBB/includes/functions_database_helper.php'; + +class phpbb_update_rows_avoiding_duplicates_test extends phpbb_database_test_case +{ +	public function getDataSet() +	{ +		return $this->createXMLDataSet(dirname(__FILE__).'/fixtures/bookmarks_duplicates.xml'); +	} + +	public static function fixture_data() +	{ +		return array( +			// description +			// from array +			// to value +			// expected count with to value post update +			array( +				'trivial', +				array(1), +				10, +				1, +			), +			array( +				'no conflict', +				array(2), +				3, +				2, +			), +			array( +				'conflict', +				array(4), +				5, +				1, +			), +			array( +				'conflict and no conflict', +				array(6), +				7, +				2, +			), +		); +	} + +	/** +	* @dataProvider fixture_data +	*/ +	public function test_update($description, $from, $to, $expected_result_count) +	{ +		$db = $this->new_dbal(); + +		phpbb_update_rows_avoiding_duplicates($db, BOOKMARKS_TABLE, 'topic_id', $from, $to); + +		$sql = 'SELECT COUNT(*) AS remaining_rows +			FROM ' . BOOKMARKS_TABLE . ' +			WHERE topic_id = ' . (int) $to; +		$result = $db->sql_query($sql); +		$result_count = $db->sql_fetchfield('remaining_rows'); +		$db->sql_freeresult($result); + +		$this->assertEquals($expected_result_count, $result_count); +	} +}  | 
