Fixes #3698, no additional select performed in Model::deleteAll() if $callbacks and $cascade are false

Fixes #2146, set 'foreignKey' => false and use association conditions to get custom join scoping
Fixes #3323, set 'foreignKey' => false to disable default conditions
Fixes #3627, added Model::$findQueryType to inform callbacks of the current find query type, i.e. 'count'


git-svn-id: https://svn.cakephp.org/repo/branches/1.2.x.x@6258 3807eeeb-6ff5-0310-8944-8be069107fe0
This commit is contained in:
nate 2007-12-25 10:37:08 +00:00
parent 75b5623f33
commit 72c80efa75
5 changed files with 199 additions and 78 deletions

View file

@ -360,8 +360,10 @@ class TreeBehavior extends ModelBehavior {
return null; return null;
} }
$results = $model->find('all', array('conditions' => array($scope, $model->escapeField($left) => '<= ' . $item[$left], $model->escapeField($right) => '>= ' . $item[$right]), $results = $model->find('all', array(
'fields' => $fields, 'order' => array($model->escapeField($left) => 'asc'), 'recursive' => $recursive)); 'conditions' => array($scope, $model->escapeField($left) => '<= ' . $item[$left], $model->escapeField($right) => '>= ' . $item[$right]),
'fields' => $fields, 'order' => array($model->escapeField($left) => 'asc'), 'recursive' => $recursive
));
return $results; return $results;
} }
/** /**
@ -380,11 +382,15 @@ class TreeBehavior extends ModelBehavior {
$id = $model->id; $id = $model->id;
} }
extract($this->settings[$model->alias]); extract($this->settings[$model->alias]);
list($node) = array_values($model->find('first', array('conditions' => array($scope, $model->escapeField() => $id), list($node) = array_values($model->find('first', array(
'fields' => array($model->primaryKey, $left, $right, $parent), 'recursive' => -1))); 'conditions' => array($scope, $model->escapeField() => $id),
'fields' => array($model->primaryKey, $left, $right, $parent), 'recursive' => -1
)));
if ($node[$parent]) { if ($node[$parent]) {
list($parentNode) = array_values($model->find('first', array('conditions' => array($scope, $model->escapeField() => $node[$parent]), list($parentNode) = array_values($model->find('first', array(
'fields' => array($model->primaryKey, $left, $right), 'recursive' => -1))); 'conditions' => array($scope, $model->escapeField() => $node[$parent]),
'fields' => array($model->primaryKey, $left, $right), 'recursive' => -1
)));
if (($node[$right] + 1) == $parentNode[$right]) { if (($node[$right] + 1) == $parentNode[$right]) {
return false; return false;
} }
@ -424,11 +430,15 @@ class TreeBehavior extends ModelBehavior {
$id = $model->id; $id = $model->id;
} }
extract($this->settings[$model->alias]); extract($this->settings[$model->alias]);
list($node) = array_values($model->find('first', array('conditions' => array($scope, $model->escapeField() => $id), list($node) = array_values($model->find('first', array(
'fields' => array($model->primaryKey, $left, $right, $parent ), 'recursive' => -1))); 'conditions' => array($scope, $model->escapeField() => $id),
'fields' => array($model->primaryKey, $left, $right, $parent ), 'recursive' => -1
)));
if ($node[$parent]) { if ($node[$parent]) {
list($parentNode) = array_values($model->find('first', array('conditions' => array( $scope, $model->escapeField() => $node[$parent]), list($parentNode) = array_values($model->find('first', array(
'fields' => array($model->primaryKey, $left, $right), 'recursive' => -1))); 'conditions' => array($scope, $model->escapeField() => $node[$parent]),
'fields' => array($model->primaryKey, $left, $right), 'recursive' => -1
)));
if (($node[$left] - 1) == $parentNode[$left]) { if (($node[$left] - 1) == $parentNode[$left]) {
return false; return false;
} }
@ -489,7 +499,7 @@ class TreeBehavior extends ModelBehavior {
} else { } else {
$parentId = $path[count($path) - 2][$model->alias][$model->primaryKey]; $parentId = $path[count($path) - 2][$model->alias][$model->primaryKey];
} }
$model->updateAll(array($parent => $parentId), array($model->primaryKey => $array[$model->alias][$model->primaryKey])); $model->updateAll(array($parent => $parentId), array($model->escapeField() => $array[$model->alias][$model->primaryKey]));
} }
} }
} }
@ -532,7 +542,10 @@ class TreeBehavior extends ModelBehavior {
$model->id = $id; $model->id = $id;
if ($delete) { if ($delete) {
$model->updateAll(array($left => null, $right => null, $parent => null), array($model->primaryKey => $id)); $model->updateAll(
array($model->escapeField($left) => null, $model->escapeField($right) => null, $model->escapeField($parent) => null),
array($model->escapeField() => $id)
);
return $model->delete($id); return $model->delete($id);
} else { } else {
$edge = $this->__getMax($model, $scope, $right); $edge = $this->__getMax($model, $scope, $right);
@ -727,7 +740,7 @@ class TreeBehavior extends ModelBehavior {
$field = $right; $field = $right;
} }
if (is_string($conditions)) { if (is_string($conditions)) {
$conditions = array($field => $conditions); $conditions = array($model->escapeField($field) => $conditions);
} }
if ($scope != '1 = 1' && $scope) { if ($scope != '1 = 1' && $scope) {
if (is_string($scope)) { if (is_string($scope)) {
@ -736,7 +749,7 @@ class TreeBehavior extends ModelBehavior {
$conditions= array_merge($conditions, $scope); $conditions= array_merge($conditions, $scope);
} }
} }
$model->updateAll(array($field => $field . ' ' . $dir . ' ' . $shift), $conditions); $model->updateAll(array($model->escapeField($field) => $model->escapeField($field) . ' ' . $dir . ' ' . $shift), $conditions);
} }
} }
?> ?>

View file

@ -456,12 +456,14 @@ class DboFirebird extends DboSource {
/** /**
* Builds final SQL statement * Builds final SQL statement
* *
* @param string $type Query type
* @param array $data Query data * @param array $data Query data
* @return string * @return string
*/ */
function renderStatement($data) { function renderStatement($type, $data) {
extract($data); extract($data);
if (strtolower($type) == 'select') {
if (preg_match('/offset\s+([0-9]+)/i', $limit, $offset)) { if (preg_match('/offset\s+([0-9]+)/i', $limit, $offset)) {
$limit = preg_replace('/\s*offset.*$/i', '', $limit); $limit = preg_replace('/\s*offset.*$/i', '', $limit);
preg_match('/top\s+([0-9]+)/i', $limit, $limitVal); preg_match('/top\s+([0-9]+)/i', $limit, $limitVal);
@ -472,6 +474,9 @@ class DboFirebird extends DboSource {
} else { } else {
return "SELECT {$limit} {$fields} FROM {$table} {$alias} {$joins} {$conditions} {$order}"; return "SELECT {$limit} {$fields} FROM {$table} {$alias} {$joins} {$conditions} {$order}";
} }
} else {
return parent::renderStatement($type, $data);
}
} }
/** /**
* Fetches the next row from the current result set * Fetches the next row from the current result set

View file

@ -510,11 +510,14 @@ class DboMssql extends DboSource {
/** /**
* Builds final SQL statement * Builds final SQL statement
* *
* @param string $type Query type
* @param array $data Query data * @param array $data Query data
* @return string * @return string
*/ */
function renderStatement($data) { function renderStatement($type, $data) {
extract($data); extract($data);
if (strtolower($type) == 'select') {
if (preg_match('/offset\s+([0-9]+)/i', $limit, $offset)) { if (preg_match('/offset\s+([0-9]+)/i', $limit, $offset)) {
$limit = preg_replace('/\s*offset.*$/i', '', $limit); $limit = preg_replace('/\s*offset.*$/i', '', $limit);
preg_match('/top\s+([0-9]+)/i', $limit, $limitVal); preg_match('/top\s+([0-9]+)/i', $limit, $limitVal);
@ -525,6 +528,9 @@ class DboMssql extends DboSource {
} else { } else {
return "SELECT {$limit} {$fields} FROM {$table} {$alias} {$joins} {$conditions} {$order}"; return "SELECT {$limit} {$fields} FROM {$table} {$alias} {$joins} {$conditions} {$order}";
} }
} else {
return parent::renderStatement($type, $data);
}
} }
/** /**
* Reverses the sort direction of ORDER statements to get paging offsets to work correctly * Reverses the sort direction of ORDER statements to get paging offsets to work correctly

View file

@ -1058,12 +1058,11 @@ class DboSource extends DataSource {
switch($type) { switch($type) {
case 'hasOne': case 'hasOne':
case 'belongsTo': case 'belongsTo':
$conditions = $this->__mergeConditions(
$assocData['conditions'],
$this->getConstraint($type, $model, $linkModel, $alias, array_merge($assocData, compact('external')))
);
if ($external) { if ($external) {
if ($type == 'hasOne') {
$conditions = $this->__mergeConditions($assocData['conditions'], array("{$alias}.{$assocData['foreignKey']}" => '{$__cakeID__$}'));
} elseif ($type == 'belongsTo') {
$conditions = $this->__mergeConditions($assocData['conditions'], array("{$alias}.{$linkModel->primaryKey}" => '{$__cakeForeignKey__$}'));
}
$query = array_merge($assocData, array( $query = array_merge($assocData, array(
'conditions' => $conditions, 'conditions' => $conditions,
'table' => $this->fullTableName($linkModel), 'table' => $this->fullTableName($linkModel),
@ -1077,19 +1076,12 @@ class DboSource extends DataSource {
$query = array_merge($query, array('order' => $assocData['order'], 'limit' => $limit)); $query = array_merge($query, array('order' => $assocData['order'], 'limit' => $limit));
} }
} else { } else {
if ($type == 'hasOne') {
$conditions = $this->__mergeConditions($assocData['conditions'], array("{$alias}.{$assocData['foreignKey']}" => '{$__cakeIdentifier[' . "{$model->alias}.{$model->primaryKey}" . ']__$}'));
} elseif ($type == 'belongsTo') {
$conditions = $this->__mergeConditions($assocData['conditions'], array("{$model->alias}.{$assocData['foreignKey']}" => '{$__cakeIdentifier[' . "{$alias}.{$linkModel->primaryKey}" . ']__$}'));
}
$join = array( $join = array(
'table' => $this->fullTableName($linkModel), 'table' => $this->fullTableName($linkModel),
'alias' => $alias, 'alias' => $alias,
'type' => 'LEFT', 'type' => 'LEFT',
'conditions' => trim($this->conditions($conditions, true, false)) 'conditions' => trim($this->conditions($conditions, true, false))
); );
$queryData['fields'] = array_merge($queryData['fields'], $fields); $queryData['fields'] = array_merge($queryData['fields'], $fields);
if (!empty($assocData['order'])) { if (!empty($assocData['order'])) {
@ -1108,7 +1100,7 @@ class DboSource extends DataSource {
)); ));
$query = array( $query = array(
'conditions' => $this->__mergeConditions(array("{$alias}.{$assocData['foreignKey']}" => array('{$__cakeID__$}')), $assocData['conditions']), 'conditions' => $this->__mergeConditions($this->getConstraint('hasMany', $model, $linkModel, $alias, $assocData), $assocData['conditions']),
'fields' => $assocData['fields'], 'fields' => $assocData['fields'],
'table' => $this->fullTableName($linkModel), 'table' => $this->fullTableName($linkModel),
'alias' => $alias, 'alias' => $alias,
@ -1125,10 +1117,10 @@ class DboSource extends DataSource {
if (isset($assocData['with']) && !empty($assocData['with'])) { if (isset($assocData['with']) && !empty($assocData['with'])) {
$joinKeys = array($assocData['foreignKey'], $assocData['associationForeignKey']); $joinKeys = array($assocData['foreignKey'], $assocData['associationForeignKey']);
list($with, $joinFields) = $this->getJoinModel($model, $assocData['with'], $joinKeys); list($with, $joinFields) = $this->getJoinModel($model, $assocData['with'], $joinKeys);
if (is_array($joinFields) && !empty($joinFields)) { if (is_array($joinFields) && !empty($joinFields)) {
$joinFields = $this->fields($model->{$with}, $model->{$with}->alias, $joinFields); $joinFields = $this->fields($model->{$with}, $model->{$with}->alias, $joinFields);
$joinAssoc = $joinAlias = $model->{$with}->alias; $joinAssoc = $joinAlias = $model->{$with}->alias;
} else { } else {
$joinFields = array(); $joinFields = array();
} }
@ -1144,11 +1136,8 @@ class DboSource extends DataSource {
'joins' => array(array( 'joins' => array(array(
'table' => $joinTbl, 'table' => $joinTbl,
'alias' => $joinAssoc, 'alias' => $joinAssoc,
'conditions' => array( 'conditions' => $this->getConstraint('hasAndBelongsToMany', $model, $linkModel, $joinAlias, $assocData, $alias)
array("{$joinAlias}.{$assocData['foreignKey']}" => '{$__cakeID__$}'),
array("{$joinAlias}.{$assocData['associationForeignKey']}" => '{$__cakeIdentifier['."{$alias}.{$linkModel->primaryKey}".']__$}')
)) ))
)
); );
break; break;
} }
@ -1157,6 +1146,46 @@ class DboSource extends DataSource {
} }
return null; return null;
} }
/**
* Returns a conditions array for the constraint between two models
*
* @param string $type Association type
* @param object $model Model object
* @param array $association Association array
* @return array Conditions array defining the constraint between $model and $association
*/
function getConstraint($type, $model, $linkModel, $alias, $assoc, $alias2 = null) {
$assoc = array_merge(array('external' => false), $assoc);
if (array_key_exists('foreignKey', $assoc) && empty($assoc['foreignKey'])) {
return array();
}
switch (true) {
case ($assoc['external'] && $type == 'hasOne'):
return array("{$alias}.{$assoc['foreignKey']}" => '{$__cakeID__$}');
break;
case ($assoc['external'] && $type == 'belongsTo'):
return array("{$alias}.{$linkModel->primaryKey}" => '{$__cakeForeignKey__$}');
break;
case (!$assoc['external'] && $type == 'hasOne'):
return array("{$alias}.{$assoc['foreignKey']}" => '{$__cakeIdentifier[' . "{$model->alias}.{$model->primaryKey}" . ']__$}');
break;
case (!$assoc['external'] && $type == 'belongsTo'):
return array("{$model->alias}.{$assoc['foreignKey']}" => '{$__cakeIdentifier[' . "{$alias}.{$linkModel->primaryKey}" . ']__$}');
break;
case ($type == 'hasMany'):
return array("{$alias}.{$assoc['foreignKey']}" => array('{$__cakeID__$}'));
break;
case ($type == 'hasAndBelongsToMany'):
return array(
array("{$alias}.{$assoc['foreignKey']}" => '{$__cakeID__$}'),
array("{$alias}.{$assoc['associationForeignKey']}" => '{$__cakeIdentifier['."{$alias2}.{$linkModel->primaryKey}".']__$}')
);
break;
}
return array();
}
/** /**
* Gets the name and fields to be used by a join model. This allows specifying join fields in the association definition. * Gets the name and fields to be used by a join model. This allows specifying join fields in the association definition.
* *
@ -1202,7 +1231,7 @@ class DboSource extends DataSource {
} }
} }
} }
return $this->renderStatement(array( return $this->renderStatement('select', array(
'conditions' => $this->conditions($query['conditions']), 'conditions' => $this->conditions($query['conditions']),
'fields' => join(', ', $query['fields']), 'fields' => join(', ', $query['fields']),
'table' => $query['table'], 'table' => $query['table'],
@ -1217,10 +1246,27 @@ class DboSource extends DataSource {
extract($data); extract($data);
return trim("{$type} JOIN {$table} {$alias} ON ({$conditions})"); return trim("{$type} JOIN {$table} {$alias} ON ({$conditions})");
} }
/**
function renderStatement($data) { * Renders a final SQL statement by putting together the component parts in the correct order
*
* @param string $type
* @param array $data
* @return string
*/
function renderStatement($type, $data) {
extract($data); extract($data);
switch (strtolower($type)) {
case 'select':
return "SELECT {$fields} FROM {$table} {$alias} {$joins} {$conditions} {$order} {$limit}"; return "SELECT {$fields} FROM {$table} {$alias} {$joins} {$conditions} {$order} {$limit}";
break;
case 'update':
return "UPDATE {$table} {$this->alias}{$alias} {$joins} SET {$fields} {$conditions}";
break;
case 'delete':
return "DELETE {$alias} FROM {$table} {$this->alias}{$alias} {$joins} {$conditions}";
break;
}
} }
/** /**
* Private method * Private method
@ -1267,9 +1313,9 @@ class DboSource extends DataSource {
foreach ($combined as $field => $value) { foreach ($combined as $field => $value) {
if ($value === null) { if ($value === null) {
$updates[] = $this->name($field) . ' = NULL'; $updates[] = $model->escapeField($field) . ' = NULL';
} else { } else {
$update = $this->name($field) . ' = '; $update = $model->escapeField($field) . ' = ';
if ($conditions == null) { if ($conditions == null) {
$update .= $this->value($value, $model->getColumnType($field)); $update .= $this->value($value, $model->getColumnType($field));
} else { } else {
@ -1286,8 +1332,10 @@ class DboSource extends DataSource {
$fields = join(', ', $updates); $fields = join(', ', $updates);
$table = $this->fullTableName($model); $table = $this->fullTableName($model);
$conditions = $this->conditions($conditions); $conditions = $this->conditions($conditions);
$alias = $this->name($model->alias);
$joins = implode(' ', $this->_getJoins($model));
if (!$this->execute("UPDATE {$table} SET {$fields} {$conditions}")) { if (!$this->execute($this->renderStatement('update', compact('table', 'alias', 'joins', 'fields', 'conditions')))) {
$model->onError(); $model->onError();
return false; return false;
} }
@ -1307,15 +1355,40 @@ class DboSource extends DataSource {
return false; return false;
} }
$alias = $this->name($model->alias);
$table = $this->fullTableName($model); $table = $this->fullTableName($model);
$conditions = $this->conditions($query); $conditions = $this->conditions($query);
$joins = implode(' ', $this->_getJoins($model));
if ($this->execute("DELETE FROM {$table} {$conditions}") === false) { if ($this->execute($this->renderStatement('delete', compact('alias', 'table', 'joins', 'conditions'))) === false) {
$model->onError(); $model->onError();
return false; return false;
} }
return true; return true;
} }
/**
* Returns an array of SQL JOIN fragments from a model's associations
*
* @param object $model
* @return array
*/
function _getJoins($model) {
$join = array();
$joins = array_merge($model->getAssociated('hasOne'), $model->getAssociated('belongsTo'));
foreach ($joins as $assoc) {
if (isset($model->{$assoc}) && $model->useDbConfig == $model->{$assoc}->useDbConfig) {
$assocData = $model->getAssociated($assoc);
$join[] = $this->buildJoinStatement(array(
'table' => $this->fullTableName($model->{$assoc}),
'alias' => $assoc,
'type' => 'LEFT',
'conditions' => trim($this->conditions($this->getConstraint($assocData['association'], $model, $model->{$assoc}, $assoc, $assocData), true, false))
));
}
}
return $join;
}
/** /**
* Deletes all the records in a table and resets the count of the auto-incrementing * Deletes all the records in a table and resets the count of the auto-incrementing
* primary key, where applicable. * primary key, where applicable.
@ -1341,7 +1414,7 @@ class DboSource extends DataSource {
if (!$model->exists()) { if (!$model->exists()) {
return false; return false;
} }
return array($model->primaryKey => (array)$model->getID()); return array("{$model->alias}.{$model->primaryKey}" => (array)$model->getID());
} }
/** /**
* Returns a key formatted like a string Model.fieldname(i.e. Post.title, or Country.name) * Returns a key formatted like a string Model.fieldname(i.e. Post.title, or Country.name)
@ -2044,4 +2117,5 @@ class DboSource extends DataSource {
return false; return false;
} }
} }
?> ?>

View file

@ -223,6 +223,13 @@ class Model extends Overloadable {
* @access public * @access public
*/ */
var $cacheSources = true; var $cacheSources = true;
/**
* Type of find query currently executing
*
* @var string
* @access public
*/
var $findQueryType = null;
/** /**
* Mapped behavior methods * Mapped behavior methods
* *
@ -1434,27 +1441,41 @@ class Model extends Overloadable {
* @return boolean True on success, false on failure * @return boolean True on success, false on failure
* @access public * @access public
*/ */
function deleteAll($conditions, $cascade = true, $callbacks = true) { function deleteAll($conditions, $cascade = true, $callbacks = false) {
if (empty($conditions)) { if (empty($conditions)) {
return false; return false;
} }
$records = $this->findAll($conditions, "{$this->alias}.{$this->primaryKey}", null, null, null, 0); $db =& ConnectionManager::getDataSource($this->useDbConfig);
if (empty($records)) { if (!$cascade && !$callbacks) {
return $db->delete($this, $conditions);
} else {
$ids = Set::extract(
$this->find('all', array_merge(array('fields' => "{$this->alias}.{$this->primaryKey}", 'recursive' => 0), compact('conditions'))),
"{n}.{$this->alias}.{$this->primaryKey}"
);
if (empty($ids)) {
return false; return false;
} }
$ids = Set::extract($records, "{n}.{$this->alias}.{$this->primaryKey}");
if ($callbacks) {
$_id = $this->id;
foreach ($ids as $id) {
$this->delete($id, $cascade);
}
$this->id = $_id;
} else {
foreach ($ids as $id) { foreach ($ids as $id) {
$this->_deleteLinks($id); $this->_deleteLinks($id);
if ($cascade) { if ($cascade) {
$this->_deleteDependent($id, $cascade); $this->_deleteDependent($id, $cascade);
} }
} }
return $db->delete($this, array($this->alias . '.' . $this->primaryKey => $ids));
$db =& ConnectionManager::getDataSource($this->useDbConfig); }
return $db->delete($this, array($this->primaryKey => $ids)); }
} }
/** /**
* Returns true if a record with set id exists. * Returns true if a record with set id exists.
@ -1514,6 +1535,7 @@ class Model extends Overloadable {
$type = $conditions; $type = $conditions;
$query = $fields; $query = $fields;
} }
$this->findQueryType = $type;
$db =& ConnectionManager::getDataSource($this->useDbConfig); $db =& ConnectionManager::getDataSource($this->useDbConfig);
$this->id = $this->getID(); $this->id = $this->getID();
@ -1576,6 +1598,7 @@ class Model extends Overloadable {
} }
$results = $db->read($this, $query); $results = $db->read($this, $query);
$this->__resetAssociations(); $this->__resetAssociations();
$this->findQueryType = null;
switch ($type) { switch ($type) {
case 'all': case 'all':
@ -1885,9 +1908,6 @@ class Model extends Overloadable {
$ruleSet = array($ruleSet); $ruleSet = array($ruleSet);
} }
if (isset($this->testing)) {
pr($ruleSet);
}
foreach ($ruleSet as $index => $validator) { foreach ($ruleSet as $index => $validator) {
if (!is_array($validator)) { if (!is_array($validator)) {
$validator = array('rule' => $validator); $validator = array('rule' => $validator);
@ -2054,6 +2074,9 @@ class Model extends Overloadable {
$field = $this->primaryKey; $field = $this->primaryKey;
} }
$db =& ConnectionManager::getDataSource($this->useDbConfig); $db =& ConnectionManager::getDataSource($this->useDbConfig);
if (strpos($field, $db->name($alias)) === 0) {
return $field;
}
return $db->name($alias) . '.' . $db->name($field); return $db->name($alias) . '.' . $db->name($field);
} }
/** /**