diff --git a/library/Zend/Db/Sql/AbstractSql.php b/library/Zend/Db/Sql/AbstractSql.php index 733d2d9e7ac..934208d230e 100644 --- a/library/Zend/Db/Sql/AbstractSql.php +++ b/library/Zend/Db/Sql/AbstractSql.php @@ -112,19 +112,30 @@ protected function processExpression(ExpressionInterface $expression, PlatformIn } /** - * @param $specification + * @param $specifications * @param $parameters * @return string * @throws Exception\RuntimeException */ - protected function createSqlFromSpecificationAndParameters($specification, $parameters) + protected function createSqlFromSpecificationAndParameters($specifications, $parameters) { - if (is_string($specification)) { - return vsprintf($specification, $parameters); + if (is_string($specifications)) { + return vsprintf($specifications, $parameters); } - $topSpec = key($specification); - $paramSpecs = $specification[$topSpec]; + $parametersCount = count($parameters); + foreach ($specifications as $specificationString => $paramSpecs) { + if ($parametersCount == count($paramSpecs)) { + break; + } + unset($specificationString, $paramSpecs); + } + + if (!isset($specificationString)) { + throw new Exception\RuntimeException( + 'A number of parameters was found that is not supported by this specification' + ); + } $topParameters = array(); foreach ($parameters as $position => $paramsForPosition) { @@ -148,7 +159,7 @@ protected function createSqlFromSpecificationAndParameters($specification, $para $topParameters[] = $paramsForPosition; } } - return vsprintf($topSpec, $topParameters); + return vsprintf($specificationString, $topParameters); } protected function processSubSelect(Select $subselect, PlatformInterface $platform, DriverInterface $driver = null, ParameterContainer $parameterContainer = null) diff --git a/library/Zend/Db/Sql/Select.php b/library/Zend/Db/Sql/Select.php index 70dfe710bd2..50554347c03 100644 --- a/library/Zend/Db/Sql/Select.php +++ b/library/Zend/Db/Sql/Select.php @@ -28,6 +28,7 @@ class Select extends AbstractSql implements SqlInterface, PreparableSqlInterface * @const */ const SELECT = 'select'; + const QUANTIFIER = 'quantifier'; const COLUMNS = 'columns'; const TABLE = 'table'; const JOINS = 'joins'; @@ -37,6 +38,8 @@ class Select extends AbstractSql implements SqlInterface, PreparableSqlInterface const ORDER = 'order'; const LIMIT = 'limit'; const OFFSET = 'offset'; + const QUANTIFIER_DISTINCT = 'DISTINCT'; + const QUANTIFIER_ALL = 'ALL'; const JOIN_INNER = 'inner'; const JOIN_OUTER = 'outer'; const JOIN_LEFT = 'left'; @@ -54,7 +57,12 @@ class Select extends AbstractSql implements SqlInterface, PreparableSqlInterface 'SELECT %1$s FROM %2$s' => array( array(1 => '%1$s', 2 => '%1$s AS %2$s', 'combinedby' => ', '), null - ) + ), + 'SELECT %1$s %2$s FROM %3$s' => array( + null, + array(1 => '%1$s', 2 => '%1$s AS %2$s', 'combinedby' => ', '), + null + ), ), self::JOINS => array( '%1$s' => array( @@ -92,6 +100,11 @@ class Select extends AbstractSql implements SqlInterface, PreparableSqlInterface */ protected $table = null; + /** + * @var null|string|Expression + */ + protected $quantifier = null; + /** * @var array */ @@ -173,6 +186,21 @@ public function from($table) return $this; } + /** + * @param string|Expression $quantifier DISTINCT|ALL + * @return Select + */ + public function quantifier($quantifier) + { + if (!is_string($quantifier) && !$quantifier instanceof Expression) { + throw new Exception\InvalidArgumentException( + 'Quantifier must be one of DISTINCT, ALL, or some platform specific Expression object' + ); + } + $this->quantifier = $quantifier; + return $this; + } + /** * Specify columns from which to select * @@ -392,6 +420,9 @@ public function reset($part) } $this->table = null; break; + case self::QUANTIFIER: + $this->quantifier = null; + break; case self::COLUMNS: $this->columns = array(); break; @@ -432,15 +463,16 @@ public function setSpecification($index, $specification) public function getRawState($key = null) { $rawState = array( - self::TABLE => $this->table, - self::COLUMNS => $this->columns, - self::JOINS => $this->joins, - self::WHERE => $this->where, - self::ORDER => $this->order, - self::GROUP => $this->group, - self::HAVING => $this->having, - self::LIMIT => $this->limit, - self::OFFSET => $this->offset + self::TABLE => $this->table, + self::QUANTIFIER => $this->quantifier, + self::COLUMNS => $this->columns, + self::JOINS => $this->joins, + self::WHERE => $this->where, + self::ORDER => $this->order, + self::GROUP => $this->group, + self::HAVING => $this->having, + self::LIMIT => $this->limit, + self::OFFSET => $this->offset ); return (isset($key) && array_key_exists($key, $rawState)) ? $rawState[$key] : $rawState; } @@ -623,7 +655,23 @@ protected function processSelect(PlatformInterface $platform, DriverInterface $d } } - return array($columns, $table); + if ($this->quantifier) { + if ($this->quantifier instanceof Expression) { + $quantifierParts = $this->processExpression($this->quantifier, $platform, $driver, 'quantifier'); + if ($parameterContainer) { + $parameterContainer->merge($quantifierParts->getParameterContainer()); + } + $quantifier = $quantifierParts->getSql(); + } else { + $quantifier = $this->quantifier; + } + } + + if (isset($quantifier)) { + return array($quantifier, $columns, $table); + } else { + return array($columns, $table); + } } protected function processJoins(PlatformInterface $platform, DriverInterface $driver = null, ParameterContainer $parameterContainer = null) diff --git a/tests/ZendTest/Db/Sql/SelectTest.php b/tests/ZendTest/Db/Sql/SelectTest.php index 83e1c88d5e7..db6bc7fe7da 100644 --- a/tests/ZendTest/Db/Sql/SelectTest.php +++ b/tests/ZendTest/Db/Sql/SelectTest.php @@ -53,6 +53,28 @@ public function testGetRawStateViaFrom(Select $select) $this->assertEquals('foo', $select->getRawState('table')); } + /** + * @testdox unit test: Test quantifier() returns Select object (is chainable) + * @covers Zend\Db\Sql\Select::quantifier + */ + public function testQuantifier() + { + $select = new Select; + $return = $select->quantifier($select::QUANTIFIER_DISTINCT); + $this->assertSame($select, $return); + return $return; + } + + /** + * @testdox unit test: Test getRawState() returns infromation populated via from() + * @covers Zend\Db\Sql\Select::getRawState + * @depends testQuantifier + */ + public function testGetRawStateViaQuantifier(Select $select) + { + $this->assertEquals(Select::QUANTIFIER_DISTINCT, $select->getRawState('quantifier')); + } + /** * @testdox unit test: Test columns() returns Select object (is chainable) * @covers Zend\Db\Sql\Select::columns @@ -979,6 +1001,23 @@ public function providerData() )) ); + $select41 = new Select; + $select41->from('foo')->quantifier(Select::QUANTIFIER_DISTINCT); + $sqlPrep41 = // same + $sqlStr41 = 'SELECT DISTINCT "foo".* FROM "foo"'; + $internalTests41 = array( + 'processSelect' => array(SELECT::QUANTIFIER_DISTINCT, array(array('"foo".*')), '"foo"'), + ); + + $select42 = new Select; + $select42->from('foo')->quantifier(new Expression('TOP ?', array(10))); + $sqlPrep42 = 'SELECT TOP ? "foo".* FROM "foo"'; + $sqlStr42 = 'SELECT TOP \'10\' "foo".* FROM "foo"'; + $internalTests42 = array( + 'processSelect' => array('TOP ?', array(array('"foo".*')), '"foo"'), + ); + + /** * $select = the select object * $sqlPrep = the sql as a result of preparation @@ -1030,6 +1069,8 @@ public function providerData() array($select38, $sqlPrep38, array(), $sqlStr38, $internalTests38), array($select39, $sqlPrep39, array(), $sqlStr39, $internalTests39), array($select40, $sqlPrep40, array(), $sqlStr40, $internalTests40), + array($select41, $sqlPrep41, array(), $sqlStr41, $internalTests41), + array($select42, $sqlPrep42, array(), $sqlStr42, $internalTests42), ); } }