Merge branch 'master' into 2.6

Conflicts:
	lib/Cake/Model/Model.php
This commit is contained in:
mark_story 2014-07-16 23:11:58 -04:00
commit 051d78c1a5
8 changed files with 172 additions and 9 deletions

View file

@ -33,14 +33,14 @@ chance of keeping on top of things.
* Core test cases should continue to pass. You can run tests locally or enable
[travis-ci](https://travis-ci.org/) for your fork, so all tests and codesniffs
will be executed.
* Your work should apply the CakePHP coding standards.
* Your work should apply the [CakePHP coding standards](http://book.cakephp.org/2.0/en/contributing/cakephp-coding-conventions.html).
## Which branch to base the work
* Bugfix branches will be based on master.
* New features that are backwards compatible will be based on next minor release
branch.
* New features or other non-BC changes will go in the next major release branch.
* New features or other non backwards compatible changes will go in the next major release branch.
## Submitting Changes
@ -51,7 +51,7 @@ chance of keeping on top of things.
## Test cases and codesniffer
CakePHP tests requires [PHPUnit](http://www.phpunit.de/manual/current/en/installation.html)
3.5 or higher. To run the test cases locally use the following command:
3.7, version 4 is not compatible. To run the test cases locally use the following command:
./lib/Cake/Console/cake test core AllTests --stderr
@ -60,7 +60,7 @@ To run the sniffs for CakePHP coding standards:
phpcs -p --extensions=php --standard=CakePHP ./lib/Cake
Check the [cakephp-codesniffer](https://github.com/cakephp/cakephp-codesniffer)
repository to setup the CakePHP standard. The README contains installation info
repository to setup the CakePHP standard. The [README](https://github.com/cakephp/cakephp-codesniffer/blob/master/README.mdown) contains installation info
for the sniff and phpcs.
# Additional Resources

View file

@ -261,6 +261,9 @@ class Postgres extends DboSource {
$this->_sequenceMap[$table][$c->name] = $sequenceName;
}
}
if ($fields[$c->name]['type'] === 'timestamp' && $fields[$c->name]['default'] === '') {
$fields[$c->name]['default'] = null;
}
if ($fields[$c->name]['type'] === 'boolean' && !empty($fields[$c->name]['default'])) {
$fields[$c->name]['default'] = constant($fields[$c->name]['default']);
}

View file

@ -2399,7 +2399,7 @@ class DboSource extends DataSource {
return $conditions;
}
$exists = $Model->exists();
if (!$exists && $conditions !== null) {
if (!$exists && ($conditions !== null || !empty($Model->__safeUpdateMode))) {
return false;
} elseif (!$exists) {
return null;

View file

@ -593,6 +593,14 @@ class Model extends Object implements CakeEventListener {
*/
public $__backContainableAssociation = array();
/**
* Safe update mode
* If true, this prevents Model::save() from generating a query with WHERE 1 = 1 on race condition.
*
* @var bool
*/
public $__safeUpdateMode = false;
// @codingStandardsIgnoreEnd
/**
@ -1689,6 +1697,7 @@ class Model extends Object implements CakeEventListener {
* @param array $fieldList List of fields to allow to be saved
* @return mixed On success Model::$data if its not empty or true, false on failure
* @throws Exception
* @throws PDOException
* @link http://book.cakephp.org/2.0/en/models/saving-your-data.html
*/
public function save($data = null, $validate = true, $fieldList = array()) {
@ -1869,7 +1878,14 @@ class Model extends Object implements CakeEventListener {
$cache = $this->_prepareUpdateFields(array_combine($fields, $values));
if (!empty($this->id)) {
$success = (bool)$db->update($this, $fields, $values);
$this->__safeUpdateMode = true;
try {
$success = (bool)$db->update($this, $fields, $values);
} catch (Exception $e) {
$this->__safeUpdateMode = false;
throw $e;
}
$this->__safeUpdateMode = false;
} else {
if (empty($this->data[$this->alias][$this->primaryKey]) && $this->_isUUIDField($this->primaryKey)) {
if (array_key_exists($this->primaryKey, $this->data[$this->alias])) {

View file

@ -162,8 +162,8 @@ class PostgresClientTestModel extends Model {
'id' => array('type' => 'integer', 'null' => '', 'default' => '', 'length' => '8', 'key' => 'primary'),
'name' => array('type' => 'string', 'null' => '', 'default' => '', 'length' => '255'),
'email' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'),
'created' => array('type' => 'datetime', 'null' => '1', 'default' => '', 'length' => ''),
'updated' => array('type' => 'datetime', 'null' => '1', 'default' => '', 'length' => null)
'created' => array('type' => 'datetime', 'null' => true, 'default' => null, 'length' => ''),
'updated' => array('type' => 'datetime', 'null' => true, 'default' => null, 'length' => null)
);
}
@ -551,6 +551,7 @@ class PostgresTest extends CakeTestCase {
'connection' => 'test',
'models' => array('DatatypeTest')
));
$schema->tables = array(
'datatype_tests' => $result['tables']['missing']['datatype_tests']
);
@ -1098,4 +1099,49 @@ class PostgresTest extends CakeTestCase {
$this->assertNotContains($scientificNotation, $result);
}
/**
* Test describe() behavior for timestamp columns.
*
* @return void
*/
public function testDescribeTimestamp() {
$this->loadFixtures('User');
$model = ClassRegistry::init('User');
$result = $this->Dbo->describe($model);
$expected = array(
'id' => array(
'type' => 'integer',
'null' => false,
'default' => null,
'length' => 11,
'key' => 'primary'
),
'user' => array(
'type' => 'string',
'null' => true,
'default' => null,
'length' => 255
),
'password' => array(
'type' => 'string',
'null' => true,
'default' => null,
'length' => 255
),
'created' => array(
'type' => 'datetime',
'null' => true,
'default' => null,
'length' => null
),
'updated' => array(
'type' => 'datetime',
'null' => true,
'default' => null,
'length' => null
)
);
$this->assertEquals($expected, $result);
}
}

View file

@ -33,6 +33,9 @@ require_once dirname(dirname(__FILE__)) . DS . 'models.php';
*/
class MockPDO extends PDO {
/**
* Constructor.
*/
public function __construct() {
}
@ -1412,4 +1415,45 @@ class DboSourceTest extends CakeTestCase {
$result = $db->insertMulti('articles', array_keys($data[0]), $data);
$this->assertTrue($result, 'Data was saved');
}
/**
* Test defaultConditions()
*
* @return void
*/
public function testDefaultConditions() {
$this->loadFixtures('Article');
$Article = ClassRegistry::init('Article');
$db = $Article->getDataSource();
// Creates a default set of conditions from the model if $conditions is null/empty.
$Article->id = 1;
$result = $db->defaultConditions($Article, null);
$this->assertEquals(array('Article.id' => 1), $result);
// $useAlias == false
$Article->id = 1;
$result = $db->defaultConditions($Article, null, false);
$this->assertEquals(array($db->fullTableName($Article, false) . '.id' => 1), $result);
// If conditions are supplied then they will be returned.
$Article->id = 1;
$result = $db->defaultConditions($Article, array('Article.title' => 'First article'));
$this->assertEquals(array('Article.title' => 'First article'), $result);
// If a model doesn't exist and no conditions were provided either null or false will be returned based on what was input.
$Article->id = 1000000;
$result = $db->defaultConditions($Article, null);
$this->assertNull($result);
$Article->id = 1000000;
$result = $db->defaultConditions($Article, false);
$this->assertFalse($result);
// Safe update mode
$Article->id = 1000000;
$Article->__safeUpdateMode = true;
$result = $db->defaultConditions($Article, null);
$this->assertFalse($result);
}
}

View file

@ -6625,6 +6625,11 @@ class ModelWriteTest extends BaseModelTest {
$this->assertEquals(array(6, 4, 5, 2), $result);
}
/**
* testToggleBoolFields method
*
* @return void
*/
public function testToggleBoolFields() {
$this->loadFixtures('CounterCacheUser', 'CounterCachePost');
$Post = new CounterCachePost();
@ -7515,4 +7520,53 @@ class ModelWriteTest extends BaseModelTest {
$this->assertFalse(isset($model->data['Bid']['name']));
$this->assertFalse(isset($model->data['Bid']['message_id']));
}
/**
* Test that Model::save() doesn't generate a query with WHERE 1 = 1 on race condition.
*
* @link https://github.com/cakephp/cakephp/issues/3857
* @return void
*/
public function testSafeUpdateMode() {
$this->loadFixtures('User');
$User = ClassRegistry::init('User');
$this->assertFalse($User->__safeUpdateMode);
$User->getEventManager()->attach(array($this, 'deleteMe'), 'Model.beforeSave');
$User->id = 1;
$User->set(array('user' => 'nobody'));
$User->save();
$users = $User->find('list', array('fields' => 'User.user'));
$expected = array(
2 => 'nate',
3 => 'larry',
4 => 'garrett',
);
$this->assertEquals($expected, $users);
$this->assertFalse($User->__safeUpdateMode);
$User->id = 2;
$User->set(array('user' => $User->getDataSource()->expression('PDO_EXCEPTION()')));
try {
$User->save(null, false);
$this->fail('No exception thrown');
} catch (PDOException $e) {
$this->assertFalse($User->__safeUpdateMode);
}
}
/**
* Emulates race condition
*
* @param CakeEvent $event containing the Model
* @return void
*/
public function deleteMe($event) {
$Model = $event->subject;
$Model->getDataSource()->delete($Model, array($Model->alias . '.' . $Model->primaryKey => $Model->id));
}
}

View file

@ -289,7 +289,7 @@ class CakeTestFixture {
foreach ($this->records as $record) {
$merge = array_values(array_merge($default, $record));
if (count($fields) !== count($merge)) {
throw new CakeException('Fixture invalid: Count of fields does not match count of values');
throw new CakeException('Fixture invalid: Count of fields does not match count of values in ' . get_class($this));
}
$values[] = $merge;
}