Adding full join model support, closes #828, closes #1845

git-svn-id: https://svn.cakephp.org/repo/branches/1.2.x.x@6299 3807eeeb-6ff5-0310-8944-8be069107fe0
This commit is contained in:
nate 2008-01-01 23:57:17 +00:00
parent 0156b9e689
commit 695ac50fd7
13 changed files with 130 additions and 94 deletions

View file

@ -426,10 +426,7 @@ class DboAdodb extends DboSource {
* @param array $values
*/
function insertMulti($table, $fields, $values) {
$count = count($values);
for ($x = 0; $x < $count; $x++) {
$this->query("INSERT INTO {$table} ({$fields}) VALUES {$values[$x]}");
}
parent::__insertMulti($table, $fields, $values);
}
}
?>

View file

@ -511,10 +511,7 @@ class DboFirebird extends DboSource {
* @param array $values
*/
function insertMulti($table, $fields, $values) {
$count = count($values);
for ($x = 0; $x < $count; $x++) {
$this->query("INSERT INTO {$table} ({$fields}) VALUES {$values[$x]}");
}
parent::__insertMulti($table, $fields, $values);
}
}
?>

View file

@ -603,11 +603,8 @@ class DboMssql extends DboSource {
* @param array $values
*/
function insertMulti($table, $fields, $values) {
$count = count($values);
for ($x = 0; $x < $count; $x++) {
$this->query("INSERT INTO {$table} ({$fields}) VALUES {$values[$x]}");
}
parent::__insertMulti($table, $fields, $values);
}
}
?>

View file

@ -624,10 +624,8 @@ class DboOracle extends DboSource {
* @param array $values
*/
function insertMulti($table, $fields, $values) {
$count = count($values);
for ($x = 0; $x < $count; $x++) {
$this->query("INSERT INTO {$table} ({$fields}) VALUES {$values[$x]}");
}
parent::__insertMulti($table, $fields, $values);
}
}
?>

View file

@ -586,10 +586,8 @@ class DboPostgres extends DboSource {
* @param array $values
*/
function insertMulti($table, $fields, $values) {
$count = count($values);
for ($x = 0; $x < $count; $x++) {
$this->query("INSERT INTO {$table} ({$fields}) VALUES {$values[$x]}");
}
parent::__insertMulti($table, $fields, $values);
}
}
?>

View file

@ -422,10 +422,7 @@ class DboSqlite extends DboSource {
* @param array $values
*/
function insertMulti($table, $fields, $values) {
$count = count($values);
for ($x = 0; $x < $count; $x++) {
$this->query("INSERT INTO {$table} ({$fields}) VALUES {$values[$x]}");
}
parent::__insertMulti($table, $fields, $values);
}
/**
* Generate a database-native column schema string

View file

@ -388,10 +388,8 @@ class DboSybase extends DboSource {
* @param array $values
*/
function insertMulti($table, $fields, $values) {
$count = count($values);
for ($x = 0; $x < $count; $x++) {
$this->query("INSERT INTO {$table} ({$fields}) VALUES {$values[$x]}");
}
parent::__insertMulti($table, $fields, $values);
}
}
?>

View file

@ -721,7 +721,7 @@ class DboSource extends DataSource {
$foreignKey = $model->hasAndBelongsToMany[$association]['foreignKey'];
$joinKeys = array($foreignKey, $model->hasAndBelongsToMany[$association]['associationForeignKey']);
list($with, $habtmFields) = $this->getJoinModel($model, $model->hasAndBelongsToMany[$association]['with'], $joinKeys);
list($with, $habtmFields) = $model->joinModel($model->hasAndBelongsToMany[$association]['with'], $joinKeys);
$habtmFieldsCount = count($habtmFields);
$q = $this->insertQueryData($query, null, $association, $assocData, $model, $linkModel, $stack);
@ -1116,7 +1116,7 @@ class DboSource extends DataSource {
if (isset($assocData['with']) && !empty($assocData['with'])) {
$joinKeys = array($assocData['foreignKey'], $assocData['associationForeignKey']);
list($with, $joinFields) = $this->getJoinModel($model, $assocData['with'], $joinKeys);
list($with, $joinFields) = $model->joinModel($assocData['with'], $joinKeys);
if (is_array($joinFields) && !empty($joinFields)) {
$joinFields = $this->fields($model->{$with}, $model->{$with}->alias, $joinFields);
@ -1186,24 +1186,6 @@ class DboSource extends DataSource {
}
return array();
}
/**
* Gets the name and fields to be used by a join model. This allows specifying join fields in the association definition.
*
* @param object $model The model to be joined
* @param mixed $with The 'with' key of the model association
* @param array $keys Any join keys which must be merged with the keys queried
* @return array
*/
function getJoinModel($model, $assoc, $keys = array()) {
if (is_string($assoc)) {
return array($assoc, array_keys($model->{$assoc}->schema()));
} elseif (is_array($assoc)) {
$with = key($assoc);
return array($with, array_unique(array_merge($assoc[$with], $keys)));
} else {
trigger_error(sprintf(__('Invalid join model settings in %s', true), $model->alias), E_USER_WARNING);
}
}
function buildJoinStatement($join) {
$data = array_merge(array(
@ -1965,9 +1947,29 @@ class DboSource extends DataSource {
* @param array $values
*/
function insertMulti($table, $fields, $values) {
if (is_object($table)) {
$table = $this->fullTableName($table);
}
$values = implode(', ', $values);
$this->query("INSERT INTO {$table} ({$fields}) VALUES {$values}");
}
/**
* Inserts multiple values into a join table
*
* @param string $table
* @param string $fields
* @param array $values
* @access protected
*/
function __insertMulti($table, $fields, $values) {
if (is_object($table)) {
$table = $this->fullTableName($table);
}
$count = count($values);
for ($x = 0; $x < $count; $x++) {
$this->query("INSERT INTO {$table} ({$fields}) VALUES {$values[$x]}");
}
}
/**
* Returns an array of the indexes in given datasource name.
*

View file

@ -722,6 +722,9 @@ class Model extends Overloadable {
$data = $class;
break;
case 'unique':
$data = true;
break;
}
$this->{$type}[$assocKey][$key] = $data;
}
@ -1170,8 +1173,11 @@ class Model extends Overloadable {
$fields = $values = array();
foreach ($this->data as $n => $v) {
if (isset($v[$n]) && in_array($n, array_keys($this->hasAndBelongsToMany))) {
$joined[] = $v;
if (isset($this->hasAndBelongsToMany[$n])) {
if (isset($v[$n])) {
$v = $v[$n];
}
$joined[$n] = $v;
} else {
if ($n === $this->alias) {
foreach (array('created', 'updated', 'modified') as $field) {
@ -1182,8 +1188,7 @@ class Model extends Overloadable {
foreach ($v as $x => $y) {
if ($this->hasField($x) && (empty($this->whitelist) || in_array($x, $this->whitelist))) {
$fields[] = $x;
$values[] = $y;
list($fields[], $values[]) = array($x, $y);
}
}
}
@ -1206,8 +1211,7 @@ class Model extends Overloadable {
foreach ($this->_schema as $field => $properties) {
if ($this->primaryKey === $field) {
if (empty($this->data[$this->alias][$this->primaryKey]) && $this->_schema[$field]['type'] === 'string' && $this->_schema[$field]['length'] === 36) {
$fields[] = $this->primaryKey;
$values[] = String::uuid();
list($fields[], $values[]) = array($this->primaryKey, String::uuid());
}
break;
}
@ -1260,43 +1264,46 @@ class Model extends Overloadable {
*/
function __saveMulti($joined, $id) {
$db =& ConnectionManager::getDataSource($this->useDbConfig);
foreach ($joined as $x => $y) {
foreach ($y as $assoc => $value) {
if (isset($this->hasAndBelongsToMany[$assoc])) {
$joinTable[$assoc] = $this->hasAndBelongsToMany[$assoc]['joinTable'];
$mainKey[$assoc] = $db->name($this->hasAndBelongsToMany[$assoc]['foreignKey']);
$keys[] = $db->name($this->hasAndBelongsToMany[$assoc]['foreignKey']);
$keys[] = $db->name($this->hasAndBelongsToMany[$assoc]['associationForeignKey']);
$fields[$assoc] = join(',', $keys);
unset($keys);
foreach ($value as $update) {
if (!empty($update)) {
$values[] = $db->value($id, $this->getColumnType($this->primaryKey));
$values[] = $db->value($update);
$values = join(',', $values);
foreach ($joined as $assoc => $value) {
if (isset($this->hasAndBelongsToMany[$assoc])) {
list($join) = $this->joinModel($this->hasAndBelongsToMany[$assoc]['with']);
$conditions = array($this->hasAndBelongsToMany[$assoc]['foreignKey'] => $id);
$links = array();
if ($this->hasAndBelongsToMany[$assoc]['unique']) {
$this->{$join}->deleteAll($conditions);
} else {
list($recursive, $fields) = array(-1, $this->hasAndBelongsToMany[$assoc]['associationForeignKey']);
$links = Set::extract(
$this->{$join}->find('all', compact('conditions', 'recursive', 'fields')),
"{n}.{$join}." . $this->hasAndBelongsToMany[$assoc]['associationForeignKey']
);
}
foreach ($value as $update) {
if (!empty($update)) {
if (is_array($update)) {
$update[$this->hasAndBelongsToMany[$assoc]['foreignKey']] = $id;
$this->{$join}->create($update);
$this->{$join}->save();
} elseif (!in_array($update, $links)) {
$values = join(',', array(
$db->value($id, $this->getColumnType($this->primaryKey)),
$db->value($update)
));
$newValues[] = "({$values})";
unset ($values);
unset($values);
}
}
if (!empty($newValues)) {
$newValue[$assoc] = $newValues;
unset($newValues);
} else {
$newValue[$assoc] = array();
}
}
}
}
if (isset($joinTable) && is_array($newValue)) {
foreach ($newValue as $loopAssoc => $val) {
$table = $db->name($db->fullTableName($joinTable[$loopAssoc]));
$db->query("DELETE FROM {$table} WHERE {$mainKey[$loopAssoc]} = '{$id}'");
if (!empty($newValue[$loopAssoc])) {
$db->insertMulti($table, $fields[$loopAssoc], $newValue[$loopAssoc]);
if (!empty($newValues)) {
$fields = join(',', array(
$db->name($this->hasAndBelongsToMany[$assoc]['foreignKey']),
$db->name($this->hasAndBelongsToMany[$assoc]['associationForeignKey'])
));
$db->insertMulti($this->{$join}, $fields, $newValues);
}
}
}
@ -2427,6 +2434,24 @@ class Model extends Overloadable {
return null;
}
}
/**
* Gets the name and fields to be used by a join model. This allows specifying join fields in the association definition.
*
* @param object $model The model to be joined
* @param mixed $with The 'with' key of the model association
* @param array $keys Any join keys which must be merged with the keys queried
* @return array
*/
function joinModel($assoc, $keys = array()) {
if (is_string($assoc)) {
return array($assoc, array_keys($this->{$assoc}->schema()));
} elseif (is_array($assoc)) {
$with = key($assoc);
return array($with, array_unique(array_merge($assoc[$with], $keys)));
} else {
trigger_error(sprintf(__('Invalid join model settings in %s', true), $model->alias), E_USER_WARNING);
}
}
/**
* Before find callback
*

View file

@ -592,6 +592,33 @@ class ModelTest extends CakeTestCase {
'JoinThing' => array('doomed' => '1', 'something_id' => '3', 'something_else_id' => '1')))));
$this->assertEqual($result, $expected);
$result = $this->model->findById(1);
$expected = array(
'Something' => 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'),
'SomethingElse' => 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('doomed' => '1', 'something_id' => '1', 'something_else_id' => '2'))));
$this->assertEqual($result, $expected);
$this->model->hasAndBelongsToMany['SomethingElse']['unique'] = false;
$this->model->create(array(
'Something' => array('id' => 1),
'SomethingElse' => array(3, array('something_else_id' => 1, 'doomed' => '1'))
));
$ts = date('Y-m-d H:i:s');
$this->model->save();
$result = $this->model->findById(1);
$expected = array(
'Something' => array('id' => '1', 'title' => 'First Post', 'body' => 'First Post Body', 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => $ts),
'SomethingElse' => 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('doomed' => '1', 'something_id' => '1', 'something_else_id' => '2')),
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' => '1', 'something_else_id' => '1')),
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',
'JoinThing' => array('doomed' => null, 'something_id' => '1', 'something_else_id' => '3'))));
$this->assertEqual($result, $expected);
}
function testFindAllRecursiveSelfJoin() {
@ -951,6 +978,8 @@ class ModelTest extends CakeTestCase {
$result = Set::combine($this->model->find('all', array('order' => 'Article.title ASC', 'fields' => array('id', 'title', 'user_id'))), '{n}.Article.id', '{n}.Article.title', '{n}.Article.user_id');
$expected = array(1 => array(1 => 'First Article', 3 => 'Third Article'), 3 => array(2 => 'Second Article'));
$this->assertEqual($result, $expected);
$this->model =& new Apple();
}
function testFindField() {
@ -1867,9 +1896,7 @@ class ModelTest extends CakeTestCase {
$result = $this->model->Comment->create() && $this->model->Comment->save($data);
$this->assertTrue($result);
$data = array('Attachment' => array(
'comment_id' => '7', 'attachment' => 'newattachment.zip', 'created' => '2007-03-18 15:02:23', 'updated' => '2007-03-18 15:04:31'
));
$data = array('Attachment' => array('comment_id' => '7', 'attachment' => 'newattachment.zip', 'created' => '2007-03-18 15:02:23', 'updated' => '2007-03-18 15:04:31'));
$result = $this->model->Comment->Attachment->save($data);
$this->assertTrue($result);
@ -3140,6 +3167,10 @@ class ModelTest extends CakeTestCase {
$this->assertEqual($afterFindData, $noAfterFindData);
}
function testAfterFindAssociation() {
}
function testDeconstructFields() {
$this->model =& new Apple();

View file

@ -478,9 +478,6 @@ class FormHelperTest extends CakeTestCase {
$this->assertPattern('/<legend>The Legend<\/legend>/', $result);
$View = ClassRegistry::getObject('view');
$View->testing = true;
$this->Form->testing = true;
unset($View->testing, $this->Form->testing);
$this->Form->params['prefix'] = 'admin';
$this->Form->action = 'admin_edit';

View file

@ -268,7 +268,6 @@ class ViewTest extends UnitTestCase {
ob_start();
$View->render('index');
$result = ob_get_clean();
//pr($result);
$expected = '
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">

View file

@ -35,7 +35,7 @@
class JoinThingFixture extends CakeTestFixture {
var $name = 'JoinThing';
var $fields = array(
'id' => array('type' => 'integer', 'key' => 'primary'),
'id' => array('type' => 'integer', 'key' => 'primary', 'extra'=> 'auto_increment'),
'something_id' => array('type' => 'integer', 'length' => 10, 'null' => true),
'something_else_id' => array('type' => 'integer', 'default' => ''),
'doomed' => array('type' => 'boolean'),