Correcting UPDATE query generation to support SQL standards and MySQL-specific features in parallel, fixes #4080

git-svn-id: https://svn.cakephp.org/repo/branches/1.2.x.x@6491 3807eeeb-6ff5-0310-8944-8be069107fe0
This commit is contained in:
nate 2008-03-01 03:12:12 +00:00
parent 1fbc3e6a9b
commit ab50975306
11 changed files with 173 additions and 189 deletions

View file

@ -300,11 +300,11 @@ class DB_ACL extends AclBase {
for ($i = 0 ; $i < count($aroPath); $i++) {
$permAlias = $this->Aro->Permission->alias;
$perms = $this->Aro->Permission->findAll(array(
$perms = $this->Aro->Permission->find('all', array('conditions' => array(
"{$permAlias}.aro_id" => $aroPath[$i][$this->Aro->alias]['id'],
"{$permAlias}.aco_id" => $acoIDs),
null, array($this->Aco->alias . '.lft' => 'desc'), null, null, 0
);
));
if (empty($perms)) {
continue;
@ -362,7 +362,6 @@ class DB_ACL extends AclBase {
trigger_error(__('DB_ACL::allow() - Invalid node', true), E_USER_WARNING);
return false;
}
if (isset($perms[0])) {
$save = $perms[0][$this->Aro->Permission->alias];
}
@ -385,15 +384,12 @@ class DB_ACL extends AclBase {
}
}
}
$save['aro_id'] = $perms['aro'];
$save['aco_id'] = $perms['aco'];
list($save['aro_id'], $save['aco_id']) = array($perms['aro'], $perms['aco']);
if ($perms['link'] != null && count($perms['link']) > 0) {
$save['id'] = $perms['link'][0][$this->Aro->Permission->alias]['id'];
}
$this->Aro->Permission->create($save);
return $this->Aro->Permission->save();
return ($this->Aro->Permission->create($save) && $this->Aro->Permission->save());
}
/**
* Deny access for $aro to action $action in $aco
@ -465,10 +461,10 @@ class DB_ACL extends AclBase {
return array(
'aro' => Set::extract($obj, 'Aro.0.'.$this->Aro->alias.'.id'),
'aco' => Set::extract($obj, 'Aco.0.'.$this->Aco->alias.'.id'),
'link' => $this->Aro->Permission->findAll(array(
'link' => $this->Aro->Permission->find('all', array('conditions' => array(
$this->Aro->Permission->alias . '.aro_id' => Set::extract($obj, 'Aro.0.'.$this->Aro->alias.'.id'),
$this->Aro->Permission->alias . '.aco_id' => Set::extract($obj, 'Aco.0.'.$this->Aco->alias.'.id')
))
)))
);
}
/**

View file

@ -52,7 +52,7 @@ class TreeBehavior extends ModelBehavior {
/*if (in_array($settings['scope'], $model->getAssociated('belongsTo'))) {
$data = $model->getAssociated($settings['scope']);
$parent =& $model->{$data['className']};
$settings['scope'] = $model->escapeField($data['foreignKey']) . ' = ' . $parent->escapeField($parent->primaryKey, $settings['scope']);
$settings['scope'] = $model->alias . '.' . $data['foreignKey']) . ' = ' . $parent->alias . '.' . $parent->primaryKey, $settings['scope']);
}*/
$this->settings[$model->alias] = $settings;
}
@ -820,7 +820,7 @@ class TreeBehavior extends ModelBehavior {
$conditions= array_merge($conditions, $scope);
}
}
$model->updateAll(array($model->escapeField($field) => $model->escapeField($field) . ' ' . $dir . ' ' . $shift), $conditions);
$model->updateAll(array($model->alias . '.' . $field => $model->alias . '.' . $field . ' ' . $dir . ' ' . $shift), $conditions);
}
}
?>

View file

@ -356,23 +356,7 @@ class DboMssql extends DboSource {
break;
}
}
if (empty($conditions)) {
return parent::update($model, $fields, $values, null);
}
return parent::_update($model, $fields, $values, $conditions);
}
/**
* Generates and executes an SQL DELETE statement
*
* @param Model $model
* @param mixed $conditions
* @return boolean Success
*/
function delete(&$model, $conditions = null) {
if (empty($conditions)) {
return parent::delete($model, null);
}
return parent::_delete($model, $conditions);
return parent::update($model, $fields, $values, null);
}
/**
* Returns a formatted error message from previous database operation.

View file

@ -203,13 +203,9 @@ class DboMysql extends DboSource {
if ($parent != null) {
return $parent;
}
if ($data === null) {
} elseif ($data === null) {
return 'NULL';
}
if ($data === '') {
} elseif ($data === '') {
return "''";
}
@ -227,9 +223,71 @@ class DboMysql extends DboSource {
$data = "'" . mysql_real_escape_string($data, $this->connection) . "'";
break;
}
return $data;
}
/**
* Generates and executes an SQL UPDATE statement for given model, fields, and values.
*
* @param Model $model
* @param array $fields
* @param array $values
* @param mixed $conditions
* @return array
*/
function update(&$model, $fields = array(), $values = null, $conditions = null) {
if ($values == null) {
$combined = $fields;
} else {
$combined = array_combine($fields, $values);
}
$fields = join(', ', $this->_prepareUpdateFields($model, $combined, empty($conditions), !empty($conditions)));
$table = $this->fullTableName($model);
$alias = $this->name($model->alias);
$joins = implode(' ', $this->_getJoins($model));
if (empty($conditions)) {
$alias = $joins = false;
}
$conditions = $this->conditions($this->defaultConditions($model, $conditions, $alias));
if ($conditions === false) {
return false;
}
if (!$this->execute($this->renderStatement('update', compact('table', 'alias', 'joins', 'fields', 'conditions')))) {
$model->onError();
return false;
}
return true;
}
/**
* Generates and executes an SQL DELETE statement for given id/conditions on given model.
*
* @param Model $model
* @param mixed $conditions
* @return boolean Success
*/
function delete(&$model, $conditions = null) {
$alias = $this->name($model->alias);
$table = $this->fullTableName($model);
$joins = implode(' ', $this->_getJoins($model));
if (empty($conditions)) {
$alias = $joins = false;
}
$conditions = $this->conditions($this->defaultConditions($model, $conditions, $alias));
if ($conditions === false) {
return false;
}
if ($this->execute($this->renderStatement('delete', compact('alias', 'table', 'joins', 'conditions'))) === false) {
$model->onError();
return false;
}
return true;
}
/**
* Begin a transaction
*

View file

@ -397,34 +397,6 @@ class DboPostgres extends DboSource {
}
return false;
}
/**
* Generates and executes an SQL UPDATE statement for given model, fields, and values.
*
* @param Model $model
* @param array $fields
* @param array $values
* @param mixed $conditions
* @return array
*/
function update(&$model, $fields = array(), $values = null, $conditions = null) {
if (empty($conditions)) {
return parent::update($model, $fields, $values, null);
}
return parent::_update($model, $fields, $values, $conditions);
}
/**
* Generates and executes an SQL DELETE statement
*
* @param Model $model
* @param mixed $conditions
* @return boolean Success
*/
function delete(&$model, $conditions = null) {
if (empty($conditions)) {
return parent::delete($model, null);
}
return parent::_delete($model, $conditions);
}
/**
* Prepares field names to be quoted by parent
*

View file

@ -218,23 +218,17 @@ class DboSqlite extends DboSource {
* @return array
*/
function update(&$model, $fields = array(), $values = null, $conditions = null) {
if (empty($conditions)) {
return parent::update($model, $fields, $values, null);
if (empty($values) && !empty($fields)) {
foreach ($fields as $field => $value) {
if (strpos($field, $model->alias . '.') !== false) {
unset($fields[$field]);
$field = str_replace($model->alias . '.', "", $field);
$field = str_replace($model->alias . '.', "", $field);
$fields[$field] = $value;
}
}
}
return parent::_update($model, $fields, $values, $conditions);
}
/**
* Generates and executes an SQL DELETE statement
*
* @param Model $model
* @param mixed $conditions
* @return boolean Success
*/
function delete(&$model, $conditions = null) {
if (empty($conditions)) {
return parent::delete($model, null);
}
return parent::_delete($model, $conditions);
return parent::update($model, $fields, $values, $conditions);
}
/**
* Begin a transaction

View file

@ -1210,7 +1210,14 @@ class DboSource extends DataSource {
}
return array();
}
/**
* Builds and generates a JOIN statement from an array. Handles final clean-up before conversion.
*
* @param array $join An array defining a JOIN statement in a query
* @return string An SQL JOIN statement to be used in a query
* @see DboSource::renderJoinStatement()
* @see DboSource::buildStatement()
*/
function buildJoinStatement($join) {
$data = array_merge(array(
'type' => null,
@ -1227,7 +1234,14 @@ class DboSource extends DataSource {
}
return $this->renderJoinStatement($data);
}
/**
* Builds and generates an SQL statement from an array. Handles final clean-up before conversion.
*
* @param array $query An array defining an SQL query
* @param object $model The model object which initiated the query
* @return string An executable SQL statement
* @see DboSource::renderStatement()
*/
function buildStatement($query, $model) {
$query = array_merge(array('offset' => null, 'joins' => array()), $query);
if (!empty($query['joins'])) {
@ -1308,6 +1322,7 @@ class DboSource extends DataSource {
}
/**
* Generates and executes an SQL UPDATE statement for given model, fields, and values.
* For databases that do not support aliases in UPDATE queries.
*
* @param Model $model
* @param array $fields
@ -1321,59 +1336,11 @@ class DboSource extends DataSource {
} else {
$combined = array_combine($fields, $values);
}
$fields = join(', ', $this->_prepareUpdateFields($model, $combined, empty($conditions), !empty($conditions)));
$table = $this->fullTableName($model);
$alias = $this->name($model->alias);
$joins = implode(' ', $this->_getJoins($model));
if (empty($conditions)) {
$alias = $joins = false;
}
$conditions = $this->conditions($this->defaultConditions($model, $conditions, $alias));
if ($conditions === false) {
return false;
}
if (!$this->execute($this->renderStatement('update', compact('table', 'alias', 'joins', 'fields', 'conditions')))) {
$model->onError();
return false;
}
return true;
}
/**
* Generates and executes an SQL UPDATE statement for given model, fields, and values.
* For databases that do not support aliases in UPDATE queries.
*
* @param Model $model
* @param array $fields
* @param array $values
* @param mixed $conditions
* @return array
*/
function _update(&$model, $fields = array(), $values = null, $conditions = null) {
if ($conditions === true) {
$conditions = $this->conditions(true);
} else {
$idList = $model->find('all', array('fields' => $model->escapeField(), 'conditions' => $conditions));
if (empty($idList)) {
return false;
}
$conditions = $this->conditions(array(
$model->primaryKey => Set::extract($idList, "{n}.{$model->alias}.{$model->primaryKey}")
));
}
if ($values == null) {
$combined = $fields;
} else {
$combined = array_combine($fields, $values);
}
$fields = join(', ', $this->_prepareUpdateFields($model, $combined, false, false));
$fields = join(', ', $this->_prepareUpdateFields($model, $combined, empty($conditions)));
$alias = $joins = null;
$table = $this->fullTableName($model);
$conditions = $this->_matchRecords($model, $conditions);
if (!$this->execute($this->renderStatement('update', compact('table', 'alias', 'joins', 'fields', 'conditions')))) {
$model->onError();
@ -1391,10 +1358,15 @@ class DboSource extends DataSource {
* @return array Fields and values, quoted and preparted
* @access protected
*/
function _prepareUpdateFields(&$model, $fields, $quoteValues, $alias) {
function _prepareUpdateFields(&$model, $fields, $quoteValues = true, $alias = false) {
$quotedAlias = $this->startQuote . $model->alias . $this->startQuote;
foreach ($fields as $field => $value) {
if ($alias) {
if ($alias && strpos($field, '.') === false) {
$quoted = $model->escapeField($field);
} elseif (!$alias && strpos($field, '.') !== false) {
$quoted = $this->name(str_replace($quotedAlias . '.', '', str_replace(
$model->alias . '.', '', $field
)));
} else {
$quoted = $this->name($field);
}
@ -1405,6 +1377,10 @@ class DboSource extends DataSource {
$update = $quoted . ' = ';
if ($quoteValues) {
$update .= $this->value($value, $model->getColumnType($field));
} elseif (!$alias) {
$update .= str_replace($quotedAlias . '.', '', str_replace(
$model->alias . '.', '', $value
));
} else {
$update .= $value;
}
@ -1414,25 +1390,17 @@ class DboSource extends DataSource {
return $updates;
}
/**
* Generates and executes an SQL DELETE statement for given id on given model.
* Generates and executes an SQL DELETE statement.
* For databases that do not support aliases in UPDATE queries.
*
* @param Model $model
* @param mixed $conditions
* @return boolean Success
*/
function delete(&$model, $conditions = null) {
$alias = $this->name($model->alias);
$alias = $joins = null;
$conditions = $this->_matchRecords($model, $conditions);
$table = $this->fullTableName($model);
$joins = implode(' ', $this->_getJoins($model));
if (empty($conditions)) {
$alias = $joins = false;
}
$conditions = $this->conditions($this->defaultConditions($model, $conditions, $alias));
if ($conditions === false) {
return false;
}
if ($this->execute($this->renderStatement('delete', compact('alias', 'table', 'joins', 'conditions'))) === false) {
$model->onError();
@ -1441,16 +1409,19 @@ class DboSource extends DataSource {
return true;
}
/**
* Generates and executes an SQL DELETE statement.
* For databases that do not support aliases in UPDATE queries.
* Gets a list of record IDs for the given conditions. Used for multi-record updates and deletes
* in databases that do not support aliases in UPDATE/DELETE queries.
*
* @param Model $model
* @param mixed $conditions
* @return boolean Success
* @return array List of record IDs
* @access protected
*/
function _delete(&$model, $conditions = null) {
function _matchRecords(&$model, $conditions = null) {
if ($conditions === true) {
$conditions = $this->conditions(true);
} elseif ($conditions === null) {
$conditions = $this->conditions($this->defaultConditions($model, $conditions, false));
} else {
$idList = $model->find('all', array('fields' => $model->escapeField(), 'conditions' => $conditions));
@ -1461,14 +1432,7 @@ class DboSource extends DataSource {
$model->primaryKey => Set::extract($idList, "{n}.{$model->alias}.{$model->primaryKey}")
));
}
$alias = $joins = null;
$table = $this->fullTableName($model);
if ($this->execute($this->renderStatement('delete', compact('alias', 'table', 'joins', 'conditions'))) === false) {
$model->onError();
return false;
}
return true;
return $conditions;
}
/**
* Returns an array of SQL JOIN fragments from a model's associations

View file

@ -1883,13 +1883,11 @@ class Model extends Overloadable {
return true;
}
/**
* Runs a direct query against the bound DataSource, and returns the result.
*
* @param string $data Query data
* @return array Result of the query
* @access public
* @deprecated
* @see Model::query
*/
function execute($data) {
trigger_error(__('(Model::execute) Deprecated, use Model::query', true), E_USER_WARNING);
$db =& ConnectionManager::getDataSource($this->useDbConfig);
$data = $db->fetchAll($data, $this->cacheQueries);

View file

@ -122,17 +122,17 @@ class AclComponentTest extends CakeTestCase {
$parent = $this->Acl->Aro->id;
$this->Acl->Aro->create(array('parent_id' => $parent, 'alias'=>'Account'));
$this->Acl->Aro->create(array('parent_id' => $parent, 'alias' => 'Account'));
$result = $this->Acl->Aro->save();
$this->assertTrue($result);
$this->Acl->Aro->create(array('parent_id' => $parent, 'alias'=>'Manager'));
$this->Acl->Aro->create(array('parent_id' => $parent, 'alias' => 'Manager'));
$result = $this->Acl->Aro->save();
$this->assertTrue($result);
$parent = $this->Acl->Aro->id;
$this->Acl->Aro->create(array('parent_id' => $parent, 'alias'=>'Secretary'));
$this->Acl->Aro->create(array('parent_id' => $parent, 'alias' => 'Secretary'));
$result = $this->Acl->Aro->save();
$this->assertTrue($result);
@ -198,7 +198,6 @@ class AclComponentTest extends CakeTestCase {
}
function testDbAclCheck() {
$result = $this->Acl->check('Secretary','Links','read');
$this->assertTrue($result);

View file

@ -140,19 +140,20 @@ class ModelTest extends CakeTestCase {
$this->Project->recursive = 3;
$result = $this->Project->findAll();
$expected = array(array('Project' => array('id' => 1, 'name' => 'Project 1'),
'Thread' => array(array('id' => 1, 'project_id' => 1, 'name' => 'Project 1, Thread 1',
'Message' => array(array('id' => 1, 'thread_id' => 1, 'name' => 'Thread 1, Message 1',
'Bid' => array('id' => 1, 'message_id' => 1, 'name' => 'Bid 1.1')))),
array('id' => 2, 'project_id' => 1, 'name' => 'Project 1, Thread 2',
'Message' => array(array('id' => 2, 'thread_id' => 2, 'name' => 'Thread 2, Message 1',
'Bid' => array('id' => 4, 'message_id' => 2, 'name' => 'Bid 2.1')))))),
array('Project' => array('id' => 2, 'name' => 'Project 2'),
'Thread' => array(array('id' => 3, 'project_id' => 2, 'name' => 'Project 2, Thread 1',
'Message' => array(array('id' => 3, 'thread_id' => 3, 'name' => 'Thread 3, Message 1',
'Bid' => array('id' => 3, 'message_id' => 3, 'name' => 'Bid 3.1')))))),
array('Project' => array('id' => 3, 'name' => 'Project 3'),
'Thread' => array()));
$expected = array(
array('Project' => array('id' => 1, 'name' => 'Project 1'),
'Thread' => array(array('id' => 1, 'project_id' => 1, 'name' => 'Project 1, Thread 1',
'Message' => array(array('id' => 1, 'thread_id' => 1, 'name' => 'Thread 1, Message 1',
'Bid' => array('id' => 1, 'message_id' => 1, 'name' => 'Bid 1.1')))),
array('id' => 2, 'project_id' => 1, 'name' => 'Project 1, Thread 2',
'Message' => array(array('id' => 2, 'thread_id' => 2, 'name' => 'Thread 2, Message 1',
'Bid' => array('id' => 4, 'message_id' => 2, 'name' => 'Bid 2.1')))))),
array('Project' => array('id' => 2, 'name' => 'Project 2'),
'Thread' => array(array('id' => 3, 'project_id' => 2, 'name' => 'Project 2, Thread 1',
'Message' => array(array('id' => 3, 'thread_id' => 3, 'name' => 'Thread 3, Message 1',
'Bid' => array('id' => 3, 'message_id' => 3, 'name' => 'Bid 3.1')))))),
array('Project' => array('id' => 3, 'name' => 'Project 3'),
'Thread' => array()));
$this->assertEqual($result, $expected);
unset($this->Project);
}
@ -172,7 +173,6 @@ class ModelTest extends CakeTestCase {
array('SomethingElse' => array('id' => '3', 'title' => 'Third Post', 'body' => 'Third Post Body', 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31'),
'Something' => array (array('id' => '2', 'title' => 'Second Post', 'body' => 'Second Post Body', 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31',
'JoinThing' => array('id' => '2', 'something_id' => '2', 'something_else_id' => '3', 'doomed' => '0', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31')))));
$this->assertEqual($result, $expected);
$result = $this->model->find('all');
@ -189,7 +189,6 @@ class ModelTest extends CakeTestCase {
'SomethingElse' => array(
array('id' => '1', 'title' => 'First Post', 'body' => 'First Post Body', 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31',
'JoinThing' => array('doomed' => '1', 'something_id' => '3', 'something_else_id' => '1')))));
$this->assertEqual($result, $expected);
$result = $this->model->findById(1);
@ -749,6 +748,21 @@ class ModelTest extends CakeTestCase {
$this->assertTrue($result);
}
function testUpdateMultiple() {
$this->loadFixtures('Comment', 'Article', 'User', 'Attachment');
$this->model =& new Comment();
$result = Set::extract($this->model->find('all'), '{n}.Comment.user_id');
$expected = array('2', '4', '1', '1', '1', '2');
$this->assertEqual($result, $expected);
$this->model->updateAll(array('Comment.user_id' => 5), array('Comment.user_id' => 2));
$result = Set::extract($this->model->find('all'), '{n}.Comment.user_id');
$expected = array('5', '4', '1', '1', '1', '5');
$this->assertEqual($result, $expected);
//pr($this->model->find('all'));
}
function testBindUnbind() {
$this->loadFixtures('User', 'Comment', 'FeatureSet');
$this->model =& new User();
@ -1952,7 +1966,7 @@ class ModelTest extends CakeTestCase {
$result = $this->model->findById(1);
$this->assertIdentical($result['Syfile']['item_count'], null);
$this->model2->save(array('name' => 'Item 7', 'syfile_id' => 1));
$this->model2->save(array('name' => 'Item 7', 'syfile_id' => 1, 'published' => false));
$result = $this->model->findById(1);
$this->assertIdentical($result['Syfile']['item_count'], '2');
@ -2785,6 +2799,12 @@ class ModelTest extends CakeTestCase {
}
function testAutoSaveUuid() {
// SQLite does not support non-integer primary keys
$db =& ConnectionManager::getDataSource('test_suite');
if ($db->config['driver'] == 'sqlite') {
return;
}
$this->loadFixtures('Uuid');
$this->model =& new Uuid();
$this->model->save(array('title' => 'Test record'));

View file

@ -38,13 +38,12 @@ class ArosAcoFixture extends CakeTestFixture {
'id' => array('type' => 'integer', 'key' => 'primary'),
'aro_id' => array('type' => 'integer', 'length' => 10, 'null' => false),
'aco_id' => array('type' => 'integer', 'length' => 10, 'null' => false),
'_create' => array('type' => 'string', 'length' => 2, 'null' => false, 'default' => '0'),
'_read' => array('type' => 'string', 'length' => 2, 'null' => false, 'default' => '0'),
'_update' => array('type' => 'string', 'length' => 2, 'null' => false, 'default' => '0'),
'_delete' => array('type' => 'string', 'length' => 2, 'null' => false, 'default' => '0')
);
var $records = array(
'_create' => array('type' => 'string', 'length' => 2, 'default' => 0),
'_read' => array('type' => 'string', 'length' => 2, 'default' => 0),
'_update' => array('type' => 'string', 'length' => 2, 'default' => 0),
'_delete' => array('type' => 'string', 'length' => 2, 'default' => 0)
);
var $records = array();
}
?>