Commit ae833d15 authored by Guilherme Blanco's avatar Guilherme Blanco

Implemented initial support for DBAL Query Builder. Still missing DELETE and UPDATE.

parent fdb0f8b1
...@@ -100,6 +100,11 @@ class Connection implements DriverConnection ...@@ -100,6 +100,11 @@ class Connection implements DriverConnection
* @var Doctrine\Common\EventManager * @var Doctrine\Common\EventManager
*/ */
protected $_eventManager; protected $_eventManager;
/**
* @var Doctrine\DBAL\Query\ExpressionBuilder
*/
protected $_expr;
/** /**
* Whether or not a connection has been established. * Whether or not a connection has been established.
...@@ -195,6 +200,9 @@ class Connection implements DriverConnection ...@@ -195,6 +200,9 @@ class Connection implements DriverConnection
$this->_config = $config; $this->_config = $config;
$this->_eventManager = $eventManager; $this->_eventManager = $eventManager;
$this->_expr = new Query\Expression\ExpressionBuilder($this);
if ( ! isset($params['platform'])) { if ( ! isset($params['platform'])) {
$this->_platform = $driver->getDatabasePlatform(); $this->_platform = $driver->getDatabasePlatform();
} else if ($params['platform'] instanceof Platforms\AbstractPlatform) { } else if ($params['platform'] instanceof Platforms\AbstractPlatform) {
...@@ -202,6 +210,7 @@ class Connection implements DriverConnection ...@@ -202,6 +210,7 @@ class Connection implements DriverConnection
} else { } else {
throw DBALException::invalidPlatformSpecified(); throw DBALException::invalidPlatformSpecified();
} }
$this->_transactionIsolationLevel = $this->_platform->getDefaultTransactionIsolationLevel(); $this->_transactionIsolationLevel = $this->_platform->getDefaultTransactionIsolationLevel();
} }
...@@ -304,7 +313,17 @@ class Connection implements DriverConnection ...@@ -304,7 +313,17 @@ class Connection implements DriverConnection
{ {
return $this->_platform; return $this->_platform;
} }
/**
* Gets the ExpressionBuilder for the connection.
*
* @return Doctrine\DBAL\Query\ExpressionBuilder
*/
public function getExpressionBuilder()
{
return $this->_expr;
}
/** /**
* Establishes the connection with the database. * Establishes the connection with the database.
* *
......
<?php
/*
* 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 LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\DBAL\Query\Expression;
/**
* Composite expression is responsible to build a group of similar expression.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class CompositeExpression implements \Countable
{
/**
* Constant that represents an AND composite expression
*/
const TYPE_AND = 'AND';
/**
* Constant that represents an OR composite expression
*/
const TYPE_OR = 'OR';
/**
* @var string Holds the instance type of composite expression
*/
private $type;
/**
* @var array Each expression part of the composite expression
*/
private $parts = array();
/**
* Constructor.
*
* @param string $type Instance type of composite expression
* @param array $parts Composition of expressions to be joined on composite expression
*/
public function __construct($type, array $parts = array())
{
$this->type = $type;
$this->addMultiple($parts);
}
/**
* Adds multiple parts to composite expression.
*
* @param array $args
*
* @return CompositeExpression
*/
public function addMultiple(array $parts = array())
{
foreach ((array) $parts as $part) {
$this->add($part);
}
return $this;
}
/**
* Adds an expression to composite expression.
*
* @param mixed $part
* @return CompositeExpression
*/
public function add($part)
{
if ( ! empty($part) || ($part instanceof self && $part->count() > 0)) {
$this->parts[] = $part;
}
return $this;
}
/**
* Retrieves the amount of expressions on composite expression.
*
* @return integer
*/
public function count()
{
return count($this->parts);
}
/**
* Retrieve the string representation of this composite expression.
*
* @return string
*/
public function __toString()
{
if ($this->count() === 1) {
return (string) $this->parts[0];
}
return '(' . implode(') ' . $this->type . ' (', $this->parts) . ')';
}
}
\ No newline at end of file
<?php
/*
* 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 LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\DBAL\Query\Expression;
use Doctrine\DBAL\Connection;
/**
* ExpressionBuilder class is responsible to dynamically create SQL query parts.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.com
* @since 1.0
* @version $Revision$
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class ExpressionBuilder
{
const EQ = '=';
const NEQ = '<>';
const LT = '<';
const LTE = '<=';
const GT = '>';
const GTE = '>=';
/**
* @var Doctrine\DBAL\Connection DBAL Connection
*/
private $connection = null;
/**
* Initializes a new <tt>ExpressionBuilder</tt>.
*
* @param Doctrine\DBAL\Connection $connection DBAL Connection
*/
public function __construct(Connection $connection)
{
$this->connection = $connection;
}
/**
* Creates a conjunction of the given boolean expressions.
*
* Example:
*
* [php]
* // (u.type = ?) AND (u.role = ?)
* $expr->andX('u.type = ?', 'u.role = ?'));
*
* @param mixed $x Optional clause. Defaults = null, but requires
* at least one defined when converting to string.
* @return CompositeExpression
*/
public function andX($x = null)
{
return new CompositeExpression(CompositeExpression::TYPE_AND, func_get_args());
}
/**
* Creates a disjunction of the given boolean expressions.
*
* Example:
*
* [php]
* // (u.type = ?) OR (u.role = ?)
* $qb->where($qb->expr()->orX('u.type = ?', 'u.role = ?'));
*
* @param mixed $x Optional clause. Defaults = null, but requires
* at least one defined when converting to string.
* @return CompositeExpression
*/
public function orX($x = null)
{
return new CompositeExpression(CompositeExpression::TYPE_OR, func_get_args());
}
/**
* Creates a comparison expression.
*
* @param mixed $x Left expression
* @param string $operator One of the ExpressionBuikder::* constants.
* @param mixed $y Right expression
* @return string
*/
public function comparison($x, $operator, $y)
{
return $x . ' ' . $operator . ' ' . $y;
}
/**
* Creates an equality comparison expression with the given arguments.
*
* First argument is considered the left expression and the second is the right expression.
* When converted to string, it will generated a <left expr> = <right expr>. Example:
*
* [php]
* // u.id = ?
* $expr->eq('u.id', '?');
*
* @param mixed $x Left expression
* @param mixed $y Right expression
* @return string
*/
public function eq($x, $y)
{
return $this->comparison($x, self::EQ, $y);
}
/**
* Creates a non equality comparison expression with the given arguments.
* First argument is considered the left expression and the second is the right expression.
* When converted to string, it will generated a <left expr> <> <right expr>. Example:
*
* [php]
* // u.id <> 1
* $q->where($q->expr()->neq('u.id', '1'));
*
* @param mixed $x Left expression
* @param mixed $y Right expression
* @return string
*/
public function neq($x, $y)
{
return $this->comparison($x, self::NEQ, $y);
}
/**
* Creates a lower-than comparison expression with the given arguments.
* First argument is considered the left expression and the second is the right expression.
* When converted to string, it will generated a <left expr> < <right expr>. Example:
*
* [php]
* // u.id < ?
* $q->where($q->expr()->lt('u.id', '?'));
*
* @param mixed $x Left expression
* @param mixed $y Right expression
* @return string
*/
public function lt($x, $y)
{
return $this->comparison($x, self::LT, $y);
}
/**
* Creates a lower-than-equal comparison expression with the given arguments.
* First argument is considered the left expression and the second is the right expression.
* When converted to string, it will generated a <left expr> <= <right expr>. Example:
*
* [php]
* // u.id <= ?
* $q->where($q->expr()->lte('u.id', '?'));
*
* @param mixed $x Left expression
* @param mixed $y Right expression
* @return string
*/
public function lte($x, $y)
{
return $this->comparison($x, self::LTE, $y);
}
/**
* Creates a greater-than comparison expression with the given arguments.
* First argument is considered the left expression and the second is the right expression.
* When converted to string, it will generated a <left expr> > <right expr>. Example:
*
* [php]
* // u.id > ?
* $q->where($q->expr()->gt('u.id', '?'));
*
* @param mixed $x Left expression
* @param mixed $y Right expression
* @return string
*/
public function gt($x, $y)
{
return $this->comparison($x, self::GT, $y);
}
/**
* Creates a greater-than-equal comparison expression with the given arguments.
* First argument is considered the left expression and the second is the right expression.
* When converted to string, it will generated a <left expr> >= <right expr>. Example:
*
* [php]
* // u.id >= ?
* $q->where($q->expr()->gte('u.id', '?'));
*
* @param mixed $x Left expression
* @param mixed $y Right expression
* @return string
*/
public function gte($x, $y)
{
return $this->comparison($x, self::GTE, $y);
}
/**
* Creates an IS NULL expression with the given arguments.
*
* @param string $x Field in string format to be restricted by IS NULL
*
* @return string
*/
public function isNull($x)
{
return $x . ' IS NULL';
}
/**
* Creates an IS NOT NULL expression with the given arguments.
*
* @param string $x Field in string format to be restricted by IS NOT NULL
*
* @return string
*/
public function isNotNull($x)
{
return $x . ' IS NOT NULL';
}
/**
* Creates a LIKE() comparison expression with the given arguments.
*
* @param string $x Field in string format to be inspected by LIKE() comparison.
* @param mixed $y Argument to be used in LIKE() comparison.
*
* @return string
*/
public function like($x, $y)
{
return $this->comparison($x, 'LIKE', $y);
}
/**
* Quotes a given input parameter.
*
* @param mixed $input Parameter to be quoted.
* @param string $type Type of the parameter.
*
* @return string
*/
public function literal($input, $type = null)
{
return $this->connection->quote($input, $type);
}
}
\ No newline at end of file
This diff is collapsed.
...@@ -55,6 +55,11 @@ class AllTests ...@@ -55,6 +55,11 @@ class AllTests
$suite->addTestSuite('Doctrine\Tests\DBAL\Schema\ComparatorTest'); $suite->addTestSuite('Doctrine\Tests\DBAL\Schema\ComparatorTest');
$suite->addTestSuite('Doctrine\Tests\DBAL\Schema\SchemaDiffTest'); $suite->addTestSuite('Doctrine\Tests\DBAL\Schema\SchemaDiffTest');
// Query Builder tests
$suite->addTestSuite('Doctrine\Tests\DBAL\Query\Expression\CompositeExpressionTest');
$suite->addTestSuite('Doctrine\Tests\DBAL\Query\Expression\ExpressionBuilderTest');
$suite->addTestSuite('Doctrine\Tests\DBAL\Query\QueryBuilderTest');
// Driver manager test // Driver manager test
$suite->addTestSuite('Doctrine\Tests\DBAL\DriverManagerTest'); $suite->addTestSuite('Doctrine\Tests\DBAL\DriverManagerTest');
......
<?php
namespace Doctrine\Tests\DBAL\Query\Expression;
use Doctrine\DBAL\Query\Expression\CompositeExpression;
require_once __DIR__ . '/../../../TestInit.php';
class CompositeExpressionTest extends \Doctrine\Tests\DbalTestCase
{
public function testCount()
{
$expr = new CompositeExpression(CompositeExpression::TYPE_OR, array('u.group_id = 1'));
$this->assertEquals(1, count($expr));
$expr->add('u.group_id = 2');
$this->assertEquals(2, count($expr));
}
/**
* @dataProvider provideDataForConvertToString
*/
public function testCompositeUsageAndGeneration($type, $parts, $expects)
{
$expr = new CompositeExpression($type, $parts);
$this->assertEquals($expects, (string) $expr);
}
public function provideDataForConvertToString()
{
return array(
array(
CompositeExpression::TYPE_AND,
array('u.user = 1'),
'u.user = 1'
),
array(
CompositeExpression::TYPE_AND,
array('u.user = 1', 'u.group_id = 1'),
'(u.user = 1) AND (u.group_id = 1)'
),
array(
CompositeExpression::TYPE_OR,
array('u.user = 1'),
'u.user = 1'
),
array(
CompositeExpression::TYPE_OR,
array('u.group_id = 1', 'u.group_id = 2'),
'(u.group_id = 1) OR (u.group_id = 2)'
),
array(
CompositeExpression::TYPE_AND,
array(
'u.user = 1',
new CompositeExpression(
CompositeExpression::TYPE_OR,
array('u.group_id = 1', 'u.group_id = 2')
)
),
'(u.user = 1) AND ((u.group_id = 1) OR (u.group_id = 2))'
),
array(
CompositeExpression::TYPE_OR,
array(
'u.group_id = 1',
new CompositeExpression(
CompositeExpression::TYPE_AND,
array('u.user = 1', 'u.group_id = 2')
)
),
'(u.group_id = 1) OR ((u.user = 1) AND (u.group_id = 2))'
),
);
}
}
\ No newline at end of file
<?php
namespace Doctrine\Tests\DBAL\Query\Expression;
use Doctrine\DBAL\Query\Expression\ExpressionBuilder,
Doctrine\DBAL\Query\Expression\CompositeExpression;
require_once __DIR__ . '/../../../TestInit.php';
class ExpressionBuilderTest extends \Doctrine\Tests\DbalTestCase
{
protected $expr;
public function setUp()
{
$conn = $this->getMock('Doctrine\DBAL\Connection', array(), array(), '', false);
$this->expr = new ExpressionBuilder($conn);
$conn->expects($this->any())
->method('getExpressionBuilder')
->will($this->returnValue($this->expr));
}
/**
* @dataProvider provideDataForAndX
*/
public function testAndX($parts, $expected)
{
$composite = $this->expr->andX();
foreach ($parts as $part) {
$composite->add($part);
}
$this->assertEquals($expected, (string) $composite);
}
public function provideDataForAndX()
{
return array(
array(
array('u.user = 1'),
'u.user = 1'
),
array(
array('u.user = 1', 'u.group_id = 1'),
'(u.user = 1) AND (u.group_id = 1)'
),
array(
array('u.user = 1'),
'u.user = 1'
),
array(
array('u.group_id = 1', 'u.group_id = 2'),
'(u.group_id = 1) AND (u.group_id = 2)'
),
array(
array(
'u.user = 1',
new CompositeExpression(
CompositeExpression::TYPE_OR,
array('u.group_id = 1', 'u.group_id = 2')
)
),
'(u.user = 1) AND ((u.group_id = 1) OR (u.group_id = 2))'
),
array(
array(
'u.group_id = 1',
new CompositeExpression(
CompositeExpression::TYPE_AND,
array('u.user = 1', 'u.group_id = 2')
)
),
'(u.group_id = 1) AND ((u.user = 1) AND (u.group_id = 2))'
),
);
}
/**
* @dataProvider provideDataForOrX
*/
public function testOrX($parts, $expected)
{
$composite = $this->expr->orX();
foreach ($parts as $part) {
$composite->add($part);
}
$this->assertEquals($expected, (string) $composite);
}
public function provideDataForOrX()
{
return array(
array(
array('u.user = 1'),
'u.user = 1'
),
array(
array('u.user = 1', 'u.group_id = 1'),
'(u.user = 1) OR (u.group_id = 1)'
),
array(
array('u.user = 1'),
'u.user = 1'
),
array(
array('u.group_id = 1', 'u.group_id = 2'),
'(u.group_id = 1) OR (u.group_id = 2)'
),
array(
array(
'u.user = 1',
new CompositeExpression(
CompositeExpression::TYPE_OR,
array('u.group_id = 1', 'u.group_id = 2')
)
),
'(u.user = 1) OR ((u.group_id = 1) OR (u.group_id = 2))'
),
array(
array(
'u.group_id = 1',
new CompositeExpression(
CompositeExpression::TYPE_AND,
array('u.user = 1', 'u.group_id = 2')
)
),
'(u.group_id = 1) OR ((u.user = 1) AND (u.group_id = 2))'
),
);
}
/**
* @dataProvider provideDataForComparison
*/
public function testComparison($leftExpr, $operator, $rightExpr, $expected)
{
$part = $this->expr->comparison($leftExpr, $operator, $rightExpr);
$this->assertEquals($expected, (string) $part);
}
public function provideDataForComparison()
{
return array(
array('u.user_id', ExpressionBuilder::EQ, '1', 'u.user_id = 1'),
array('u.user_id', ExpressionBuilder::NEQ, '1', 'u.user_id <> 1'),
array('u.salary', ExpressionBuilder::LT, '10000', 'u.salary < 10000'),
array('u.salary', ExpressionBuilder::LTE, '10000', 'u.salary <= 10000'),
array('u.salary', ExpressionBuilder::GT, '10000', 'u.salary > 10000'),
array('u.salary', ExpressionBuilder::GTE, '10000', 'u.salary >= 10000'),
);
}
public function testEq()
{
$this->assertEquals('u.user_id = 1', $this->expr->eq('u.user_id', '1'));
}
public function testNeq()
{
$this->assertEquals('u.user_id <> 1', $this->expr->neq('u.user_id', '1'));
}
public function testLt()
{
$this->assertEquals('u.salary < 10000', $this->expr->lt('u.salary', '10000'));
}
public function testLte()
{
$this->assertEquals('u.salary <= 10000', $this->expr->lte('u.salary', '10000'));
}
public function testGt()
{
$this->assertEquals('u.salary > 10000', $this->expr->gt('u.salary', '10000'));
}
public function testGte()
{
$this->assertEquals('u.salary >= 10000', $this->expr->gte('u.salary', '10000'));
}
public function testIsNull()
{
$this->assertEquals('u.deleted IS NULL', $this->expr->isNull('u.deleted'));
}
public function testIsNotNull()
{
$this->assertEquals('u.updated IS NOT NULL', $this->expr->isNotNull('u.updated'));
}
}
\ No newline at end of file
<?php
namespace Doctrine\Tests\DBAL\Query;
use Doctrine\DBAL\Query\Expression\ExpressionBuilder,
Doctrine\DBAL\Query\QueryBuilder;
require_once __DIR__ . '/../../TestInit.php';
class QueryBuilderTest extends \Doctrine\Tests\DbalTestCase
{
protected $conn;
public function setUp()
{
$this->conn = $this->getMock('Doctrine\DBAL\Connection', array(), array(), '', false);
$expressionBuilder = new ExpressionBuilder($this->conn);
$this->conn->expects($this->any())
->method('getExpressionBuilder')
->will($this->returnValue($expressionBuilder));
}
public function testSimpleSelect()
{
$qb = new QueryBuilder($this->conn);
$qb->select('u.id')
->from('users', 'u');
$this->assertEquals('SELECT u.id FROM users u', (string) $qb);
}
public function testSelectWithSimpleWhere()
{
$qb = new QueryBuilder($this->conn);
$expr = $qb->expr();
$qb->select('u.id')
->from('users', 'u')
->where($expr->andX($expr->eq('u.nickname', '?')));
$this->assertEquals("SELECT u.id FROM users u WHERE u.nickname = ?", (string) $qb);
}
public function testSelectWithJoin()
{
$qb = new QueryBuilder($this->conn);
$expr = $qb->expr();
$qb->select('u.*', 'p.*')
->from('users', 'u')
->leftJoin('u', 'phones', 'p', $expr->eq('p.user_id', 'u.id'));
$this->assertEquals('SELECT u.*, p.* FROM users u LEFT JOIN phones p ON p.user_id = u.id', (string) $qb);
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment