Changeset 3423944
- Timestamp:
- 12/19/2025 07:14:56 PM (3 months ago)
- Location:
- 404-solution
- Files:
-
- 10 edited
- 1 copied
-
tags/3.1.7 (copied) (copied from 404-solution/trunk)
-
tags/3.1.7/404-solution.php (modified) (1 diff)
-
tags/3.1.7/CHANGELOG.md (modified) (1 diff)
-
tags/3.1.7/README.md (modified) (1 diff)
-
tags/3.1.7/includes/DatabaseUpgradesEtc.php (modified) (2 diffs)
-
tags/3.1.7/readme.txt (modified) (2 diffs)
-
trunk/404-solution.php (modified) (1 diff)
-
trunk/CHANGELOG.md (modified) (1 diff)
-
trunk/README.md (modified) (1 diff)
-
trunk/includes/DatabaseUpgradesEtc.php (modified) (2 diffs)
-
trunk/readme.txt (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
404-solution/tags/3.1.7/404-solution.php
r3423238 r3423944 8 8 Author URI: https://www.ajexperience.com/404-solution/ 9 9 10 Version: 3.1. 610 Version: 3.1.7 11 11 Requires at least: 5.0 12 12 Requires PHP: 7.4 -
404-solution/tags/3.1.7/CHANGELOG.md
r3423238 r3423944 1 1 # Changelog # 2 3 ## Version 3.1.7 (Dec 19, 2025) ## 4 * FIX: Prevent invalid SQL during missing-index creation by parsing index definitions from the plugin SQL templates and emitting structured `ALTER TABLE ... ADD INDEX ...` statements. 5 * Test: Add regression coverage for parsing log table composite index definitions and for missing index definitions in SQL templates. 2 6 3 7 ## Version 3.1.6 (Dec 18, 2025) ## -
404-solution/tags/3.1.7/README.md
r3423238 r3423944 81 81 ## Changelog ## 82 82 83 ## Version 3.1.7 (Dec 19, 2025) ## 84 * FIX: Prevent invalid SQL during missing-index creation by parsing index definitions from the plugin SQL templates and emitting structured `ALTER TABLE ... ADD INDEX ...` statements. 85 * Test: Add regression coverage for parsing log table composite index definitions and for missing index definitions in SQL templates. 86 83 87 ## Version 3.1.6 (Dec 18, 2025) ## 84 88 * FIX: Redirects table pagination/search no longer fails on some MariaDB versions with a SQL syntax error while updating the table. -
404-solution/tags/3.1.7/includes/DatabaseUpgradesEtc.php
r3423122 r3423944 597 597 // get the indexes. 598 598 // Pattern matches lines starting with "KEY" / "UNIQUE KEY" - handles composite indexes with commas inside parens 599 $existingTableMatches = null; 600 $goalTableMatches = null; 601 preg_match_all('/^\s*((?:unique\s+)?key\s+.+?)\s*,?\s*$/im', $existingTableSQL, $existingTableMatches); 602 preg_match_all('/^\s*((?:unique\s+)?key\s+.+?)\s*,?\s*$/im', $createTableStatementGoal, $goalTableMatches); 603 604 $extractIndexName = function($indexDDL) { 605 $matches = null; 606 // Matches: KEY `name` (...) or UNIQUE KEY `name` (...) 607 preg_match('/\bkey\b\s+`([^`]+)`/i', $indexDDL, $matches); 608 return $matches[1] ?? null; 609 }; 610 611 $goalTableMatchesColumnDDL = $goalTableMatches[1] ?? []; 612 $existingTableMatchesColumnDDL = $existingTableMatches[1] ?? []; 613 614 $goalIndexesByName = []; 615 foreach ($goalTableMatchesColumnDDL as $indexDDL) { 616 $indexName = $extractIndexName($indexDDL); 617 if (!empty($indexName)) { 618 $goalIndexesByName[$indexName] = $indexDDL; 599 // Indexes: treat the CREATE TABLE SQL as source of truth, and treat the database as truth 600 // for what exists (SHOW INDEX). Avoid parsing SHOW CREATE TABLE output, which is vendor/format dependent. 601 $goalSpecsByName = $this->parseIndexSpecsFromCreateTableSql($createTableStatementGoal); 602 603 $missingIndexNames = []; 604 foreach (array_keys($goalSpecsByName) as $indexName) { 605 if (!$this->indexExists($tableName, $indexName)) { 606 $missingIndexNames[] = $indexName; 619 607 } 620 608 } 621 622 $existingIndexNames = []; 623 foreach ($existingTableMatchesColumnDDL as $indexDDL) { 624 $indexName = $extractIndexName($indexDDL); 625 if (!empty($indexName)) { 626 $existingIndexNames[$indexName] = true; 627 } 628 } 629 630 // Compare by index name to avoid false positives from harmless formatting differences (e.g., "USING BTREE"). 631 $missingIndexNames = array_diff(array_keys($goalIndexesByName), array_keys($existingIndexNames)); 632 633 // say why we're doing what we're doing. 609 634 610 if (count($missingIndexNames) > 0) { 635 611 $this->logger->infoMessage(self::$uniqID . ": On {$tableName} I'm adding missing indexes: " . implode(', ', $missingIndexNames)); 636 612 } 637 613 638 614 foreach ($missingIndexNames as $indexName) { 639 $ indexDDL = $goalIndexesByName[$indexName] ?? null;640 if (empty($ indexDDL)) {615 $spec = $goalSpecsByName[$indexName] ?? null; 616 if (empty($spec)) { 641 617 continue; 642 618 } 643 // Skip if the index is already present (idempotent / concurrent creation). 644 if ($this->indexExists($tableName, $indexName)) { 645 continue; 646 } 647 648 // If we're adding a unique key then remove the duplicates. 649 // This was causing issues for some people. 619 650 620 $spellingCacheTableName = $this->dao->doTableNameReplacements('{wp_abj404_spelling_cache}'); 651 if (strtolower($tableName) == $spellingCacheTableName ) {621 if (strtolower($tableName) == $spellingCacheTableName && !empty($spec['unique'])) { 652 622 $this->dao->deleteSpellingCache(); 653 623 } 654 655 // Create the index. 656 $addStatement = $this->buildAddIndexStatement($tableName, $indexDDL); 624 625 $addStatement = $this->buildAddIndexStatementFromParts($tableName, $spec['name'], $spec['columns'], $spec['unique']); 657 626 $this->dao->queryAndGetResults($addStatement); 658 627 $this->logger->infoMessage("I added an index: " . $addStatement); … … 679 648 // Normalize "KEY `name` (...)" / "UNIQUE KEY `name` (...)" into a form usable with "IF NOT EXISTS". 680 649 // MariaDB supports: ADD [UNIQUE] INDEX IF NOT EXISTS `name` (...) 681 if ($supportsIfNotExists) { 682 $matches = []; 683 if (preg_match('/^(unique\\s+)?key\\s+`([^`]+)`\\s*(\\(.+\\))\\s*$/i', $indexDDL, $matches)) { 684 $unique = !empty($matches[1]); 685 $name = $matches[2]; 686 $cols = $matches[3]; 687 $type = $unique ? 'unique index' : 'index'; 688 return "alter table " . $tableName . " add " . $type . " if not exists `" . $name . "` " . $cols; 650 if ($supportsIfNotExists) { 651 $matches = []; 652 if (preg_match('/^(unique\\s+)?key\\s+`([^`]+)`\\s*(\\(.+\\))\\s*$/i', $indexDDL, $matches)) { 653 $unique = !empty($matches[1]); 654 $name = $matches[2]; 655 $cols = $matches[3]; 656 $type = $unique ? 'unique index' : 'index'; 657 return "alter table " . $tableName . " add " . $type . " if not exists `" . $name . "` " . $cols; 658 } 659 if (preg_match('/^`([^`]+)`\\s*(\\(.+\\))\\s*$/', $indexDDL, $matches)) { 660 $name = $matches[1]; 661 $cols = $matches[2]; 662 return "alter table " . $tableName . " add index if not exists `" . $name . "` " . $cols; 663 } 664 } 665 666 // If we were given a bare index DDL like "`name` (...)", make it valid for MySQL too. 667 if (preg_match('/^`[^`]+`\\s*\\(.+\\)\\s*$/', $indexDDL)) { 668 return "alter table " . $tableName . " add index " . $indexDDL; 669 } 670 671 // If we were given a bare index DDL like "name (...)", make it valid too. 672 if (preg_match('/^([A-Za-z0-9_]+)\\s*(\\(.+\\))\\s*$/', $indexDDL, $matches)) { 673 $name = $matches[1]; 674 $cols = $matches[2]; 675 return "alter table " . $tableName . " add index `" . $name . "` " . $cols; 676 } 677 678 // Fallback: use the DDL as-is (already contains KEY/UNIQUE KEY). 679 return "alter table " . $tableName . " add " . $indexDDL; 680 } 681 682 /** 683 * Parse an index DDL line from our CREATE TABLE SQL into a structured spec. 684 * 685 * Accepts forms like: 686 * - KEY `name` (`col`(190), `other`) 687 * - UNIQUE KEY `name` (`col`) 688 * - KEY `name` (`col`) USING BTREE 689 * 690 * Returns null if the line doesn't look like a KEY/UNIQUE KEY definition. 691 * 692 * @param string $indexDDL 693 * @return array|null {name:string, columns:string, unique:bool} 694 */ 695 private function parseIndexDDLToSpec($indexDDL) { 696 $indexDDL = trim($indexDDL); 697 $matches = []; 698 if (!preg_match('/^(unique\\s+)?key\\s+`?([^`\\s]+)`?\\s*(\\(.+\\))\\s*(?:using\\s+\\w+)?\\s*$/i', $indexDDL, $matches)) { 699 return null; 700 } 701 702 return [ 703 'name' => $matches[2], 704 'columns' => $matches[3], 705 'unique' => !empty($matches[1]), 706 ]; 707 } 708 709 /** 710 * Extract index specs from a CREATE TABLE statement (plugin SQL templates). 711 * 712 * @param string $createTableSql 713 * @return array<string, array{name:string, columns:string, unique:bool}> keyed by index name 714 */ 715 private function parseIndexSpecsFromCreateTableSql($createTableSql) { 716 if (!is_string($createTableSql) || $createTableSql === '') { 717 return []; 718 } 719 720 $matches = []; 721 preg_match_all('/^\\s*(?:unique\\s+)?key\\s+.+?\\s*$/im', $createTableSql, $matches); 722 $lines = $matches[0] ?? []; 723 724 $specsByName = []; 725 foreach ($lines as $line) { 726 $spec = $this->parseIndexDDLToSpec($line); 727 if (empty($spec) || empty($spec['name'])) { 728 continue; 689 729 } 690 if (preg_match('/^`([^`]+)`\\s*(\\(.+\\))\\s*$/', $indexDDL, $matches)) { 691 $name = $matches[1]; 692 $cols = $matches[2]; 693 return "alter table " . $tableName . " add index if not exists `" . $name . "` " . $cols; 694 } 730 $specsByName[$spec['name']] = $spec; 695 731 } 696 732 697 // Fallback: use the DDL as-is (already contains KEY/UNIQUE KEY). 698 return "alter table " . $tableName . " add " . $indexDDL; 733 return $specsByName; 699 734 } 700 735 701 private function ensureLogsCompositeIndex($logsTable) { 702 $indexName = 'idx_requested_url_timestamp'; 703 $indexDDL = "`{$indexName}` (`requested_url`(190), `timestamp`)"; 704 if ($this->indexExists($logsTable, $indexName)) { 705 return; 706 } 707 $query = $this->buildAddIndexStatement($logsTable, $indexDDL); 708 $results = $this->dao->queryAndGetResults($query); 736 /** 737 * Build a valid ALTER TABLE ... ADD INDEX statement from structured parts. 738 * 739 * @param string $tableName 740 * @param string $indexName 741 * @param string $columnsSql Must include surrounding parentheses, e.g. "(`a`, `b`(190))" 742 * @param bool $unique 743 * @return string 744 */ 745 private function buildAddIndexStatementFromParts($tableName, $indexName, $columnsSql, $unique) { 746 global $wpdb; 747 $serverVersion = method_exists($wpdb, 'db_version') ? $wpdb->db_version() : ''; 748 $serverInfo = property_exists($wpdb, 'db_server_info') ? $wpdb->db_server_info : ''; 749 750 $isMaria = stripos($serverInfo, 'mariadb') !== false || stripos($serverVersion, 'maria') !== false; 751 $supportsIfNotExists = $isMaria && version_compare(preg_replace('/[^\d\.]/', '', $serverVersion), '10.5', '>='); 752 753 $indexType = $unique ? 'unique index' : 'index'; 754 $ifNotExists = $supportsIfNotExists ? ' if not exists' : ''; 755 756 return "alter table " . $tableName . " add " . $indexType . $ifNotExists . " `" . $indexName . "` " . trim($columnsSql); 757 } 758 759 private function ensureLogsCompositeIndex($logsTable, $createSqlOverride = null) { 760 $indexName = 'idx_requested_url_timestamp'; 761 $createSql = is_string($createSqlOverride) ? $createSqlOverride : ABJ_404_Solution_Functions::readFileContents(__DIR__ . "/sql/createLogTable.sql"); 762 $specsByName = $this->parseIndexSpecsFromCreateTableSql($createSql); 763 $spec = $specsByName[$indexName] ?? null; 764 if (empty($spec)) { 765 $this->logger->errorMessage("Failed to add {$indexName} to {$logsTable}: index definition not found in createLogTable.sql"); 766 return; 767 } 768 769 if ($this->indexExists($logsTable, $indexName)) { 770 return; 771 } 772 $query = $this->buildAddIndexStatementFromParts($logsTable, $spec['name'], $spec['columns'], $spec['unique']); 773 $results = $this->dao->queryAndGetResults($query); 709 774 if (!empty($results['last_error'])) { 710 $this->logger->errorMessage("Failed to add {$indexName} to {$logsTable}: " . $results['last_error'] );775 $this->logger->errorMessage("Failed to add {$indexName} to {$logsTable}: " . $results['last_error'] . " (query: {$query})"); 711 776 } else { 712 777 $this->logger->infoMessage("Added {$indexName} to {$logsTable} using query: {$query}"); -
404-solution/tags/3.1.7/readme.txt
r3423238 r3423944 6 6 Requires PHP: 7.4 7 7 Tested up to: 6.9 8 Stable tag: 3.1. 68 Stable tag: 3.1.7 9 9 License: GPL-3.0-or-later 10 10 License URI: https://www.gnu.org/licenses/gpl-3.0.html … … 237 237 238 238 == Changelog == 239 240 = Version 3.1.7 (Dec 19, 2025) = 241 * FIX: Prevent invalid SQL during missing-index creation by parsing index definitions from the plugin SQL templates and emitting structured `ALTER TABLE ... ADD INDEX ...` statements. 242 * Test: Add regression coverage for parsing log table composite index definitions and for missing index definitions in SQL templates. 239 243 240 244 = Version 3.1.6 (Dec 18, 2025) = -
404-solution/trunk/404-solution.php
r3423238 r3423944 8 8 Author URI: https://www.ajexperience.com/404-solution/ 9 9 10 Version: 3.1. 610 Version: 3.1.7 11 11 Requires at least: 5.0 12 12 Requires PHP: 7.4 -
404-solution/trunk/CHANGELOG.md
r3423238 r3423944 1 1 # Changelog # 2 3 ## Version 3.1.7 (Dec 19, 2025) ## 4 * FIX: Prevent invalid SQL during missing-index creation by parsing index definitions from the plugin SQL templates and emitting structured `ALTER TABLE ... ADD INDEX ...` statements. 5 * Test: Add regression coverage for parsing log table composite index definitions and for missing index definitions in SQL templates. 2 6 3 7 ## Version 3.1.6 (Dec 18, 2025) ## -
404-solution/trunk/README.md
r3423238 r3423944 81 81 ## Changelog ## 82 82 83 ## Version 3.1.7 (Dec 19, 2025) ## 84 * FIX: Prevent invalid SQL during missing-index creation by parsing index definitions from the plugin SQL templates and emitting structured `ALTER TABLE ... ADD INDEX ...` statements. 85 * Test: Add regression coverage for parsing log table composite index definitions and for missing index definitions in SQL templates. 86 83 87 ## Version 3.1.6 (Dec 18, 2025) ## 84 88 * FIX: Redirects table pagination/search no longer fails on some MariaDB versions with a SQL syntax error while updating the table. -
404-solution/trunk/includes/DatabaseUpgradesEtc.php
r3423122 r3423944 597 597 // get the indexes. 598 598 // Pattern matches lines starting with "KEY" / "UNIQUE KEY" - handles composite indexes with commas inside parens 599 $existingTableMatches = null; 600 $goalTableMatches = null; 601 preg_match_all('/^\s*((?:unique\s+)?key\s+.+?)\s*,?\s*$/im', $existingTableSQL, $existingTableMatches); 602 preg_match_all('/^\s*((?:unique\s+)?key\s+.+?)\s*,?\s*$/im', $createTableStatementGoal, $goalTableMatches); 603 604 $extractIndexName = function($indexDDL) { 605 $matches = null; 606 // Matches: KEY `name` (...) or UNIQUE KEY `name` (...) 607 preg_match('/\bkey\b\s+`([^`]+)`/i', $indexDDL, $matches); 608 return $matches[1] ?? null; 609 }; 610 611 $goalTableMatchesColumnDDL = $goalTableMatches[1] ?? []; 612 $existingTableMatchesColumnDDL = $existingTableMatches[1] ?? []; 613 614 $goalIndexesByName = []; 615 foreach ($goalTableMatchesColumnDDL as $indexDDL) { 616 $indexName = $extractIndexName($indexDDL); 617 if (!empty($indexName)) { 618 $goalIndexesByName[$indexName] = $indexDDL; 599 // Indexes: treat the CREATE TABLE SQL as source of truth, and treat the database as truth 600 // for what exists (SHOW INDEX). Avoid parsing SHOW CREATE TABLE output, which is vendor/format dependent. 601 $goalSpecsByName = $this->parseIndexSpecsFromCreateTableSql($createTableStatementGoal); 602 603 $missingIndexNames = []; 604 foreach (array_keys($goalSpecsByName) as $indexName) { 605 if (!$this->indexExists($tableName, $indexName)) { 606 $missingIndexNames[] = $indexName; 619 607 } 620 608 } 621 622 $existingIndexNames = []; 623 foreach ($existingTableMatchesColumnDDL as $indexDDL) { 624 $indexName = $extractIndexName($indexDDL); 625 if (!empty($indexName)) { 626 $existingIndexNames[$indexName] = true; 627 } 628 } 629 630 // Compare by index name to avoid false positives from harmless formatting differences (e.g., "USING BTREE"). 631 $missingIndexNames = array_diff(array_keys($goalIndexesByName), array_keys($existingIndexNames)); 632 633 // say why we're doing what we're doing. 609 634 610 if (count($missingIndexNames) > 0) { 635 611 $this->logger->infoMessage(self::$uniqID . ": On {$tableName} I'm adding missing indexes: " . implode(', ', $missingIndexNames)); 636 612 } 637 613 638 614 foreach ($missingIndexNames as $indexName) { 639 $ indexDDL = $goalIndexesByName[$indexName] ?? null;640 if (empty($ indexDDL)) {615 $spec = $goalSpecsByName[$indexName] ?? null; 616 if (empty($spec)) { 641 617 continue; 642 618 } 643 // Skip if the index is already present (idempotent / concurrent creation). 644 if ($this->indexExists($tableName, $indexName)) { 645 continue; 646 } 647 648 // If we're adding a unique key then remove the duplicates. 649 // This was causing issues for some people. 619 650 620 $spellingCacheTableName = $this->dao->doTableNameReplacements('{wp_abj404_spelling_cache}'); 651 if (strtolower($tableName) == $spellingCacheTableName ) {621 if (strtolower($tableName) == $spellingCacheTableName && !empty($spec['unique'])) { 652 622 $this->dao->deleteSpellingCache(); 653 623 } 654 655 // Create the index. 656 $addStatement = $this->buildAddIndexStatement($tableName, $indexDDL); 624 625 $addStatement = $this->buildAddIndexStatementFromParts($tableName, $spec['name'], $spec['columns'], $spec['unique']); 657 626 $this->dao->queryAndGetResults($addStatement); 658 627 $this->logger->infoMessage("I added an index: " . $addStatement); … … 679 648 // Normalize "KEY `name` (...)" / "UNIQUE KEY `name` (...)" into a form usable with "IF NOT EXISTS". 680 649 // MariaDB supports: ADD [UNIQUE] INDEX IF NOT EXISTS `name` (...) 681 if ($supportsIfNotExists) { 682 $matches = []; 683 if (preg_match('/^(unique\\s+)?key\\s+`([^`]+)`\\s*(\\(.+\\))\\s*$/i', $indexDDL, $matches)) { 684 $unique = !empty($matches[1]); 685 $name = $matches[2]; 686 $cols = $matches[3]; 687 $type = $unique ? 'unique index' : 'index'; 688 return "alter table " . $tableName . " add " . $type . " if not exists `" . $name . "` " . $cols; 650 if ($supportsIfNotExists) { 651 $matches = []; 652 if (preg_match('/^(unique\\s+)?key\\s+`([^`]+)`\\s*(\\(.+\\))\\s*$/i', $indexDDL, $matches)) { 653 $unique = !empty($matches[1]); 654 $name = $matches[2]; 655 $cols = $matches[3]; 656 $type = $unique ? 'unique index' : 'index'; 657 return "alter table " . $tableName . " add " . $type . " if not exists `" . $name . "` " . $cols; 658 } 659 if (preg_match('/^`([^`]+)`\\s*(\\(.+\\))\\s*$/', $indexDDL, $matches)) { 660 $name = $matches[1]; 661 $cols = $matches[2]; 662 return "alter table " . $tableName . " add index if not exists `" . $name . "` " . $cols; 663 } 664 } 665 666 // If we were given a bare index DDL like "`name` (...)", make it valid for MySQL too. 667 if (preg_match('/^`[^`]+`\\s*\\(.+\\)\\s*$/', $indexDDL)) { 668 return "alter table " . $tableName . " add index " . $indexDDL; 669 } 670 671 // If we were given a bare index DDL like "name (...)", make it valid too. 672 if (preg_match('/^([A-Za-z0-9_]+)\\s*(\\(.+\\))\\s*$/', $indexDDL, $matches)) { 673 $name = $matches[1]; 674 $cols = $matches[2]; 675 return "alter table " . $tableName . " add index `" . $name . "` " . $cols; 676 } 677 678 // Fallback: use the DDL as-is (already contains KEY/UNIQUE KEY). 679 return "alter table " . $tableName . " add " . $indexDDL; 680 } 681 682 /** 683 * Parse an index DDL line from our CREATE TABLE SQL into a structured spec. 684 * 685 * Accepts forms like: 686 * - KEY `name` (`col`(190), `other`) 687 * - UNIQUE KEY `name` (`col`) 688 * - KEY `name` (`col`) USING BTREE 689 * 690 * Returns null if the line doesn't look like a KEY/UNIQUE KEY definition. 691 * 692 * @param string $indexDDL 693 * @return array|null {name:string, columns:string, unique:bool} 694 */ 695 private function parseIndexDDLToSpec($indexDDL) { 696 $indexDDL = trim($indexDDL); 697 $matches = []; 698 if (!preg_match('/^(unique\\s+)?key\\s+`?([^`\\s]+)`?\\s*(\\(.+\\))\\s*(?:using\\s+\\w+)?\\s*$/i', $indexDDL, $matches)) { 699 return null; 700 } 701 702 return [ 703 'name' => $matches[2], 704 'columns' => $matches[3], 705 'unique' => !empty($matches[1]), 706 ]; 707 } 708 709 /** 710 * Extract index specs from a CREATE TABLE statement (plugin SQL templates). 711 * 712 * @param string $createTableSql 713 * @return array<string, array{name:string, columns:string, unique:bool}> keyed by index name 714 */ 715 private function parseIndexSpecsFromCreateTableSql($createTableSql) { 716 if (!is_string($createTableSql) || $createTableSql === '') { 717 return []; 718 } 719 720 $matches = []; 721 preg_match_all('/^\\s*(?:unique\\s+)?key\\s+.+?\\s*$/im', $createTableSql, $matches); 722 $lines = $matches[0] ?? []; 723 724 $specsByName = []; 725 foreach ($lines as $line) { 726 $spec = $this->parseIndexDDLToSpec($line); 727 if (empty($spec) || empty($spec['name'])) { 728 continue; 689 729 } 690 if (preg_match('/^`([^`]+)`\\s*(\\(.+\\))\\s*$/', $indexDDL, $matches)) { 691 $name = $matches[1]; 692 $cols = $matches[2]; 693 return "alter table " . $tableName . " add index if not exists `" . $name . "` " . $cols; 694 } 730 $specsByName[$spec['name']] = $spec; 695 731 } 696 732 697 // Fallback: use the DDL as-is (already contains KEY/UNIQUE KEY). 698 return "alter table " . $tableName . " add " . $indexDDL; 733 return $specsByName; 699 734 } 700 735 701 private function ensureLogsCompositeIndex($logsTable) { 702 $indexName = 'idx_requested_url_timestamp'; 703 $indexDDL = "`{$indexName}` (`requested_url`(190), `timestamp`)"; 704 if ($this->indexExists($logsTable, $indexName)) { 705 return; 706 } 707 $query = $this->buildAddIndexStatement($logsTable, $indexDDL); 708 $results = $this->dao->queryAndGetResults($query); 736 /** 737 * Build a valid ALTER TABLE ... ADD INDEX statement from structured parts. 738 * 739 * @param string $tableName 740 * @param string $indexName 741 * @param string $columnsSql Must include surrounding parentheses, e.g. "(`a`, `b`(190))" 742 * @param bool $unique 743 * @return string 744 */ 745 private function buildAddIndexStatementFromParts($tableName, $indexName, $columnsSql, $unique) { 746 global $wpdb; 747 $serverVersion = method_exists($wpdb, 'db_version') ? $wpdb->db_version() : ''; 748 $serverInfo = property_exists($wpdb, 'db_server_info') ? $wpdb->db_server_info : ''; 749 750 $isMaria = stripos($serverInfo, 'mariadb') !== false || stripos($serverVersion, 'maria') !== false; 751 $supportsIfNotExists = $isMaria && version_compare(preg_replace('/[^\d\.]/', '', $serverVersion), '10.5', '>='); 752 753 $indexType = $unique ? 'unique index' : 'index'; 754 $ifNotExists = $supportsIfNotExists ? ' if not exists' : ''; 755 756 return "alter table " . $tableName . " add " . $indexType . $ifNotExists . " `" . $indexName . "` " . trim($columnsSql); 757 } 758 759 private function ensureLogsCompositeIndex($logsTable, $createSqlOverride = null) { 760 $indexName = 'idx_requested_url_timestamp'; 761 $createSql = is_string($createSqlOverride) ? $createSqlOverride : ABJ_404_Solution_Functions::readFileContents(__DIR__ . "/sql/createLogTable.sql"); 762 $specsByName = $this->parseIndexSpecsFromCreateTableSql($createSql); 763 $spec = $specsByName[$indexName] ?? null; 764 if (empty($spec)) { 765 $this->logger->errorMessage("Failed to add {$indexName} to {$logsTable}: index definition not found in createLogTable.sql"); 766 return; 767 } 768 769 if ($this->indexExists($logsTable, $indexName)) { 770 return; 771 } 772 $query = $this->buildAddIndexStatementFromParts($logsTable, $spec['name'], $spec['columns'], $spec['unique']); 773 $results = $this->dao->queryAndGetResults($query); 709 774 if (!empty($results['last_error'])) { 710 $this->logger->errorMessage("Failed to add {$indexName} to {$logsTable}: " . $results['last_error'] );775 $this->logger->errorMessage("Failed to add {$indexName} to {$logsTable}: " . $results['last_error'] . " (query: {$query})"); 711 776 } else { 712 777 $this->logger->infoMessage("Added {$indexName} to {$logsTable} using query: {$query}"); -
404-solution/trunk/readme.txt
r3423238 r3423944 6 6 Requires PHP: 7.4 7 7 Tested up to: 6.9 8 Stable tag: 3.1. 68 Stable tag: 3.1.7 9 9 License: GPL-3.0-or-later 10 10 License URI: https://www.gnu.org/licenses/gpl-3.0.html … … 237 237 238 238 == Changelog == 239 240 = Version 3.1.7 (Dec 19, 2025) = 241 * FIX: Prevent invalid SQL during missing-index creation by parsing index definitions from the plugin SQL templates and emitting structured `ALTER TABLE ... ADD INDEX ...` statements. 242 * Test: Add regression coverage for parsing log table composite index definitions and for missing index definitions in SQL templates. 239 243 240 244 = Version 3.1.6 (Dec 18, 2025) =
Note: See TracChangeset
for help on using the changeset viewer.