- cached counts followed posts' last_changed, which WordPress moves on any
post meta write; themes write meta on page views (on dev36 Blocksy saved a
dynamic-CSS descriptor for each listing a crawler visited), so the cache
was invalidated every few seconds and keyword/filter searches recounted
- counts now follow their own last_changed group (tmdo_posts), bumped on
clean_post_cache (post saved or deleted; hooked in every mode) and on flat
writes through the Hook Bus, plus terms' last_changed; a routed search reads
no unmanaged meta
- Verify: PostQueryRouterFoundRowsTest (unrelated meta write keeps the count,
save or flat write recounts), integration 588, unit 703, phpcs, stan;
dev36 front-end
- PR: PR-117 (2.3 acceptance)
- keyword searches carry wpdb's placeholder escape for "%" ({<hash>}), which
is new in every request, so md5(count SQL) never matched across requests
and every keyword search recounted (the 2.2 "keyword + filter routed slower
than native" finding). The key now uses remove_placeholder_escape()
- found in the 2.3 acceptance: dev36 keyword + price routed 6.2 s vs native
4.9 s; PR-091's in-process check reused one process, one escape
- Verify: PostQueryRouterFoundRowsTest (two escapes, one COUNT), integration
588, unit 703, phpcs, stan; dev36 front-end
- PR: PR-116 (2.3 acceptance)
- a deferred group whose stored definition matches again (reverted, or
upgraded by another process) goes through end_deferral() instead of staying
native for good (review F2)
- the tmdo_ensure_group_indexes handler upgrades deferred tables itself: with
ALTERNATE_WP_CRON the event runs after init in a visitor's request (F3)
- a new group table created while the entity writes to flat stays native
until the drift fix has filled it from native meta; an opted-in HivePress
attribute no longer routes searches to an empty table (F6)
- direct_read() returns native values for a deferred group (F10); the Hook
Bus ignores deferral in aeav_only, as the router does
- dbDelta failures: the definition is recorded only when every column exists
(schema.upgrade_failed otherwise), so a failed ALTER is retried and the
group is not released onto missing columns
- test bootstrap: get_metadata() reads the real meta table
- Verify: SchemaUpgradeDeferralTest (6), Hook Bus direct read, integration
587, unit 703, phpcs, stan
- PR: PR-114 (2.3 acceptance correctness review)
- TMDO_Logger::notice() is always written to the error log (it had no
callers); bridge_mode_changed and snapshot_deleted use it. Since PR-095
info() is silent without WP_DEBUG_LOG, which left no record of who switched
a mode or deleted a snapshot (security review L1)
- Verify: LoggerTest (notice always logged, info gated), unit 703,
integration 585, phpcs, stan
- PR: PR-113 (2.3 acceptance security review)
- security review H1: anonymous /wp-cron.php requests pass ddl_allowed(),
and dbDelta on an existing table had no lock, so concurrent requests each
ran the ALTER on a large table. The upgrade now runs under the same MySQL
GET_LOCK as the index DDL (skipped when another process holds it) and
re-checks the stored hash once locked; this covers aeav_only too. The table
build moves unchanged to write_table(); lock()/unlock() are shared
- security review M1: after a deferred upgrade the group stays native until
tmdo_drift_resync has rebuilt the rows written meanwhile
(TMDO_Schema_Manager::resynced() from run_scheduled_fix()); with no flat
writes (disabled) it is released at once
- Verify: SchemaUpgradeDeferralTest (lock held elsewhere, resync release),
integration 585, unit 702, phpcs, stan
- PR: PR-111 (2.3 acceptance security review)
- Changed: deferred table upgrades (PR-107), index lock on the primary
(PR-108), cold cache flush by version (PR-110); Fixed: dropped tables in
post status sync (PR-106)
- Verify: composer test
- PR: PR-101 (follow-up)
- TMDO_Cache_Layer::flush_group() bumps the post type's cold cache version
(option wpdo_cold_cache_version) instead of wp_cache_flush_group(), which
Redis Object Cache runs as one server-side scan that blocks Redis on a large
group; entries under older keys expire by their TTL
- TMDO_Zone_Cold::cache_key() builds every cold blob key; version 0 keeps the
current keys, so upgrading invalidates nothing
- Verify: CacheLayerTest (new keys, no scan, other post types untouched),
unit 702, integration 583, phpcs, stan
- PR: PR-110
- when $wpdb has send_reads_to_masters() (HyperDB, LudicrousDB), it is called
before SELECT GET_LOCK, which a replica would take without locking out the
ALTER on the primary
- Verify: GroupCompositeIndexTest (call order through a wpdb proxy),
integration 583, unit 701, phpcs, stan
- PR: PR-108
- a changed group definition no longer runs dbDelta on an existing table
inside a web request (seconds to minutes on a large table, and every
concurrent request tried it): the group is recorded in tmdo_schema_deferred
and tmdo_ensure_group_indexes is scheduled, whose request upgrades it
- meanwhile the group stays native: Hook Bus field lookups return no field and
the router treats it as drifting; afterwards a drift fix
(tmdo_drift_resync) rebuilds the writes that skipped the flat table
- new tables are still created at once; aeav_only upgrades at once (the flat
table is the only copy); doctor lists deferred groups
- Verify: SchemaUpgradeDeferralTest, Hook Bus and router tests, integration
582, unit 701, phpcs, stan; dev36 live cycle in the 2.3 acceptance
- PR: PR-107
- on_transition() skips a recorded group whose table no longer exists and
removes it from tmdo_post_status_columns; every post status change used to
UPDATE the missing table and print a database error (seen on dev36 after a
throwaway group's table was dropped)
- Verify: PostStatusSyncTest (dropped table), integration 578, unit 701, phpcs, stan
- PR: PR-106
- version 1.3.0; CHANGELOG and readme.txt for PR-090a, 091, 093 (verified,
no change), 094–098, with the post-upgrade steps (first CLI / cron run adds
the HivePress indexes; INFO lines need WP_DEBUG_LOG)
- Verify: composer test (701)
- PR: PR-101
- $bootstrap_files, $bootstrap_ok, $f, $result, $snapshot_id and $dir become
$wpdo_* like the file's other globals (WordPress.org Plugin Check)
- Verify: PrefixAllGlobals sniff 0, phpcs, UninstallGuardTest and
InstallerCleanupTest, unit 701
- PR: PR-098
- the main page's form handlers move unchanged from render_page() to
handle_post(), hooked on load-{page}: inside render_page() the admin
header was already sent, so their wp_safe_redirect() calls could not work
- the setup wizard's step 4 snapshot post moves to handle_snapshot_post() on
its load hook; a failure redirects with snap_failed=1 for the notice
- Verify: unit 701, integration 577, phpcs, stan; dev36 load hook handles a
posted reset and redirects before output
- PR: PR-097
- rest_exposable() also withholds keys named like an email, phone, address or
IP, even when a field declares show_in_rest (defence in depth)
- Verify: RestApiTest (private and look-alike public keys), unit 701,
integration 577, phpcs, stan; dev36 live rest suite
- PR: PR-096
- TMDO_Logger::info()/notice() write to the PHP error log only with
WP_DEBUG_LOG (filter tmdo_log_info); every CLI run and production request
printed [WPDO][INFO] lines
- debug() keeps its WP_DEBUG gate; warning() and error() are unchanged
- Verify: LoggerTest (2 new), unit 700, integration 577, phpcs, stan
- PR: PR-095
- blocked POST /listings/{id}/view requests update wpdo_rl_stats 1 in 10,
each sample adding 10: anonymous traffic no longer turns 1:1 into DB writes,
and the admin top list stays representative (unbiased estimate)
- Verify: RestApiTest counts option writes (about 20 for 200 blocked requests),
unit 698, integration 577, phpcs, stan
- PR: PR-094
- a routed search with post__not_in (HivePress sends a random set per page)
caches the COUNT without the exclusion and subtracts a live primary-key
count of the excluded posts that match; the total stays exact
- the excluded count drops STRAIGHT_JOIN so MySQL starts from posts.ID
- Verify: PostQueryRouterFoundRowsTest, unit 698, integration 577, phpcs, stan;
dev36 live router suite and keyword + price search
- PR: PR-091
- ensure_group_indexes() runs the ALTER only under WP-CLI or WP-Cron (filter
tmdo_schema_ddl_allowed); web requests, including /wp-admin/ before
auth_redirect() and admin-post.php, schedule tmdo_ensure_group_indexes instead
- MySQL GET_LOCK keeps it to one process (add_option() is INSERT ... ON
DUPLICATE KEY UPDATE and cannot serve as a lock); released by MySQL if the
process dies
- the applied signature is recorded only when every ALTER succeeds; failures
log schema.index_failed and stay in pending_indexes(), which doctor reports
- new tables get their declared cidx_ indexes in CREATE TABLE
- test stub wpdb::query()/insert() return false on SQL errors like wpdb
- Verify: GroupCompositeIndexTest 7/7, unit 698, integration 576, phpcs, stan
- PR: PR-090a