Feature: Do global search/replace @require-mysql Scenario: Basic search/replace Given a WP install When I run `wp search-replace foo bar` Then STDOUT should contain: """ guid """ When I run `wp search-replace foo bar --skip-tables=wp_posts` Then STDOUT should not contain: """ wp_posts """ When I run `wp search-replace foo bar --skip-tables=wp_post\*` Then STDOUT should not contain: """ wp_posts """ And STDOUT should not contain: """ wp_postmeta """ And STDOUT should contain: """ wp_users """ When I run `wp search-replace foo bar --skip-columns=guid` Then STDOUT should not contain: """ guid """ 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 | @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"` And I run `wp search-replace foo bar --network` Then STDOUT should be a table containing rows: | Table | Column | Replacements | Type | | 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;"` When I run `wp search-replace foo bar` Then STDOUT should not contain: """ wp_awesome """ When I run `wp search-replace foo bar --all-tables-with-prefix` Then STDOUT should contain: """ 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;"` When I run `wp search-replace foo bar` Then STDOUT should not contain: """ awesome_table """ When I run `wp search-replace foo bar --all-tables` Then STDOUT should contain: """ awesome_table """ @require-mysql Scenario: Run on all tables matching string with wildcard Given a WP install When I run `wp option set bar fooz` And I run `wp option get bar` Then STDOUT should be: """ fooz """ When I run `wp post create --post_title=bar --porcelain` Then save STDOUT as {POST_ID} When I run `wp post meta add {POST_ID} fooz bar` Then STDOUT should not be empty 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 | And STDOUT should not contain: """ wp_options """ When I run `wp post get {POST_ID} --field=title` Then STDOUT should be: """ burrito """ When I run `wp post meta get {POST_ID} fooz` Then STDOUT should be: """ bar """ When I run `wp option get bar` Then STDOUT should be: """ fooz """ When I try `wp search-replace fooz burrito wp_opt\*on` Then STDERR should be: """ Error: Couldn't find any tables matching: wp_opt*on """ And the return code should be 1 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 | And STDOUT should not contain: """ wp_posts """ When I run `wp option get bar` Then STDOUT should be: """ burrito """ When I run `wp post meta get {POST_ID} burrito` Then STDOUT should be: """ 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` And save STDOUT as {POSTID} When I run `wp search-replace 'Replace' 'Replaced' --verbose` Then STDOUT should contain: """ Checking: wp_posts.post_title 1 rows affected """ When I run `wp search-replace 'Replace' 'Replaced' --verbose --precise` Then STDOUT should contain: """ Checking: wp_posts.post_title 1 rows affected """ Scenario: Regex search/replace Given a WP install When I run `wp search-replace '(Hello)\s(world)' '$2, $1' --regex` Then STDOUT should contain: """ wp_posts """ When I run `wp post list --fields=post_title` Then STDOUT should contain: """ world, Hello """ Scenario: Regex search/replace with a incorrect `--regex-flags` Given a WP install When I try `wp search-replace '(Hello)\s(world)' '$2, $1' --regex --regex-flags='kppr'` Then STDERR should be: """ Error: The regex pattern '(Hello)\s(world)' with default delimiter 'chr(1)' and flags 'kppr' fails. preg_match(): Unknown modifier 'k'. """ 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: """ 'https://subdomain.example.com/foo.jpg' ) ); """ And I run `wp eval-file setup-theme-mod.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:\/\/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 | 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 | 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"} | @require-mysql Scenario: Search and replace with quoted strings Given a WP install When I run `wp post create --post_content='Apple' --porcelain` Then save STDOUT as {POST_ID} When I run `wp post get {POST_ID} --field=content` Then STDOUT should be: """ Apple """ 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 | 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 | When I run `wp search-replace 'Google' 'Apple' --dry-run` Then STDOUT should contain: """ 1 replacement to be made. """ When I run `wp post get {POST_ID} --field=content` Then STDOUT should be: """ Google """ Scenario: Search and replace with the same terms Given a WP install When I try `wp search-replace foo foo` Then STDERR should be: """ Warning: Replacement value 'foo' is identical to search value 'foo'. Skipping operation. """ 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 "` And I run `wp db query "INSERT INTO wp_multicol VALUES (1, 'foo', 'bar')"` And I run `wp db query "INSERT INTO wp_multicol VALUES (2, 'bar', 'foo')"` When I run `wp search-replace bar replaced wp_multicol --all-tables` Then STDOUT should be a table containing rows: | Table | Column | Replacements | Type | | wp_multicol | name | 1 | SQL | | wp_multicol | value | 1 | SQL | # Skip on 5.0 for now due to difficulties introduced by https://core.trac.wordpress.org/changeset/42981 @less-than-wp-5.0 Scenario Outline: Large guid search/replace where replacement contains search (or not) Given a WP install And I run `wp option get siteurl` And save STDOUT as {SITEURL} And I run `wp site empty --yes` And I run `wp post generate --count=20` When I run `wp search-replace {SITEURL} ` Then STDOUT should be a table containing rows: | Table | Column | Replacements | Type | | wp_posts | guid | 20 | SQL | Examples: | replacement | flags | | {SITEURL}/subdir | | | 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` And save STDOUT as {SITEURL} When I run `wp search-replace {SITEURL} https://wordpress.org` Then STDOUT should be a table containing rows: | Table | Column | Replacements | Type | | wp_options | option_value | 2 | | | wp_posts | post_title | 0 | | Examples: | flags | serial | noserial | | | 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` And save STDOUT as {CONTROLPOST} And I run `wp search-replace --precise foo bar` And I run `wp post get {CONTROLPOST} --field=content` And save STDOUT as {CONTROL} And I run `wp post create --post_content='' --porcelain` And save STDOUT as {TESTPOST} And I run `wp search-replace foo bar` When I run `wp post get {TESTPOST} --field=content` Then STDOUT should be: """ {CONTROL} """ Examples: | input | | 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 | When I run `wp option get home` Then STDOUT should be: """ https://example.com """ 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 | When I run `wp option get home` Then STDOUT should be: """ 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 | When I run `wp option get home` Then STDOUT should be: """ https://example.jp """ 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 | When I run `wp option get home` Then STDOUT should be: """ https://example.com """ # NOTE: The preg_match() error message is a substring of the actual message that matches across supported PHP versions. # In PHP 8.2, the error message changed from # "preg_match(): Delimiter must not be alphanumeric or backslash." # to # "preg_match(): Delimiter must not be alphanumeric, backslash, or NUL" When I try `wp search-replace 'HTTPS://EXAMPLE.COM' 'https://example.jp/' wp_options --regex --regex-flags=i --regex-delimiter='1'` Then STDERR should contain: """ Error: The regex '1HTTPS://EXAMPLE.COM1i' fails. preg_match(): Delimiter must not be alphanumeric """ And the return code should be 1 When I try `wp search-replace 'regex error)' '' --regex` Then STDERR should contain: """ Error: The regex pattern 'regex error)' with default delimiter 'chr(1)' and no flags fails. """ And STDERR should contain: """ preg_match(): Compilation failed: """ And STDERR should contain: """ at offset 11 """ And the return code should be 1 When I try `wp search-replace 'regex error)' '' --regex --regex-flags=u` Then STDERR should contain: """ Error: The regex pattern 'regex error)' with default delimiter 'chr(1)' and flags 'u' fails. """ And STDERR should contain: """ preg_match(): Compilation failed: """ And STDERR should contain: """ at offset 11 """ And the return code should be 1 When I try `wp search-replace 'regex error)' '' --regex --regex-delimiter=/` Then STDERR should contain: """ Error: The regex '/regex error)/' fails. """ And STDERR should contain: """ preg_match(): Compilation failed: """ And STDERR should contain: """ at offset 11 """ And the return code should be 1 When I try `wp search-replace 'regex error)' '' --regex --regex-delimiter=/ --regex-flags=u` Then STDERR should contain: """ Error: The regex '/regex error)/u' fails. """ And STDERR should contain: """ preg_match(): Compilation failed: """ And STDERR should contain: """ at offset 11 """ 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'` # --quite should suppress --format=count When I run `wp search-replace 'ALPHA.example.com' 'BETA.example.com' --quiet --format=count` Then STDOUT should be empty # --format=count should suppress --verbose When I run `wp search-replace 'BETA.example.com' 'ALPHA.example.com' --format=count --verbose` Then STDOUT should be: """ 1 """ # The normal command When I run `wp search-replace 'ALPHA.example.com' 'BETA.example.com' --format=count` Then STDOUT should be: """ 1 """ # Lets just make sure that zero works, too. When I run `wp search-replace 'DELTA.example.com' 'ALPHA.example.com' --format=count` Then STDOUT should be: """ 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: """ CREATE TABLE `TABLE` (`KEY` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, `VALUES` TEXT, `back``tick` TEXT, `single'double"quote` TEXT, PRIMARY KEY (`KEY`) ); INSERT INTO `TABLE` (`VALUES`, `back``tick`, `single'double"quote`) VALUES ('v"vvvv_v1', 'v"vvvv_v1', 'v"vvvv_v1' ); INSERT INTO `TABLE` (`VALUES`, `back``tick`, `single'double"quote`) VALUES ('v"vvvv_v2', 'v"vvvv_v2', 'v"vvvv_v2' ); """ When I run `wp db query "SOURCE esc_sql_ident.sql;"` Then STDERR should be empty When I run `wp search-replace 'v"vvvv_v' 'w"wwww_w' TABLE --format=count --all-tables` Then STDOUT should be: """ 6 """ And STDERR should be empty # Regex uses wpdb::update() which can't handle backticks in field names so avoid `back``tick` column. When I run `wp search-replace 'w"wwww_w' 'v"vvvv_v' TABLE --regex --include-columns='VALUES,single'\''double"quote' --format=count --all-tables` Then STDOUT should be: """ 4 """ And STDERR should be empty @require-mysql @suppress_report__only_changes Scenario: Suppress report or only report changes Given a WP install When I run `wp option set foo baz` And I run `wp option get foo` Then STDOUT should be: """ baz """ When I run `wp post create --post_title=baz --porcelain` Then save STDOUT as {POST_ID} When I run `wp post meta add {POST_ID} foo baz` Then STDOUT should not be empty When I run `wp search-replace baz baz1` Then STDOUT should contain: """ 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 | And STDERR should be empty When I run `wp search-replace baz1 baz2 --report` Then STDOUT should contain: """ 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 | And STDERR should be empty When I run `wp search-replace baz2 baz3 --no-report` Then STDOUT should contain: """ Success: Made 3 replacements. """ And STDOUT should not contain: """ Table Column Replacements Type """ And STDOUT should not contain: """ wp_commentmeta meta_key 0 SQL """ And STDOUT should not contain: """ wp_options option_value 1 PHP """ And STDERR should be empty When I run `wp search-replace baz3 baz4 --no-report-changed-only` Then STDOUT should contain: """ 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 | And STDERR should be empty When I run `wp search-replace baz4 baz5 --report-changed-only` Then STDOUT should contain: """ 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 | And STDOUT should not contain: """ wp_commentmeta meta_key 0 SQL """ And STDOUT should not contain: """ wp_users display_name 0 SQL """ And STDERR should be empty When I run `wp search-replace nobaz1 baz6 --report-changed-only` Then STDOUT should contain: """ Success: Made 0 replacements. """ And STDOUT should not contain: """ Table Column Replacements Type """ And STDERR should be empty @require-mysql @no_table__no_primary_key Scenario: Deal with non-existent table and table with no primary keys Given a WP install When I try `wp search-replace foo bar no_such_table --all-tables` Then STDOUT should be empty And STDERR should be: """ Error: Couldn't find any tables matching: no_such_table """ And the return code should be 1 When I run `wp db query "CREATE TABLE no_key ( awesome_stuff TEXT );"` And I run `wp search-replace foo bar no_key --all-tables` Then STDOUT should contain: """ Success: Made 0 replacements. """ And STDOUT should end with a table containing rows: | 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` Then STDOUT should contain: """ Success: Made 0 replacements. """ And STDOUT should not contain: """ | Table | Column | Replacements | Type | | no_key | | skipped | | """ And STDERR should be empty When I try `wp search-replace foo bar no_key --no-report --all-tables` Then STDOUT should contain: """ Success: Made 0 replacements. """ And STDOUT should not contain: """ Table Column Replacements Type """ And STDERR should be: """ Warning: No primary keys for table 'no_key'. """ 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` Then save STDOUT as {POST_ID} When I run `wp search-replace sensitive insensitive` Then STDOUT should contain: """ Success: Made 0 replacements. """ And STDERR should be empty When I run `wp search-replace sensitive insensitive --dry-run` Then STDOUT should contain: """ Success: 0 replacements to be made. """ And STDERR should be empty When I run `wp search-replace Sensitive insensitive --dry-run` Then STDOUT should contain: """ Success: 1 replacement to be made. """ And STDERR should be empty When I run `wp search-replace Sensitive insensitive` Then STDOUT should contain: """ Success: Made 1 replacement. """ And STDERR should be empty @require-mysql Scenario: Logging with simple replace Given a WP install When I run `wp post create --post_title='Title_baz__baz_' --post_content='Content_baz_12345678901234567890_baz_12345678901234567890' --porcelain` Then save STDOUT as {POST_ID} When I run `wp search-replace '_baz_' '_' wp_posts --dry-run --log --before_context=10 --after_context=10` Then STDOUT should contain: """ 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 | And STDOUT should contain: """ wp_posts.post_content:{POST_ID} < Content_baz_1234567890 [...] 1234567890_baz_1234567890 > Content_1234567890 [...] 1234567890_1234567890 """ And STDOUT should contain: """ wp_posts.post_title:{POST_ID} < Title_baz__baz_ > Title__ """ And STDERR should be empty When I run `wp search-replace '_baz_' '' wp_posts --dry-run --log=replace.log` Then STDOUT should contain: """ Success: 2 replacements to be made. """ And STDOUT should not contain: """ < Content """ And the replace.log file should contain: """ wp_posts.post_content:{POST_ID} < Content_baz_12345678901234567890_baz_12345678901234567890 > Content1234567890123456789012345678901234567890 """ And the replace.log file should contain: """ wp_posts.post_title:{POST_ID} < Title_baz__baz_ > Title """ And STDERR should be empty # kana with diacritic and decomposed "a" + umlaut. When I run `wp search-replace '_baz_' '_バäz_' wp_posts --log=- --before_context=10 --after_context=20` Then STDOUT should contain: """ Success: Made 2 replacements. """ And STDOUT should contain: """ wp_posts.post_content:{POST_ID} < Content_baz_12345678901234567890 [...] 1234567890_baz_12345678901234567890 > Content_バäz_12345678901234567890 [...] 1234567890_バäz_12345678901234567890 """ And STDERR should be empty # Testing UTF-8 context When I run `wp search-replace 'z_' 'zzzz_' wp_posts --log --before_context=2 --after_context=1` Then STDOUT should contain: """ Success: Made 2 replacements. """ And STDOUT should contain: """ wp_posts.post_content:{POST_ID} < バäz_1 [...] バäz_1 > バäzzzz_1 [...] バäzzzz_1 """ And STDERR should be empty When I run `wp option set foobar '_bar1_ _bar1_12345678901234567890123456789012345678901234567890_bar1_ _bar1_1234567890123456789012345678901234567890'` When I run `wp search-replace '_bar1_' '_baz1_' wp_options --log` Then STDOUT should contain: """ < _bar1_ _bar1_1234567890123456789012345678901234567890 [...] 1234567890123456789012345678901234567890_bar1_ _bar1_1234567890123456789012345678901234567890 > _baz1_ _baz1_1234567890123456789012345678901234567890 [...] 1234567890123456789012345678901234567890_baz1_ _baz1_1234567890123456789012345678901234567890 """ And STDERR should be empty When I run `wp option get foobar` Then STDOUT should be: """ _baz1_ _baz1_12345678901234567890123456789012345678901234567890_baz1_ _baz1_1234567890123456789012345678901234567890 """ When I run `wp search-replace '_baz1_' '_bar1_' wp_options --log --before_context=10 --after_context=10` Then STDOUT should contain: """ < _baz1_ _baz1_1234567890 [...] 1234567890_baz1_ _baz1_1234567890 > _bar1_ _bar1_1234567890 [...] 1234567890_bar1_ _bar1_1234567890 """ And STDERR should be empty When I run `wp option set foobar2 '12345678901234567890_bar2_1234567890_bar2_ _bar2_ _bar2_'` When I run `wp search-replace '_bar2_' '_baz2baz2_' wp_options --log --before_context=10 --after_context=10` Then STDOUT should contain: """ < 1234567890_bar2_1234567890 [...] 1234567890_bar2_ _bar2_ _bar2_ > 1234567890_baz2baz2_1234567890 [...] 1234567890_baz2baz2_ _baz2baz2_ _baz2baz2_ """ And STDERR should be empty When I run `wp option get foobar2` Then STDOUT should be: """ 12345678901234567890_baz2baz2_1234567890_baz2baz2_ _baz2baz2_ _baz2baz2_ """ When I run `wp search-replace '_baz2baz2_' '_barz2_' wp_options --log --before_context=10 --after_context=4` Then STDOUT should contain: """ < 1234567890_baz2baz2_1234 [...] 1234567890_baz2baz2_ _baz2baz2_ _baz2baz2_ > 1234567890_barz2_1234 [...] 1234567890_barz2_ _barz2_ _barz2_ """ And STDERR should be empty When I run `wp option set foobar3 '_bar3 _bar3 _bar3 _bar3'` When I run `wp search-replace '_bar3' 'baz3' wp_options --log` Then STDOUT should contain: """ < _bar3 _bar3 _bar3 _bar3 > baz3 baz3 baz3 baz3 """ And STDERR should be empty When I run `wp option get foobar3` Then STDOUT should be: """ baz3 baz3 baz3 baz3 """ When I run `wp search-replace 'baz3' 'baz\3' wp_options --dry-run --log` Then STDOUT should contain: """ < baz3 baz3 baz3 baz3 > baz\3 baz\3 baz\3 baz\3 """ And STDERR should be empty Scenario: Logging with regex replace Given a WP install When I run `wp post create --post_title='Title_baz__boz_' --post_content='Content_baz_1234567890_bez_1234567890_biz_1234567890_boz_1234567890_buz_' --porcelain` Then save STDOUT as {POST_ID} When I run `wp search-replace '_b[aeiou]z_' '_bz_' wp_posts --regex --dry-run --log --before_context=11 --after_context=11` Then STDOUT should contain: """ 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 | And STDOUT should contain: """ wp_posts.post_content:{POST_ID} < Content_baz_1234567890_bez_1234567890_biz_1234567890_boz_1234567890_buz_ > Content_bz_1234567890_bz_1234567890_bz_1234567890_bz_1234567890_bz_ """ And STDOUT should contain: """ wp_posts.post_title:{POST_ID} < Title_baz__boz_ > Title_bz__bz_ """ And STDERR should be empty When I run `wp search-replace '_b([aeiou])z_' '_$1b\\1z_\0' wp_posts --regex --log --before_context=11 --after_context=11` Then STDOUT should contain: """ Success: Made 2 replacements. """ And STDOUT should contain: """ wp_posts.post_content:{POST_ID} < Content_baz_1234567890_bez_1234567890_biz_1234567890_boz_1234567890_buz_ > Content_ab\1z__baz_1234567890_eb\1z__bez_1234567890_ib\1z__biz_1234567890_ob\1z__boz_1234567890_ub\1z__buz_ """ And STDOUT should contain: """ wp_posts.post_title:{POST_ID} < Title_baz__boz_ > Title_ab\1z__baz__ob\1z__boz_ """ And STDERR should be empty When I run `wp post get {POST_ID} --field=title` Then STDOUT should be: """ Title_ab\1z__baz__ob\1z__boz_ """ When I run `wp post get {POST_ID} --field=content` Then STDOUT should be: """ 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'` When I run `WP_CLI_SEARCH_REPLACE_LOG_PREFIXES='- ,+ ' wp search-replace Just Yet --dry-run --log` Then STDOUT should contain: """ - Just another WordPress site + Yet another WordPress site """ And STDERR should be empty When I run `WP_CLI_SEARCH_REPLACE_LOG_PREFIXES=',' wp search-replace Just Yet --dry-run --log` Then STDOUT should not contain: """ < Just """ And STDOUT should contain: """ Just """ And STDOUT should not contain: """ > Yet """ And STDOUT should contain: """ Yet """ And STDERR should be empty When I run `SHELL_PIPE=0 wp search-replace WordPress WP --dry-run --log` Then STDOUT should strictly contain: """ wp_options.option_value: """ And STDOUT should strictly contain: """ < Just another WordPress site > Just another WP site """ And STDERR should be empty When I run `SHELL_PIPE=0 WP_CLI_SEARCH_REPLACE_LOG_COLORS='%b,%r,%g' wp search-replace WordPress WP --dry-run --log` Then STDOUT should strictly contain: """ wp_options.option_value: """ And STDOUT should strictly contain: """ < Just another WordPress site > Just another WP site """ And STDERR should be empty When I run `SHELL_PIPE=0 WP_CLI_SEARCH_REPLACE_LOG_COLORS='%b,%r,%g' wp search-replace WordPress WP --dry-run --log=replace.log` Then STDOUT should not contain: """ wp_options.option_value """ And the replace.log file should strictly contain: """ wp_options.option_value: """ And the replace.log file should strictly contain: """ < Just another WordPress site > Just another WP site """ And STDERR should be empty When I run `SHELL_PIPE=0 wp search-replace WordPress WP --dry-run --log=replace.log` Then STDOUT should not contain: """ wp_options.option_value """ And the replace.log file should contain: """ wp_options.option_value: """ And the replace.log file should contain: """ < Just another WordPress site > Just another WP site """ And STDERR should be empty When I run `SHELL_PIPE=0 WP_CLI_SEARCH_REPLACE_LOG_COLORS=',,' wp search-replace WordPress WP --dry-run --log` Then STDOUT should contain: """ wp_options.option_value: """ And STDOUT should contain: """ < Just another WordPress site > Just another WP site """ 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`) ) 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'); INSERT INTO `wp_123_test` VALUES ('quote\'quote','wp_123_test_value_X','2016-11-15 14:41:33','2016-11-15 21:41:33'); INSERT INTO `wp_123_test` VALUES ('0','wp_123_test_value_X','2016-11-15 14:41:33','2016-11-15 21:41:33'); INSERT INTO `wp_123_test` VALUES ('','wp_123_test_value_X','2016-11-15 14:41:33','2016-11-15 21:41:33'); INSERT INTO `wp_123_test` VALUES ('18446744073709551616','wp_123_test_value_X','2016-11-15 14:41:33','2016-11-15 21:41:33'); INSERT INTO `wp_123_test` VALUES ('-18446744073709551615','wp_123_test_value_X','2016-11-15 14:41:33','2016-11-15 21:41:33'); INSERT INTO `wp_123_test` VALUES ('123456789012345678801234567890','wp_123_test_value_X','2016-11-15 14:41:33','2016-11-15 21:41:33'); CREATE TABLE `wp_123_test2` (`bigint_unsigned_key` BIGINT UNSIGNED NOT NULL, `value` VARCHAR(255), PRIMARY KEY (`bigint_unsigned_key`) ); INSERT INTO `wp_123_test2` VALUES ('18446744073709551615','wp_123_test2_value_X'); CREATE TABLE `wp_123_test3` (`bigint_signed_key` BIGINT SIGNED NOT NULL, `value` VARCHAR(255), PRIMARY KEY (`bigint_signed_key`) ); INSERT INTO `wp_123_test3` VALUES ('-9223372036854775808','wp_123_test3_value_X'); """ And I run `wp db query "SOURCE test_db.sql;"` When I run `wp search-replace --dry-run --regex 'mytestdomain.com\/' 'mytestdomain2.com/' --all-tables-with-prefix --skip-columns=guid,domain` Then STDOUT should contain: """ Success: 0 replacements to be made. """ When I run `wp search-replace --dry-run --regex 'wp_123_test_value_X' 'wp_123_test_value_Y' --all-tables-with-prefix` Then STDOUT should contain: """ Success: 8 replacements to be made. """ When I run `wp search-replace --dry-run --regex 'wp_123_test2_value_X' 'wp_123_test2_value_Y' --all-tables-with-prefix` Then STDOUT should contain: """ Success: 1 replacement to be made. """ When I run `wp search-replace --dry-run --regex 'wp_123_test3_value_X' 'wp_123_test3_value_Y' --all-tables-with-prefix` Then STDOUT should contain: """ Success: 1 replacement to be made. """ # 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 And I run `wp option add cereal_isation 'a:1:{i:0;O:10:"CornFlakes":0:{}}'` When I try `wp search-replace CornFlakes Smacks` Then STDERR should contain: """ Warning: Skipping an uninitialized class "CornFlakes", replacements might not be complete. """ And STDOUT should contain: """ Success: Made 0 replacements. """ When I run `wp option get cereal_isation` Then STDOUT should contain: """ 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."` When I run `wp search-replace --regex "ap{2}le" "orange" --regex-limit=1 --log` Then STDOUT should contain: """ I have a pen, I have an orange. Pen, pine-apple, apple-pen. """ Scenario: Regex search/replace with `--regex-limit=2` 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."` When I run `wp search-replace --regex "ap{2}le" "orange" --regex-limit=2 --log` Then STDOUT should contain: """ I have a pen, I have an orange. Pen, pine-orange, apple-pen. """ Scenario: Regex search/replace with incorrect or default `--regex-limit` Given a WP install When I try `wp search-replace '(Hello)\s(world)' '$2, $1' --regex --regex-limit=asdf` Then STDERR should be: """ Error: `--regex-limit` expects a non-zero positive integer or -1. """ When I try `wp search-replace '(Hello)\s(world)' '$2, $1' --regex --regex-limit=0` Then STDERR should be: """ Error: `--regex-limit` expects a non-zero positive integer or -1. """ When I try `wp search-replace '(Hello)\s(world)' '$2, $1' --regex --regex-limit=-2` Then STDERR should be: """ Error: `--regex-limit` expects a non-zero positive integer or -1. """ When I run `wp search-replace '(Hello)\s(world)' '$2, $1' --regex --regex-limit=-1` Then STDOUT should contain: """ 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: """ #!/bin/bash echo "CREATE TABLE \`wp_123_test\` (\`key\` INT(5) UNSIGNED NOT NULL AUTO_INCREMENT, \`text\` TEXT, PRIMARY KEY (\`key\`) );" > test_db.sql echo "INSERT INTO \`wp_123_test\` (\`text\`) VALUES" >> test_db.sql 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` done 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` 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 """ And I run `bash create_sql_file.sh` And I run `wp db query "SOURCE test_db.sql;"` When I run `wp search-replace --dry-run 'abc' 'def' --all-tables-with-prefix --skip-columns=guid,domain --precise` Then STDOUT should contain: """ Success: 4050 replacements to be made. """ When I run `wp search-replace 'abc' 'def' --all-tables-with-prefix --skip-columns=guid,domain --precise` Then STDOUT should contain: """ Success: Made 4050 replacements. """ When I run `wp search-replace --dry-run 'abc' 'def' --all-tables-with-prefix --skip-columns=guid,domain --precise` Then STDOUT should contain: """ Success: 0 replacements to be made. """ When I run `wp search-replace 'abc' 'def' --all-tables-with-prefix --skip-columns=guid,domain --precise` Then STDOUT should contain: """ 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: """ #!/bin/bash echo "CREATE TABLE \`wp_123_test\` (\`key\` INT(5) UNSIGNED NOT NULL AUTO_INCREMENT, \`text\` TEXT, PRIMARY KEY (\`key\`) );" > test_db.sql echo "INSERT INTO \`wp_123_test\` (\`text\`) VALUES" >> test_db.sql 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` done 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` 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 """ And I run `bash create_sql_file.sh` And I run `wp db query "SOURCE test_db.sql;"` When I run `wp search-replace --dry-run 'abc' 'def' --all-tables-with-prefix --skip-columns=guid,domain --regex` Then STDOUT should contain: """ Success: 4050 replacements to be made. """ When I run `wp search-replace 'abc' 'def' --all-tables-with-prefix --skip-columns=guid,domain --regex` Then STDOUT should contain: """ Success: Made 4050 replacements. """ When I run `wp search-replace --dry-run 'abc' 'def' --all-tables-with-prefix --skip-columns=guid,domain --regex` Then STDOUT should contain: """ Success: 0 replacements to be made. """ When I run `wp search-replace 'abc' 'def' --all-tables-with-prefix --skip-columns=guid,domain --regex` Then STDOUT should contain: """ Success: Made 0 replacements. """