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

Fixes Issue #318 - SelectDecorator - subselect limit parameter not prefixed #329

Merged
merged 2 commits into from
Aug 7, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ All notable changes to this project will be documented in this file, in reverse

### Fixed

- Nothing.
- [#329](https://github.com/zendframework/zend-db/pull/329) fix Exception
thrown when calling prepareStatementForSqlObject on a Select with a
sub-Select that has limit and/or offset set

## 2.9.3 - 2018-04-09

Expand Down
4 changes: 2 additions & 2 deletions src/Sql/Platform/Mysql/SelectDecorator.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ protected function processLimit(
if ($parameterContainer) {
$paramPrefix = $this->processInfo['paramPrefix'];
$parameterContainer->offsetSet($paramPrefix . 'limit', $this->limit, ParameterContainer::TYPE_INTEGER);
return [$driver->formatParameterName('limit')];
return [$driver->formatParameterName($paramPrefix . 'limit')];
}

return [$this->limit];
Expand All @@ -69,7 +69,7 @@ protected function processOffset(
if ($parameterContainer) {
$paramPrefix = $this->processInfo['paramPrefix'];
$parameterContainer->offsetSet($paramPrefix . 'offset', $this->offset, ParameterContainer::TYPE_INTEGER);
return [$driver->formatParameterName('offset')];
return [$driver->formatParameterName($paramPrefix . 'offset')];
}

return [$this->offset];
Expand Down
90 changes: 84 additions & 6 deletions test/unit/Sql/Platform/Mysql/SelectDecoratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@
namespace ZendTest\Db\Sql\Platform\Mysql;

use PHPUnit\Framework\TestCase;
use Zend\Db\Adapter\Driver\Mysqli\Connection;
use ZendTest\Db\TestAsset\TrustingMysqlPlatform;
use Zend\Db\Adapter\ParameterContainer;
use Zend\Db\Adapter\Platform\Mysql as MysqlPlatform;
use Zend\Db\Sql\Expression;
use Zend\Db\Sql\Sql;
use Zend\Db\Sql\Platform\Mysql\SelectDecorator;
use Zend\Db\Sql\Select;

Expand Down Expand Up @@ -54,6 +57,40 @@ public function testPrepareStatement(Select $select, $expectedSql, $expectedPara
self::assertEquals($expectedParams, $parameterContainer->getNamedArray());
}

/**
* @testdox integration test: Testing SelectDecorator will use Select an internal state to prepare
* a proper limit/offset sql statement
* @covers \Zend\Db\Sql\Platform\Mysql\SelectDecorator::prepareStatement
* @covers \Zend\Db\Sql\Platform\Mysql\SelectDecorator::processLimit
* @covers \Zend\Db\Sql\Platform\Mysql\SelectDecorator::processOffset
* @dataProvider dataProvider
*/
public function testPrepareStatementForSqlObject(
Select $select,
$ignore,
$expectedParams,
$alsoIgnore,
$expectedPdoSql
) {
// mock the adapter, driver, and parts
$newStatement = new \Zend\Db\Adapter\Driver\Mysqli\Statement();
$driver = new \Zend\Db\Adapter\Driver\Pdo\Pdo(new \Zend\Db\Adapter\Driver\Pdo\Connection());
$mockAdapter = $this->getMockBuilder('Zend\Db\Adapter\Adapter')
->setConstructorArgs([$driver, new TrustingMysqlPlatform()])
->getMock();
$trustingPlatform = new TrustingMysqlPlatform();
$mockAdapter->expects($this->any())->method('getPlatform')->will($this->returnValue($trustingPlatform));
$mockAdapter->expects($this->any())->method('getDriver')->will($this->returnValue($driver));
// setup mock adapter
$this->mockAdapter = $mockAdapter;

$this->sql = new Sql($this->mockAdapter, 'foo');
$selectDecorator = new SelectDecorator;
$selectDecorator->setSubject($select);
$statement = $this->sql->prepareStatementForSqlObject($select, $newStatement);
self::assertEquals($expectedPdoSql, $statement->getSql());
}

/**
* @testdox integration test: Testing SelectDecorator will use Select an internal state to prepare
* a proper limit/offset sql statement
Expand All @@ -71,28 +108,31 @@ public function testGetSqlString(Select $select, $ignore, $alsoIgnore, $expected

$selectDecorator = new SelectDecorator;
$selectDecorator->setSubject($select);
self::assertEquals($expectedSql, $selectDecorator->getSqlString(new MysqlPlatform));
self::assertEquals($expectedSql, $selectDecorator->getSqlString(new TrustingMysqlPlatform));
}

public function dataProvider()
{
$select0 = new Select;
$select0->from('foo')->limit(5)->offset(10);
$expectedPrepareSql0 = 'SELECT `foo`.* FROM `foo` LIMIT ? OFFSET ?';
$expectedPrepareObjectSql0 = 'SELECT `foo`.* FROM `foo` LIMIT :limit OFFSET :offset';
$expectedParams0 = ['offset' => 10, 'limit' => 5];
$expectedSql0 = 'SELECT `foo`.* FROM `foo` LIMIT 5 OFFSET 10';

// offset without limit
$select1 = new Select;
$select1->from('foo')->offset(10);
$expectedPrepareSql1 = 'SELECT `foo`.* FROM `foo` LIMIT 18446744073709551615 OFFSET ?';
$expectedPrepareObjectSql1 = 'SELECT `foo`.* FROM `foo` LIMIT 18446744073709551615 OFFSET :offset';
$expectedParams1 = ['offset' => 10];
$expectedSql1 = 'SELECT `foo`.* FROM `foo` LIMIT 18446744073709551615 OFFSET 10';

// offset and limit are not type casted when injected into parameter container
$select2 = new Select;
$select2->from('foo')->limit('5')->offset('10000000000000000000');
$expectedPrepareSql2 = 'SELECT `foo`.* FROM `foo` LIMIT ? OFFSET ?';
$expectedPrepareObjectSql2 = 'SELECT `foo`.* FROM `foo` LIMIT :limit OFFSET :offset';
$expectedParams2 = ['offset' => '10000000000000000000', 'limit' => '5'];
$expectedSql2 = 'SELECT `foo`.* FROM `foo` LIMIT 5 OFFSET 10000000000000000000';

Expand All @@ -113,6 +153,9 @@ public function dataProvider()
$expectedPrepareSql3 =
"SELECT (SELECT count(foo1.id) AS `cnt` FROM `foo1` LIMIT ? OFFSET ?) AS `res`"
. " FROM `foo` LIMIT ? OFFSET ?";
$expectedPrepareObjectSql3 =
"SELECT (SELECT count(foo1.id) AS `cnt` FROM `foo1` LIMIT :subselect1limit OFFSET :subselect1offset) AS `res`"
. " FROM `foo` LIMIT :limit OFFSET :offset";
$expectedParams3 = [
'subselect1limit' => 100,
'subselect1offset' => 500,
Expand Down Expand Up @@ -148,6 +191,10 @@ public function dataProvider()
"SELECT (SELECT count(foo1.id) AS `cnt` FROM `foo1` LIMIT ? OFFSET ?) AS `res`,"
. " (SELECT count(foo2.id) AS `cnt` FROM `foo2` LIMIT ? OFFSET ?) AS `res0`"
. " FROM `foo` LIMIT ? OFFSET ?";
$expectedPrepareObjectSql4 =
"SELECT (SELECT count(foo1.id) AS `cnt` FROM `foo1` LIMIT :subselect1limit OFFSET :subselect1offset)"
. " AS `res`, (SELECT count(foo2.id) AS `cnt` FROM `foo2` LIMIT :subselect2limit OFFSET :subselect2offset)"
. " AS `res0` FROM `foo` LIMIT :limit OFFSET :offset";
$expectedParams4 = [
'subselect1limit' => 100,
'subselect1offset' => 500,
Expand All @@ -160,12 +207,43 @@ public function dataProvider()
. " (SELECT count(foo2.id) AS `cnt` FROM `foo2` LIMIT 50 OFFSET 101) AS `res0`"
. " FROM `foo` LIMIT 10 OFFSET 5";

// nested limit in field param, no limit in containing select
$nestedSelect0 = new Select;
$nestedSelect0->from('foo1')
->columns([
'cnt' => new Expression('count(foo1.id)')
]);
$nestedSelect0->where->equalTo('foo2', 'ab');
$nestedSelect0->limit(1);

$select5 = new Select;
$select5->from('foo')
->columns([
'res' => $nestedSelect0,
]);

$expectedPrepareSql5 =
"SELECT (SELECT count(foo1.id) AS `cnt` FROM `foo1` WHERE `foo2` = ? LIMIT ?) AS `res`"
. " FROM `foo`";
$expectedPrepareObjectSql5 =
"SELECT (SELECT count(foo1.id) AS `cnt` FROM `foo1` WHERE `foo2` = :subselect1where1 LIMIT"
. " :subselect1limit) AS `res` FROM `foo`";
$expectedParams5 = [
'subselect1limit' => 1,
'subselect1where1' => 'ab'
];
$expectedSql5 = "SELECT (SELECT count(foo1.id) AS `cnt`"
. " FROM `foo1` WHERE `foo2` = 'ab' LIMIT 1) AS `res`"
. " FROM `foo`";


return [
[$select0, $expectedPrepareSql0, $expectedParams0, $expectedSql0],
[$select1, $expectedPrepareSql1, $expectedParams1, $expectedSql1],
[$select2, $expectedPrepareSql2, $expectedParams2, $expectedSql2],
[$select3, $expectedPrepareSql3, $expectedParams3, $expectedSql3],
[$select4, $expectedPrepareSql4, $expectedParams4, $expectedSql4],
[$select0, $expectedPrepareSql0, $expectedParams0, $expectedSql0, $expectedPrepareObjectSql0],
[$select1, $expectedPrepareSql1, $expectedParams1, $expectedSql1, $expectedPrepareObjectSql1],
[$select2, $expectedPrepareSql2, $expectedParams2, $expectedSql2, $expectedPrepareObjectSql2],
[$select3, $expectedPrepareSql3, $expectedParams3, $expectedSql3, $expectedPrepareObjectSql3],
[$select4, $expectedPrepareSql4, $expectedParams4, $expectedSql4, $expectedPrepareObjectSql4],
[$select5, $expectedPrepareSql5, $expectedParams5, $expectedSql5, $expectedPrepareObjectSql5],
];
}
}