From 8a85370e325b99da7406bcb91827f3588b6c2379 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sun, 5 Jul 2026 17:41:26 -0500 Subject: [PATCH] fix(installer): drop legacy remote columns in PHP, not PREPARE-based SQL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 02.56.01.sql used PREPARE/EXECUTE/DEALLOCATE to conditionally drop the 26 legacy remote_storage/ftp_*/sftp_*/gdrive_*/s3_* columns portably. Joomla's installer rejects those statements on MySQL 8 (error 1295: "This command is not supported in the prepared statement protocol yet"), aborting the whole component install when updating from an old schema. Neutralize 02.56.01.sql (now a no-op comment) and move the purge into Pkg_MokoSuiteBackupInstallerScript::dropLegacyRemoteColumns(): gate on INFORMATION_SCHEMA, then issue one plain ALTER dropping only the columns that still exist — portable on MariaDB and MySQL 8, idempotent, non-fatal. Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i --- CHANGELOG.md | 3 ++ .../sql/updates/mysql/02.56.01.sql | 41 ++++----------- source/script.php | 52 +++++++++++++++++++ 3 files changed, 66 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b1eacb2..26def769 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## [Unreleased] +### Fixed +- Installer no longer aborts on MySQL 8 with "This command is not supported in the prepared statement protocol yet" (error 1295). The legacy remote-column purge migration (`02.56.01.sql`) used `PREPARE`/`EXECUTE`/`DEALLOCATE`, which Joomla's installer rejects; the drop now runs in the package installer script via an INFORMATION_SCHEMA-gated plain `ALTER` (portable across MariaDB and MySQL 8). (#213 update path) + ## [02.57.00] --- 2026-07-05 ### Added diff --git a/source/packages/com_mokosuitebackup/sql/updates/mysql/02.56.01.sql b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.56.01.sql index 04a5b622..533fca7b 100644 --- a/source/packages/com_mokosuitebackup/sql/updates/mysql/02.56.01.sql +++ b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.56.01.sql @@ -1,32 +1,13 @@ --- Purge legacy single-remote storage columns from installs where they are still present. +-- Legacy single-remote storage columns are now purged by the package installer +-- script (Pkg_MokoSuiteBackupInstallerScript::dropLegacyRemoteColumns()), NOT here. -- --- Background: 02.52.25.sql originally used `DROP COLUMN IF EXISTS`, which is a --- MariaDB-only extension and errors on Oracle MySQL 8.x. On MySQL 8 installs the --- migration failed but Joomla still recorded the schema as applied, leaving all 26 --- legacy remote_storage/ftp_*/sftp_*/gdrive_*/s3_* columns stranded on the profiles --- table. This migration removes them portably. +-- The 26 legacy remote_storage/ftp_*/sftp_*/gdrive_*/s3_* columns must be removed +-- portably across MariaDB and MySQL 8. `DROP COLUMN IF EXISTS` is MariaDB-only +-- (errors on MySQL 8); plain `DROP COLUMN` errors when a column is absent. The +-- previous version gated the ALTER on INFORMATION_SCHEMA and ran it via +-- PREPARE/EXECUTE/DEALLOCATE — but those statements are rejected by Joomla's +-- installer SQL path (MySQL error 1295: "This command is not supported in the +-- prepared statement protocol yet"), which ABORTED the whole component install. -- --- It must be safe on BOTH engines AND on installs where the columns are already gone --- (MariaDB, or anyone who ran the corrected 02.52.25). Plain `DROP COLUMN` errors when --- a column is absent, and `DROP COLUMN IF EXISTS` errors on MySQL 8 — so neither works --- unconditionally. We gate the drop on INFORMATION_SCHEMA and build the ALTER via a --- prepared statement, which runs on MySQL 8 and MariaDB alike. All 26 columns were --- created and dropped together, so the presence of `remote_storage` gates the whole set. --- When the columns are absent this is a no-op (`DO 0`). - -SET @moko_has_legacy_remote := ( - SELECT COUNT(*) - FROM INFORMATION_SCHEMA.COLUMNS - WHERE TABLE_SCHEMA = DATABASE() - AND TABLE_NAME = '#__mokosuitebackup_profiles' - AND COLUMN_NAME = 'remote_storage' -); - -SET @moko_drop_legacy_remote := IF(@moko_has_legacy_remote > 0, - 'ALTER TABLE `#__mokosuitebackup_profiles` DROP COLUMN `remote_storage`, DROP COLUMN `ftp_host`, DROP COLUMN `ftp_port`, DROP COLUMN `ftp_username`, DROP COLUMN `ftp_password`, DROP COLUMN `ftp_path`, DROP COLUMN `ftp_passive`, DROP COLUMN `ftp_ssl`, DROP COLUMN `sftp_host`, DROP COLUMN `sftp_port`, DROP COLUMN `sftp_username`, DROP COLUMN `sftp_auth_type`, DROP COLUMN `sftp_password`, DROP COLUMN `sftp_key_data`, DROP COLUMN `sftp_passphrase`, DROP COLUMN `sftp_path`, DROP COLUMN `gdrive_client_id`, DROP COLUMN `gdrive_client_secret`, DROP COLUMN `gdrive_refresh_token`, DROP COLUMN `gdrive_folder_id`, DROP COLUMN `s3_endpoint`, DROP COLUMN `s3_region`, DROP COLUMN `s3_access_key`, DROP COLUMN `s3_secret_key`, DROP COLUMN `s3_bucket`, DROP COLUMN `s3_path`', - 'DO 0' -); - -PREPARE moko_stmt FROM @moko_drop_legacy_remote; -EXECUTE moko_stmt; -DEALLOCATE PREPARE moko_stmt; +-- The drop is now done in PHP (INFORMATION_SCHEMA gate + a plain ALTER for only the +-- columns that still exist), so this migration is intentionally a no-op. diff --git a/source/script.php b/source/script.php index dbd12a54..4e209884 100644 --- a/source/script.php +++ b/source/script.php @@ -177,6 +177,10 @@ class Pkg_MokoSuiteBackupInstallerScript briefly used the "Type - Name" convention. Self-healing on every update. */ $this->removeOrphanedComponent(); + /* Drop legacy single-remote storage columns portably (replaces the old + PREPARE/EXECUTE migration that MySQL 8 rejected in Joomla's installer). */ + $this->dropLegacyRemoteColumns(); + /* Install completion notice (install and update) */ $this->installSuccessful(); @@ -273,6 +277,54 @@ class Pkg_MokoSuiteBackupInstallerScript } } + /** + * Drop the 26 legacy single-remote storage columns from the profiles table if + * they are still present. Replaces sql/updates/mysql/02.56.01.sql, whose + * PREPARE/EXECUTE/DEALLOCATE approach is rejected by Joomla's installer on + * MySQL 8 (error 1295) and aborted the whole install. Done here in PHP: gate on + * INFORMATION_SCHEMA, then issue a plain ALTER for only the columns that exist — + * portable across MariaDB and MySQL 8. Idempotent and non-fatal. + * + * @return void + */ + private function dropLegacyRemoteColumns(): void + { + try { + $db = Factory::getDbo(); + $table = $db->getPrefix() . 'mokosuitebackup_profiles'; + + $columns = [ + 'remote_storage', + 'ftp_host', 'ftp_port', 'ftp_username', 'ftp_password', 'ftp_path', 'ftp_passive', 'ftp_ssl', + 'sftp_host', 'sftp_port', 'sftp_username', 'sftp_auth_type', 'sftp_password', 'sftp_key_data', 'sftp_passphrase', 'sftp_path', + 'gdrive_client_id', 'gdrive_client_secret', 'gdrive_refresh_token', 'gdrive_folder_id', + 's3_endpoint', 's3_region', 's3_access_key', 's3_secret_key', 's3_bucket', 's3_path', + ]; + + // Which legacy columns still exist on this install? + $db->setQuery( + $db->getQuery(true) + ->select($db->quoteName('COLUMN_NAME')) + ->from($db->quoteName('INFORMATION_SCHEMA.COLUMNS')) + ->where($db->quoteName('TABLE_SCHEMA') . ' = DATABASE()') + ->where($db->quoteName('TABLE_NAME') . ' = ' . $db->quote($table)) + ->where($db->quoteName('COLUMN_NAME') . ' IN (' . implode(',', array_map([$db, 'quote'], $columns)) . ')') + ); + $present = $db->loadColumn(); + + if (empty($present)) { + return; + } + + $drops = array_map(fn($c) => 'DROP COLUMN ' . $db->quoteName($c), $present); + $db->setQuery('ALTER TABLE ' . $db->quoteName($table) . ' ' . implode(', ', $drops)); + $db->execute(); + } catch (\Throwable $e) { + /* Best-effort — a leftover column is harmless and must never abort the install */ + Log::add('MokoSuiteBackup legacy-remote-column drop failed: ' . $e->getMessage(), Log::WARNING, 'jerror'); + } + } + /** * Recursively delete a directory (version-independent, no Folder dependency). *