Skip to content

Commit

Permalink
Manually merged #125 (expression support in query builders)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidbyoung committed Feb 8, 2020
1 parent 002553f commit 3917c8f
Show file tree
Hide file tree
Showing 12 changed files with 191 additions and 140 deletions.
1 change: 0 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@
"psr/log": "~1.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "~2.14",
"phpunit/phpunit": "~8.0"
},
"replace": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@

namespace Opulence\Framework\Databases\Migrations;

use Aphiria\DependencyInjection\DependencyInjectionException;
use Aphiria\DependencyInjection\IContainer;
use Aphiria\DependencyInjection\ResolutionException;
use Opulence\Databases\Migrations\IMigration;
use Opulence\Databases\Migrations\IMigrationResolver;
use Opulence\Databases\Migrations\MigrationResolutionException;
Expand Down Expand Up @@ -41,7 +41,7 @@ public function resolve(string $migrationClassName): IMigration
{
try {
return $this->container->resolve($migrationClassName);
} catch (DependencyInjectionException $ex) {
} catch (ResolutionException $ex) {
throw new MigrationResolutionException("Failed to resolve migration $migrationClassName", 0, $ex);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@

namespace Opulence\Framework\Tests\Databases\Migrations;

use Aphiria\DependencyInjection\DependencyInjectionException;
use Aphiria\DependencyInjection\IContainer;
use Aphiria\DependencyInjection\ResolutionException;
use Opulence\Databases\Migrations\IMigration;
use Opulence\Databases\Migrations\MigrationResolutionException;
use Opulence\Framework\Databases\Migrations\ContainerMigrationResolver;
Expand Down Expand Up @@ -51,7 +51,7 @@ public function testDependencyInjectionExceptionsAreConverted(): void
$this->container->expects($this->once())
->method('resolve')
->with('foo')
->willThrowException(new DependencyInjectionException('blah'));
->willThrowException(new ResolutionException('blah', null));
$this->migrationResolver->resolve('foo');
}
}
78 changes: 78 additions & 0 deletions src/QueryBuilders/src/Expression.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

/**
* Opulence
*
* @link https://www.opulencephp.com
* @copyright Copyright (C) 2020 David Young
* @license https://github.com/opulencephp/Opulence/blob/master/LICENSE.md
*/

declare(strict_types=1);

namespace Opulence\QueryBuilders;

/**
* Expression is designed to be used for setting values in INSERT and UPDATE statements
* It is not intended to be used in WHERE clauses or as columns in SELECT queries
*/
class Expression
{
/** @var string The expression to use */
protected string $expression;
/** @var array[] */
protected array $values = [];

/**
* Expression constructor.
*
* @param string $expression
* @param mixed ...$values
*
* @throws InvalidQueryException
*/
public function __construct(string $expression, ...$values)
{
$this->expression = $expression;

foreach ($values as $value) {
if (is_scalar($value)) {
$value = [$value, \PDO::PARAM_STR];
}

if (!is_array($value) || count($value) !== 2) {
throw new InvalidQueryException('Incorrect number of items in expression value array');
}

if (!array_key_exists(0, $value) || !array_key_exists(1, $value)) {
throw new InvalidQueryException('Incorrect keys in expression value array');
}

if (!is_scalar($value[0]) || !is_numeric($value[1]) || $value[1] < 0) {
throw new InvalidQueryException('Incorrect expression values');
}

$this->values[] = $value;
}
}

/**
* Gets the parameters in the expression
*
* @return array The list of parameters
*/
public function getParameters(): array
{
return $this->values;
}

/**
* Gets the SQL that makes up the expression
*
* @return string The SQL of the expression
*/
public function getSql(): string
{
return $this->expression;
}
}
20 changes: 13 additions & 7 deletions src/QueryBuilders/src/InsertQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,19 @@ public function addColumnValues(array $columnNamesToValues): self
*/
public function getSql(): string
{
$sql = "INSERT INTO {$this->tableName}"
. ' (' . implode(', ', array_keys($this->augmentingQueryBuilder->getColumnNamesToValues())) . ') VALUES ('
. implode(
', ',
array_fill(0, count(array_values($this->augmentingQueryBuilder->getColumnNamesToValues())), '?')
)
. ')';
$namesToValues = $this->augmentingQueryBuilder->getColumnNamesToValues();
$sql = 'INSERT INTO ' . $this->tableName . ' (' . implode(', ', array_keys($namesToValues)) . ') VALUES (';
$values = [];

foreach ($namesToValues as $value) {
if ($value instanceof Expression) {
$values[] = $value->getSql();
} else {
$values[] = '?';
}
}

$sql .= implode(', ', $values) . ')';

return $sql;
}
Expand Down
5 changes: 5 additions & 0 deletions src/QueryBuilders/src/Query.php
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ public function addUnnamedPlaceholderValue($value, int $dataType = PDO::PARAM_ST
public function addUnnamedPlaceholderValues(array $placeholderValues): self
{
foreach ($placeholderValues as $value) {
if ($value instanceof Expression) {
$this->addUnnamedPlaceholderValues($value->getParameters());
continue;
}

if (is_array($value)) {
if (count($value) !== 2) {
throw new InvalidQueryException('Incorrect number of items in value array');
Expand Down
8 changes: 6 additions & 2 deletions src/QueryBuilders/src/UpdateQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,14 @@ public function getSql(): string
$sql = 'UPDATE ' . $this->tableName . (empty($this->tableAlias) ? '' : ' AS ' . $this->tableAlias) . ' SET';

foreach ($this->augmentingQueryBuilder->getColumnNamesToValues() as $columnName => $value) {
$sql .= ' ' . $columnName . ' = ?,';
if ($value instanceof Expression) {
$sql .= ' ' . $columnName . ' = ' . $value->getSql() . ',';
} else {
$sql .= ' ' . $columnName . ' = ?,';
}
}

$sql = trim($sql, ',');
$sql = rtrim($sql, ',');
// Add any conditions
$sql .= $this->conditionalQueryBuilder
->getClauseConditionSql('WHERE', $this->conditionalQueryBuilder->getWhereConditions());
Expand Down
43 changes: 37 additions & 6 deletions src/QueryBuilders/tests/InsertQueryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

namespace Opulence\QueryBuilders\tests;

use Opulence\QueryBuilders\Expression;
use Opulence\QueryBuilders\InsertQuery;
use PHPUnit\Framework\TestCase;

Expand Down Expand Up @@ -41,17 +42,47 @@ public function testBasicQuery(): void
], $query->getParameters());
}

/**
* Tests all the methods in a single, complicated query
*/
public function testComplexExpression(): void
{
$query = new InsertQuery('users',
[
'name' => 'dave',
'email' => 'foo@bar.com',
'is_val_even' => new Expression('(val + ?) % ?', ['1', \PDO::PARAM_INT], [2, \PDO::PARAM_INT])
]);
$this->assertEquals('INSERT INTO users (name, email, is_val_even) VALUES (?, ?, (val + ?) % ?)',
$query->getSql());
$this->assertSame([
['dave', \PDO::PARAM_STR],
['foo@bar.com', \PDO::PARAM_STR],
['1', \PDO::PARAM_INT],
[2, \PDO::PARAM_INT],
], $query->getParameters());
}

public function testEverything(): void
{
$expr = new Expression('(val + ?) % ?', ['1', \PDO::PARAM_INT]);
$query = new InsertQuery('users', ['name' => 'dave']);
$query->addColumnValues(['email' => 'foo@bar.com']);
$this->assertEquals('INSERT INTO users (name, email) VALUES (?, ?)', $query->getSql());
$query->addColumnValues(['email' => 'foo@bar.com', 'is_val_even' => $expr])
->addUnnamedPlaceholderValues([[2, \PDO::PARAM_INT]]);
$this->assertEquals('INSERT INTO users (name, email, is_val_even) VALUES (?, ?, (val + ?) % ?)', $query->getSql());
$this->assertSame([
['dave', \PDO::PARAM_STR],
['foo@bar.com', \PDO::PARAM_STR],
['1', \PDO::PARAM_INT],
[2, \PDO::PARAM_INT]
], $query->getParameters());
}

public function testSimpleExpression(): void
{
$query = new InsertQuery('users',
['name' => 'dave', 'email' => 'foo@bar.com', 'valid_until' => new Expression('NOW()')]);
$this->assertEquals('INSERT INTO users (name, email, valid_until) VALUES (?, ?, NOW())', $query->getSql());
$this->assertEquals([
['dave', \PDO::PARAM_STR],
['foo@bar.com', \PDO::PARAM_STR]
['foo@bar.com', \PDO::PARAM_STR],
], $query->getParameters());
}
}
16 changes: 11 additions & 5 deletions src/QueryBuilders/tests/PostgreSql/InsertQueryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

namespace Opulence\QueryBuilders\Tests\PostgreSql;

use Opulence\QueryBuilders\Expression;
use Opulence\QueryBuilders\PostgreSql\InsertQuery;
use PDO;
use PHPUnit\Framework\TestCase;
Expand All @@ -37,14 +38,19 @@ public function testAddReturning(): void
*/
public function testEverything(): void
{
$expr = new Expression("(val + ?) % ?", ['1', \PDO::PARAM_INT]);
$query = new InsertQuery('users', ['name' => 'dave']);
$query->addColumnValues(['email' => 'foo@bar.com'])
$query->addColumnValues(['email' => 'foo@bar.com', 'is_val_even' => $expr])
->returning('id')
->addReturning('name');
$this->assertEquals('INSERT INTO users (name, email) VALUES (?, ?) RETURNING id, name', $query->getSql());
$this->assertEquals([
->addReturning('name')
->addUnnamedPlaceholderValues([[2, \PDO::PARAM_INT]]);
$this->assertEquals('INSERT INTO users (name, email, is_val_even) VALUES (?, ?, (val + ?) % ?) RETURNING id, name',
$query->getSql());
$this->assertSame([
['dave', PDO::PARAM_STR],
['foo@bar.com', PDO::PARAM_STR]
['foo@bar.com', PDO::PARAM_STR],
['1', \PDO::PARAM_INT],
[2, \PDO::PARAM_INT],
], $query->getParameters());
}

Expand Down
16 changes: 7 additions & 9 deletions src/QueryBuilders/tests/PostgreSql/UpdateQueryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

namespace Opulence\QueryBuilders\Tests\PostgreSql;

use Opulence\QueryBuilders\Expression;
use Opulence\QueryBuilders\PostgreSql\UpdateQuery;
use PDO;
use PHPUnit\Framework\TestCase;
Expand All @@ -21,9 +22,6 @@
*/
class UpdateQueryTest extends TestCase
{
/**
* Tests adding to a "RETURNING" clause
*/
public function testAddReturning(): void
{
$query = new UpdateQuery('users', '', ['name' => 'david']);
Expand All @@ -35,26 +33,26 @@ public function testAddReturning(): void
], $query->getParameters());
}

/**
* Tests all the methods in a single, complicated query
*/
public function testEverything(): void
{
$expr = new Expression('(val + ?) % ?', ['1', PDO::PARAM_INT]);
$query = new UpdateQuery('users', 'u', ['name' => 'david']);
$query->addColumnValues(['email' => 'bar@foo.com'])
$query->addColumnValues(['email' => 'bar@foo.com', 'is_val_even' => $expr])
->where('u.id = ?', 'emails.userid = u.id', 'emails.email = ?')
->orWhere('u.name = ?')
->andWhere('subscriptions.userid = u.id', "subscriptions.type = 'customer'")
->returning('u.id')
->addReturning('u.name')
->addUnnamedPlaceholderValues([[18175, PDO::PARAM_INT], 'foo@bar.com', 'dave']);
->addUnnamedPlaceholderValues([[2, PDO::PARAM_INT], [18175, PDO::PARAM_INT], 'foo@bar.com', 'dave']);
$this->assertEquals(
"UPDATE users AS u SET name = ?, email = ? WHERE (u.id = ?) AND (emails.userid = u.id) AND (emails.email = ?) OR (u.name = ?) AND (subscriptions.userid = u.id) AND (subscriptions.type = 'customer') RETURNING u.id, u.name",
"UPDATE users AS u SET name = ?, email = ?, is_val_even = (val + ?) % ? WHERE (u.id = ?) AND (emails.userid = u.id) AND (emails.email = ?) OR (u.name = ?) AND (subscriptions.userid = u.id) AND (subscriptions.type = 'customer') RETURNING u.id, u.name",
$query->getSql()
);
$this->assertEquals([
['david', PDO::PARAM_STR],
['bar@foo.com', PDO::PARAM_STR],
['1', PDO::PARAM_INT],
[2, PDO::PARAM_INT],
[18175, PDO::PARAM_INT],
['foo@bar.com', PDO::PARAM_STR],
['dave', PDO::PARAM_STR]
Expand Down
Loading

0 comments on commit 3917c8f

Please sign in to comment.