Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Parameter parsing fixes #309

Merged
merged 1 commit into from

7 participants

Lars Strojny doctrinebot Lukas Kahwe Smith Benjamin Eberlei Guilherme Blanco Christophe Coevoet Fabio B. Silva
Lars Strojny

Fix for #301 has been reverted to address http://www.doctrine-project.org/jira/browse/DBAL-496. This fix addresses both issues in a consistent manner and ads a few more tests.

doctrinebot
Collaborator

Hello,

thank you for creating this pull request. I have automatically opened an issue
on our Jira Bug Tracker for you. See the issue link:

http://www.doctrine-project.org/jira/browse/DBAL-501

We use Jira to track the state of pull requests and the versions they got
included in.

Christophe Coevoet stof commented on the diff
lib/Doctrine/DBAL/SQLParserUtils.php
((16 lines not shown))
+ if (isset($paramsOrTypes[$paramName])) {
+ return $paramsOrTypes[$paramName];
+ }
+
+ // Hash keys can be prefixed with a colon for compatibility
+ if (isset($paramsOrTypes[':' . $paramName])) {
+ return $paramsOrTypes[':' . $paramName];
+ }
+
+ if (null !== $defaultValue) {
+ return $defaultValue;
+ }
+
+ if ($isParam) {
+ throw SQLParserUtilsException::missingParam($paramName);
+ } else {
Christophe Coevoet
stof added a note

no need for else as the if throws an exception

Mutual exclusiveness is better communicated that way. That's why there is an else.

Christophe Coevoet
stof added a note

but it does not follow our coding standards

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Lukas Kahwe Smith

using this branch I was able to successfully run the Jackalope Doctrine DBAL test suite with MySQL. Note for SQLite I wasnt able to run the test suite since it seems like drop FK statements are no longer ignored which makes the DB rest (https://github.com/jackalope/jackalope-doctrine-dbal/blob/master/src/Jackalope/Transport/DoctrineDBAL/RepositorySchema.php#L120) in our test suite fail:

PHP Fatal error:  Uncaught exception 'Doctrine\DBAL\DBALException' with message 'Sqlite platform does not support alter foreign key.' in /Users/lsmith/htdocs/cmf-sandbox/vendor/jackalope/jackalope-doctrine-dbal/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SqlitePlatform.php:646

but that is another story :-/

Benjamin Eberlei beberlei commented on the diff
lib/Doctrine/DBAL/SQLParserUtils.php
((6 lines not shown))
+ * @param string $paramName The name of the parameter (without a colon in front)
+ * @param array $paramsOrTypes A hash of parameters or types
+ * @param bool $isParam
+ * @param mixed $defaultValue An optional default value. If omitted, an exception is thrown
+ *
+ * @throws SQLParserUtilsException
+ * @return mixed
+ */
+ static private function extractParam($paramName, $paramsOrTypes, $isParam, $defaultValue = null)
+ {
+ if (isset($paramsOrTypes[$paramName])) {
+ return $paramsOrTypes[$paramName];
+ }
+
+ // Hash keys can be prefixed with a colon for compatibility
+ if (isset($paramsOrTypes[':' . $paramName])) {
Benjamin Eberlei Owner

wait, so PDO supports $stmt->bindParam(':foo', $val); instead of $stmt->bindParam('foo', $val);

yes .. it supports both

Yep, that was the root cause of the compatibility break with the Jackalope DBAL component.

Benjamin Eberlei Owner

I didn't know that, hence that code was never tested. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Fabio B. Silva FabioBatSilva commented on the diff
lib/Doctrine/DBAL/SQLParserUtils.php
@@ -199,4 +200,35 @@ static private function getUnquotedStatementFragments($statement)
return $fragments[1];
}
+
+ /**
+ * @param string $paramName The name of the parameter (without a colon in front)
+ * @param array $paramsOrTypes A hash of parameters or types
+ * @param bool $isParam
+ * @param mixed $defaultValue An optional default value. If omitted, an exception is thrown
+ *
+ * @throws SQLParserUtilsException
+ * @return mixed
+ */
+ static private function extractParam($paramName, $paramsOrTypes, $isParam, $defaultValue = null)
Fabio B. Silva Owner

Could you break it into extractParam and extractType ?

Thought about that as well but it isn't really worth introducing another redirection (another method) or duplicating the extraction code.

Fabio B. Silva Owner

I'd vote for using two methods here.
This one breaks the single responsibility principle.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Lars Strojny

@beberlei could you advise on where to go from here?

Benjamin Eberlei
Owner

@lstrojny thinking about the SRP violation, a solution would be three methods,l but not sure if this is necessary here. Pondering with the problem... :)

Lars Strojny

Sure, three methods would work extractType, extractParam, and doExtract. But this code is complex as hell as it is, no need to add more. I was even thinking about inlining the whole conditions to spare a few method calls (as n can get pretty large potentially).

Benjamin Eberlei beberlei merged commit 38f0d51 into from
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Apr 21, 2013
  1. Lars Strojny
This page is out of date. Refresh to see the latest.
52 lib/Doctrine/DBAL/SQLParserUtils.php
View
@@ -80,7 +80,8 @@ static public function getPlaceholderPositions($statement, $isPositional = true)
* @param string $query The SQL query to execute.
* @param array $params The parameters to bind to the query.
* @param array $types The types the previous parameters are in.
- *
+ *
+ * @throws SQLParserUtilsException
* @return array
*/
static public function expandListParameters($query, $params, $types)
@@ -103,7 +104,7 @@ static public function expandListParameters($query, $params, $types)
$arrayPositions[$name] = false;
}
- if (( ! $arrayPositions && $isPositional) || (count($params) != count($types))) {
+ if (( ! $arrayPositions && $isPositional)) {
return array($query, $params, $types);
}
@@ -130,9 +131,9 @@ static public function expandListParameters($query, $params, $types)
$types = array_merge(
array_slice($types, 0, $needle),
- $count ?
+ $count ?
array_fill(0, $count, $types[$needle] - Connection::ARRAY_PARAM_OFFSET) : // array needles are at PDO::PARAM_* + 100
- array(),
+ array(),
array_slice($types, $needle + 1)
);
@@ -152,16 +153,16 @@ static public function expandListParameters($query, $params, $types)
$paramsOrd = array();
foreach ($paramPos as $pos => $paramName) {
- $paramLen = strlen($paramName) + 1;
- $value = $params[$paramName];
+ $paramLen = strlen($paramName) + 1;
+ $value = static::extractParam($paramName, $params, true);
- if ( ! isset($arrayPositions[$paramName])) {
+ if ( ! isset($arrayPositions[$paramName]) && ! isset($arrayPositions[':' . $paramName])) {
$pos += $queryOffset;
$queryOffset -= ($paramLen - 1);
$paramsOrd[] = $value;
- $typesOrd[] = $types[$paramName];
+ $typesOrd[] = static::extractParam($paramName, $types, false, \PDO::PARAM_STR);
$query = substr($query, 0, $pos) . '?' . substr($query, ($pos + $paramLen));
-
+
continue;
}
@@ -170,7 +171,7 @@ static public function expandListParameters($query, $params, $types)
foreach ($value as $val) {
$paramsOrd[] = $val;
- $typesOrd[] = $types[$paramName] - Connection::ARRAY_PARAM_OFFSET;
+ $typesOrd[] = static::extractParam($paramName, $types, false) - Connection::ARRAY_PARAM_OFFSET;
}
$pos += $queryOffset;
@@ -199,4 +200,35 @@ static private function getUnquotedStatementFragments($statement)
return $fragments[1];
}
+
+ /**
+ * @param string $paramName The name of the parameter (without a colon in front)
+ * @param array $paramsOrTypes A hash of parameters or types
+ * @param bool $isParam
+ * @param mixed $defaultValue An optional default value. If omitted, an exception is thrown
+ *
+ * @throws SQLParserUtilsException
+ * @return mixed
+ */
+ static private function extractParam($paramName, $paramsOrTypes, $isParam, $defaultValue = null)
Fabio B. Silva Owner

Could you break it into extractParam and extractType ?

Thought about that as well but it isn't really worth introducing another redirection (another method) or duplicating the extraction code.

Fabio B. Silva Owner

I'd vote for using two methods here.
This one breaks the single responsibility principle.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ {
+ if (isset($paramsOrTypes[$paramName])) {
+ return $paramsOrTypes[$paramName];
+ }
+
+ // Hash keys can be prefixed with a colon for compatibility
+ if (isset($paramsOrTypes[':' . $paramName])) {
Benjamin Eberlei Owner

wait, so PDO supports $stmt->bindParam(':foo', $val); instead of $stmt->bindParam('foo', $val);

yes .. it supports both

Yep, that was the root cause of the compatibility break with the Jackalope DBAL component.

Benjamin Eberlei Owner

I didn't know that, hence that code was never tested. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ return $paramsOrTypes[':' . $paramName];
+ }
+
+ if (null !== $defaultValue) {
+ return $defaultValue;
+ }
+
+ if ($isParam) {
+ throw SQLParserUtilsException::missingParam($paramName);
+ } else {
Christophe Coevoet
stof added a note

no need for else as the if throws an exception

Mutual exclusiveness is better communicated that way. That's why there is an else.

Christophe Coevoet
stof added a note

but it does not follow our coding standards

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ throw SQLParserUtilsException::missingType($paramName);
+ }
+ }
}
43 lib/Doctrine/DBAL/SQLParserUtilsException.php
View
@@ -0,0 +1,43 @@
+<?php
+/*
+ * $Id: $
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the MIT license. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL;
+
+/**
+ * Doctrine\DBAL\ConnectionException
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.4
+ * @author Lars Strojny <lars@strojny.net>
+ */
+class SQLParserUtilsException extends DBALException
+{
+ public static function missingParam($paramName)
+ {
+ return new self(sprintf('Value for :%1$s not found in params array. Params array key should be "%1$s"', $paramName));
+ }
+
+ public static function missingType($typeName)
+ {
+ return new self(sprintf('Value for :%1$s not found in types array. Types array key should be "%1$s"', $typeName));
+ }
+}
98 tests/Doctrine/Tests/DBAL/SQLParserUtilsTest.php
View
@@ -237,6 +237,55 @@ static public function dataExpandListParameters()
array(),
array()
),
+ array(
+ "SELECT * FROM Foo WHERE foo IN (:foo) OR bar = :bar OR baz = :baz",
+ array('foo' => array(1, 2), 'bar' => 'bar', 'baz' => 'baz'),
+ array('foo' => Connection::PARAM_INT_ARRAY, 'baz' => 'string'),
+ 'SELECT * FROM Foo WHERE foo IN (?, ?) OR bar = ? OR baz = ?',
+ array(1, 2, 'bar', 'baz'),
+ array(\PDO::PARAM_INT, \PDO::PARAM_INT, \PDO::PARAM_STR, 'string')
+ ),
+ array(
+ "SELECT * FROM Foo WHERE foo IN (:foo) OR bar = :bar",
+ array('foo' => array(1, 2), 'bar' => 'bar'),
+ array('foo' => Connection::PARAM_INT_ARRAY),
+ 'SELECT * FROM Foo WHERE foo IN (?, ?) OR bar = ?',
+ array(1, 2, 'bar'),
+ array(\PDO::PARAM_INT, \PDO::PARAM_INT, \PDO::PARAM_STR)
+ ),
+ // Params/types with colons
+ array(
+ "SELECT * FROM Foo WHERE foo = :foo OR bar = :bar",
+ array(':foo' => 'foo', ':bar' => 'bar'),
+ array(':foo' => \PDO::PARAM_INT),
+ 'SELECT * FROM Foo WHERE foo = ? OR bar = ?',
+ array('foo', 'bar'),
+ array(\PDO::PARAM_INT, \PDO::PARAM_STR)
+ ),
+ array(
+ "SELECT * FROM Foo WHERE foo = :foo OR bar = :bar",
+ array(':foo' => 'foo', ':bar' => 'bar'),
+ array(':foo' => \PDO::PARAM_INT, 'bar' => \PDO::PARAM_INT),
+ 'SELECT * FROM Foo WHERE foo = ? OR bar = ?',
+ array('foo', 'bar'),
+ array(\PDO::PARAM_INT, \PDO::PARAM_INT)
+ ),
+ array(
+ "SELECT * FROM Foo WHERE foo IN (:foo) OR bar = :bar",
+ array(':foo' => array(1, 2), ':bar' => 'bar'),
+ array('foo' => Connection::PARAM_INT_ARRAY),
+ 'SELECT * FROM Foo WHERE foo IN (?, ?) OR bar = ?',
+ array(1, 2, 'bar'),
+ array(\PDO::PARAM_INT, \PDO::PARAM_INT, \PDO::PARAM_STR)
+ ),
+ array(
+ "SELECT * FROM Foo WHERE foo IN (:foo) OR bar = :bar",
+ array('foo' => array(1, 2), 'bar' => 'bar'),
+ array(':foo' => Connection::PARAM_INT_ARRAY),
+ 'SELECT * FROM Foo WHERE foo IN (?, ?) OR bar = ?',
+ array(1, 2, 'bar'),
+ array(\PDO::PARAM_INT, \PDO::PARAM_INT, \PDO::PARAM_STR)
+ ),
);
}
@@ -257,4 +306,53 @@ public function testExpandListParameters($q, $p, $t, $expectedQuery, $expectedPa
$this->assertEquals($expectedParams, $params, "Params dont match");
$this->assertEquals($expectedTypes, $types, "Types dont match");
}
+
+ public static function dataQueryWithMissingParameters()
+ {
+ return array(
+ array(
+ "SELECT * FROM foo WHERE bar = :param",
+ array('other' => 'val'),
+ array(),
+ ),
+ array(
+ "SELECT * FROM foo WHERE bar = :param",
+ array(),
+ array(),
+ ),
+ array(
+ "SELECT * FROM foo WHERE bar = :param",
+ array(),
+ array('param' => Connection::PARAM_INT_ARRAY),
+ ),
+ array(
+ "SELECT * FROM foo WHERE bar = :param",
+ array(),
+ array(':param' => Connection::PARAM_INT_ARRAY),
+ ),
+ array(
+ "SELECT * FROM foo WHERE bar = :param",
+ array(),
+ array('bar' => Connection::PARAM_INT_ARRAY),
+ ),
+ array(
+ "SELECT * FROM foo WHERE bar = :param",
+ array('bar' => 'value'),
+ array('bar' => Connection::PARAM_INT_ARRAY),
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider dataQueryWithMissingParameters
+ */
+ public function testExceptionIsThrownForMissingParam($query, $params, $types = array())
+ {
+ $this->setExpectedException(
+ 'Doctrine\DBAL\SQLParserUtilsException',
+ 'Value for :param not found in params array. Params array key should be "param"'
+ );
+
+ SQLParserUtils::expandListParameters($query, $params, $types);
+ }
}
Something went wrong with that request. Please try again.