PluginProbe
404 Solution / 4.3.3
404 Solution v4.3.3
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 / DatabaseSchemaErrorTaxonomy.php

DatabaseSchemaErrorTaxonomy.php in 404 Solution 4.3.3, at includes/database/DatabaseSchemaErrorTaxonomy.php

157 lines 5.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Pure database schema / data-shape error taxonomy.
4 *
5 * Owns string-based classification of integrity failures: corrupted or
6 * crashed tables (MyISAM "marked as crashed", "Incorrect key file"),
7 * missing plugin tables, transient view-build table churn, and data-shape
8 * issues (invalid UTF-8, collation mismatch). These are the error classes
9 * the auto-repair, table-recreate, and collation-degrade policies act on.
10 *
11 * No side effects: callers reuse the matchers without inheriting recovery
12 * behavior.
13 */
14
15 if (!defined('ABSPATH')) {
16 exit;
17 }
18
19 // allow-no-test-found: covered through DatabaseInfrastructureErrorTaxonomy facade by tests/ErrorClassifierTest.php and tests/StagedBuildHostQuirksTest.php
20 class ABJ_404_Solution_DatabaseSchemaErrorTaxonomy {
21
22 /**
23 * Failures of the STATEMENT rather than of the host: the server answered,
24 * and its answer was that the query cannot be run as written. Re-running it
25 * produces the same answer however long the caller waits.
26 *
27 * @var array<int, string>
28 */
29 private const MALFORMED_STATEMENT_MARKERS = array(
30 'error in your sql syntax',
31 'error 1064',
32 'errno 1064',
33 'unknown column',
34 'unknown table',
35 'unknown database',
36 "doesn't exist",
37 'no such table',
38 );
39
40 /** @var ABJ_404_Solution_Functions */
41 private $f;
42
43 /**
44 * @param ABJ_404_Solution_Functions $functions
45 */
46 public function __construct($functions) {
47 $this->f = $functions;
48 }
49
50 /** @param string $errorText @return bool */
51 public function isCrashedTableError(string $errorText): bool {
52 if ($errorText === '') {
53 return false;
54 }
55 return stripos($errorText, 'is marked as crashed') !== false;
56 }
57
58 /** @param string $errorText @return bool */
59 public function isIncorrectKeyFileError(string $errorText): bool {
60 if ($errorText === '') {
61 return false;
62 }
63 return stripos($errorText, 'Incorrect key file') !== false;
64 }
65
66 /** @param string $errorText @return bool */
67 public function isMissingPluginTableError(string $errorText): bool {
68 if ($errorText === '') {
69 return false;
70 }
71 $lower = strtolower($errorText);
72 if ($this->f->strpos($lower, '_abj404_logs_hits') !== false) {
73 return false;
74 }
75 return ($this->f->strpos($lower, "doesn't exist") !== false &&
76 $this->f->strpos($lower, '_abj404_') !== false);
77 }
78
79 /** @param string $errorText @return bool */
80 public function isTransientViewBuildTableError(string $errorText): bool {
81 if ($errorText === '') {
82 return false;
83 }
84 $lower = strtolower($errorText);
85 return ($this->f->strpos($lower, '_abj404_view_build') !== false ||
86 $this->f->strpos($lower, '_abj404_view_done') !== false ||
87 $this->f->strpos($lower, '_abj404_view_deleteme') !== false);
88 }
89
90 /**
91 * Determine whether an error indicates invalid text/charset payload.
92 *
93 * @param mixed $errorText
94 * @return bool
95 */
96 public function isInvalidDataError($errorText): bool {
97 if (!is_string($errorText) || $errorText === '') {
98 return false;
99 }
100 $lower = strtolower($errorText);
101 return (
102 $this->f->strpos($lower, 'contains invalid data') !== false ||
103 $this->f->strpos($lower, 'incorrect string value') !== false ||
104 $this->f->strpos($lower, 'invalid utf8') !== false
105 );
106 }
107
108 /**
109 * True when the statement itself is wrong rather than the host being
110 * temporarily unable to answer it: a syntax error, an unknown column, or a
111 * table that does not exist.
112 *
113 * The distinction matters wherever a caller decides whether to try again.
114 * A dropped connection is worth retrying; a query the server has already
115 * rejected on its own terms is not, and retrying it on a short cadence only
116 * buries the one report that would have said what is broken.
117 *
118 * Deliberately NOT part of {@see isInfrastructureSqlError()}: that union
119 * drives notice state and repair for HOST problems, and a malformed
120 * statement is neither. It is a defect in the plugin or in the install's
121 * schema, and it belongs in front of a human.
122 *
123 * @param string $errorText
124 * @return bool
125 */
126 public function isMalformedStatementError(string $errorText): bool {
127 if ($errorText === '') {
128 return false;
129 }
130 // View-build scratch tables are created and dropped continuously, so
131 // one of them missing is ordinary churn rather than a broken statement.
132 // Reading it as permanent would stop a pipeline that recovers on its
133 // own next pass.
134 if ($this->isTransientViewBuildTableError($errorText)) {
135 return false;
136 }
137 $lower = strtolower($errorText);
138 foreach (self::MALFORMED_STATEMENT_MARKERS as $marker) {
139 if ($this->f->strpos($lower, $marker) !== false) {
140 return true;
141 }
142 }
143 return false;
144 }
145
146 /** @param string $errorText @return bool */
147 public function isCollationError(string $errorText): bool {
148 if ($errorText === '') {
149 return false;
150 }
151 $lower = strtolower($errorText);
152 return ($this->f->strpos($lower, 'illegal mix of collations') !== false ||
153 $this->f->strpos($lower, 'unknown collation') !== false ||
154 $this->f->strpos($lower, 'collation') !== false && $this->f->strpos($lower, 'not valid') !== false);
155 }
156 }
157