diff --git a/features/search-replace-export.feature b/features/search-replace-export.feature index e1cb6447..6bbacc52 100644 --- a/features/search-replace-export.feature +++ b/features/search-replace-export.feature @@ -1,5 +1,6 @@ Feature: Search / replace with file export + @require-mysql Scenario: Search / replace export to STDOUT Given a WP install And I run `echo ' '` @@ -76,6 +77,7 @@ Feature: Search / replace with file export https://example.net """ + @require-mysql Scenario: Search / replace export to file Given a WP install And I run `wp post generate --count=100` @@ -129,6 +131,7 @@ Feature: Search / replace with file export 101 """ + @require-mysql Scenario: Search / replace export to file with verbosity Given a WP install @@ -151,6 +154,7 @@ Feature: Search / replace with file export Error: You cannot supply --dry-run and --export at the same time. """ + @require-mysql Scenario: Search / replace shouldn't affect primary key Given a WP install And I run `wp post create --post_title=foo --porcelain` @@ -191,6 +195,7 @@ Feature: Search / replace with file export Error: Unable to open export file "foo/bar.sql" for writing: """ + @require-mysql Scenario: Search / replace specific table Given a WP install @@ -225,6 +230,7 @@ Feature: Search / replace with file export foo """ + @require-mysql Scenario: Search / replace export should cater for field/table names that use reserved words or unusual characters Given a WP install # Unlike search-replace.features version, don't use `back``tick` column name as WP_CLI\Iterators\Table::build_fields() can't handle it. @@ -268,6 +274,7 @@ Feature: Search / replace with file export """ And STDERR should be empty + @require-mysql Scenario: Suppress report or only report changes on export to file Given a WP install @@ -365,6 +372,7 @@ Feature: Search / replace with file export """ And STDERR should be empty + @require-mysql Scenario: Search / replace should remove placeholder escape on export Given a WP install And I run `wp post create --post_title=test-remove-placeholder-escape% --porcelain` @@ -380,6 +388,7 @@ Feature: Search / replace with file export 'test-remove-placeholder-escape{' """ + @require-mysql Scenario: NULLs exported as NULL and not null string Given a WP install And I run `wp db query "INSERT INTO wp_postmeta VALUES (9999, 9999, NULL, 'foo')"` diff --git a/features/search-replace.feature b/features/search-replace.feature index afc1684b..0fc72252 100644 --- a/features/search-replace.feature +++ b/features/search-replace.feature @@ -1,5 +1,6 @@ Feature: Do global search/replace + @require-mysql Scenario: Basic search/replace Given a WP install @@ -37,10 +38,11 @@ Feature: Do global search/replace When I run `wp search-replace foo bar --include-columns=post_content` Then STDOUT should be a table containing rows: - | Table | Column | Replacements | Type | - | wp_posts | post_content | 0 | SQL | + | Table | Column | Replacements | Type | + | wp_posts | post_content | 0 | SQL | + @require-mysql Scenario: Multisite search/replace Given a WP multisite install And I run `wp site create --slug="foo" --title="foo" --email="foo@example.com"` @@ -50,6 +52,7 @@ Feature: Do global search/replace | wp_2_options | option_value | 4 | PHP | | wp_blogs | path | 1 | SQL | + @require-mysql Scenario: Don't run on unregistered tables by default Given a WP install And I run `wp db query "CREATE TABLE wp_awesome ( id int(11) unsigned NOT NULL AUTO_INCREMENT, awesome_stuff TEXT, PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=latin1;"` @@ -66,6 +69,7 @@ Feature: Do global search/replace wp_awesome """ + @require-mysql Scenario: Run on unregistered, unprefixed tables with --all-tables flag Given a WP install And I run `wp db query "CREATE TABLE awesome_table ( id int(11) unsigned NOT NULL AUTO_INCREMENT, awesome_stuff TEXT, PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=latin1;"` @@ -82,6 +86,7 @@ Feature: Do global search/replace awesome_table """ + @require-mysql Scenario: Run on all tables matching string with wildcard Given a WP install @@ -100,8 +105,8 @@ Feature: Do global search/replace When I run `wp search-replace bar burrito wp_post\?` And STDOUT should be a table containing rows: - | Table | Column | Replacements | Type | - | wp_posts | post_title | 1 | SQL | + | Table | Column | Replacements | Type | + | wp_posts | post_title | 1 | SQL | And STDOUT should not contain: """ wp_options @@ -134,9 +139,9 @@ Feature: Do global search/replace When I run `wp search-replace fooz burrito wp_opt\* wp_postme\*` Then STDOUT should be a table containing rows: - | Table | Column | Replacements | Type | - | wp_options | option_value | 1 | PHP | - | wp_postmeta | meta_key | 1 | SQL | + | Table | Column | Replacements | Type | + | wp_options | option_value | 1 | PHP | + | wp_postmeta | meta_key | 1 | SQL | And STDOUT should not contain: """ wp_posts @@ -154,12 +159,14 @@ Feature: Do global search/replace bar """ + @require-mysql Scenario: Quiet search/replace Given a WP install When I run `wp search-replace foo bar --quiet` Then STDOUT should be empty + @require-mysql Scenario: Verbose search/replace Given a WP install And I run `wp post create --post_title='Replace this text' --porcelain` @@ -202,6 +209,7 @@ Feature: Do global search/replace """ And the return code should be 1 + @require-mysql Scenario: Search and replace within theme mods Given a WP install And a setup-theme-mod.php file: @@ -213,24 +221,25 @@ Feature: Do global search/replace When I run `wp theme mod get header_image_data` Then STDOUT should be a table containing rows: - | key | value | - | header_image_data | {"url":"https:\/\/subdomain.example.com\/foo.jpg"} | + | key | value | + | header_image_data | {"url":"https:\/\/subdomain.example.com\/foo.jpg"} | When I run `wp search-replace subdomain.example.com example.com --no-recurse-objects` Then STDOUT should be a table containing rows: - | Table | Column | Replacements | Type | - | wp_options | option_value | 0 | PHP | + | Table | Column | Replacements | Type | + | wp_options | option_value | 0 | PHP | When I run `wp search-replace subdomain.example.com example.com` Then STDOUT should be a table containing rows: - | Table | Column | Replacements | Type | - | wp_options | option_value | 1 | PHP | + | Table | Column | Replacements | Type | + | wp_options | option_value | 1 | PHP | When I run `wp theme mod get header_image_data` Then STDOUT should be a table containing rows: - | key | value | - | header_image_data | {"url":"https:\/\/example.com\/foo.jpg"} | + | key | value | + | header_image_data | {"url":"https:\/\/example.com\/foo.jpg"} | + @require-mysql Scenario: Search and replace with quoted strings Given a WP install @@ -245,13 +254,13 @@ Feature: Do global search/replace When I run `wp search-replace 'Apple' 'Google' --dry-run` Then STDOUT should be a table containing rows: - | Table | Column | Replacements | Type | - | wp_posts | post_content | 1 | SQL | + | Table | Column | Replacements | Type | + | wp_posts | post_content | 1 | SQL | When I run `wp search-replace 'Apple' 'Google'` Then STDOUT should be a table containing rows: - | Table | Column | Replacements | Type | - | wp_posts | post_content | 1 | SQL | + | Table | Column | Replacements | Type | + | wp_posts | post_content | 1 | SQL | When I run `wp search-replace 'Google' 'Apple' --dry-run` Then STDOUT should contain: @@ -276,6 +285,7 @@ Feature: Do global search/replace And STDOUT should be empty And the return code should be 0 + @require-mysql Scenario: Search and replace a table that has a multi-column primary key Given a WP install And I run `wp db query "CREATE TABLE wp_multicol ( "id" bigint(20) NOT NULL AUTO_INCREMENT,"name" varchar(60) NOT NULL,"value" text NOT NULL,PRIMARY KEY ("id","name"),UNIQUE KEY "name" ("name") ) ENGINE=InnoDB DEFAULT CHARSET=utf8 "` @@ -308,6 +318,7 @@ Feature: Do global search/replace | https://newdomain.com | | | https://newdomain.com | --dry-run | + @require-mysql Scenario Outline: Choose replacement method (PHP or MySQL/MariaDB) given proper flags or data. Given a WP install And I run `wp option get siteurl` @@ -324,6 +335,7 @@ Feature: Do global search/replace | | PHP | SQL | | --precise | PHP | PHP | + @require-mysql Scenario Outline: Ensure search and replace uses PHP (precise) mode when serialized data is found Given a WP install And I run `wp post create --post_content='' --porcelain` @@ -346,13 +358,14 @@ Feature: Do global search/replace | a:1:{s:3:"bar";s:3:"foo";} | | O:8:"stdClass":1:{s:1:"a";s:3:"foo";} | + @require-mysql Scenario: Search replace with a regex flag Given a WP install When I run `wp search-replace 'EXAMPLE.com' 'BAXAMPLE.com' wp_options --regex` Then STDOUT should be a table containing rows: - | Table | Column | Replacements | Type | - | wp_options | option_value | 0 | PHP | + | Table | Column | Replacements | Type | + | wp_options | option_value | 0 | PHP | When I run `wp option get home` Then STDOUT should be: @@ -362,8 +375,8 @@ Feature: Do global search/replace When I run `wp search-replace 'EXAMPLE.com' 'BAXAMPLE.com' wp_options --regex --regex-flags=i` Then STDOUT should be a table containing rows: - | Table | Column | Replacements | Type | - | wp_options | option_value | 5 | PHP | + | Table | Column | Replacements | Type | + | wp_options | option_value | 5 | PHP | When I run `wp option get home` Then STDOUT should be: @@ -371,13 +384,14 @@ Feature: Do global search/replace https://BAXAMPLE.com """ + @require-mysql Scenario: Search replace with a regex delimiter Given a WP install When I run `wp search-replace 'HTTPS://EXAMPLE.COM' 'https://example.jp/' wp_options --regex --regex-flags=i --regex-delimiter='#'` Then STDOUT should be a table containing rows: - | Table | Column | Replacements | Type | - | wp_options | option_value | 2 | PHP | + | Table | Column | Replacements | Type | + | wp_options | option_value | 2 | PHP | When I run `wp option get home` Then STDOUT should be: @@ -387,8 +401,8 @@ Feature: Do global search/replace When I run `wp search-replace 'https://example.jp/' 'https://example.com/' wp_options --regex-delimiter='/'` Then STDOUT should be a table containing rows: - | Table | Column | Replacements | Type | - | wp_options | option_value | 2 | PHP | + | Table | Column | Replacements | Type | + | wp_options | option_value | 2 | PHP | When I run `wp option get home` Then STDOUT should be: @@ -469,6 +483,7 @@ Feature: Do global search/replace """ And the return code should be 1 + @require-mysql Scenario: Formatting as count-only Given a WP install And I run `wp option set foo 'ALPHA.example.com'` @@ -498,6 +513,7 @@ Feature: Do global search/replace 0 """ + @require-mysql Scenario: Search / replace should cater for field/table names that use reserved words or unusual characters Given a WP install And a esc_sql_ident.sql file: @@ -525,7 +541,7 @@ Feature: Do global search/replace """ And STDERR should be empty - @suppress_report__only_changes + @require-mysql @suppress_report__only_changes Scenario: Suppress report or only report changes Given a WP install @@ -548,12 +564,12 @@ Feature: Do global search/replace Success: Made 3 replacements. """ And STDOUT should be a table containing rows: - | Table | Column | Replacements | Type | - | wp_commentmeta | meta_key | 0 | SQL | - | wp_options | option_value | 1 | PHP | - | wp_postmeta | meta_value | 1 | SQL | - | wp_posts | post_title | 1 | SQL | - | wp_users | display_name | 0 | SQL | + | Table | Column | Replacements | Type | + | wp_commentmeta | meta_key | 0 | SQL | + | wp_options | option_value | 1 | PHP | + | wp_postmeta | meta_value | 1 | SQL | + | wp_posts | post_title | 1 | SQL | + | wp_users | display_name | 0 | SQL | And STDERR should be empty When I run `wp search-replace baz1 baz2 --report` @@ -562,12 +578,12 @@ Feature: Do global search/replace Success: Made 3 replacements. """ And STDOUT should be a table containing rows: - | Table | Column | Replacements | Type | - | wp_commentmeta | meta_key | 0 | SQL | - | wp_options | option_value | 1 | PHP | - | wp_postmeta | meta_value | 1 | SQL | - | wp_posts | post_title | 1 | SQL | - | wp_users | display_name | 0 | SQL | + | Table | Column | Replacements | Type | + | wp_commentmeta | meta_key | 0 | SQL | + | wp_options | option_value | 1 | PHP | + | wp_postmeta | meta_value | 1 | SQL | + | wp_posts | post_title | 1 | SQL | + | wp_users | display_name | 0 | SQL | And STDERR should be empty When I run `wp search-replace baz2 baz3 --no-report` @@ -595,12 +611,12 @@ Feature: Do global search/replace Success: Made 3 replacements. """ And STDOUT should be a table containing rows: - | Table | Column | Replacements | Type | - | wp_commentmeta | meta_key | 0 | SQL | - | wp_options | option_value | 1 | PHP | - | wp_postmeta | meta_value | 1 | SQL | - | wp_posts | post_title | 1 | SQL | - | wp_users | display_name | 0 | SQL | + | Table | Column | Replacements | Type | + | wp_commentmeta | meta_key | 0 | SQL | + | wp_options | option_value | 1 | PHP | + | wp_postmeta | meta_value | 1 | SQL | + | wp_posts | post_title | 1 | SQL | + | wp_users | display_name | 0 | SQL | And STDERR should be empty When I run `wp search-replace baz4 baz5 --report-changed-only` @@ -609,10 +625,10 @@ Feature: Do global search/replace Success: Made 3 replacements. """ And STDOUT should end with a table containing rows: - | Table | Column | Replacements | Type | - | wp_options | option_value | 1 | PHP | - | wp_postmeta | meta_value | 1 | SQL | - | wp_posts | post_title | 1 | SQL | + | Table | Column | Replacements | Type | + | wp_options | option_value | 1 | PHP | + | wp_postmeta | meta_value | 1 | SQL | + | wp_posts | post_title | 1 | SQL | And STDOUT should not contain: """ wp_commentmeta meta_key 0 SQL @@ -634,7 +650,7 @@ Feature: Do global search/replace """ And STDERR should be empty - @no_table__no_primary_key + @require-mysql @no_table__no_primary_key Scenario: Deal with non-existent table and table with no primary keys Given a WP install @@ -653,8 +669,8 @@ Feature: Do global search/replace Success: Made 0 replacements. """ And STDOUT should end with a table containing rows: - | Table | Column | Replacements | Type | - | no_key | | skipped | | + | Table | Column | Replacements | Type | + | no_key | | skipped | | And STDERR should be empty And I run `wp search-replace foo bar no_key --report-changed-only --all-tables` @@ -684,6 +700,7 @@ Feature: Do global search/replace """ And the return code should be 0 + @require-mysql Scenario: Search / replace is case sensitive Given a WP install When I run `wp post create --post_title='Case Sensitive' --porcelain` @@ -717,6 +734,7 @@ Feature: Do global search/replace """ And STDERR should be empty + @require-mysql Scenario: Logging with simple replace Given a WP install @@ -729,9 +747,9 @@ Feature: Do global search/replace Success: 2 replacements to be made. """ And STDOUT should end with a table containing rows: - | Table | Column | Replacements | Type | - | wp_posts | post_content | 1 | SQL | - | wp_posts | post_title | 1 | SQL | + | Table | Column | Replacements | Type | + | wp_posts | post_content | 1 | SQL | + | wp_posts | post_title | 1 | SQL | And STDOUT should contain: """ @@ -882,9 +900,9 @@ Feature: Do global search/replace Success: 2 replacements to be made. """ And STDOUT should end with a table containing rows: - | Table | Column | Replacements | Type | - | wp_posts | post_content | 1 | PHP | - | wp_posts | post_title | 1 | PHP | + | Table | Column | Replacements | Type | + | wp_posts | post_content | 1 | PHP | + | wp_posts | post_title | 1 | PHP | And STDOUT should contain: """ @@ -932,6 +950,7 @@ Feature: Do global search/replace Content_ab\1z__baz_1234567890_eb\1z__bez_1234567890_ib\1z__biz_1234567890_ob\1z__boz_1234567890_ub\1z__buz_ """ + @require-mysql Scenario: Logging with prefixes and custom colors Given a WP install And I run `wp option set blogdescription 'Just another WordPress site'` @@ -1032,16 +1051,17 @@ Feature: Do global search/replace And STDERR should be empty # Regression test for https://github.com/wp-cli/search-replace-command/issues/58 + @require-mysql Scenario: The parameters --regex and --all-tables-with-prefix produce valid SQL Given a WP install And a test_db.sql file: """ CREATE TABLE `wp_123_test` ( - `name` varchar(50), - `value` varchar(5000), - `created_at` datetime NOT NULL, - `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`name`) + `name` varchar(50), + `value` varchar(5000), + `created_at` datetime NOT NULL, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`name`) ) ENGINE=InnoDB; INSERT INTO `wp_123_test` VALUES ('test_val','wp_123_test_value_X','2016-11-15 14:41:33','2016-11-15 21:41:33'); INSERT INTO `wp_123_test` VALUES ('123.','wp_123_test_value_X','2016-11-15 14:41:33','2016-11-15 21:41:33'); @@ -1085,6 +1105,7 @@ Feature: Do global search/replace """ # Regression test for https://github.com/wp-cli/search-replace-command/issues/68 + @require-mysql Scenario: Incomplete classes are handled gracefully during (un)serialization Given a WP install @@ -1106,6 +1127,129 @@ Feature: Do global search/replace a:1:{i:0;O:10:"CornFlakes":0:{}} """ + @require-mysql @less-than-php-8.0 + Scenario: Warn and ignore type-hinted objects that have some error in deserialization (PHP < 8.0) + Given a WP install + And I run `wp db query "INSERT INTO wp_options (option_name,option_value) VALUES ('cereal_isation','O:13:\"mysqli_result\":5:{s:13:\"current_field\";N;s:11:\"field_count\";N;s:7:\"lengths\";N;s:8:\"num_rows\";N;s:4:\"type\";N;}')"` + And I run `wp db query "INSERT INTO wp_options (option_name,option_value) VALUES ('cereal_isation_2','O:8:\"mysqli_result\":5:{s:13:\"current_field\";i:1;s:11:\"field_count\";i:2;s:7:\"lengths\";a:1:{i:0;s:4:\"blah\";}s:8:\"num_rows\";i:1;s:4:\"type\";i:2;}')"` + + When I try `wp search-replace mysqli_result stdClass` + Then STDERR should contain: + """ + Warning: WP_CLI\SearchReplacer::run_recursively(): Couldn't fetch mysqli_result + """ + And STDOUT should contain: + """ + Success: Made 1 replacement. + """ + + When I run `wp db query "SELECT option_value from wp_options where option_name='cereal_isation_2'" --skip-column-names` + Then STDOUT should contain: + """ + O:8:"stdClass":5:{s:13:"current_field";i:1;s:11:"field_count";i:2;s:7:"lengths";a:1:{i:0;s:4:"blah";}s:8:"num_rows";i:1;s:4:"type";i:2;} + """ + Then save STDOUT as {SERIALIZED_RESULT} + And a test_php.php file: + """ + 1 + """ + Then STDOUT should contain: + """ + [field_count] => 2 + """ + + @require-mysql @require-php-8.0 @less-than-php-8.1 + Scenario: Warn and ignore type-hinted objects that have some error in deserialization (PHP 8.0) + Given a WP install + And I run `wp db query "INSERT INTO wp_options (option_name,option_value) VALUES ('cereal_isation','O:13:\"mysqli_result\":5:{s:13:\"current_field\";N;s:11:\"field_count\";N;s:7:\"lengths\";N;s:8:\"num_rows\";N;s:4:\"type\";N;}')"` + And I run `wp db query "INSERT INTO wp_options (option_name,option_value) VALUES ('cereal_isation_2','O:8:\"mysqli_result\":5:{s:13:\"current_field\";i:1;s:11:\"field_count\";i:2;s:7:\"lengths\";a:1:{i:0;s:4:\"blah\";}s:8:\"num_rows\";i:1;s:4:\"type\";i:2;}')"` + + When I try `wp search-replace mysqli_result stdClass` + Then STDERR should contain: + """ + Warning: Skipping an inconvertible serialized object of type "mysqli_result", replacements might not be complete. Reason: mysqli_result object is already closed. + """ + And STDOUT should contain: + """ + Success: Made 1 replacement. + """ + + When I run `wp db query "SELECT option_value from wp_options where option_name='cereal_isation_2'" --skip-column-names` + Then STDOUT should contain: + """ + O:8:"stdClass":5:{s:13:"current_field";i:1;s:11:"field_count";i:2;s:7:"lengths";a:1:{i:0;s:4:"blah";}s:8:"num_rows";i:1;s:4:"type";i:2;} + """ + Then save STDOUT as {SERIALIZED_RESULT} + And a test_php.php file: + """ + 1 + """ + Then STDOUT should contain: + """ + [field_count] => 2 + """ + + @require-mysql @require-php-8.1 + Scenario: Warn and ignore type-hinted objects that have some error in deserialization (PHP 8.1+) + Given a WP install + And I run `wp db query "INSERT INTO wp_options (option_name,option_value) VALUES ('cereal_isation','O:13:\"mysqli_result\":5:{s:13:\"current_field\";N;s:11:\"field_count\";N;s:7:\"lengths\";N;s:8:\"num_rows\";N;s:4:\"type\";N;}')"` + And I run `wp db query "INSERT INTO wp_options (option_name,option_value) VALUES ('cereal_isation_2','O:8:\"mysqli_result\":5:{s:13:\"current_field\";i:1;s:11:\"field_count\";i:2;s:7:\"lengths\";a:1:{i:0;s:4:\"blah\";}s:8:\"num_rows\";i:1;s:4:\"type\";i:2;}')"` + + When I try `wp search-replace mysqli_result stdClass` + Then STDERR should contain: + """ + Warning: Skipping an inconvertible serialized object: "O:13:"mysqli_result":5:{s:13:"current_field";N;s:11:"field_count";N;s:7:"lengths";N;s:8:"num_rows";N;s:4:"type";N;}", replacements might not be complete. Reason: Cannot assign null to property mysqli_result::$current_field of type int. + """ + And STDOUT should contain: + """ + Success: Made 1 replacement. + """ + + When I run `wp db query "SELECT option_value from wp_options where option_name='cereal_isation_2'" --skip-column-names` + Then STDOUT should contain: + """ + O:8:"stdClass":5:{s:13:"current_field";i:1;s:11:"field_count";i:2;s:7:"lengths";a:1:{i:0;s:4:"blah";}s:8:"num_rows";i:1;s:4:"type";i:2;} + """ + Then save STDOUT as {SERIALIZED_RESULT} + And a test_php.php file: + """ + 1 + """ + Then STDOUT should contain: + """ + [field_count] => 2 + """ + Scenario: Regex search/replace with `--regex-limit=1` option Given a WP install And I run `wp post create --post_content="I have a pen, I have an apple. Pen, pine-apple, apple-pen."` @@ -1149,6 +1293,7 @@ Feature: Do global search/replace Success: """ + @require-mysql Scenario: Chunking a precise search and replace works without skipping lines Given a WP install And a create_sql_file.sh file: @@ -1159,19 +1304,19 @@ Feature: Do global search/replace index=1 while [[ $index -le 199 ]]; do - echo "('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc')," >> test_db.sql - index=`expr $index + 1` + echo "('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc')," >> test_db.sql + index=`expr $index + 1` done - echo "('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc');" >> test_db.sql + echo "('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc');" >> test_db.sql echo "CREATE TABLE \`wp_123_test_multikey\` (\`key1\` INT(5) UNSIGNED NOT NULL AUTO_INCREMENT, \`key2\` INT(5) UNSIGNED NOT NULL, \`key3\` INT(5) UNSIGNED NOT NULL, \`text\` TEXT, PRIMARY KEY (\`key1\`,\`key2\`,\`key3\`) );" >> test_db.sql echo "INSERT INTO \`wp_123_test_multikey\` (\`key2\`,\`key3\`,\`text\`) VALUES" >> test_db.sql index=1 while [[ $index -le 204 ]]; do - echo "(0,0,'abc'),(1,1,'abc'),(2,2,'abc'),(3,3,'abc'),(4,4,'abc'),(5,0,'abc'),(6,1,'abc'),(7,2,'abc'),(8,3,'abc'),(9,4,'abc')," >> test_db.sql - index=`expr $index + 1` + echo "(0,0,'abc'),(1,1,'abc'),(2,2,'abc'),(3,3,'abc'),(4,4,'abc'),(5,0,'abc'),(6,1,'abc'),(7,2,'abc'),(8,3,'abc'),(9,4,'abc')," >> test_db.sql + index=`expr $index + 1` done - echo "(0,0,'abc'),(1,1,'abc'),(2,2,'abc'),(3,3,'abc'),(4,4,'abc'),(5,0,'abc'),(6,1,'abc'),(7,2,'abc'),(8,3,'abc'),(9,4,'abc');" >> test_db.sql + echo "(0,0,'abc'),(1,1,'abc'),(2,2,'abc'),(3,3,'abc'),(4,4,'abc'),(5,0,'abc'),(6,1,'abc'),(7,2,'abc'),(8,3,'abc'),(9,4,'abc');" >> test_db.sql """ And I run `bash create_sql_file.sh` And I run `wp db query "SOURCE test_db.sql;"` @@ -1200,6 +1345,7 @@ Feature: Do global search/replace Success: Made 0 replacements. """ + @require-mysql Scenario: Chunking a regex search and replace works without skipping lines Given a WP install And a create_sql_file.sh file: @@ -1210,19 +1356,19 @@ Feature: Do global search/replace index=1 while [[ $index -le 199 ]]; do - echo "('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc')," >> test_db.sql - index=`expr $index + 1` + echo "('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc')," >> test_db.sql + index=`expr $index + 1` done - echo "('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc');" >> test_db.sql + echo "('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc');" >> test_db.sql echo "CREATE TABLE \`wp_123_test_multikey\` (\`key1\` INT(5) UNSIGNED NOT NULL AUTO_INCREMENT, \`key2\` INT(5) UNSIGNED NOT NULL, \`key3\` INT(5) UNSIGNED NOT NULL, \`text\` TEXT, PRIMARY KEY (\`key1\`,\`key2\`,\`key3\`) );" >> test_db.sql echo "INSERT INTO \`wp_123_test_multikey\` (\`key2\`,\`key3\`,\`text\`) VALUES" >> test_db.sql index=1 while [[ $index -le 204 ]]; do - echo "(0,0,'abc'),(1,1,'abc'),(2,2,'abc'),(3,3,'abc'),(4,4,'abc'),(5,0,'abc'),(6,1,'abc'),(7,2,'abc'),(8,3,'abc'),(9,4,'abc')," >> test_db.sql - index=`expr $index + 1` + echo "(0,0,'abc'),(1,1,'abc'),(2,2,'abc'),(3,3,'abc'),(4,4,'abc'),(5,0,'abc'),(6,1,'abc'),(7,2,'abc'),(8,3,'abc'),(9,4,'abc')," >> test_db.sql + index=`expr $index + 1` done - echo "(0,0,'abc'),(1,1,'abc'),(2,2,'abc'),(3,3,'abc'),(4,4,'abc'),(5,0,'abc'),(6,1,'abc'),(7,2,'abc'),(8,3,'abc'),(9,4,'abc');" >> test_db.sql + echo "(0,0,'abc'),(1,1,'abc'),(2,2,'abc'),(3,3,'abc'),(4,4,'abc'),(5,0,'abc'),(6,1,'abc'),(7,2,'abc'),(8,3,'abc'),(9,4,'abc');" >> test_db.sql """ And I run `bash create_sql_file.sh` And I run `wp db query "SOURCE test_db.sql;"` diff --git a/src/Search_Replace_Command.php b/src/Search_Replace_Command.php index a8470e03..e109bf77 100644 --- a/src/Search_Replace_Command.php +++ b/src/Search_Replace_Command.php @@ -585,6 +585,12 @@ static function ( $key ) { continue; } + // In case a needed re-serialization was unsuccessful, we should not update the value, + // as this implies we hit an exception while processing. + if ( gettype( $value ) !== gettype( $col_value ) ) { + continue; + } + if ( $this->log_handle ) { $this->log_php_diff( $col, $keys, $table, $old, $new, $replacer->get_log_data() ); $replacer->clear_log_data(); diff --git a/src/WP_CLI/SearchReplacer.php b/src/WP_CLI/SearchReplacer.php index f8e314ad..06fada15 100644 --- a/src/WP_CLI/SearchReplacer.php +++ b/src/WP_CLI/SearchReplacer.php @@ -83,12 +83,29 @@ private function run_recursively( $data, $serialised, $recursion_level = 0, $vis } } - // The error suppression operator is not enough in some cases, so we disable - // reporting of notices and warnings as well. - $error_reporting = error_reporting(); - error_reporting( $error_reporting & ~E_NOTICE & ~E_WARNING ); - $unserialized = is_string( $data ) ? @unserialize( $data ) : false; - error_reporting( $error_reporting ); + try { + // The error suppression operator is not enough in some cases, so we disable + // reporting of notices and warnings as well. + $error_reporting = error_reporting(); + error_reporting( $error_reporting & ~E_NOTICE & ~E_WARNING ); + $unserialized = is_string( $data ) ? @unserialize( $data ) : false; + error_reporting( $error_reporting ); + + } catch ( \TypeError $exception ) { // phpcs:ignore PHPCompatibility.Classes.NewClasses.typeerrorFound + // This type error is thrown when trying to unserialize a string that does not fit the + // type declarations of the properties it is supposed to fill. + // This type checking was introduced with PHP 8.1. + // See https://github.com/wp-cli/search-replace-command/issues/191 + \WP_CLI::warning( + sprintf( + 'Skipping an inconvertible serialized object: "%s", replacements might not be complete. Reason: %s.', + $data, + $exception->getMessage() + ) + ); + + throw new Exception( $exception->getMessage(), $exception->getCode(), $exception ); + } if ( false !== $unserialized ) { $data = $this->run_recursively( $unserialized, true, $recursion_level + 1 ); @@ -107,8 +124,23 @@ private function run_recursively( $data, $serialised, $recursion_level = 0, $vis ) ); } else { - foreach ( $data as $key => $value ) { - $data->$key = $this->run_recursively( $value, false, $recursion_level + 1, $visited_data ); + try { + foreach ( $data as $key => $value ) { + $data->$key = $this->run_recursively( $value, false, $recursion_level + 1, $visited_data ); + } + } catch ( \Error $exception ) { // phpcs:ignore PHPCompatibility.Classes.NewClasses.errorFound + // This error is thrown when the object that was unserialized cannot be iterated upon. + // The most notable reason is an empty `mysqli_result` object which is then considered to be "already closed". + // See https://github.com/wp-cli/search-replace-command/pull/192#discussion_r1412310179 + \WP_CLI::warning( + sprintf( + 'Skipping an inconvertible serialized object of type "%s", replacements might not be complete. Reason: %s.', + is_object( $data ) ? get_class( $data ) : gettype( $data ), + $exception->getMessage() + ) + ); + + throw new Exception( $exception->getMessage(), $exception->getCode(), $exception ); } } } elseif ( is_string( $data ) ) { @@ -142,7 +174,7 @@ private function run_recursively( $data, $serialised, $recursion_level = 0, $vis if ( $serialised ) { return serialize( $data ); } - } catch ( Exception $error ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch -- Deliberally empty. + } catch ( Exception $exception ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch -- Intentionally empty. }