Options, Meta APIs: Improve logic to avoid unnecessary database writes in `update_option()`.

Prior to this change, a strict comparison between the old and new database value could lead to a false negative, since database values are generally stored as strings. For example, passing an integer to `update_option()` would almost always result in an update given any existing database value for that option would be that number cast to a string.

This changeset adjusts the logic to perform an intentional "loose-y" comparison by casting the values to strings. Extensive coverage previously added in [56648] provides additional confidence that this does not introduce any backward compatibility issues.

Props mukesh27, costdev, spacedmonkey, joemcgill, flixos90, nacin, atimmer, duck_, boonebgorges.
Fixes #22192.

Built from https://develop.svn.wordpress.org/trunk@56681


git-svn-id: http://core.svn.wordpress.org/trunk@56193 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
Felix Arntz 2023-09-25 16:25:19 +00:00
parent 28af9eafb0
commit bfbcb02444
2 changed files with 47 additions and 8 deletions

View File

@ -776,21 +776,23 @@ function update_option( $option, $value, $autoload = null ) {
*/
$value = apply_filters( 'pre_update_option', $value, $option, $old_value );
/** This filter is documented in wp-includes/option.php */
$default_value = apply_filters( "default_option_{$option}", false, $option, false );
/*
* If the new and old values are the same, no need to update.
*
* Unserialized values will be adequate in most cases. If the unserialized
* data differs, the (maybe) serialized data is checked to avoid
* unnecessary database calls for otherwise identical object instances.
* An exception applies when no value is set in the database, i.e. the old value is the default.
* In that case, the new value should always be added as it may be intentional to store it rather than relying on the default.
*
* See https://core.trac.wordpress.org/ticket/38903
* See https://core.trac.wordpress.org/ticket/38903 and https://core.trac.wordpress.org/ticket/22192.
*/
if ( $value === $old_value || maybe_serialize( $value ) === maybe_serialize( $old_value ) ) {
if ( $old_value !== $default_value && _is_equal_database_value( $old_value, $value ) ) {
return false;
}
/** This filter is documented in wp-includes/option.php */
if ( apply_filters( "default_option_{$option}", false, $option, false ) === $old_value ) {
if ( $old_value === $default_value ) {
// Default setting for new options is 'yes'.
if ( null === $autoload ) {
$autoload = 'yes';
@ -2887,3 +2889,40 @@ function filter_default_option( $default_value, $option, $passed_default ) {
return $registered[ $option ]['default'];
}
/**
* Determines whether two values will be equal when stored in the database.
*
* @since 6.4.0
* @access private
*
* @param mixed $old_value The old value to compare.
* @param mixed $new_value The new value to compare.
* @return bool True if the values are equal, false otherwise.
*/
function _is_equal_database_value( $old_value, $new_value ) {
$values = array(
'old' => $old_value,
'new' => $new_value,
);
foreach ( $values as $_key => &$_value ) {
// Cast scalars or null to a string so type discrepancies don't result in cache misses.
if ( null === $_value || is_scalar( $_value ) ) {
$_value = (string) $_value;
}
}
if ( $values['old'] === $values['new'] ) {
return true;
}
/*
* Unserialized values will be adequate in most cases. If the unserialized
* data differs, the (maybe) serialized data is checked to avoid
* unnecessary database calls for otherwise identical object instances.
*
* See https://core.trac.wordpress.org/ticket/38903
*/
return maybe_serialize( $old_value ) === maybe_serialize( $new_value );
}

View File

@ -16,7 +16,7 @@
*
* @global string $wp_version
*/
$wp_version = '6.4-alpha-56680';
$wp_version = '6.4-alpha-56681';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.