PluginProbe
404 Solution / trunk
404 Solution vtrunk
4.3.5 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 4.2.0 4.1.19 4.1.18 4.1.17 4.1.16 4.1.15 4.1.13 4.1.12 4.1.11 4.1.10 4.1.9 4.1.8 4.1.7 4.1.6 4.1.5 4.1.4 4.1.3 trunk 2.30.0 All 109 releases
404-solution / includes / database / upgrades / DatabaseUpgradeAddedColumnBackfill.php

DatabaseUpgradeAddedColumnBackfill.php in 404 Solution trunk, at includes/database/upgrades/DatabaseUpgradeAddedColumnBackfill.php

134 lines 6.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('ABSPATH')) {
4 exit;
5 }
6
7 /**
8 * One-time data backfills and cache invalidations triggered by a column that
9 * was just added to a plugin table.
10 *
11 * Peer of the other column-triggered backfill components
12 * ({@see ABJ_404_Solution_DatabaseUpgradeCanonicalUrlBackfill},
13 * {@see ABJ_404_Solution_DatabaseUpgradeRedirectsDenormBackfill}): those make
14 * sure a column EXISTS, this one seeds the data that a column needs once it
15 * does. Reached from
16 * {@see ABJ_404_Solution_DatabaseUpgradeSchemaDiff::verifyColumns()}, the only
17 * caller that knows a column was actually added rather than already present.
18 *
19 * Extracted from ABJ_404_Solution_DatabaseTableDdlExecutor, where it shared a
20 * file with permanent-DDL discovery and execution but never shared a call
21 * graph with them: nothing in that file called it and it called nothing in
22 * that file, and it owned that file's only use of the content repository.
23 * Seeding table DATA is a different job from executing table DDL; the two only
24 * ever met because both happen during an upgrade.
25 */
26 class ABJ_404_Solution_DatabaseUpgradeAddedColumnBackfill extends ABJ_404_Solution_DatabaseUpgradeComponent {
27
28 /**
29 * Post-creation, column-triggered one-time data backfills. Dispatches
30 * on (tableName, colName) to exactly two cases:
31 * - abj404_logsv2.min_log_id: runs the seed SQL (backfillLogsMinLogId)
32 * and ensures the composite index that depends on it.
33 * - abj404_permalink_cache.url_length: truncates the permalink cache
34 * so the new column gets populated on next rebuild.
35 * Any other (tableName, colName) pair is a no-op.
36 *
37 * Takes a single associative array (rather than two positional strings)
38 * so the table name and column name -- both plain strings -- cannot be
39 * silently transposed at the call site.
40 *
41 * @param array{tableName: string, colName: string} $context
42 * @return void
43 */
44 public function runBackfillsForAddedColumn(array $context) {
45 $tableName = isset($context['tableName']) && is_string($context['tableName']) ? $context['tableName'] : '';
46 $colName = isset($context['colName']) && is_string($context['colName']) ? $context['colName'] : '';
47 if (empty($tableName)) {
48 return;
49 }
50
51 if (strpos($tableName, 'abj404_logsv2') !== false && $colName == 'min_log_id') {
52 $this->backfillLogsMinLogId($tableName);
53 }
54 if (strpos($tableName, 'abj404_permalink_cache') !== false && $colName == 'url_length') {
55 // clear the permalink cache so that the url length column will be populated.
56 // this could be more efficient but I'll assume that's not necessary.
57 $this->contentRepo->truncatePermalinkCacheTable();
58 }
59 }
60
61 /**
62 * Continue resumable backfills even after the triggering column exists.
63 * A failed or partially completed data update must remain reachable on the
64 * next schema verification.
65 *
66 * @param string $tableName
67 * @return void
68 */
69 public function runPendingBackfills(string $tableName): void {
70 if (strpos($tableName, 'abj404_logsv2') !== false) {
71 $this->backfillLogsMinLogId($tableName);
72 }
73 }
74
75 /**
76 * One-time backfill for the abj404_logsv2.min_log_id column: runs the
77 * seed SQL, then ensures the composite index that depends on it exists.
78 * Extracted out of runBackfillsForAddedColumn() so that method stays a plain
79 * column-name dispatcher; the data-access step (SQL file load + execute)
80 * lives in its own method instead of inline in the dispatch logic.
81 *
82 * @param string $tableName
83 * @return void
84 */
85 private function backfillLogsMinLogId($tableName) {
86 try {
87 $query = ABJ_404_Solution_FileSystemService::readFileContents(__DIR__ . "/../../sql/logsSetMinLogID.sql");
88 } catch (Exception $e) {
89 $this->logger->errorMessage(
90 'Could not read logsSetMinLogID.sql backfill file for ' . $tableName
91 . ': ' . $e->getMessage() . '. Skipping min_log_id backfill and composite '
92 . 'index creation this run; will retry on the next upgrade check.'
93 );
94 return;
95 }
96 $queryResponse = $this->dbCore->queryAndGetResults($query);
97 $lastErrorValue = $queryResponse['last_error'] ?? null;
98 if (!is_string($lastErrorValue)) {
99 $this->logger->errorMessage(
100 'min_log_id backfill query returned invalid last_error type ('
101 . gettype($lastErrorValue) . ') for ' . $tableName
102 . '. Skipping composite index creation this run; will retry on the next upgrade check.'
103 );
104 return;
105 }
106 $lastError = $lastErrorValue;
107 if ($lastError !== '') {
108 // Don't create the composite index on the strength of a backfill
109 // that didn't actually run: the index exists to make min_log_id
110 // lookups fast, and building it now would just lock in whatever
111 // stale/default values the column already has. Skipping is safe
112 // (idempotent) -- the next upgrade check retries both steps.
113 $this->logger->errorMessage(
114 'min_log_id backfill query failed for ' . $tableName . ': ' . $lastError
115 . '. Skipping composite index creation this run; will retry on the next upgrade check.'
116 );
117 return;
118 }
119 $rowsAffected = $queryResponse['rows_affected'] ?? null;
120 if (!is_int($rowsAffected) && !is_numeric($rowsAffected)) {
121 $this->logger->errorMessage(
122 'min_log_id backfill query returned invalid rows_affected type ('
123 . gettype($rowsAffected) . ') for ' . $tableName
124 . '. Skipping composite index creation this run; will retry on the next upgrade check.'
125 );
126 return;
127 }
128 if ((int)$rowsAffected > 0) {
129 return;
130 }
131 $this->upgrades()->indexesUpgrade()->ensureLogsCompositeIndex($tableName);
132 }
133 }
134