mirror of
https://github.com/kamilwylegala/cakephp2-php8.git
synced 2024-11-15 03:18:26 +00:00
Add support for having/lock options
This commit is contained in:
parent
4f98f01427
commit
923b73a7ba
7 changed files with 323 additions and 13 deletions
|
@ -591,4 +591,14 @@ class Sqlite extends DboSource {
|
|||
return $this->useNestedTransactions && version_compare($this->getVersion(), '3.6.8', '>=');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a locking hint for the given mode.
|
||||
* Sqlite Datasource doesn't support row-level locking.
|
||||
*
|
||||
* @param mixed $mode Lock mode
|
||||
* @return string|null
|
||||
*/
|
||||
public function getLockingHint($mode) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -526,6 +526,9 @@ class Sqlserver extends DboSource {
|
|||
extract($data);
|
||||
$fields = trim($fields);
|
||||
|
||||
$having = !empty($having) ? " $having" : '';
|
||||
$lock = !empty($lock) ? " $lock" : '';
|
||||
|
||||
if (strpos($limit, 'TOP') !== false && strpos($fields, 'DISTINCT ') === 0) {
|
||||
$limit = 'DISTINCT ' . trim($limit);
|
||||
$fields = substr($fields, 9);
|
||||
|
@ -547,7 +550,7 @@ class Sqlserver extends DboSource {
|
|||
$rowCounter = static::ROW_COUNTER;
|
||||
$sql = "SELECT {$limit} * FROM (
|
||||
SELECT {$fields}, ROW_NUMBER() OVER ({$order}) AS {$rowCounter}
|
||||
FROM {$table} {$alias} {$joins} {$conditions} {$group}
|
||||
FROM {$table} {$alias}{$lock} {$joins} {$conditions} {$group}{$having}
|
||||
) AS _cake_paging_
|
||||
WHERE _cake_paging_.{$rowCounter} > {$offset}
|
||||
ORDER BY _cake_paging_.{$rowCounter}
|
||||
|
@ -555,9 +558,9 @@ class Sqlserver extends DboSource {
|
|||
return trim($sql);
|
||||
}
|
||||
if (strpos($limit, 'FETCH') !== false) {
|
||||
return trim("SELECT {$fields} FROM {$table} {$alias} {$joins} {$conditions} {$group} {$order} {$limit}");
|
||||
return trim("SELECT {$fields} FROM {$table} {$alias}{$lock} {$joins} {$conditions} {$group}{$having} {$order} {$limit}");
|
||||
}
|
||||
return trim("SELECT {$limit} {$fields} FROM {$table} {$alias} {$joins} {$conditions} {$group} {$order}");
|
||||
return trim("SELECT {$limit} {$fields} FROM {$table} {$alias}{$lock} {$joins} {$conditions} {$group}{$having} {$order}");
|
||||
case "schema":
|
||||
extract($data);
|
||||
|
||||
|
@ -814,4 +817,17 @@ class Sqlserver extends DboSource {
|
|||
return $this->config['schema'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a locking hint for the given mode.
|
||||
* Currently, this method only returns WITH (UPDLOCK) when the mode is true.
|
||||
*
|
||||
* @param mixed $mode Lock mode
|
||||
* @return string|null
|
||||
*/
|
||||
public function getLockingHint($mode) {
|
||||
if ($mode !== true) {
|
||||
return null;
|
||||
}
|
||||
return ' WITH (UPDLOCK)';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -213,7 +213,9 @@ class DboSource extends DataSource {
|
|||
'limit' => null,
|
||||
'joins' => array(),
|
||||
'group' => null,
|
||||
'offset' => null
|
||||
'offset' => null,
|
||||
'having' => null,
|
||||
'lock' => null,
|
||||
);
|
||||
|
||||
/**
|
||||
|
@ -1732,7 +1734,9 @@ class DboSource extends DataSource {
|
|||
'joins' => $queryData['joins'],
|
||||
'conditions' => $queryData['conditions'],
|
||||
'order' => $queryData['order'],
|
||||
'group' => $queryData['group']
|
||||
'group' => $queryData['group'],
|
||||
'having' => $queryData['having'],
|
||||
'lock' => $queryData['lock'],
|
||||
),
|
||||
$Model
|
||||
);
|
||||
|
@ -2011,7 +2015,9 @@ class DboSource extends DataSource {
|
|||
'order' => $this->order($query['order'], 'ASC', $Model),
|
||||
'limit' => $this->limit($query['limit'], $query['offset']),
|
||||
'joins' => implode(' ', $query['joins']),
|
||||
'group' => $this->group($query['group'], $Model)
|
||||
'group' => $this->group($query['group'], $Model),
|
||||
'having' => $this->having($query['having'], true, $Model),
|
||||
'lock' => $this->getLockingHint($query['lock']),
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -2041,7 +2047,9 @@ class DboSource extends DataSource {
|
|||
|
||||
switch (strtolower($type)) {
|
||||
case 'select':
|
||||
return trim("SELECT {$fields} FROM {$table} {$alias} {$joins} {$conditions} {$group} {$order} {$limit}");
|
||||
$having = !empty($having) ? " $having" : '';
|
||||
$lock = !empty($lock) ? " $lock" : '';
|
||||
return trim("SELECT {$fields} FROM {$table} {$alias} {$joins} {$conditions} {$group}{$having} {$order} {$limit}{$lock}");
|
||||
case 'create':
|
||||
return "INSERT INTO {$table} ({$fields}) VALUES ({$values})";
|
||||
case 'update':
|
||||
|
@ -2549,6 +2557,8 @@ class DboSource extends DataSource {
|
|||
static $base = null;
|
||||
if ($base === null) {
|
||||
$base = array_fill_keys(array('conditions', 'fields', 'joins', 'order', 'limit', 'offset', 'group'), array());
|
||||
$base['having'] = null;
|
||||
$base['lock'] = null;
|
||||
$base['callbacks'] = null;
|
||||
}
|
||||
return (array)$data + $base;
|
||||
|
@ -3125,6 +3135,35 @@ class DboSource extends DataSource {
|
|||
return ' GROUP BY ' . $this->_quoteFields($fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a HAVING SQL clause.
|
||||
*
|
||||
* @param mixed $fields Array or string of conditions
|
||||
* @param bool $quoteValues If true, values should be quoted
|
||||
* @param Model $Model A reference to the Model instance making the query
|
||||
* @return string|null
|
||||
*/
|
||||
public function having($fields, $quoteValues = true, Model $Model = null) {
|
||||
if (!$fields) {
|
||||
return null;
|
||||
}
|
||||
return ' HAVING ' . $this->conditions($fields, $quoteValues, false, $Model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a locking hint for the given mode.
|
||||
* Currently, this method only returns FOR UPDATE when the mode is true.
|
||||
*
|
||||
* @param mixed $mode Lock mode
|
||||
* @return string|null
|
||||
*/
|
||||
public function getLockingHint($mode) {
|
||||
if ($mode !== true) {
|
||||
return null;
|
||||
}
|
||||
return ' FOR UPDATE';
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects database, kills the connection and says the connection is closed.
|
||||
*
|
||||
|
|
|
@ -626,4 +626,25 @@ SQL;
|
|||
$this->assertEquals($expected, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Sqlite Datasource doesn't support locking hint
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testBuildStatementWithoutLockingHint() {
|
||||
$model = new TestModel();
|
||||
$sql = $this->Dbo->buildStatement(
|
||||
array(
|
||||
'fields' => array('id'),
|
||||
'table' => 'users',
|
||||
'alias' => 'User',
|
||||
'order' => array('id'),
|
||||
'limit' => 1,
|
||||
'lock' => true,
|
||||
),
|
||||
$model
|
||||
);
|
||||
$expected = 'SELECT id FROM users AS "User" WHERE 1 = 1 ORDER BY "id" ASC LIMIT 1';
|
||||
$this->assertEquals($expected, $sql);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -707,4 +707,117 @@ SQL;
|
|||
$this->assertEquals(2, $result['value']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test build statement
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testBuildStatement() {
|
||||
$db = $this->getMock('SqlserverTestDb', array('getVersion'), array($this->Dbo->config));
|
||||
|
||||
$db->expects($this->any())
|
||||
->method('getVersion')
|
||||
->will($this->returnValue('11.00.0000'));
|
||||
|
||||
// HAVING
|
||||
$query = array(
|
||||
'fields' => array('user_id', 'COUNT(*) AS count'),
|
||||
'table' => 'articles',
|
||||
'alias' => 'Article',
|
||||
'group' => 'user_id',
|
||||
'order' => array('COUNT(*)' => 'DESC'),
|
||||
'limit' => 5,
|
||||
'having' => array('COUNT(*) >' => 10),
|
||||
);
|
||||
|
||||
$sql = $db->buildStatement($query, $this->model);
|
||||
$expected = 'SELECT TOP 5 user_id, COUNT(*) AS count FROM articles AS [Article] WHERE 1 = 1 GROUP BY user_id HAVING COUNT(*) > 10 ORDER BY COUNT(*) DESC';
|
||||
$this->assertEquals($expected, $sql);
|
||||
|
||||
$sql = $db->buildStatement(array('offset' => 15) + $query, $this->model);
|
||||
$expected = 'SELECT user_id, COUNT(*) AS count FROM articles AS [Article] WHERE 1 = 1 GROUP BY user_id HAVING COUNT(*) > 10 ORDER BY COUNT(*) DESC OFFSET 15 ROWS FETCH FIRST 5 ROWS ONLY';
|
||||
$this->assertEquals($expected, $sql);
|
||||
|
||||
// WITH (UPDLOCK)
|
||||
$query = array(
|
||||
'fields' => array('id'),
|
||||
'table' => 'users',
|
||||
'alias' => 'User',
|
||||
'order' => array('id'),
|
||||
'limit' => 1,
|
||||
'lock' => true,
|
||||
);
|
||||
|
||||
$sql = $db->buildStatement($query, $this->model);
|
||||
$expected = 'SELECT TOP 1 id FROM users AS [User] WITH (UPDLOCK) WHERE 1 = 1 ORDER BY [id] ASC';
|
||||
$this->assertEquals($expected, $sql);
|
||||
|
||||
$sql = $db->buildStatement(array('offset' => 15) + $query, $this->model);
|
||||
$expected = 'SELECT id FROM users AS [User] WITH (UPDLOCK) WHERE 1 = 1 ORDER BY [id] ASC OFFSET 15 ROWS FETCH FIRST 1 ROWS ONLY';
|
||||
$this->assertEquals($expected, $sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test build statement with legacy version
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testBuildStatementWithLegacyVersion() {
|
||||
$db = $this->getMock('SqlserverTestDb', array('getVersion'), array($this->Dbo->config));
|
||||
|
||||
$db->expects($this->any())
|
||||
->method('getVersion')
|
||||
->will($this->returnValue('10.00.0000'));
|
||||
|
||||
// HAVING
|
||||
$query = array(
|
||||
'fields' => array('user_id', 'COUNT(*) AS count'),
|
||||
'table' => 'articles',
|
||||
'alias' => 'Article',
|
||||
'group' => 'user_id',
|
||||
'order' => array('COUNT(*)' => 'DESC'),
|
||||
'limit' => 5,
|
||||
'having' => array('COUNT(*) >' => 10),
|
||||
);
|
||||
|
||||
$sql = $db->buildStatement($query, $this->model);
|
||||
$expected = 'SELECT TOP 5 user_id, COUNT(*) AS count FROM articles AS [Article] WHERE 1 = 1 GROUP BY user_id HAVING COUNT(*) > 10 ORDER BY COUNT(*) DESC';
|
||||
$this->assertEquals($expected, $sql);
|
||||
|
||||
$sql = $db->buildStatement(array('offset' => 15) + $query, $this->model);
|
||||
$expected = <<<SQL
|
||||
SELECT TOP 5 * FROM (
|
||||
SELECT user_id, COUNT(*) AS count, ROW_NUMBER() OVER ( ORDER BY COUNT(*) DESC) AS _cake_page_rownum_
|
||||
FROM articles AS [Article] WHERE 1 = 1 GROUP BY user_id HAVING COUNT(*) > 10
|
||||
) AS _cake_paging_
|
||||
WHERE _cake_paging_._cake_page_rownum_ > 15
|
||||
ORDER BY _cake_paging_._cake_page_rownum_
|
||||
SQL;
|
||||
$this->assertEquals($expected, preg_replace('/^\s+|\s+$/m', '', $sql));
|
||||
|
||||
// WITH (UPDLOCK)
|
||||
$query = array(
|
||||
'fields' => array('id'),
|
||||
'table' => 'users',
|
||||
'alias' => 'User',
|
||||
'order' => array('id'),
|
||||
'limit' => 1,
|
||||
'lock' => true,
|
||||
);
|
||||
|
||||
$sql = $db->buildStatement($query, $this->model);
|
||||
$expected = 'SELECT TOP 1 id FROM users AS [User] WITH (UPDLOCK) WHERE 1 = 1 ORDER BY [id] ASC';
|
||||
$this->assertEquals($expected, $sql);
|
||||
|
||||
$sql = $db->buildStatement(array('offset' => 15) + $query, $this->model);
|
||||
$expected = <<<SQL
|
||||
SELECT TOP 1 * FROM (
|
||||
SELECT id, ROW_NUMBER() OVER ( ORDER BY [id] ASC) AS _cake_page_rownum_
|
||||
FROM users AS [User] WITH (UPDLOCK) WHERE 1 = 1
|
||||
) AS _cake_paging_
|
||||
WHERE _cake_paging_._cake_page_rownum_ > 15
|
||||
ORDER BY _cake_paging_._cake_page_rownum_
|
||||
SQL;
|
||||
$this->assertEquals($expected, preg_replace('/^\s+|\s+$/m', '', $sql));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1294,6 +1294,33 @@ class DboSourceTest extends CakeTestCase {
|
|||
$this->assertEquals(' GROUP BY created', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test having method
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testHaving() {
|
||||
$this->loadFixtures('User');
|
||||
|
||||
$result = $this->testDb->having(array('COUNT(*) >' => 0));
|
||||
$this->assertEquals(' HAVING COUNT(*) > 0', $result);
|
||||
|
||||
$User = ClassRegistry::init('User');
|
||||
$result = $this->testDb->having('COUNT(User.id) > 0', true, $User);
|
||||
$this->assertEquals(' HAVING COUNT(`User`.`id`) > 0', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getLockingHint method
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testGetLockingHint() {
|
||||
$this->assertEquals(' FOR UPDATE', $this->testDb->getLockingHint(true));
|
||||
$this->assertNull($this->testDb->getLockingHint(false));
|
||||
$this->assertNull($this->testDb->getLockingHint(null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getting the last error.
|
||||
*
|
||||
|
@ -1427,24 +1454,56 @@ class DboSourceTest extends CakeTestCase {
|
|||
*/
|
||||
public function testBuildStatementDefaults() {
|
||||
$conn = $this->getMock('MockPDO', array('quote'));
|
||||
$conn->expects($this->at(0))
|
||||
$conn->expects($this->any())
|
||||
->method('quote')
|
||||
->will($this->returnValue('foo bar'));
|
||||
->will($this->returnArgument(0));
|
||||
$db = new DboTestSource();
|
||||
$db->setConnection($conn);
|
||||
|
||||
$subQuery = $db->buildStatement(
|
||||
array(
|
||||
'fields' => array('DISTINCT(AssetsTag.asset_id)'),
|
||||
'table' => "assets_tags",
|
||||
'alias' => "AssetsTag",
|
||||
'conditions' => array("Tag.name" => 'foo bar'),
|
||||
'table' => 'assets_tags',
|
||||
'alias' => 'AssetsTag',
|
||||
'conditions' => array('Tag.name' => 'foo bar'),
|
||||
'limit' => null,
|
||||
'group' => "AssetsTag.asset_id"
|
||||
'group' => 'AssetsTag.asset_id'
|
||||
),
|
||||
$this->Model
|
||||
);
|
||||
$expected = 'SELECT DISTINCT(AssetsTag.asset_id) FROM assets_tags AS AssetsTag WHERE Tag.name = foo bar GROUP BY AssetsTag.asset_id';
|
||||
$this->assertEquals($expected, $subQuery);
|
||||
|
||||
// HAVING
|
||||
$sql = $db->buildStatement(
|
||||
array(
|
||||
'fields' => array('user_id', 'COUNT(*) AS count'),
|
||||
'table' => 'articles',
|
||||
'alias' => 'Article',
|
||||
'group' => 'user_id',
|
||||
'order' => array('COUNT(*)' => 'DESC'),
|
||||
'limit' => 5,
|
||||
'having' => array('COUNT(*) >' => 10),
|
||||
),
|
||||
$this->Model
|
||||
);
|
||||
$expected = 'SELECT user_id, COUNT(*) AS count FROM articles AS Article WHERE 1 = 1 GROUP BY user_id HAVING COUNT(*) > 10 ORDER BY COUNT(*) DESC LIMIT 5';
|
||||
$this->assertEquals($expected, $sql);
|
||||
|
||||
// FOR UPDATE
|
||||
$sql = $db->buildStatement(
|
||||
array(
|
||||
'fields' => array('id'),
|
||||
'table' => 'users',
|
||||
'alias' => 'User',
|
||||
'order' => array('id'),
|
||||
'limit' => 1,
|
||||
'lock' => true,
|
||||
),
|
||||
$this->Model
|
||||
);
|
||||
$expected = 'SELECT id FROM users AS User WHERE 1 = 1 ORDER BY id ASC LIMIT 1 FOR UPDATE';
|
||||
$this->assertEquals($expected, $sql);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2024,4 +2083,29 @@ class DboSourceTest extends CakeTestCase {
|
|||
$result = $this->db->length("enum('One Value','ANOTHER ... VALUE ...')");
|
||||
$this->assertEquals(21, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test find with locking hint
|
||||
*/
|
||||
public function testFindWithLockingHint() {
|
||||
$db = $this->getMock('DboTestSource', array('connect', '_execute', 'execute', 'describ'));
|
||||
|
||||
$Test = $this->getMock('Test', array('getDataSource'));
|
||||
$Test->expects($this->any())
|
||||
->method('getDataSource')
|
||||
->will($this->returnValue($db));
|
||||
|
||||
$expected = 'SELECT Test.id FROM tests AS Test WHERE id = 1 ORDER BY Test.id ASC LIMIT 1 FOR UPDATE';
|
||||
|
||||
$db->expects($this->once())
|
||||
->method('execute')
|
||||
->with($expected);
|
||||
|
||||
$Test->find('first', array(
|
||||
'recursive' => -1,
|
||||
'fields' => array('id'),
|
||||
'conditions' => array('id' => 1),
|
||||
'lock' => true,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -352,6 +352,33 @@ class ModelReadTest extends BaseModelTest {
|
|||
$this->assertEquals($expected, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test find method with having clause
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testHaving() {
|
||||
$this->loadFixtures('Comment');
|
||||
|
||||
$Comment = ClassRegistry::init('Comment');
|
||||
$comments = $Comment->find('all', array(
|
||||
'fields' => array('user_id', 'COUNT(*) AS count'),
|
||||
'group' => array('user_id'),
|
||||
'having' => array('COUNT(*) >' => 1),
|
||||
'order' => array('COUNT(*)' => 'DESC'),
|
||||
'recursive' => -1,
|
||||
));
|
||||
|
||||
$results = Hash::combine($comments, '{n}.Comment.user_id', '{n}.0.count');
|
||||
|
||||
$expected = array(
|
||||
1 => 3,
|
||||
2 => 2,
|
||||
);
|
||||
|
||||
$this->assertEquals($expected, $results);
|
||||
}
|
||||
|
||||
/**
|
||||
* testOldQuery method
|
||||
*
|
||||
|
|
Loading…
Reference in a new issue