Skip to content
This repository has been archived by the owner on Jan 21, 2020. It is now read-only.

Commit

Permalink
Merge pull request #1836 from fvovan/issue-1836
Browse files Browse the repository at this point in the history
Add ability to use 'replace' SQL operator in CM_Model_StorageAdapter_Database
  • Loading branch information
fvovan committed Jul 23, 2015
2 parents 5473aac + 3c012c2 commit 3c6eb3f
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 33 deletions.
33 changes: 27 additions & 6 deletions library/CM/Model/Abstract.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,28 +50,49 @@ final protected function _construct(array $id = null, array $data = null) {
$this->_getData(); // Make sure data can be loaded
}

public function commit() {
/**
* @param bool|null $useReplace
* @throws CM_Exception_Invalid
* @throws CM_Exception_InvalidParam
* @throws CM_Exception_Nonexistent
* @throws CM_Exception_NotImplemented
*/
public function commit($useReplace = null) {
$useReplace = (boolean) $useReplace;

$persistence = $this->_getPersistence();
if (!$persistence) {
throw new CM_Exception_Invalid('Cannot create model without persistence');
}

$type = $this->getType();
$dataSchema = $this->_getSchemaData();
if ($this->hasIdRaw()) {
$dataSchema = $this->_getSchemaData();
if (!empty($dataSchema)) {
$persistence->save($this->getType(), $this->getIdRaw(), $dataSchema);
$persistence->save($type, $this->getIdRaw(), $dataSchema);
}

if ($cache = $this->_getCache()) {
$cache->save($this->getType(), $this->getIdRaw(), $this->_getData());
$cache->save($type, $this->getIdRaw(), $this->_getData());
}
$this->_onChange();
} else {
$this->_validateFields($this->_getData(), true);
$this->_id = self::_castIdRaw($persistence->create($this->getType(), $this->_getSchemaData()));
if ($useReplace) {
if (!$persistence instanceof CM_Model_StorageAdapter_ReplaceableInterface) {
$adapterName = get_class($persistence);
throw new CM_Exception_NotImplemented("Param `useReplace` is not allowed with {$adapterName}");
}
$idRaw = $persistence->replace($type, $dataSchema);
} else {
$idRaw = $persistence->create($type, $dataSchema);
}

$this->_id = self::_castIdRaw($idRaw);

if ($cache = $this->_getCache()) {
$this->_loadAssets(true);
$cache->save($this->getType(), $this->getIdRaw(), $this->_getData());
$cache->save($type, $this->getIdRaw(), $this->_getData());
}
$this->_onChange();
$this->_changeContainingCacheables();
Expand Down
12 changes: 10 additions & 2 deletions library/CM/Model/StorageAdapter/Database.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

class CM_Model_StorageAdapter_Database extends CM_Model_StorageAdapter_AbstractAdapter implements CM_Model_StorageAdapter_FindableInterface {
class CM_Model_StorageAdapter_Database extends CM_Model_StorageAdapter_AbstractAdapter implements CM_Model_StorageAdapter_FindableInterface, CM_Model_StorageAdapter_ReplaceableInterface {

public function load($type, array $id) {
return CM_Db_Db::select($this->_getTableName($type), '*', $id)->fetch();
Expand Down Expand Up @@ -46,13 +46,21 @@ public function save($type, array $id, array $data) {
}

public function create($type, array $data) {
$id = CM_Db_Db::insert($this->_getTableName($type), $data);
$id = CM_Db_Db::insert($this->_getTableName($type), $data, null, null, 'INSERT');
if (null === $id) {
throw new CM_Exception_Invalid('Insert statement did not return an ID');
}
return array('id' => (int) $id);
}

public function replace($type, array $data) {
$id = CM_Db_Db::insert($this->_getTableName($type), $data, null, null, 'REPLACE');
if (null === $id) {
throw new CM_Exception_Invalid('Replace statement did not return an ID');
}
return array('id' => (int) $id);
}

public function delete($type, array $id) {
CM_Db_Db::delete($this->_getTableName($type), $id);
}
Expand Down
11 changes: 11 additions & 0 deletions library/CM/Model/StorageAdapter/ReplaceableInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

interface CM_Model_StorageAdapter_ReplaceableInterface {

/**
* @param int $type
* @param array $data
* @return array
*/
public function replace($type, array $data);
}
30 changes: 30 additions & 0 deletions tests/library/CM/Model/AbstractTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1144,6 +1144,21 @@ public function testDebugInfoWithoutId() {

$this->assertSame('CM_Model_DebugInfoMock2', $model->getDebugInfo());
}

public function testCommitWithReplace() {
CM_Config::get()->CM_Model_Abstract->types[CM_ModelMock3::getTypeStatic()] = 'CM_ModelMock3';
$dbModel = new CM_ModelMock3();
$dbModel->_set('foo', 'bar1');
$dbModel->commit(true);

$cacheModel = new CM_ModelMock4();
$cacheModel->_set('bar', 5);
$exception = $this->catchException(function () use ($cacheModel) {
$cacheModel->commit(true);
});
$this->isInstanceOf('CM_Exception_NotImplemented', $exception);
$this->assertSame('Param `useReplace` is not allowed with ' . CM_ModelMock4::getPersistenceClass(), $exception->getMessage());
}
}

class CM_ModelMock extends CM_Model_Abstract {
Expand Down Expand Up @@ -1321,3 +1336,18 @@ public static function getTypeStatic() {
return 4;
}
}

class CM_ModelMock4 extends CM_Model_Abstract {

public function _getSchema() {
return new CM_Model_Schema_Definition(array('bar' => array('type' => 'int')));
}

public static function getPersistenceClass() {
return 'CM_Model_StorageAdapter_Cache';
}

public static function getTypeStatic() {
return 5;
}
}
79 changes: 54 additions & 25 deletions tests/library/CM/Model/StorageAdapter/DatabaseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ public static function setupBeforeClass() {
CM_Db_Db::exec("CREATE TABLE `mock_modelStorageAdapter` (
`id` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
`foo` VARCHAR(32),
`bar` INT
`bar` INT,
`baz` INT NOT NULL,
`qux` INT NOT NULL,
UNIQUE `unq_baz_qux` (`baz`, `qux`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
");
}
Expand Down Expand Up @@ -34,24 +37,27 @@ public function testGetTableName() {
}

public function testLoad() {
$id1 = CM_Db_Db::insert('mock_modelStorageAdapter', array('foo' => 'foo1', 'bar' => 1));
$id2 = CM_Db_Db::insert('mock_modelStorageAdapter', array('foo' => 'foo2', 'bar' => 2));
$id1 = CM_Db_Db::insert('mock_modelStorageAdapter', array('foo' => 'foo1', 'bar' => 1, 'baz' => 1, 'qux' => 2));
$id2 = CM_Db_Db::insert('mock_modelStorageAdapter', array('foo' => 'foo2', 'bar' => 2, 'baz' => 1, 'qux' => 3));
$type = 99;

$adapter = $this->getMockBuilder('CM_Model_StorageAdapter_Database')->setMethods(array('_getTableName'))->getMock();
$adapter->expects($this->any())->method('_getTableName')->will($this->returnValue('mock_modelStorageAdapter'));
/** @var CM_Model_StorageAdapter_Database $adapter */

$this->assertSame(array('id' => $id1, 'foo' => 'foo1', 'bar' => '1'), $adapter->load($type, array('id' => $id1)));
$this->assertSame(array('id' => $id1, 'foo' => 'foo1', 'bar' => '1'), $adapter->load($type, array('id' => $id1, 'foo' => 'foo1')));
$this->assertSame(array('id' => $id2, 'foo' => 'foo2', 'bar' => '2'), $adapter->load($type, array('id' => $id2)));
$this->assertSame(array('id' => $id1, 'foo' => 'foo1', 'bar' => '1', 'baz' => '1', 'qux' => '2'), $adapter->load($type, array('id' => $id1)));
$this->assertSame(
array('id' => $id1, 'foo' => 'foo1', 'bar' => '1', 'baz' => '1', 'qux' => '2'),
$adapter->load($type, array('id' => $id1, 'foo' => 'foo1'))
);
$this->assertSame(array('id' => $id2, 'foo' => 'foo2', 'bar' => '2', 'baz' => '1', 'qux' => '3'), $adapter->load($type, array('id' => $id2)));
$this->assertFalse($adapter->load($type, array('id' => '9999')));
$this->assertFalse($adapter->load($type, array('id' => $id1, 'foo' => '9999')));
}

public function testSave() {
$id1 = CM_Db_Db::insert('mock_modelStorageAdapter', array('foo' => 'foo1', 'bar' => 1));
$id2 = CM_Db_Db::insert('mock_modelStorageAdapter', array('foo' => 'foo2', 'bar' => 2));
$id1 = CM_Db_Db::insert('mock_modelStorageAdapter', array('foo' => 'foo1', 'bar' => 1, 'baz' => 1, 'qux' => 2));
$id2 = CM_Db_Db::insert('mock_modelStorageAdapter', array('foo' => 'foo2', 'bar' => 2, 'baz' => 1, 'qux' => 3));
$type = 99;

$adapter = $this->getMockBuilder('CM_Model_StorageAdapter_Database')->setMethods(array('_getTableName'))->getMock();
Expand Down Expand Up @@ -81,6 +87,29 @@ public function testCreate() {
$this->assertRow('mock_modelStorageAdapter', array('id' => $id['id'], 'foo' => 'foo1', 'bar' => '23'));
}

public function testReplace() {
$type = 99;

$adapter = $this->getMockBuilder('CM_Model_StorageAdapter_Database')->setMethods(array('_getTableName'))->getMock();
$adapter->expects($this->any())->method('_getTableName')->will($this->returnValue('mock_modelStorageAdapter'));
/** @var CM_Model_StorageAdapter_Database $adapter */

$id1 = $adapter->replace($type, array('foo' => 'foo1', 'bar' => 23, 'baz' => 1, 'qux' => 3));
$this->assertInternalType('array', $id1);
$this->assertCount(1, $id1);
$this->assertArrayHasKey('id', $id1);
$this->assertInternalType('int', $id1['id']);
$this->assertRow('mock_modelStorageAdapter', array('id' => $id1['id'], 'foo' => 'foo1', 'bar' => '23', 'baz' => '1', 'qux' => '3'));

$id2 = $adapter->replace($type, array('foo' => 'foo2', 'bar' => 24, 'baz' => 1, 'qux' => 3));
$this->assertInternalType('array', $id2);
$this->assertCount(1, $id2);
$this->assertArrayHasKey('id', $id2);
$this->assertInternalType('int', $id2['id']);
$this->assertNotRow('mock_modelStorageAdapter', array('id' => $id1['id']));
$this->assertRow('mock_modelStorageAdapter', array('id' => $id2['id'], 'foo' => 'foo2', 'bar' => '24', 'baz' => '1', 'qux' => '3'));
}

public function testDelete() {
$type = 99;

Expand All @@ -96,16 +125,16 @@ public function testDelete() {
}

public function testLoadMultiple() {
$id1 = CM_Db_Db::insert('mock_modelStorageAdapter', ['foo' => 'foo1', 'bar' => 1]);
$id2 = CM_Db_Db::insert('mock_modelStorageAdapter', ['foo' => 'foo2', 'bar' => 2]);
$id3 = CM_Db_Db::insert('mock_modelStorageAdapter', ['foo' => 'foo3', 'bar' => 3]);
$id4 = CM_Db_Db::insert('mock_modelStorageAdapter', ['foo' => 'foo4', 'bar' => 4]);
$id5 = CM_Db_Db::insert('mock_modelStorageAdapter', ['foo' => 'foo5', 'bar' => 5]);
$id6 = CM_Db_Db::insert('mock_modelStorageAdapter', ['foo' => 'foo6', 'bar' => 6]);
$id7 = CM_Db_Db::insert('mock_modelStorageAdapter', ['foo' => 'foo7', 'bar' => 7]);
$id8 = CM_Db_Db::insert('mock_modelStorageAdapter', ['foo' => 'foo8', 'bar' => 8]);
$id9 = CM_Db_Db::insert('mock_modelStorageAdapter', ['foo' => 'foo9', 'bar' => 9]);
$id10 = CM_Db_Db::insert('mock_modelStorageAdapter', ['foo' => 'foo10', 'bar' => 10]);
$id1 = CM_Db_Db::insert('mock_modelStorageAdapter', ['foo' => 'foo1', 'bar' => 1, 'baz' => 1, 'qux' => 1]);
$id2 = CM_Db_Db::insert('mock_modelStorageAdapter', ['foo' => 'foo2', 'bar' => 2, 'baz' => 1, 'qux' => 3]);
$id3 = CM_Db_Db::insert('mock_modelStorageAdapter', ['foo' => 'foo3', 'bar' => 3, 'baz' => 1, 'qux' => 4]);
$id4 = CM_Db_Db::insert('mock_modelStorageAdapter', ['foo' => 'foo4', 'bar' => 4, 'baz' => 2, 'qux' => 1]);
$id5 = CM_Db_Db::insert('mock_modelStorageAdapter', ['foo' => 'foo5', 'bar' => 5, 'baz' => 2, 'qux' => 2]);
$id6 = CM_Db_Db::insert('mock_modelStorageAdapter', ['foo' => 'foo6', 'bar' => 6, 'baz' => 3, 'qux' => 1]);
$id7 = CM_Db_Db::insert('mock_modelStorageAdapter', ['foo' => 'foo7', 'bar' => 7, 'baz' => 3, 'qux' => 2]);
$id8 = CM_Db_Db::insert('mock_modelStorageAdapter', ['foo' => 'foo8', 'bar' => 8, 'baz' => 3, 'qux' => 6]);
$id9 = CM_Db_Db::insert('mock_modelStorageAdapter', ['foo' => 'foo9', 'bar' => 9, 'baz' => 5, 'qux' => 3]);
$id10 = CM_Db_Db::insert('mock_modelStorageAdapter', ['foo' => 'foo10', 'bar' => 10, 'baz' => 6, 'qux' => 1]);

$adapter = $this->getMockBuilder('CM_Model_StorageAdapter_Database')->setMethods(['_getTableName'])->getMock();
$adapter->expects($this->any())->method('_getTableName')->will($this->returnValue('mock_modelStorageAdapter'));
Expand All @@ -119,11 +148,11 @@ public function testLoadMultiple() {
'foo2' => ['type' => 2, 'id' => ['id' => $id8]],
];
$expected = [
1 => ['id' => $id1, 'foo' => 'foo1', 'bar' => '1'],
'2' => ['id' => $id3, 'foo' => 'foo3', 'bar' => '3'],
'foo' => ['id' => $id8, 'foo' => 'foo8', 'bar' => '8'],
'foo2' => ['id' => $id8, 'foo' => 'foo8', 'bar' => '8'],
'bar' => ['id' => $id10, 'foo' => 'foo10', 'bar' => '10'],
1 => ['id' => $id1, 'foo' => 'foo1', 'bar' => '1', 'baz' => '1', 'qux' => '1'],
'2' => ['id' => $id3, 'foo' => 'foo3', 'bar' => '3', 'baz' => '1', 'qux' => '4'],
'foo' => ['id' => $id8, 'foo' => 'foo8', 'bar' => '8', 'baz' => '3', 'qux' => '6'],
'foo2' => ['id' => $id8, 'foo' => 'foo8', 'bar' => '8', 'baz' => '3', 'qux' => '6'],
'bar' => ['id' => $id10, 'foo' => 'foo10', 'bar' => '10', 'baz' => '6', 'qux' => '1'],
];

$values = $adapter->loadMultiple($idsTypes);
Expand All @@ -132,8 +161,8 @@ public function testLoadMultiple() {
}

public function testFindByData() {
$id1 = CM_Db_Db::insert('mock_modelStorageAdapter', array('foo' => 'foo1', 'bar' => 1));
$id2 = CM_Db_Db::insert('mock_modelStorageAdapter', array('foo' => 'foo2', 'bar' => 2));
$id1 = CM_Db_Db::insert('mock_modelStorageAdapter', array('foo' => 'foo1', 'bar' => 1, 'baz' => 1, 'qux' => 2));
$id2 = CM_Db_Db::insert('mock_modelStorageAdapter', array('foo' => 'foo2', 'bar' => 2, 'baz' => 1, 'qux' => 3));
$type = 99;

$adapter = $this->getMockBuilder('CM_Model_StorageAdapter_Database')->setMethods(array('_getTableName'))->getMock();
Expand Down

0 comments on commit 3c6eb3f

Please sign in to comment.