43c43
< 		$query = preg_replace( "/%(?:%|$|(?!($allowed_format)?[sdfFi]))/", '%%\\1', $query );
---
> 		$query = preg_replace( "/%(?:%|$|(?!($allowed_format|\.\.\.)?[sdfFi]))/", '%%\\1', $query );
46c46
< 		$split_query = preg_split( "/(^|[^%]|(?:%%)+)(%(?:$allowed_format)?[sdfFi])/", $query, -1, PREG_SPLIT_DELIM_CAPTURE );
---
> 		$split_query = preg_split( "/(^|[^%]|(?:%%)+)(%(?:$allowed_format|\.\.\.)?[sdfFi])/", $query, -1, PREG_SPLIT_DELIM_CAPTURE );
57a58,60
> 		if ( $passed_as_array && isset( $split_query[2] ) && substr( $split_query[2], 1, -1 ) === '...' && false === is_array( $args[0][0] ) ) {
> 			$passed_as_array = false; // The first (and only) placeholder, is using variadics (e.g. '%...d'), but the args were *not* passed as an array, e.g. $wpdb->prepare('id IN (%...d)', [ [ 1, 2, 3 ] ] );
> 		}
66a70
> 		$arg_variadics   = array();
106c110,121
< 				if ( 'i' === $type ) {
---
> 				if ( '...' === $format ) {
> 					if ( 'i' === $type ) {
> 						$new_placeholder   = '`%s`';
> 						$arg_identifiers[] = $arg_id;
> 					} elseif ( 'd' === $type || 'F' === $type ) {
> 						$new_placeholder = '%' . $type; // No need to quote integers or floats.
> 					} else {
> 						$new_placeholder = "'%" . $type . "'";
> 					}
> 					$placeholder     = substr( str_repeat( $new_placeholder . ',', count( $args[ $arg_id ] ) ), 0, -1 );
> 					$arg_variadics[] = $arg_id;
> 				} else if ( 'i' === $type ) {
260c275,281
< 			if ( in_array( $i, $arg_identifiers, true ) ) {
---
> 			if ( in_array( $i, $arg_variadics, true ) ) {
> 				if ( in_array( $i, $arg_identifiers, true ) ) {
> 					$args_escaped = array_merge( $args_escaped, array_map( array( $this, '_escape_identifier_value' ), $value ) );
> 				} else {
> 					$args_escaped = array_merge( $args_escaped, array_map( array( $this, '_real_escape' ), $value ) );
> 				}
> 			} elseif ( in_array( $i, $arg_identifiers, true ) ) {