diff --git a/ChangeLog b/ChangeLog
index 53e2f747b2..c46eae09b6 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -120,6 +120,33 @@ phpMyAdmin - ChangeLog
- issue #18210 Fixed add replica replication user on MariaDB doesn't work (SQL syntax)
- issue #19041 Fix footer.twig gets printed to Binary File Download
- issue #19091 Fix to stop processing queries on error
+- issue #18241 Fix copy SQL query button on error messages
+- issue #17190 Fix an error with SELECT ... FOR UPDATE queries
+- issue #19145 Remove duplicate server and lang parameters from links
+- issue #19158 Fix an issue with backticks on the query generator
+- issue #19174 Fix an issue with column alias with asterisk on the query generator
+- issue #19146 Fix column sorting with limit subquery
+- issue #19152 Fix the number of lines being ignored in GIS visualization after a search
+- issue #19189 Fix issue with column sorting when using 'group by'
+- issue #19188 Fix issue with simulated queries reporting syntax errors
+- issue #19141 Add cookie prefix '-__Secure-' to cookies to help prevent cookie smuggling
+- issue #19218 Fix textarea horizontal resizing with Bootstap theme
+- issue #19199 Add support for fractional seconds to current_timestamp()
+- issue #19221 Fix query statistics for queries with count(*)
+- issue #19203 Fix single quotes and backslashes for the query generator
+- issue #19163 Fix queries with IS NULL or IS NOT NULL for the query generator
+- issue #19181 Fix query generator support for IN() and NOT IN()
+- issue #19167 Fix criteria on column '*' for the query generator
+- issue #19213 Fix possible issue when exporting a large data set
+- issue #19217 Fix issue when editing a cell of a JSON column
+- issue #19244 Add yarn 1.22 to the package.json's packageManager field
+- issue #19185 Fix visual issue when a row has only empty cells
+- issue #19257 Fix issue when adding an index with an invalid name
+- issue #19276 Fix compatibility with Twig 3.12
+- issue #19283 Fix issue when the server starts with skip-innodb option
+- issue #19299 Fix charset in procedure's parameter type
+- issue #19316 Fix input size for hexadecimal values
+- issue #19321 Suppress deprecation message of E_STRICT constant
5.2.1 (2023-02-07)
- issue #17522 Fix case where the routes cache file is invalid
diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
index 9072a49d65..515d396c2e 100644
--- a/phpstan-baseline.neon
+++ b/phpstan-baseline.neon
@@ -11919,7 +11919,7 @@ parameters:
-
message: "#^Parameter \\#1 \\$str of method PhpMyAdmin\\\\DatabaseInterface\\:\\:quoteString\\(\\) expects string, mixed given\\.$#"
- count: 1
+ count: 2
path: src/Replication/Replication.php
-
@@ -11938,6 +11938,16 @@ parameters:
count: 8
path: src/Replication/ReplicationGui.php
+ -
+ message: "#^Cannot access offset 'Replica_IO_Running' on mixed\\.$#"
+ count: 1
+ path: src/Replication/ReplicationGui.php
+
+ -
+ message: "#^Cannot access offset 'Replica_SQL_Running' on mixed\\.$#"
+ count: 2
+ path: src/Replication/ReplicationGui.php
+
-
message: "#^Cannot access offset 'Slave_IO_Running' on mixed\\.$#"
count: 1
diff --git a/psalm-baseline.xml b/psalm-baseline.xml
index 64d35f1d4f..f595daed20 100644
--- a/psalm-baseline.xml
+++ b/psalm-baseline.xml
@@ -9581,6 +9581,7 @@
+
@@ -9592,6 +9593,7 @@
+
@@ -9630,8 +9632,6 @@
-
-
@@ -13098,6 +13098,7 @@
+
@@ -13123,6 +13124,8 @@
+
+
@@ -14066,6 +14069,7 @@
+
diff --git a/resources/js/src/sql.ts b/resources/js/src/sql.ts
index 6296e46a48..eda96b949b 100644
--- a/resources/js/src/sql.ts
+++ b/resources/js/src/sql.ts
@@ -422,7 +422,7 @@ AJAX.registerTeardown('sql.js', function () {
$(document).off('click', 'a.delete_row.ajax');
$(document).off('submit', '.bookmarkQueryForm');
$('input#bkm_label').off('input');
- $(document).off('makegrid', '.sqlqueryresults');
+ $(document).off('makeGrid', '.sqlqueryresults');
$('#togglequerybox').off('click');
$(document).off('click', '#button_submit_query');
$(document).off('change', '#id_bookmark');
@@ -649,11 +649,11 @@ AJAX.registerOnload('sql.js', function () {
}); // end of Copy to Clipboard action
/**
- * Attach the {@link makegrid} function to a custom event, which will be
+ * Attach the {@link makeGrid} function to a custom event, which will be
* triggered manually everytime the table of results is reloaded
* @memberOf jQuery
*/
- $(document).on('makegrid', '.sqlqueryresults', function () {
+ $(document).on('makeGrid', '.sqlqueryresults', function () {
$('.table_results').each(function () {
window.makeGrid(this);
});
@@ -887,7 +887,7 @@ AJAX.registerOnload('sql.js', function () {
};
}
- $('.sqlqueryresults').trigger('makegrid');
+ $('.sqlqueryresults').trigger('makeGrid');
$('#togglequerybox').show();
if (typeof data.action_bookmark === 'undefined') {
@@ -927,7 +927,7 @@ AJAX.registerOnload('sql.js', function () {
var $sqlqueryresults = $form.parents('.sqlqueryresults');
$sqlqueryresults
.html(data.message)
- .trigger('makegrid');
+ .trigger('makeGrid');
highlightSql($sqlqueryresults);
}); // end $.post()
@@ -1307,7 +1307,7 @@ AJAX.registerOnload('sql.js', function () {
/**
* create resizable table
*/
- $('.sqlqueryresults').trigger('makegrid');
+ $('.sqlqueryresults').trigger('makeGrid');
/**
* Check if there is any saved query
diff --git a/resources/js/src/table/select.ts b/resources/js/src/table/select.ts
index d42b4bd6d6..d691e147e0 100644
--- a/resources/js/src/table/select.ts
+++ b/resources/js/src/table/select.ts
@@ -166,7 +166,7 @@ AJAX.registerOnload('table/select.js', function () {
$('#sqlqueryresultsouter').html(data.sql_query);
} else { // results found
$('#sqlqueryresultsouter').html(data.message);
- $('.sqlqueryresults').trigger('makegrid');
+ $('.sqlqueryresults').trigger('makeGrid');
}
$('#tbl_search_form')
diff --git a/src/Controllers/Server/BinlogController.php b/src/Controllers/Server/BinlogController.php
index e0fccd6d86..ccb315796d 100644
--- a/src/Controllers/Server/BinlogController.php
+++ b/src/Controllers/Server/BinlogController.php
@@ -31,7 +31,7 @@ final class BinlogController implements InvocableController
public function __construct(private readonly ResponseRenderer $response, private readonly DatabaseInterface $dbi)
{
- $this->binaryLogs = $this->dbi->fetchResult('SHOW MASTER LOGS', 'Log_name');
+ $this->binaryLogs = $this->dbi->fetchResult('SHOW BINARY LOGS', 'Log_name');
}
public function __invoke(ServerRequest $request): Response
diff --git a/src/DatabaseInterface.php b/src/DatabaseInterface.php
index 9ddeaede15..661d4bbda4 100644
--- a/src/DatabaseInterface.php
+++ b/src/DatabaseInterface.php
@@ -1990,6 +1990,12 @@ class DatabaseInterface implements DbalInterface
return $this->versionComment;
}
+ /** Whether connection is MySQL */
+ public function isMySql(): bool
+ {
+ return ! $this->isMariaDb;
+ }
+
/**
* Whether connection is MariaDB
*/
diff --git a/src/Dbal/DbalInterface.php b/src/Dbal/DbalInterface.php
index 374dbb26b7..b4f0c2fcfe 100644
--- a/src/Dbal/DbalInterface.php
+++ b/src/Dbal/DbalInterface.php
@@ -578,6 +578,9 @@ interface DbalInterface
*/
public function getVersionComment(): string;
+ /** Whether connection is MySQL */
+ public function isMySql(): bool;
+
/**
* Whether connection is MariaDB
*/
diff --git a/src/Error/Error.php b/src/Error/Error.php
index 6cee9bb01c..a750701ff9 100644
--- a/src/Error/Error.php
+++ b/src/Error/Error.php
@@ -40,7 +40,6 @@ use const E_ERROR;
use const E_NOTICE;
use const E_PARSE;
use const E_RECOVERABLE_ERROR;
-use const E_STRICT;
use const E_USER_DEPRECATED;
use const E_USER_ERROR;
use const E_USER_NOTICE;
@@ -271,7 +270,7 @@ class Error extends Message
E_USER_ERROR => 'User Error',
E_USER_WARNING => 'User Warning',
E_USER_NOTICE => 'User Notice',
- E_STRICT => 'Runtime Notice',
+ 2048 => 'Runtime Notice', // E_STRICT
E_DEPRECATED => 'Deprecation Notice',
E_USER_DEPRECATED => 'Deprecation Notice',
E_RECOVERABLE_ERROR => 'Catchable Fatal Error',
@@ -284,7 +283,7 @@ class Error extends Message
default => MessageType::Error,
E_NOTICE,
E_USER_NOTICE,
- E_STRICT,
+ 2048, // E_STRICT
E_DEPRECATED,
E_USER_DEPRECATED => MessageType::Notice,
};
diff --git a/src/Error/ErrorHandler.php b/src/Error/ErrorHandler.php
index dc452b52d6..aafa4f3097 100644
--- a/src/Error/ErrorHandler.php
+++ b/src/Error/ErrorHandler.php
@@ -33,7 +33,6 @@ use const E_ERROR;
use const E_NOTICE;
use const E_PARSE;
use const E_RECOVERABLE_ERROR;
-use const E_STRICT;
use const E_USER_DEPRECATED;
use const E_USER_ERROR;
use const E_USER_NOTICE;
@@ -283,7 +282,7 @@ class ErrorHandler
}
switch ($error->getErrorNumber()) {
- case E_STRICT:
+ case 2048: // E_STRICT
case E_DEPRECATED:
case E_NOTICE:
case E_WARNING:
diff --git a/src/Menu.php b/src/Menu.php
index 70c191ae93..ee76af24f3 100644
--- a/src/Menu.php
+++ b/src/Menu.php
@@ -424,7 +424,7 @@ class Menu
if (SessionCache::has('binary_logs')) {
$binaryLogs = SessionCache::get('binary_logs');
} else {
- $binaryLogs = $this->dbi->fetchResult('SHOW MASTER LOGS', 'Log_name');
+ $binaryLogs = $this->dbi->fetchResult('SHOW BINARY LOGS', 'Log_name');
SessionCache::set('binary_logs', $binaryLogs);
}
diff --git a/src/Query/Compatibility.php b/src/Query/Compatibility.php
index f89addaddf..fd8d714ee2 100644
--- a/src/Query/Compatibility.php
+++ b/src/Query/Compatibility.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace PhpMyAdmin\Query;
use PhpMyAdmin\DatabaseInterface;
+use PhpMyAdmin\Dbal\DbalInterface;
use PhpMyAdmin\Util;
use function array_keys;
@@ -260,4 +261,18 @@ class Compatibility
{
return $isMariaDb && $version >= 100402 || ! $isMariaDb && $version >= 50706;
}
+
+ /** @return non-empty-string */
+ public static function getShowBinLogStatusStmt(DbalInterface $dbal): string
+ {
+ if ($dbal->isMySql() && $dbal->getVersion() >= 80200) {
+ return 'SHOW BINARY LOG STATUS';
+ }
+
+ if ($dbal->isMariaDB() && $dbal->getVersion() >= 100502) {
+ return 'SHOW BINLOG STATUS';
+ }
+
+ return 'SHOW MASTER STATUS';
+ }
}
diff --git a/src/Replication/Replication.php b/src/Replication/Replication.php
index 2a1866f394..d1b5fb3bb3 100644
--- a/src/Replication/Replication.php
+++ b/src/Replication/Replication.php
@@ -10,6 +10,7 @@ use PhpMyAdmin\DatabaseInterface;
use PhpMyAdmin\Dbal\Connection;
use PhpMyAdmin\Dbal\ConnectionType;
use PhpMyAdmin\Dbal\ResultInterface;
+use PhpMyAdmin\Query\Compatibility;
use function explode;
use function mb_strtoupper;
@@ -68,6 +69,13 @@ class Replication
return -1;
}
+ if (
+ $this->dbi->isMySql() && $this->dbi->getVersion() >= 80022
+ || $this->dbi->isMariaDB() && $this->dbi->getVersion() >= 100501
+ ) {
+ return $this->dbi->tryQuery($action . ' REPLICA ' . $control . ';', $connectionType);
+ }
+
return $this->dbi->tryQuery($action . ' SLAVE ' . $control . ';', $connectionType);
}
@@ -98,16 +106,29 @@ class Replication
$this->replicaControl('STOP', null, $connectionType);
}
- $out = $this->dbi->tryQuery(
- 'CHANGE MASTER TO ' .
- 'MASTER_HOST=' . $this->dbi->quoteString($host) . ',' .
- 'MASTER_PORT=' . $port . ',' .
- 'MASTER_USER=' . $this->dbi->quoteString($user) . ',' .
- 'MASTER_PASSWORD=' . $this->dbi->quoteString($password) . ',' .
- 'MASTER_LOG_FILE=' . $this->dbi->quoteString($pos['File']) . ',' .
- 'MASTER_LOG_POS=' . $pos['Position'] . ';',
- $connectionType,
- );
+ if ($this->dbi->isMySql() && $this->dbi->getVersion() >= 80023) {
+ $out = $this->dbi->tryQuery(
+ 'CHANGE REPLICATION SOURCE TO ' .
+ 'SOURCE_HOST=' . $this->dbi->quoteString($host) . ',' .
+ 'SOURCE_PORT=' . $port . ',' .
+ 'SOURCE_USER=' . $this->dbi->quoteString($user) . ',' .
+ 'SOURCE_PASSWORD=' . $this->dbi->quoteString($password) . ',' .
+ 'SOURCE_LOG_FILE=' . $this->dbi->quoteString($pos['File']) . ',' .
+ 'SOURCE_LOG_POS=' . $pos['Position'] . ';',
+ $connectionType,
+ );
+ } else {
+ $out = $this->dbi->tryQuery(
+ 'CHANGE MASTER TO ' .
+ 'MASTER_HOST=' . $this->dbi->quoteString($host) . ',' .
+ 'MASTER_PORT=' . $port . ',' .
+ 'MASTER_USER=' . $this->dbi->quoteString($user) . ',' .
+ 'MASTER_PASSWORD=' . $this->dbi->quoteString($password) . ',' .
+ 'MASTER_LOG_FILE=' . $this->dbi->quoteString($pos['File']) . ',' .
+ 'MASTER_LOG_POS=' . $pos['Position'] . ';',
+ $connectionType,
+ );
+ }
if ($start) {
$this->replicaControl('START', null, $connectionType);
@@ -154,7 +175,12 @@ class Replication
*/
public function replicaBinLogPrimary(ConnectionType $connectionType): array
{
- $data = $this->dbi->fetchResult('SHOW MASTER STATUS', null, null, $connectionType);
+ $data = $this->dbi->fetchResult(
+ Compatibility::getShowBinLogStatusStmt($this->dbi),
+ null,
+ null,
+ $connectionType,
+ );
$output = [];
if ($data !== []) {
diff --git a/src/Replication/ReplicationGui.php b/src/Replication/ReplicationGui.php
index b6cc69df56..7081da8d13 100644
--- a/src/Replication/ReplicationGui.php
+++ b/src/Replication/ReplicationGui.php
@@ -74,7 +74,14 @@ class ReplicationGui
): string {
if (! $hasReplicaClearScreen) {
$primaryStatusTable = $this->getHtmlForReplicationStatusTable($connection, 'primary', true, false);
- $replicas = DatabaseInterface::getInstance()->fetchResult('SHOW SLAVE HOSTS');
+ $dbi = DatabaseInterface::getInstance();
+ if ($dbi->isMySql() && $dbi->getVersion() >= 80022) {
+ $replicas = $dbi->fetchResult('SHOW REPLICAS', null, null);
+ } elseif ($dbi->isMariaDB() && $dbi->getVersion() >= 100501) {
+ $replicas = $dbi->fetchResult('SHOW REPLICA HOSTS', null, null);
+ } else {
+ $replicas = $dbi->fetchResult('SHOW SLAVE HOSTS');
+ }
$urlParams = $GLOBALS['urlParams'];
$urlParams['primary_add_user'] = true;
@@ -126,13 +133,34 @@ class ReplicationGui
array $serverReplicaReplication,
bool $replicaConfigure,
): string {
- $serverReplicaMultiReplication = DatabaseInterface::getInstance()->fetchResult('SHOW ALL SLAVES STATUS');
+ $dbi = DatabaseInterface::getInstance();
+
+ $serverReplicaMultiReplication = [];
+ if ($dbi->isMariaDB() && $dbi->getVersion() >= 100501) {
+ $serverReplicaMultiReplication = $dbi->fetchResult('SHOW ALL REPLICAS STATUS');
+ } elseif ($dbi->isMariaDB()) {
+ $serverReplicaMultiReplication = $dbi->fetchResult('SHOW ALL SLAVES STATUS');
+ }
+
+ $isReplicaIoRunning = false;
+ $isReplicaSqlRunning = false;
+
if ($serverReplicaStatus) {
$urlParams = $GLOBALS['urlParams'];
$urlParams['sr_take_action'] = true;
$urlParams['sr_replica_server_control'] = true;
- if ($serverReplicaReplication[0]['Slave_IO_Running'] === 'No') {
+ $isReplicaIoRunning = isset($serverReplicaReplication[0]['Slave_IO_Running'])
+ && $serverReplicaReplication[0]['Slave_IO_Running'] !== 'No'
+ || isset($serverReplicaReplication[0]['Replica_IO_Running'])
+ && $serverReplicaReplication[0]['Replica_SQL_Running'] !== 'No';
+
+ $isReplicaSqlRunning = isset($serverReplicaReplication[0]['Slave_SQL_Running'])
+ && $serverReplicaReplication[0]['Slave_SQL_Running'] !== 'No'
+ || isset($serverReplicaReplication[0]['Replica_SQL_Running'])
+ && $serverReplicaReplication[0]['Replica_SQL_Running'] !== 'No';
+
+ if (! $isReplicaIoRunning) {
$urlParams['sr_replica_action'] = 'start';
} else {
$urlParams['sr_replica_action'] = 'stop';
@@ -141,7 +169,7 @@ class ReplicationGui
$urlParams['sr_replica_control_param'] = 'IO_THREAD';
$replicaControlIoLink = Url::getCommon($urlParams, '', false);
- if ($serverReplicaReplication[0]['Slave_SQL_Running'] === 'No') {
+ if (! $isReplicaSqlRunning) {
$urlParams['sr_replica_action'] = 'start';
} else {
$urlParams['sr_replica_action'] = 'stop';
@@ -150,10 +178,7 @@ class ReplicationGui
$urlParams['sr_replica_control_param'] = 'SQL_THREAD';
$replicaControlSqlLink = Url::getCommon($urlParams, '', false);
- if (
- $serverReplicaReplication[0]['Slave_IO_Running'] === 'No'
- || $serverReplicaReplication[0]['Slave_SQL_Running'] === 'No'
- ) {
+ if (! $isReplicaIoRunning || ! $isReplicaSqlRunning) {
$urlParams['sr_replica_action'] = 'start';
} else {
$urlParams['sr_replica_action'] = 'stop';
@@ -177,9 +202,6 @@ class ReplicationGui
$reconfigurePrimaryLink = Url::getCommon($urlParams, '', false);
$replicaStatusTable = $this->getHtmlForReplicationStatusTable($connection, 'replica', true, false);
-
- $replicaIoRunning = $serverReplicaReplication[0]['Slave_IO_Running'] !== 'No';
- $replicaSqlRunning = $serverReplicaReplication[0]['Slave_SQL_Running'] !== 'No';
}
return $this->template->render('server/replication/replica_configuration', [
@@ -188,8 +210,8 @@ class ReplicationGui
'primary_connection' => $connection ?? '',
'server_replica_status' => $serverReplicaStatus,
'replica_status_table' => $replicaStatusTable ?? '',
- 'replica_sql_running' => $replicaSqlRunning ?? false,
- 'replica_io_running' => $replicaIoRunning ?? false,
+ 'replica_sql_running' => $isReplicaIoRunning,
+ 'replica_io_running' => $isReplicaSqlRunning,
'replica_control_full_link' => $replicaControlFullLink ?? '',
'replica_control_reset_link' => $replicaControlResetLink ?? '',
'replica_control_sql_link' => $replicaControlSqlLink ?? '',
@@ -258,21 +280,33 @@ class ReplicationGui
$replicationInfo->load($connection);
$replicationVariables = $replicationInfo->primaryVariables;
- $variablesAlerts = null;
- $variablesOks = null;
+ $variablesAlerts = [];
+ $variablesOks = [];
$serverReplication = $replicationInfo->getPrimaryStatus();
if ($type === 'replica') {
$replicationVariables = $replicationInfo->replicaVariables;
- $variablesAlerts = ['Slave_IO_Running' => 'No', 'Slave_SQL_Running' => 'No'];
- $variablesOks = ['Slave_IO_Running' => 'Yes', 'Slave_SQL_Running' => 'Yes'];
+ $variablesAlerts = [
+ 'Slave_IO_Running' => 'No',
+ 'Slave_SQL_Running' => 'No',
+ 'Replica_IO_Running' => 'No',
+ 'Replica_SQL_Running' => 'No',
+ ];
+ $variablesOks = [
+ 'Slave_IO_Running' => 'Yes',
+ 'Slave_SQL_Running' => 'Yes',
+ 'Replica_IO_Running' => 'Yes',
+ 'Replica_SQL_Running' => 'Yes',
+ ];
$serverReplication = $replicationInfo->getReplicaStatus();
}
$variables = [];
foreach ($replicationVariables as $variable) {
- $serverReplicationVariable = isset($serverReplication[0])
- ? $serverReplication[0][$variable]
- : '';
+ if (! isset($serverReplication[0], $serverReplication[0][$variable])) {
+ continue;
+ }
+
+ $serverReplicationVariable = $serverReplication[0][$variable];
$variables[$variable] = ['name' => $variable, 'status' => '', 'value' => $serverReplicationVariable];
@@ -529,7 +563,13 @@ class ReplicationGui
{
if ($srReplicaAction === 'reset') {
$qStop = $this->replication->replicaControl('STOP', null, ConnectionType::User);
- $qReset = DatabaseInterface::getInstance()->tryQuery('RESET SLAVE;');
+ $dbi = DatabaseInterface::getInstance();
+ if ($dbi->isMySql() && $dbi->getVersion() >= 80022 || $dbi->isMariaDB() && $dbi->getVersion() >= 100501) {
+ $qReset = $dbi->tryQuery('RESET REPLICA;');
+ } else {
+ $qReset = $dbi->tryQuery('RESET SLAVE;');
+ }
+
$qStart = $this->replication->replicaControl('START', null, ConnectionType::User);
return $qStop !== false && $qStop !== -1 && $qReset !== false && $qStart !== false && $qStart !== -1;
@@ -544,7 +584,12 @@ class ReplicationGui
{
$dbi = DatabaseInterface::getInstance();
$qStop = $this->replication->replicaControl('STOP', null, ConnectionType::User);
- $qSkip = $dbi->tryQuery('SET GLOBAL SQL_SLAVE_SKIP_COUNTER = ' . $srSkipErrorsCount . ';');
+ if ($dbi->isMySql() && $dbi->getVersion() >= 80400) {
+ $qSkip = $dbi->tryQuery('SET GLOBAL SQL_REPLICA_SKIP_COUNTER = ' . $srSkipErrorsCount . ';');
+ } else {
+ $qSkip = $dbi->tryQuery('SET GLOBAL SQL_SLAVE_SKIP_COUNTER = ' . $srSkipErrorsCount . ';');
+ }
+
$qStart = $this->replication->replicaControl('START', null, ConnectionType::User);
return $qStop !== false && $qStop !== -1 && $qSkip !== false && $qStart !== false && $qStart !== -1;
diff --git a/src/Replication/ReplicationInfo.php b/src/Replication/ReplicationInfo.php
index ab4a2f7d83..73c0d10a21 100644
--- a/src/Replication/ReplicationInfo.php
+++ b/src/Replication/ReplicationInfo.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace PhpMyAdmin\Replication;
use PhpMyAdmin\DatabaseInterface;
+use PhpMyAdmin\Query\Compatibility;
use function explode;
use function sprintf;
@@ -17,17 +18,26 @@ final class ReplicationInfo
/** @var string[] */
public array $replicaVariables = [
'Slave_IO_State',
+ 'Replica_IO_State',
'Master_Host',
+ 'Source_Host',
'Master_User',
+ 'Source_User',
'Master_Port',
+ 'Source_Port',
'Connect_Retry',
'Master_Log_File',
+ 'Source_Log_File',
'Read_Master_Log_Pos',
+ 'Read_Source_Log_Pos',
'Relay_Log_File',
'Relay_Log_Pos',
'Relay_Master_Log_File',
+ 'Relay_Source_Log_File',
'Slave_IO_Running',
+ 'Replica_IO_Running',
'Slave_SQL_Running',
+ 'Replica_SQL_Running',
'Replicate_Do_DB',
'Replicate_Ignore_DB',
'Replicate_Do_Table',
@@ -38,17 +48,25 @@ final class ReplicationInfo
'Last_Error',
'Skip_Counter',
'Exec_Master_Log_Pos',
+ 'Exec_Source_Log_Pos',
'Relay_Log_Space',
'Until_Condition',
'Until_Log_File',
'Until_Log_Pos',
'Master_SSL_Allowed',
+ 'Source_SSL_Allowed',
'Master_SSL_CA_File',
+ 'Source_SSL_CA_File',
'Master_SSL_CA_Path',
+ 'Source_SSL_CA_Path',
'Master_SSL_Cert',
+ 'Source_SSL_Cert',
'Master_SSL_Cipher',
+ 'Source_SSL_Cipher',
'Master_SSL_Key',
+ 'Source_SSL_Key',
'Seconds_Behind_Master',
+ 'Seconds_Behind_Source',
];
/** @var mixed[] */
@@ -92,7 +110,7 @@ final class ReplicationInfo
private function setPrimaryStatus(): void
{
- $this->primaryStatus = $this->dbi->fetchResult('SHOW MASTER STATUS');
+ $this->primaryStatus = $this->dbi->fetchResult(Compatibility::getShowBinLogStatusStmt($this->dbi));
}
/** @return mixed[] */
@@ -103,7 +121,14 @@ final class ReplicationInfo
private function setReplicaStatus(): void
{
- $this->replicaStatus = $this->dbi->fetchResult('SHOW SLAVE STATUS');
+ if (
+ $this->dbi->isMySql() && $this->dbi->getVersion() >= 80022
+ || $this->dbi->isMariaDB() && $this->dbi->getVersion() >= 100501
+ ) {
+ $this->replicaStatus = $this->dbi->fetchResult('SHOW REPLICA STATUS');
+ } else {
+ $this->replicaStatus = $this->dbi->fetchResult('SHOW SLAVE STATUS');
+ }
}
/** @return mixed[] */
@@ -114,7 +139,12 @@ final class ReplicationInfo
private function setMultiPrimaryStatus(): void
{
- $this->multiPrimaryStatus = $this->dbi->fetchResult('SHOW ALL SLAVES STATUS');
+ $this->multiPrimaryStatus = [];
+ if ($this->dbi->isMariaDB() && $this->dbi->getVersion() >= 100501) {
+ $this->multiPrimaryStatus = $this->dbi->fetchResult('SHOW ALL REPLICAS STATUS');
+ } elseif ($this->dbi->isMariaDB()) {
+ $this->multiPrimaryStatus = $this->dbi->fetchResult('SHOW ALL SLAVES STATUS');
+ }
}
private function setDefaultPrimaryConnection(string $connection): void
diff --git a/src/Table/Search.php b/src/Table/Search.php
index 2b306fdf83..17e226e2fd 100644
--- a/src/Table/Search.php
+++ b/src/Table/Search.php
@@ -44,7 +44,7 @@ final class Search
// (more efficient and this helps prevent a problem in IE
// if one of the rows is edited and we come back to the Select results)
if (isset($_POST['zoom_submit']) || ! empty($_POST['displayAllColumns'])) {
- $sqlQuery .= '* ';
+ $sqlQuery .= '*';
} else {
$columnsToDisplay = $_POST['columnsToDisplay'];
$quotedColumns = [];
diff --git a/tests/unit/Error/ErrorHandlerTest.php b/tests/unit/Error/ErrorHandlerTest.php
index ece16f497a..87871d426a 100644
--- a/tests/unit/Error/ErrorHandlerTest.php
+++ b/tests/unit/Error/ErrorHandlerTest.php
@@ -19,8 +19,14 @@ use Throwable;
use function array_keys;
use function array_pop;
+use const E_COMPILE_WARNING;
+use const E_CORE_WARNING;
use const E_ERROR;
+use const E_NOTICE;
use const E_RECOVERABLE_ERROR;
+use const E_STRICT;
+use const E_USER_DEPRECATED;
+use const E_USER_ERROR;
use const E_USER_NOTICE;
use const E_USER_WARNING;
use const E_WARNING;
@@ -131,6 +137,33 @@ class ErrorHandlerTest extends AbstractTestCase
);
}
+ #[DataProvider('addErrorProvider')]
+ public function testAddError(int $errorNumber, string $expected): void
+ {
+ $errorHandler = new ErrorHandler();
+ $errorHandler->addError('[em]Error[/em]', $errorNumber, 'error.txt', 15);
+ $errors = $errorHandler->getCurrentErrors();
+ self::assertCount(1, $errors);
+ $error = array_pop($errors);
+ self::assertSame($errorNumber, $error->getErrorNumber());
+ self::assertSame($expected, $error->getMessage());
+ }
+
+ /** @return iterable */
+ public static function addErrorProvider(): iterable
+ {
+ yield 'E_STRICT' => [@E_STRICT, '[em]Error[/em]'];
+ yield 'E_NOTICE' => [E_NOTICE, '[em]Error[/em]'];
+ yield 'E_WARNING' => [E_WARNING, '[em]Error[/em]'];
+ yield 'E_CORE_WARNING' => [E_CORE_WARNING, '[em]Error[/em]'];
+ yield 'E_COMPILE_WARNING' => [E_COMPILE_WARNING, '[em]Error[/em]'];
+ yield 'E_RECOVERABLE_ERROR' => [E_RECOVERABLE_ERROR, '[em]Error[/em]'];
+ yield 'E_USER_NOTICE' => [E_USER_NOTICE, 'Error'];
+ yield 'E_USER_WARNING' => [E_USER_WARNING, 'Error'];
+ yield 'E_USER_ERROR' => [E_USER_ERROR, 'Error'];
+ yield 'E_USER_DEPRECATED' => [E_USER_DEPRECATED, 'Error'];
+ }
+
/**
* Test for sliceErrors
*/
diff --git a/tests/unit/Error/ErrorTest.php b/tests/unit/Error/ErrorTest.php
index de50fd5c01..67195e2c38 100644
--- a/tests/unit/Error/ErrorTest.php
+++ b/tests/unit/Error/ErrorTest.php
@@ -13,6 +13,21 @@ use function preg_match;
use function str_replace;
use const DIRECTORY_SEPARATOR;
+use const E_COMPILE_ERROR;
+use const E_COMPILE_WARNING;
+use const E_CORE_ERROR;
+use const E_CORE_WARNING;
+use const E_DEPRECATED;
+use const E_ERROR;
+use const E_NOTICE;
+use const E_PARSE;
+use const E_RECOVERABLE_ERROR;
+use const E_STRICT;
+use const E_USER_DEPRECATED;
+use const E_USER_ERROR;
+use const E_USER_NOTICE;
+use const E_USER_WARNING;
+use const E_WARNING;
#[CoversClass(Error::class)]
class ErrorTest extends AbstractTestCase
@@ -146,6 +161,60 @@ class ErrorTest extends AbstractTestCase
self::assertStringEndsWith('' . "\n", $actual);
}
+ #[DataProvider('errorLevelProvider')]
+ public function testGetLevel(int $errorNumber, string $expected): void
+ {
+ self::assertSame($expected, (new Error($errorNumber, 'Error', 'error.txt', 15))->getContext());
+ }
+
+ /** @return iterable */
+ public static function errorLevelProvider(): iterable
+ {
+ yield 'internal error' => [0, 'danger'];
+ yield 'E_ERROR error' => [E_ERROR, 'danger'];
+ yield 'E_WARNING error' => [E_WARNING, 'danger'];
+ yield 'E_PARSE error' => [E_PARSE, 'danger'];
+ yield 'E_NOTICE notice' => [E_NOTICE, 'primary'];
+ yield 'E_CORE_ERROR error' => [E_CORE_ERROR, 'danger'];
+ yield 'E_CORE_WARNING error' => [E_CORE_WARNING, 'danger'];
+ yield 'E_COMPILE_ERROR error' => [E_COMPILE_ERROR, 'danger'];
+ yield 'E_COMPILE_WARNING error' => [E_COMPILE_WARNING, 'danger'];
+ yield 'E_USER_ERROR error' => [E_USER_ERROR, 'danger'];
+ yield 'E_USER_WARNING error' => [E_USER_WARNING, 'danger'];
+ yield 'E_USER_NOTICE notice' => [E_USER_NOTICE, 'primary'];
+ yield 'E_STRICT notice' => [@E_STRICT, 'primary'];
+ yield 'E_DEPRECATED notice' => [E_DEPRECATED, 'primary'];
+ yield 'E_USER_DEPRECATED notice' => [E_USER_DEPRECATED, 'primary'];
+ yield 'E_RECOVERABLE_ERROR error' => [E_RECOVERABLE_ERROR, 'danger'];
+ }
+
+ #[DataProvider('errorTypeProvider')]
+ public function testGetType(int $errorNumber, string $expected): void
+ {
+ self::assertSame($expected, (new Error($errorNumber, 'Error', 'error.txt', 15))->getType());
+ }
+
+ /** @return iterable */
+ public static function errorTypeProvider(): iterable
+ {
+ yield 'internal error' => [0, 'Internal error'];
+ yield 'E_ERROR error' => [E_ERROR, 'Error'];
+ yield 'E_WARNING warning' => [E_WARNING, 'Warning'];
+ yield 'E_PARSE error' => [E_PARSE, 'Parsing Error'];
+ yield 'E_NOTICE notice' => [E_NOTICE, 'Notice'];
+ yield 'E_CORE_ERROR error' => [E_CORE_ERROR, 'Core Error'];
+ yield 'E_CORE_WARNING warning' => [E_CORE_WARNING, 'Core Warning'];
+ yield 'E_COMPILE_ERROR error' => [E_COMPILE_ERROR, 'Compile Error'];
+ yield 'E_COMPILE_WARNING warning' => [E_COMPILE_WARNING, 'Compile Warning'];
+ yield 'E_USER_ERROR error' => [E_USER_ERROR, 'User Error'];
+ yield 'E_USER_WARNING warning' => [E_USER_WARNING, 'User Warning'];
+ yield 'E_USER_NOTICE notice' => [E_USER_NOTICE, 'User Notice'];
+ yield 'E_STRICT notice' => [@E_STRICT, 'Runtime Notice'];
+ yield 'E_DEPRECATED notice' => [E_DEPRECATED, 'Deprecation Notice'];
+ yield 'E_USER_DEPRECATED notice' => [E_USER_DEPRECATED, 'Deprecation Notice'];
+ yield 'E_RECOVERABLE_ERROR error' => [E_RECOVERABLE_ERROR, 'Catchable Fatal Error'];
+ }
+
/**
* Test for getHtmlTitle
*/
diff --git a/tests/unit/Query/CompatibilityTest.php b/tests/unit/Query/CompatibilityTest.php
index 4398a10ff1..976f7ad4c2 100644
--- a/tests/unit/Query/CompatibilityTest.php
+++ b/tests/unit/Query/CompatibilityTest.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace PhpMyAdmin\Tests\Query;
use PhpMyAdmin\DatabaseInterface;
+use PhpMyAdmin\Dbal\DbalInterface;
use PhpMyAdmin\Query\Compatibility;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
@@ -59,4 +60,24 @@ class CompatibilityTest extends TestCase
'MariaDB 10.7.0' => [true, true, 100700],
];
}
+
+ #[DataProvider('showBinLogStatusProvider')]
+ public function testGetShowBinLogStatusStmt(string $serverName, int $version, string $expected): void
+ {
+ $dbal = self::createStub(DbalInterface::class);
+ $dbal->method('isMySql')->willReturn($serverName === 'MySQL');
+ $dbal->method('isMariaDB')->willReturn($serverName === 'MariaDB');
+ $dbal->method('getVersion')->willReturn($version);
+ self::assertSame($expected, Compatibility::getShowBinLogStatusStmt($dbal));
+ }
+
+ /** @return iterable */
+ public static function showBinLogStatusProvider(): iterable
+ {
+ yield ['MySQL', 80200, 'SHOW BINARY LOG STATUS'];
+ yield ['MariaDB', 100502, 'SHOW BINLOG STATUS'];
+ yield ['MySQL', 80199, 'SHOW MASTER STATUS'];
+ yield ['MariaDB', 100501, 'SHOW MASTER STATUS'];
+ yield ['MySQL', 100502, 'SHOW BINARY LOG STATUS'];
+ }
}
diff --git a/tests/unit/Stubs/DbiDummy.php b/tests/unit/Stubs/DbiDummy.php
index 4822c95a7f..f989504add 100644
--- a/tests/unit/Stubs/DbiDummy.php
+++ b/tests/unit/Stubs/DbiDummy.php
@@ -384,7 +384,7 @@ class DbiDummy implements DbiExtension
'result' => [['1']],
],
[
- 'query' => 'SHOW MASTER LOGS',
+ 'query' => 'SHOW BINARY LOGS',
'columns' => ['Log_name', 'File_size'],
'result' => [['index1', 100], ['index2', 200]],
],
diff --git a/tests/unit/Table/SearchTest.php b/tests/unit/Table/SearchTest.php
index a07bbfe74a..7d102646c6 100644
--- a/tests/unit/Table/SearchTest.php
+++ b/tests/unit/Table/SearchTest.php
@@ -33,14 +33,14 @@ class SearchTest extends AbstractTestCase
$_POST['customWhereClause'] = "name='pma'";
self::assertSame(
- 'SELECT DISTINCT * FROM `PMA` WHERE name=\'pma\' ORDER BY `name` asc',
+ 'SELECT DISTINCT * FROM `PMA` WHERE name=\'pma\' ORDER BY `name` asc',
$this->search->buildSqlQuery(),
);
unset($_POST['customWhereClause']);
self::assertSame(
- 'SELECT DISTINCT * FROM `PMA` ORDER BY `name` asc',
+ 'SELECT DISTINCT * FROM `PMA` ORDER BY `name` asc',
$this->search->buildSqlQuery(),
);
@@ -58,7 +58,7 @@ class SearchTest extends AbstractTestCase
'BETWEEN',
];
- $expected = 'SELECT DISTINCT * FROM `PMA` WHERE `name` != \'value1\''
+ $expected = 'SELECT DISTINCT * FROM `PMA` WHERE `name` != \'value1\''
. ' AND `id` > value2 AND `index` IS NULL AND `index2` LIKE \'%value4%\''
. ' AND `index3` REGEXP ^value5$ AND `index4` IN (value6) AND `index5`'
. ' BETWEEN value7 AND value8 ORDER BY `name` asc';
@@ -74,14 +74,14 @@ class SearchTest extends AbstractTestCase
$_POST['table'] = 'PMA';
self::assertSame(
- 'SELECT * FROM `PMA`',
+ 'SELECT * FROM `PMA`',
$this->search->buildSqlQuery(),
);
$_POST['customWhereClause'] = '`table` = \'WhereClause\'';
self::assertSame(
- 'SELECT * FROM `PMA` WHERE `table` = \'WhereClause\'',
+ 'SELECT * FROM `PMA` WHERE `table` = \'WhereClause\'',
$this->search->buildSqlQuery(),
);
@@ -92,7 +92,7 @@ class SearchTest extends AbstractTestCase
$_POST['criteriaColumnTypes'] = ['int(11)', 'int(11)', 'int(11)', 'int(11)'];
self::assertSame(
- 'SELECT * FROM `PMA` WHERE `b` <= 10 AND `a` = 2 AND `c` IS NULL AND `d` IS NOT NULL',
+ 'SELECT * FROM `PMA` WHERE `b` <= 10 AND `a` = 2 AND `c` IS NULL AND `d` IS NOT NULL',
$this->search->buildSqlQuery(),
);
}
@@ -103,14 +103,14 @@ class SearchTest extends AbstractTestCase
$_POST['table'] = 'PMA';
self::assertSame(
- 'SELECT * FROM `PMA`',
+ 'SELECT * FROM `PMA`',
$this->search->buildSqlQuery(),
);
$_POST['customWhereClause'] = '`table` = \'WhereClause\'';
self::assertSame(
- 'SELECT * FROM `PMA` WHERE `table` = \'WhereClause\'',
+ 'SELECT * FROM `PMA` WHERE `table` = \'WhereClause\'',
$this->search->buildSqlQuery(),
);
@@ -122,7 +122,7 @@ class SearchTest extends AbstractTestCase
$_POST['criteriaColumnTypes'] = ['geometry'];
self::assertSame(
- 'SELECT * FROM `PMA` WHERE Dimension(`b`) = \'1\'',
+ 'SELECT * FROM `PMA` WHERE Dimension(`b`) = \'1\'',
$this->search->buildSqlQuery(),
);
}
@@ -133,14 +133,14 @@ class SearchTest extends AbstractTestCase
$_POST['table'] = 'PMA';
self::assertSame(
- 'SELECT * FROM `PMA`',
+ 'SELECT * FROM `PMA`',
$this->search->buildSqlQuery(),
);
$_POST['customWhereClause'] = '`table` = \'WhereClause\'';
self::assertSame(
- 'SELECT * FROM `PMA` WHERE `table` = \'WhereClause\'',
+ 'SELECT * FROM `PMA` WHERE `table` = \'WhereClause\'',
$this->search->buildSqlQuery(),
);
@@ -152,7 +152,7 @@ class SearchTest extends AbstractTestCase
$_POST['criteriaColumnTypes'] = ['enum(\'G\', \'PG\', \'PG-13\', \'R\', \'NC-17\')'];
self::assertSame(
- 'SELECT * FROM `PMA` WHERE `rating` = \'PG-13\'',
+ 'SELECT * FROM `PMA` WHERE `rating` = \'PG-13\'',
$this->search->buildSqlQuery(),
);
}
@@ -163,14 +163,14 @@ class SearchTest extends AbstractTestCase
$_POST['table'] = 'PMA';
self::assertSame(
- 'SELECT * FROM `PMA`',
+ 'SELECT * FROM `PMA`',
$this->search->buildSqlQuery(),
);
$_POST['customWhereClause'] = '';
self::assertSame(
- 'SELECT * FROM `PMA`',
+ 'SELECT * FROM `PMA`',
$this->search->buildSqlQuery(),
);
@@ -182,7 +182,7 @@ class SearchTest extends AbstractTestCase
$_POST['criteriaColumnTypes'] = ['uuid'];
self::assertSame(
- "SELECT * FROM `PMA` WHERE `id` = '07ca1fdd-4805-11ed-a4dc-0242ac110002'",
+ "SELECT * FROM `PMA` WHERE `id` = '07ca1fdd-4805-11ed-a4dc-0242ac110002'",
$this->search->buildSqlQuery(),
);
}
@@ -201,6 +201,6 @@ class SearchTest extends AbstractTestCase
$_POST['ajax_request'] = 'true';
$_POST['displayAllColumns'] = 'true';
- self::assertSame('SELECT * FROM `world_cities`', $this->search->buildSqlQuery());
+ self::assertSame('SELECT * FROM `world_cities`', $this->search->buildSqlQuery());
}
}