Skip to content

Fix W3 Total Cache DbCache fatal in batch_insert#77

Open
ilicfilip wants to merge 1 commit into
developfrom
filip/fix-w3tc-dbcache-fatal
Open

Fix W3 Total Cache DbCache fatal in batch_insert#77
ilicfilip wants to merge 1 commit into
developfrom
filip/fix-w3tc-dbcache-fatal

Conversation

@ilicfilip
Copy link
Copy Markdown
Collaborator

@ilicfilip ilicfilip commented May 6, 2026

Summary

Fixes a fatal error reported by a user via WP.org support topic #4979 running this plugin alongside W3 Total Cache with Database Cache enabled:

PHP Fatal error: Uncaught TypeError: mysqli_num_fields(): Argument #1 ($result) must be of type mysqli_result, true given
in /wp-includes/class-wpdb.php:3876
…
#3 /wp-content/plugins/w3-total-cache/DbCache_WpdbInjection_QueryCaching.php(230): wpdb->__get('col_info')
#4 /wp-content/plugins/w3-total-cache/DbCache_WpdbNew.php(216): W3TC\DbCache_WpdbInjection_QueryCaching->query()
#5 /wp-content/plugins/aaa-option-optimizer/src/class-database.php(199): W3TC\DbCache_WpdbNew->query()

The crash fires on shutdown when Database::batch_insert() issues BEGIN.

Root cause

W3TC's DbCache layer wraps $wpdb and, after each cacheable query, reads $wpdb->col_info to stash for the cache. That triggers wpdb::load_col_info()mysqli_num_fields($this->result). For non-SELECT queries the mysqli_query result is the boolean true — hence the type error.

W3TC has an explicit bypass that disables caching for transaction control and write statements via this regex (DbCache_WpdbInjection_QueryCaching.php):

~^\s*start transaction\b~is
~^\s*insert\b|^\s*delete\b|^\s*update\b|^\s*replace\b|^\s*commit\b|^\s*truncate\b|^\s*drop\b|^\s*create\b~is

Note the gap: START TRANSACTION and COMMIT are matched, but BEGIN (a MySQL alias for START TRANSACTION) is not. Our INSERT queries and the closing COMMIT already short-circuit caching, but the opening BEGIN slips through — and that's the query that fatals.

Fix

Use START TRANSACTION instead of BEGIN in Database::batch_insert(). The two are semantically identical in MySQL, but START TRANSACTION matches W3TC's bypass regex, so the col_info path is never entered.

Testing

Reproduced locally against a Valet site with W3TC 2.9.4 (DbCache enabled, file engine):

  • Before the fix: wp eval 'Database::batch_insert([...])' produces the identical stack trace to the user's report (same files, same line numbers, same mysqli_num_fields(true) fatal).
  • After the fix: batch_insert() completes cleanly; rows land in wp_option_optimizer_tracked as expected.

Notes

  • Reported via https://wordpress.org/support/topic/fatal-error-4979/ (plugin version 1.6.1).
  • Version bump and changelog entry are intentionally not part of this PR — will be done before the next release per Filip.
  • This is a one-line behavior-preserving change; no functional impact when W3TC is not in use.

🤖 Generated with Claude Code

W3TC's DbCache wrapper reads `$wpdb->col_info` after each cacheable
query, which triggers `wpdb::load_col_info()` -> `mysqli_num_fields()`.
For non-SELECT queries the result is `true` (bool), so this fatals
with "Argument #1 ($result) must be of type mysqli_result, true given".

W3TC bypasses caching for write queries via a regex that matches
`START TRANSACTION`, `INSERT`, `COMMIT`, etc. — but not `BEGIN`.
Switching `BEGIN` to `START TRANSACTION` (semantically identical in
MySQL) lets W3TC's regex catch it and skip the col_info path.

Reproduced locally with W3TC 2.9.4: identical stack trace with `BEGIN`,
no fatal with `START TRANSACTION`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 6, 2026

Test on Playground
Test this pull request on the Playground
or download the zip

@ilicfilip ilicfilip marked this pull request as ready for review May 6, 2026 12:37
@ilicfilip ilicfilip requested a review from jdevalk May 6, 2026 12:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant