From 169e15be81c454e7485817ba3b1b242fc1ff361a Mon Sep 17 00:00:00 2001 From: Nisarg Jhaveri Date: Fri, 15 Aug 2014 18:26:31 +0530 Subject: [PATCH] feature 857: Regex search and replace - Implemented basic regex search and replace for tables. - Added a checkbox to use regex in 'Find and Replace' view. - Fixed some tests for this feature Signed-off-by: Nisarg Jhaveri --- libraries/TableSearch.class.php | 138 ++++++++++++++----- tbl_find_replace.php | 2 + test/classes/PMA_TableReplaceSearch_test.php | 22 ++- test/classes/PMA_TableSearch_test.php | 13 +- 4 files changed, 119 insertions(+), 56 deletions(-) diff --git a/libraries/TableSearch.class.php b/libraries/TableSearch.class.php index 409ee29209..4491730f63 100644 --- a/libraries/TableSearch.class.php +++ b/libraries/TableSearch.class.php @@ -1362,40 +1362,81 @@ EOT; } } $htmlOutput .= ''; + + $htmlOutput .= '
' . PMA_Util::getCheckbox('useRegex' , __('Use regular expression'), false, false); return $htmlOutput; } /** - * Returns HTML for previewing strings found and their replacements + * Finds and returns Regex pattern and their replacements * * @param int $columnIndex index of the column * @param string $find string to find in the column * @param string $replaceWith string to replace with * @param string $charSet character set of the connection * - * @return string HTML for previewing strings found and their replacements + * @return array Array containing original values, replcaed values and count */ - function getReplacePreview($columnIndex, $find, $replaceWith, $charSet) + function _getRegexReplaceRows($columnIndex, $find, $replaceWith, $charSet) { $column = $this->_columnNames[$columnIndex]; $sql_query = "SELECT " . PMA_Util::backquote($column) . "," - . " REPLACE(" - . PMA_Util::backquote($column) . ", '" . $find . "', '" . $replaceWith - . "')," + . " 1," // to add an extra column that will have replaced value . " COUNT(*)" . " FROM " . PMA_Util::backquote($this->_db) . "." . PMA_Util::backquote($this->_table) . " WHERE " . PMA_Util::backquote($column) - . " LIKE '%" . $find . "%' COLLATE " . $charSet . "_bin"; // here we + . " RLIKE '" . PMA_Util::sqlAddSlashes($find) . "' COLLATE " . $charSet . "_bin"; // here we // change the collation of the 2nd operand to a case sensitive // binary collation to make sure that the comparison is case sensitive $sql_query .= " GROUP BY " . PMA_Util::backquote($column) . " ORDER BY " . PMA_Util::backquote($column) . " ASC"; - $resultSet = $GLOBALS['dbi']->query( - $sql_query, null, PMA_DatabaseInterface::QUERY_STORE - ); + $result = $GLOBALS['dbi']->fetchResult($sql_query, 0); + + if (is_array($result)) { + foreach ($result as $index=>$row) { + $result[$index][1] = preg_replace("/" . $find . "/" , $replaceWith, $row[0]); + } + } + return $result; + } + + /** + * Returns HTML for previewing strings found and their replacements + * + * @param int $columnIndex index of the column + * @param string $find string to find in the column + * @param string $replaceWith string to replace with + * @param boolean $useRegex to use Regex replace or not + * @param string $charSet character set of the connection + * + * @return string HTML for previewing strings found and their replacements + */ + function getReplacePreview($columnIndex, $find, $replaceWith, $useRegex, $charSet) + { + $column = $this->_columnNames[$columnIndex]; + if( $useRegex ) { + $result = $this->_getRegexReplaceRows($columnIndex, $find, $replaceWith, $charSet); + } else { + $sql_query = "SELECT " + . PMA_Util::backquote($column) . "," + . " REPLACE(" + . PMA_Util::backquote($column) . ", '" . $find . "', '" . $replaceWith + . "')," + . " COUNT(*)" + . " FROM " . PMA_Util::backquote($this->_db) + . "." . PMA_Util::backquote($this->_table) + . " WHERE " . PMA_Util::backquote($column) + . " LIKE '%" . $find . "%' COLLATE " . $charSet . "_bin"; // here we + // change the collation of the 2nd operand to a case sensitive + // binary collation to make sure that the comparison is case sensitive + $sql_query .= " GROUP BY " . PMA_Util::backquote($column) + . " ORDER BY " . PMA_Util::backquote($column) . " ASC"; + + $result = $GLOBALS['dbi']->fetchResult($sql_query, 0); + } $htmlOutput = '
'; @@ -1404,9 +1445,11 @@ EOT; $htmlOutput .= ''; $htmlOutput .= ''; + . ' value="' . htmlspecialchars($find) . '" />'; $htmlOutput .= ''; + . ' value="' . htmlspecialchars($replaceWith) . '" />'; + $htmlOutput .= ''; $htmlOutput .= '
'; $htmlOutput .= '' . __('Find and replace - preview') . ''; @@ -1420,18 +1463,20 @@ EOT; $htmlOutput .= ''; $odd = true; - while ($row = $GLOBALS['dbi']->fetchRow($resultSet)) { - $val = $row[0]; - $replaced = $row[1]; - $count = $row[2]; + if (is_array($result)) { + foreach ($result as $row) { + $val = $row[0]; + $replaced = $row[1]; + $count = $row[2]; - $htmlOutput .= ''; - $htmlOutput .= '' . htmlspecialchars($count) . ''; - $htmlOutput .= '' . htmlspecialchars($val) . ''; - $htmlOutput .= '' . htmlspecialchars($replaced) . ''; - $htmlOutput .= ''; + $htmlOutput .= ''; + $htmlOutput .= '' . htmlspecialchars($count) . ''; + $htmlOutput .= '' . htmlspecialchars($val) . ''; + $htmlOutput .= '' . htmlspecialchars($replaced) . ''; + $htmlOutput .= ''; - $odd = ! $odd; + $odd = ! $odd; + } } $htmlOutput .= ''; $htmlOutput .= ''; @@ -1449,26 +1494,45 @@ EOT; /** * Replaces a given string in a column with a give replacement * - * @param int $columnIndex index of the column - * @param string $find string to find in the column - * @param string $replaceWith string to replace with - * @param string $charSet character set of the connection + * @param int $columnIndex index of the column + * @param string $find string to find in the column + * @param string $replaceWith string to replace with + * @param boolean $useRegex to use Regex replace or not + * @param string $charSet character set of the connection * * @return void */ - function replace($columnIndex, $find, $replaceWith, $charSet) + function replace($columnIndex, $find, $replaceWith, $useRegex, $charSet) { $column = $this->_columnNames[$columnIndex]; - $sql_query = "UPDATE " . PMA_Util::backquote($this->_db) - . "." . PMA_Util::backquote($this->_table) - . " SET " . PMA_Util::backquote($column) . " =" - . " REPLACE(" - . PMA_Util::backquote($column) . ", '" . $find . "', '" . $replaceWith - . "')" - . " WHERE " . PMA_Util::backquote($column) - . " LIKE '%" . $find . "%' COLLATE " . $charSet . "_bin"; // here we - // change the collation of the 2nd operand to a case sensitive - // binary collation to make sure that the comparison is case sensitive + if ($useRegex) { + $toReplace = $this->_getRegexReplaceRows($columnIndex, $find, $replaceWith, $charSet); + $sql_query = "UPDATE " . PMA_Util::backquote($this->_db) + . "." . PMA_Util::backquote($this->_table) + . " SET " . PMA_Util::backquote($column) . " = CASE"; + if (is_array($toReplace)) { + foreach ($toReplace as $row) { + $sql_query .= "\n WHEN " . PMA_Util::backquote($column) . " = '" . PMA_Util::sqlAddSlashes($row[0]) . "' THEN '" . PMA_Util::sqlAddSlashes($row[1]) . "'"; + } + } + $sql_query .= " END" + . " WHERE " . PMA_Util::backquote($column) + . " RLIKE '" . PMA_Util::sqlAddSlashes($find) . "' COLLATE " . $charSet . "_bin"; // here we + // change the collation of the 2nd operand to a case sensitive + // binary collation to make sure that the comparison is case sensitive + } + else { + $sql_query = "UPDATE " . PMA_Util::backquote($this->_db) + . "." . PMA_Util::backquote($this->_table) + . " SET " . PMA_Util::backquote($column) . " =" + . " REPLACE(" + . PMA_Util::backquote($column) . ", '" . $find . "', '" . $replaceWith + . "')" + . " WHERE " . PMA_Util::backquote($column) + . " LIKE '%" . $find . "%' COLLATE " . $charSet . "_bin"; // here we + // change the collation of the 2nd operand to a case sensitive + // binary collation to make sure that the comparison is case sensitive + } $GLOBALS['dbi']->query( $sql_query, null, PMA_DatabaseInterface::QUERY_STORE ); diff --git a/tbl_find_replace.php b/tbl_find_replace.php index 92f3e079fd..2611b89840 100644 --- a/tbl_find_replace.php +++ b/tbl_find_replace.php @@ -25,6 +25,7 @@ if (isset($_POST['find'])) { $_POST['columnIndex'], $_POST['find'], $_POST['replaceWith'], + $_POST['useRegex'], $connectionCharSet ); $response->addJSON('preview', $preview); @@ -43,6 +44,7 @@ if (isset($_POST['replace'])) { $_POST['columnIndex'], $_POST['findString'], $_POST['replaceWith'], + $_POST['useRegex'], $connectionCharSet ); $htmlOutput .= PMA_Util::getMessage( diff --git a/test/classes/PMA_TableReplaceSearch_test.php b/test/classes/PMA_TableReplaceSearch_test.php index 3b36b79f88..b3480f19ba 100644 --- a/test/classes/PMA_TableReplaceSearch_test.php +++ b/test/classes/PMA_TableReplaceSearch_test.php @@ -74,6 +74,7 @@ class PMA_TableReplaceSearchTest extends PHPUnit_Framework_TestCase $find = 'findValue'; $replaceWith = 'replaceWithValue'; + $useRegex = false; $charSet = 'charSetValue'; $expectedQuery = "SELECT `column1`, REPLACE(`column1`, '" . $find . "', '" @@ -82,20 +83,14 @@ class PMA_TableReplaceSearchTest extends PHPUnit_Framework_TestCase . " `column1` ORDER BY `column1` ASC"; // set expectations $dbi->expects($this->once()) - ->method('query') - ->with($expectedQuery); - $dbi->expects($this->at(1)) - ->method('fetchRow') - ->will($this->returnValue(array('val1', 'replace1', 5))); - $dbi->expects($this->at(2)) - ->method('fetchRow') - ->will($this->returnValue(array('va<2', 'replac<2', 1))); - $dbi->expects($this->at(3)) - ->method('fetchRow') - ->will($this->returnValue(false)); + ->method('fetchResult') + ->will($this->returnValue(array( + array('val1', 'replace1', 5), + array('va<2', 'replac<2', 1) + ))); $GLOBALS['dbi'] = $dbi; - $ret = $this->_object->getReplacePreview(0, $find, $replaceWith, $charSet); + $ret = $this->_object->getReplacePreview(0, $find, $replaceWith, $useRegex, $charSet); // assert whether hidden values are properly set $this->assertContains( @@ -141,6 +136,7 @@ class PMA_TableReplaceSearchTest extends PHPUnit_Framework_TestCase $find = 'findValue'; $replaceWith = 'replaceWithValue'; + $useRegex = false; $charSet = 'charSetValue'; $expectedQuery = "UPDATE `dbName`.`tableName`" @@ -153,7 +149,7 @@ class PMA_TableReplaceSearchTest extends PHPUnit_Framework_TestCase ->with($expectedQuery); $GLOBALS['dbi'] = $dbi; - $this->_object->replace(0, $find, $replaceWith, $charSet); + $this->_object->replace(0, $find, $replaceWith, $useRegex, $charSet); } } ?> diff --git a/test/classes/PMA_TableSearch_test.php b/test/classes/PMA_TableSearch_test.php index 2cc946f3c6..2847dd1406 100644 --- a/test/classes/PMA_TableSearch_test.php +++ b/test/classes/PMA_TableSearch_test.php @@ -239,8 +239,9 @@ class PMA_TableSearch_Test extends PHPUnit_Framework_TestCase $columnIndex = 0; $find = "Field"; $replaceWith = "Column"; + $useRegex = false; $charSet = "UTF-8"; - $tableSearch->replace($columnIndex, $find, $replaceWith, $charSet); + $tableSearch->replace($columnIndex, $find, $replaceWith, $useRegex, $charSet); $sql_query = $GLOBALS['sql_query']; $result = "UPDATE `PMA`.`PMA_BookMark` SET `Field1` = " @@ -288,11 +289,9 @@ class PMA_TableSearch_Test extends PHPUnit_Framework_TestCase $dbi = $GLOBALS['dbi']; - $dbi->expects($this->at(3))->method('fetchRow') - ->will($this->returnValue($value)); - - $dbi->expects($this->at(4))->method('fetchRow') - ->will($this->returnValue(false)); + $dbi->expects($this->once()) + ->method('fetchResult') + ->will($this->returnValue(array($value))); $GLOBALS['dbi'] = $dbi; @@ -300,12 +299,14 @@ class PMA_TableSearch_Test extends PHPUnit_Framework_TestCase $columnIndex = 0; $find = "Field"; $replaceWith = "Column"; + $useRegex = false; $charSet = "UTF-8"; $html = $tableSearch->getReplacePreview( $columnIndex, $find, $replaceWith, + $useRegex, $charSet );