Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions lib/internal/Magento/Framework/DB/Adapter/AdapterInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -819,6 +819,61 @@ public function getCheckSql($condition, $true, $false);
*/
public function getIfNullSql($expression, $value = 0);

/**
* GROUP_CONCAT / string_agg equivalent
*
* @param string|\Zend_Db_Expr $expression
* @param string $separator
* @param string|\Zend_Db_Expr|null $orderBy
* @param bool $distinct
* @return \Zend_Db_Expr
*/
public function getGroupConcatSql($expression, $separator = ',', $orderBy = null, $distinct = false);

/**
* FIELD() / CASE equivalent for ORDER BY a fixed list
*
* @param string|\Zend_Db_Expr $expression
* @param array $values
* @return \Zend_Db_Expr
*/
public function getFieldSql($expression, array $values);

/**
* Cast an expression to text for UNION type alignment
*
* @param string|\Zend_Db_Expr $expression
* @return \Zend_Db_Expr
*/
public function castToText($expression);

/**
* Cast an expression to a numeric type for arithmetic
*
* @param string|\Zend_Db_Expr $expression
* @return \Zend_Db_Expr
*/
public function castToNumeric($expression);

/**
* CREATE TABLE new LIKE origin
*
* @param string $newTableName
* @param string $originTableName
* @return \Zend_Db_Statement_Interface
*/
public function createTableLike($newTableName, $originTableName);

/**
* CREATE TEMPORARY TABLE from a SELECT (and optional index definitions)
*
* @param string $name
* @param string[] $indexStatements
* @param \Magento\Framework\DB\Select $select
* @return \Zend_Db_Statement_Interface
*/
public function createTemporaryTableFromSelect($name, array $indexStatements, \Magento\Framework\DB\Select $select);

/**
* Generate fragment of SQL, that combine together (concatenate) the results from data array
*
Expand Down
73 changes: 73 additions & 0 deletions lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php
Original file line number Diff line number Diff line change
Expand Up @@ -3458,6 +3458,79 @@ public function getIfNullSql($expression, $value = 0)
return new \Zend_Db_Expr($expression);
}

/**
* @inheritdoc
*/
public function getGroupConcatSql($expression, $separator = ',', $orderBy = null, $distinct = false)
{
$sql = 'GROUP_CONCAT(' . ($distinct ? 'DISTINCT ' : '') . $expression;
if ($orderBy !== null) {
$sql .= ' ORDER BY ' . $orderBy;
}
return new \Zend_Db_Expr($sql . ' SEPARATOR ' . $this->quote($separator) . ')');
}

/**
* @inheritdoc
*/
public function getFieldSql($expression, array $values)
{
$parts = [];
foreach ($values as $value) {
if ($value === '') {
continue;
}
$parts[] = $value;
}
if (!$parts) {
return new \Zend_Db_Expr('0');
}
return new \Zend_Db_Expr('FIELD(' . $expression . ', ' . implode(', ', $parts) . ')');
}

/**
* @inheritdoc
*/
public function createTableLike($newTableName, $originTableName)
{
return $this->query(sprintf(
'CREATE TABLE %s LIKE %s',
$this->quoteIdentifier($newTableName),
$this->quoteIdentifier($originTableName)
));
}

/**
* @inheritdoc
*/
public function createTemporaryTableFromSelect($name, array $indexStatements, Select $select)
{
$sql = sprintf(
'CREATE TEMPORARY TABLE %s %s ENGINE=%s IGNORE (%s)',
$this->quoteIdentifier($name),
$indexStatements ? '(' . implode(',', $indexStatements) . ')' : '',
$this->quoteIdentifier('innodb'),
$select
);
return $this->query($sql, $select->getBind());
}

/**
* @inheritdoc
*/
public function castToText($expression)
{
return new \Zend_Db_Expr((string) $expression);
}

/**
* @inheritdoc
*/
public function castToNumeric($expression)
{
return new \Zend_Db_Expr('CAST(' . $expression . ' AS DECIMAL(20,6))');
}

/**
* Generates case SQL fragment
*
Expand Down
11 changes: 10 additions & 1 deletion lib/internal/Magento/Framework/DB/Query/BatchIterator.php
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,16 @@ private function calculateBatchSize(Select $select)
]
);
$row = $this->connection->fetchRow($wrapperSelect);
$this->minValue = $row['max'];
// empty (cnt below ends up 0 and the iterator stops right after this call
// anyway), don't overwrite minValue with that null. A leftover null minValue
// fed into initSelectObject()'s "> ?" bind is otherwise silently coerced to an
// empty-string parameter by PDO, which MySQL's loose bigint/string comparison
// tolerates (implicitly treating '' as 0) but Postgres rejects outright
// ("invalid input syntax for type bigint"). Real-world trigger: any
// FieldDataConverter::convert() call (e.g. Theme's ConvertSerializedData data
// patch) whose target rows are exhausted after fewer than batchSize rows, or -
// as first hit here - never existed at all.
$this->minValue = $row['max'] ?? $this->minValue;
return (int)$row['cnt'];
}

Expand Down
7 changes: 5 additions & 2 deletions lib/internal/Magento/Framework/DB/Select.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,15 @@ class Select extends \Zend_Db_Select
* Class constructor
* Add straight join support
*
* @param Adapter\Pdo\Mysql $adapter
* Typed as Zend_Db_Adapter_Abstract (parent constructor requirement) rather than
* Pdo\Mysql so any AdapterInterface implementation can call select().
*
* @param \Zend_Db_Adapter_Abstract $adapter
* @param Select\SelectRenderer $selectRenderer
* @param array $parts
*/
public function __construct(
\Magento\Framework\DB\Adapter\Pdo\Mysql $adapter,
\Zend_Db_Adapter_Abstract $adapter,
\Magento\Framework\DB\Select\SelectRenderer $selectRenderer,
$parts = []
) {
Expand Down
17 changes: 3 additions & 14 deletions lib/internal/Magento/Framework/DB/TemporaryTableService.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
*/
class TemporaryTableService
{
const INDEX_METHOD_HASH = 'HASH';
const DB_ENGINE_INNODB = 'INNODB';
public const INDEX_METHOD_HASH = 'HASH';
public const DB_ENGINE_INNODB = 'INNODB';

/**
* @var string[]
Expand Down Expand Up @@ -120,18 +120,7 @@ public function createFromSelect(
$indexStatements[] = sprintf('%s(%s)', $indexType, $renderedColumns);
}

$statement = sprintf(
'CREATE TEMPORARY TABLE %s %s ENGINE=%s IGNORE (%s)',
$adapter->quoteIdentifier($name),
$indexStatements ? '(' . implode(',', $indexStatements) . ')' : '',
$adapter->quoteIdentifier($dbEngine),
"{$select}"
);

$adapter->query(
$statement,
$select->getBind()
);
$adapter->createTemporaryTableFromSelect($name, $indexStatements, $select);

$this->createdTableAdapters[$name] = $adapter;

Expand Down
10 changes: 9 additions & 1 deletion lib/internal/Magento/Framework/Data/Collection/AbstractDb.php
Original file line number Diff line number Diff line change
Expand Up @@ -736,7 +736,15 @@ protected function _renderOrders()
if (!$this->_isOrdersRendered) {
foreach ($this->_orders as $field => $direction) {
if (isset($this->sqlReservedWords[strtoupper($field)])) {
$field = "`$field`";
// coincidence only on MySQL (whose quote character happens to be a
// backtick), and it bypasses the adapter's own quoteIdentifier()
// entirely, so Postgres (whose reserved-word set differs anyway -
// e.g. "position" isn't reserved there, but this same $field is
// wrapped unconditionally once it matches MySQL's list) received a
// literal, invalid backtick instead of a real identifier quote.
// quoteIdentifier() is exactly what every other quoting path in this
// class already goes through.
$field = $this->getConnection()->quoteIdentifier($field);
}

$this->_select->order(new \Zend_Db_Expr($field . ' ' . $direction));
Expand Down
3 changes: 2 additions & 1 deletion setup/src/Magento/Setup/Model/ConfigOptionsList.php
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,8 @@ private function validateDbSettings(array $options, DeploymentConfig $deployment
$options[ConfigOptionsListConstants::INPUT_KEY_DB_HOST],
$options[ConfigOptionsListConstants::INPUT_KEY_DB_USER],
$options[ConfigOptionsListConstants::INPUT_KEY_DB_PASSWORD],
$driverOptions
$driverOptions,
$options[ConfigOptionsListConstants::INPUT_KEY_DB_ENGINE] ?? null
);
} catch (\Exception $exception) {
$errors[] = $exception->getMessage();
Expand Down
6 changes: 5 additions & 1 deletion setup/src/Magento/Setup/Model/Installer.php
Original file line number Diff line number Diff line change
Expand Up @@ -1667,7 +1667,11 @@ private function assertDbAccessible()
ConfigOptionsListConstants::CONFIG_PATH_DB_CONNECTION_DEFAULT .
'/' . ConfigOptionsListConstants::KEY_PASSWORD
),
$driverOptions
$driverOptions,
$this->deploymentConfig->get(
ConfigOptionsListConstants::CONFIG_PATH_DB_CONNECTION_DEFAULT .
'/' . ConfigOptionsListConstants::KEY_ENGINE
)
);
$prefix = $this->deploymentConfig->get(
ConfigOptionsListConstants::CONFIG_PATH_DB_CONNECTION_DEFAULT .
Expand Down
57 changes: 33 additions & 24 deletions setup/src/Magento/Setup/Validator/DbValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ public function checkDatabaseConnection($dbName, $dbHost, $dbUser, $dbPass = '')
* @param string $dbUser
* @param string $dbPass
* @param array $driverOptions
* @param string|null $engine Database engine (mysql, postgresql)
* @return bool
* @throws \Magento\Setup\Exception
*/
Expand All @@ -97,40 +98,48 @@ public function checkDatabaseConnectionWithDriverOptions(
$dbHost,
$dbUser,
$dbPass = '',
$driverOptions = []
$driverOptions = [],
$engine = null
) {
// establish connection to information_schema view to retrieve information about user and table privileges
$connection = $this->connectionFactory->create(
[
ConfigOptionsListConstants::KEY_NAME => 'information_schema',
ConfigOptionsListConstants::KEY_HOST => $dbHost,
ConfigOptionsListConstants::KEY_USER => $dbUser,
ConfigOptionsListConstants::KEY_PASSWORD => $dbPass,
ConfigOptionsListConstants::KEY_ACTIVE => true,
ConfigOptionsListConstants::KEY_DRIVER_OPTIONS => $driverOptions,
]
);
$connectionConfig = [
ConfigOptionsListConstants::KEY_NAME => 'information_schema',
ConfigOptionsListConstants::KEY_HOST => $dbHost,
ConfigOptionsListConstants::KEY_USER => $dbUser,
ConfigOptionsListConstants::KEY_PASSWORD => $dbPass,
ConfigOptionsListConstants::KEY_ACTIVE => true,
ConfigOptionsListConstants::KEY_DRIVER_OPTIONS => $driverOptions,
];
if ($engine !== null && $engine !== '') {
$connectionConfig[ConfigOptionsListConstants::KEY_ENGINE] = $engine;
}
$connection = $this->connectionFactory->create($connectionConfig);

if (!$connection) {
throw new \Magento\Setup\Exception('Database connection failure.');
}

$mysqlVersion = $connection->fetchOne('SELECT version()');
if ($mysqlVersion) {
if (preg_match('/^([0-9\.]+)/', $mysqlVersion, $matches)) {
if (isset($matches[1]) && !empty($matches[1])) {
if (version_compare($matches[1], Installer::MYSQL_VERSION_REQUIRED) < 0) {
throw new \Magento\Setup\Exception(
'Sorry, but we support MySQL version ' . Installer::MYSQL_VERSION_REQUIRED . ' or later.'
);
}
}
}
}
$this->assertSupportedMysqlVersion((string) $connection->fetchOne('SELECT version()'));

return $this->checkDatabaseName($connection, $dbName) && $this->checkDatabasePrivileges($connection, $dbName);
}

/**
* Reject MySQL servers older than Magento's minimum version.
*
* @throws \Magento\Setup\Exception
*/
private function assertSupportedMysqlVersion(string $mysqlVersion): void
{
if ($mysqlVersion === '' || !preg_match('/^([0-9\.]+)/', $mysqlVersion, $matches)) {
return;
}
if (version_compare($matches[1], Installer::MYSQL_VERSION_REQUIRED) < 0) {
throw new \Magento\Setup\Exception(
'Sorry, but we support MySQL version ' . Installer::MYSQL_VERSION_REQUIRED . ' or later.'
);
}
}

/**
* Checks if specified database exists and visible to current user
*
Expand Down