Why do you got the failure?
The cause of the message is an attempt to execute a DDL statement inside a transaction.
By default, PDO
allows to execute the DDL statements inside a transaction:
Some databases, including MySQL, automatically issue an implicit COMMIT when a database definition language (DDL) statement such as DROP TABLE or CREATE TABLE is issued within a transaction. The implicit COMMIT will prevent you from rolling back any other changes within the transaction boundary.
http://php.net/manual/en/pdo.begintransaction.php
So, it is allowed in PDO
, but it breaks the possibility to rollback the transaction.
For this reason, Magento 2 denies to execute the DDL statement inside a transaction:
/**
* Check transaction level in case of DDL query
*
* @param string|\Magento\Framework\DB\Select $sql
* @return void
* @throws \Zend_Db_Adapter_Exception
*/
protected function _checkDdlTransaction($sql)
{
if ($this->getTransactionLevel() > 0) {
$sql = ltrim(preg_replace('/\s+/', ' ', $sql));
$sqlMessage = explode(' ', $sql, 3);
$startSql = strtolower(substr($sqlMessage[0], 0, 3));
if (in_array($startSql, $this->_ddlRoutines) && strcasecmp($sqlMessage[1], 'temporary') !== 0) {
trigger_error(AdapterInterface::ERROR_DDL_MESSAGE, E_USER_ERROR);
}
}
}
/**
* All possible DDL statements
* First 3 symbols for each statement
*
* @var string[]
*/
protected $_ddlRoutines = ['alt', 'cre', 'ren', 'dro', 'tru'];
How to fix it
Method 1: \Magento\Framework\Model\ResourceModel\AbstractResource::addCommitCallback()
Use the \Magento\Framework\Model\ResourceModel\AbstractResource::addCommitCallback()
method to run your DDL statements after the transaction has been committed :
Examples from the core
$this->_getResource()->addCommitCallback([$this, 'priceReindexCallback']);
$this->_getResource()->addCommitCallback([$this, 'eavReindexCallback']);
$result = parent::afterSave();
$this->_getResource()->addCommitCallback([$this, 'reindex']);
/**
* Reindex on product save
*
* @param \Magento\Catalog\Model\ResourceModel\Product $productResource
* @param \Closure $proceed
* @param \Magento\Framework\Model\AbstractModel $product
* @return \Magento\Catalog\Model\ResourceModel\Product
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function aroundSave(
\Magento\Catalog\Model\ResourceModel\Product $productResource,
\Closure $proceed,
\Magento\Framework\Model\AbstractModel $product
) {
$productResource->addCommitCallback(function () use ($product) {
$this->reindexRow($product->getEntityId());
});
return $proceed($product);
}
Method 2: \Magento\Framework\Model\AbstractModel::afterCommitCallback()
Override the \Magento\Framework\Model\AbstractModel::afterCommitCallback()
method in your model to run your DDL statements after the transaction has been committed :
/**
* Callback function which called after transaction commit in resource model
*
* @return $this
*/
public function afterCommitCallback()
{
$this->_eventManager->dispatch('model_save_commit_after', ['object' => $this]);
$this->_eventManager->dispatch($this->_eventPrefix . '_save_commit_after', $this->_getEventData());
return $this;
}
The core never uses this possibility, but it does work.
Method 3: model_save_commit_after
and <prefix>_save_commit_after
Subscribe to the model_save_commit_after
or the <prefix>_save_commit_after
event:
/**
* Callback function which called after transaction commit in resource model
*
* @return $this
*/
public function afterCommitCallback()
{
$this->_eventManager->dispatch('model_save_commit_after', ['object' => $this]);
$this->_eventManager->dispatch($this->_eventPrefix . '_save_commit_after', $this->_getEventData());
return $this;
}
Examples from the core