Test Case
In our module we have a setup file that is very basic:
/app/code/local/CompanyName/{ModuleName}/sql/{modulename}_setup/mysql4-install-1.0.0.php
1 2 3 4 5 6 |
/* @var $installer Mage_Eav_Model_Entity_Setup */ $installer = $this->startSetup(); $installer->createEntityTables( $this->getTable('module_entity') ); |
The above results in:
1 |
Mage_Eav_Exception: Can't create table: module_entity |
The Solution
First some debugging. In the createEntityTables() method, you’ll see the following near the end:
1 2 3 4 5 6 7 8 9 10 11 |
$connection->beginTransaction(); try { foreach ($tables as $tableName => $table) { $connection->createTable($table); } $connection->commit(); } catch (Exception $e) { Zend_Debug::dump($e->getMessage()); $connection->rollBack(); throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Can\'t create table: %s', $tableName)); } |
Line 8 was added because the default exception is useless. Here we see the underlying problem:
1 |
User Error: DDL statements are not allowed in transactions
|
So now we have more digging. If you follow $connection->commit() you’ll notice the following method is called:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
/** * Check transaction level in case of DDL query * * @param string|Zend_Db_Select $sql * @throws Zend_Db_Adapter_Exception */ protected function _checkDdlTransaction($sql) { if (is_string($sql) && $this->getTransactionLevel() > 0) { $startSql = strtolower(substr(ltrim($sql), 0, 3)); if (in_array($startSql, $this->_ddlRoutines)) { trigger_error(Varien_Db_Adapter_Interface::ERROR_DDL_MESSAGE, E_USER_ERROR); } } } |
$this->_ddlRoutines is an array of three-letter strings of common DDL commands (CREATE, DROP, etc).
Putting it all together, the problem is that MySQL is not able to roll back DDL transactions. The easy fix would be to comment out the trigger_error method, since this would allow all other transactions to be rolled back in case of a problem. This is an issue of course since it’s a core file. Funny enough, in this bug report Magento Team recommends this as a solution.
For a method that is only used by module developers, this doesn’t look like sound advice. (If you take a peek in core files, this method isn’t used) Instead you have two options. The first is to do what the core does: create abstracted SQL (a major pain!) -
1 2 3 4 5 6 7 8 9 10 11 12 |
// Example of MySQL API /** * Create table array('catalog/product', 'decimal') */ $table = $installer->getConnection() ->newTable($installer->getTable(array('catalog/product', 'decimal'))) ->addColumn('value_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( 'identity' => true, 'nullable' => false, 'primary' => true, ), 'Value ID') ->addColumn(... |
If you are partial to saving time in the long run, just extend the method instead. You’ll create a new helper file below, and you’ll be copying the entire createEntityTables() method into your helper file. (For brevity, only a portion of the code in the method is shown — you’ll need to comment out two lines)
app/code/local/{CompanyName}/{ModuleName}/Setup/Helper.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<?php class CompanyName_ModuleName_Setup_Helper extends Mage_Eav_Model_Entity_Setup { public function createEntityTables($baseTableName, array $options = array()) { ... /** * Remove transaction code due to issues with errors. */ //$connection->beginTransaction(); try { foreach ($tables as $tableName => $table) { $connection->createTable($table); } $connection->commit(); } catch (Exception $e) { //$connection->rollBack(); throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Can\'t create table: %s', $tableName)); } } } |
Note that you might not be able to rely on createEntityTables() long. In the same bug report listed above, Mage Team goes on to say this could be deprecated in the future since the core doesn’t make use of the method. Which begs the question… why doesn’t it?