Skip to content

Commit

Permalink
Fixed composite "in" condition on QueryBuilder
Browse files Browse the repository at this point in the history
  • Loading branch information
edgardmessias committed Apr 2, 2018
1 parent b7f728b commit dee1f4e
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 80 deletions.
39 changes: 33 additions & 6 deletions src/ColumnSchema.php
Expand Up @@ -26,17 +26,36 @@ class ColumnSchema extends \yii\db\ColumnSchema
*/
protected function typecast($value)
{

if ($value === '' && $this->type !== Schema::TYPE_TEXT && $this->type !== Schema::TYPE_STRING && $this->type !== Schema::TYPE_BINARY) {
if ($value === ''
&& !in_array(
$this->type,
[
Schema::TYPE_TEXT,
Schema::TYPE_STRING,
Schema::TYPE_BINARY,
Schema::TYPE_CHAR
],
true)
) {
return null;
}
if ($value instanceof ExpressionInterface) {
return $value;
}
if ($value === null || gettype($value) === $this->phpType || $value instanceof Expression) {

if ($value === null
|| gettype($value) === $this->phpType
|| $value instanceof ExpressionInterface
|| $value instanceof Query
) {
return $value;
}

if (is_array($value)
&& count($value) === 2
&& isset($value[1])
&& in_array($value[1], $this->getPdoParamTypes(), true)
) {
return new \yii\db\PdoValue($value[0], $value[1]);
}

switch ($this->phpType) {
case 'resource':
case 'string':
Expand All @@ -61,4 +80,12 @@ protected function typecast($value)

return $value;
}

/**
* @return int[] array of numbers that represent possible PDO parameter types
*/
private function getPdoParamTypes()
{
return [\PDO::PARAM_BOOL, \PDO::PARAM_INT, \PDO::PARAM_STR, \PDO::PARAM_LOB, \PDO::PARAM_NULL, \PDO::PARAM_STMT];
}
}
42 changes: 42 additions & 0 deletions src/ExpressionBuilder.php
@@ -0,0 +1,42 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/

namespace edgardmessias\db\firebird;

use yii\db\Expression;
use yii\db\ExpressionBuilderInterface;
use yii\db\ExpressionBuilderTrait;
use yii\db\ExpressionInterface;

/**
* {@inheritdoc}
*/
class ExpressionBuilder implements ExpressionBuilderInterface
{
use ExpressionBuilderTrait;


/**
* {@inheritdoc}
* @param Expression|ExpressionInterface $expression the expression to be built
*/
public function build(ExpressionInterface $expression, array &$params = [])
{
$params = array_merge($params, $expression->params);
$string = $expression->__toString();

static $expressionMap = [
"strftime('%Y')" => "EXTRACT(YEAR FROM TIMESTAMP 'now')"
];

if (isset($expressionMap[$string])) {
return $expressionMap[$string];
}

return $string;
}
}
104 changes: 34 additions & 70 deletions src/QueryBuilder.php
Expand Up @@ -60,6 +60,14 @@ public function init()
parent::init();
}

protected function defaultExpressionBuilders()
{
return array_merge(parent::defaultExpressionBuilders(), [
'yii\db\Expression' => 'edgardmessias\db\firebird\ExpressionBuilder',
'yii\db\conditions\InCondition' => 'edgardmessias\db\firebird\conditions\InConditionBuilder',
]);
}

/**
* Generates a SELECT SQL statement from a [[Query]] object.
* @param Query $query the [[Query]] object from which the SQL statement will be generated.
Expand Down Expand Up @@ -133,32 +141,6 @@ public function buildSelect($columns, &$params, $distinct = false, $selectOption
return parent::buildSelect($columns, $params, $distinct, $selectOption);
}

/**
* @inheritdoc
*/
protected function buildCompositeInCondition($operator, $columns, $values, &$params)
{
$quotedColumns = [];
foreach ($columns as $i => $column) {
$quotedColumns[$i] = strpos($column, '(') === false ? $this->db->quoteColumnName($column) : $column;
}
$vss = [];
foreach ($values as $value) {
$vs = [];
foreach ($columns as $i => $column) {
if (isset($value[$column])) {
$phName = self::PARAM_PREFIX . count($params);
$params[$phName] = $value[$column];
$vs[] = $quotedColumns[$i] . ($operator === 'IN' ? ' = ' : ' != ') . $phName;
} else {
$vs[] = $quotedColumns[$i] . ($operator === 'IN' ? ' IS' : ' IS NOT') . ' NULL';
}
}
$vss[] = '(' . implode($operator === 'IN' ? ' AND ' : ' OR ', $vs) . ')';
}
return '(' . implode($operator === 'IN' ? ' OR ' : ' AND ', $vss) . ')';
}

/**
* @inheritdoc
*/
Expand Down Expand Up @@ -226,38 +208,14 @@ public function buildUnion($unions, &$params)
return trim($result);
}

/**
*
* @param Expression $value
* @return Expression
*/
protected function convertExpression($value)
{
if (!($value instanceof Expression)) {
return $value;
}

$expressionMap = [
"strftime('%Y')" => "EXTRACT(YEAR FROM TIMESTAMP 'now')"
];

if (isset($expressionMap[$value->expression])) {
return new Expression($expressionMap[$value->expression]);
}
return $value;
}

/**
* @inheritdoc
*/
public function insert($table, $columns, &$params)
public function prepareInsertValues($table, $columns, $params = [])
{
$schema = $this->db->getSchema();
if (($tableSchema = $schema->getTableSchema($table)) !== null) {
$columnSchemas = $tableSchema->columns;
} else {
$columnSchemas = [];
}
$tableSchema = $schema->getTableSchema($table);
$columnSchemas = $tableSchema !== null ? $tableSchema->columns : [];

//Empty insert
if (empty($columns) && !empty($columnSchemas)) {
Expand All @@ -271,15 +229,19 @@ public function insert($table, $columns, &$params)

if (is_array($columns)) {
foreach ($columns as $name => $value) {
if ($value instanceof Expression) {
$columns[$name] = $this->convertExpression($value);
} elseif (isset($columnSchemas[$name]) && in_array($columnSchemas[$name]->type, [Schema::TYPE_TEXT, Schema::TYPE_BINARY])) {
$columns[$name] = [$value, \PDO::PARAM_LOB];
if ($value instanceof \yii\db\ExpressionInterface) {
continue;
}
if ($value instanceof \yii\db\PdoValue) {
continue;
}
if (isset($columnSchemas[$name]) && in_array($columnSchemas[$name]->type, [Schema::TYPE_TEXT, Schema::TYPE_BINARY])) {
$columns[$name] = new \yii\db\PdoValue($value, \PDO::PARAM_LOB);
}
}
}

return parent::insert($table, $columns, $params);
return parent::prepareInsertValues($table, $columns, $params);
}

/**
Expand All @@ -294,28 +256,30 @@ protected function prepareInsertSelectSubQuery($columns, $schema, $params = [])
throw new NotSupportedException('Firebird < 3.0.0 has the "Unstable Cursor" problem');
}

return parent::prepareInsertSelectSubQuery($columns, $schema, $params = []);
return parent::prepareInsertSelectSubQuery($columns, $schema, $params);
}

/**
* @inheritdoc
*/
public function update($table, $columns, $condition, &$params)
public function prepareUpdateSets($table, $columns, $params = [])
{
$schema = $this->db->getSchema();
if (($tableSchema = $schema->getTableSchema($table)) !== null) {
$columnSchemas = $tableSchema->columns;
} else {
$columnSchemas = [];
}
$tableSchema = $schema->getTableSchema($table);
$columnSchemas = $tableSchema !== null ? $tableSchema->columns : [];

foreach ($columns as $name => $value) {
if ($value instanceof Expression) {
$columns[$name] = $this->convertExpression($value);
} elseif (isset($columnSchemas[$name]) && in_array($columnSchemas[$name]->type, [Schema::TYPE_TEXT, Schema::TYPE_BINARY])) {
$columns[$name] = [$value, \PDO::PARAM_LOB];
if ($value instanceof \yii\db\ExpressionInterface) {
continue;
}
if ($value instanceof \yii\db\PdoValue) {
continue;
}
if (isset($columnSchemas[$name]) && in_array($columnSchemas[$name]->type, [Schema::TYPE_TEXT, Schema::TYPE_BINARY])) {
$columns[$name] = new \yii\db\PdoValue($value, \PDO::PARAM_LOB);
}
}
return parent::update($table, $columns, $condition, $params);
return parent::prepareUpdateSets($table, $columns, $condition, $params);
}

/**
Expand Down
40 changes: 40 additions & 0 deletions src/conditions/InConditionBuilder.php
@@ -0,0 +1,40 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/

namespace edgardmessias\db\firebird\conditions;

/**
* {@inheritdoc}
*/
class InConditionBuilder extends \yii\db\conditions\InConditionBuilder
{
/**
* {@inheritdoc}
*/
protected function buildCompositeInCondition($operator, $columns, $values, &$params)
{
$quotedColumns = [];
foreach ($columns as $i => $column) {
$quotedColumns[$i] = strpos($column, '(') === false ? $this->queryBuilder->db->quoteColumnName($column) : $column;
}
$vss = [];
foreach ($values as $value) {
$vs = [];
foreach ($columns as $i => $column) {
if (isset($value[$column])) {
$phName = $this->queryBuilder->bindParam($value[$column], $params);
$vs[] = $quotedColumns[$i] . ($operator === 'IN' ? ' = ' : ' != ') . $phName;
} else {
$vs[] = $quotedColumns[$i] . ($operator === 'IN' ? ' IS' : ' IS NOT') . ' NULL';
}
}
$vss[] = '(' . implode($operator === 'IN' ? ' AND ' : ' OR ', $vs) . ')';
}

return '(' . implode($operator === 'IN' ? ' OR ' : ' AND ', $vss) . ')';
}
}
17 changes: 13 additions & 4 deletions tests/QueryBuilderTest.php
Expand Up @@ -152,9 +152,9 @@ public function conditionProvider()
[':qp0' => 1, ':qp1' => 'oy', ':qp2' => 2, ':qp3' => 'yo']
];

$conditions[53] = [ ['=', 'date', (new Query())->select('max(date)')->from('test')->where(['id' => 5])], 'date = (SELECT max(date) AS max_date FROM test WHERE id=:qp0)', [':qp0' => 5] ];
$conditions[58] = [ ['in', ['id', 'name'], [['id' => 1, 'name' => 'foo'], ['id' => 2, 'name' => 'bar']]], '((id = :qp0 AND name = :qp1) OR (id = :qp2 AND name = :qp3))', [':qp0' => 1, ':qp1' => 'foo', ':qp2' => 2, ':qp3' => 'bar']];
$conditions[59] = [ ['not in', ['id', 'name'], [['id' => 1, 'name' => 'foo'], ['id' => 2, 'name' => 'bar']]], '((id != :qp0 OR name != :qp1) AND (id != :qp2 OR name != :qp3))', [':qp0' => 1, ':qp1' => 'foo', ':qp2' => 2, ':qp3' => 'bar']];
$conditions[44] = [ ['=', 'date', (new Query())->select('max(date)')->from('test')->where(['id' => 5])], '"date" = (SELECT max(date) AS max_date FROM test WHERE id=:qp0)', [':qp0' => 5] ];
$conditions[51] = [ ['in', ['id', 'name'], [['id' => 1, 'name' => 'foo'], ['id' => 2, 'name' => 'bar']]], '((id = :qp0 AND name = :qp1) OR (id = :qp2 AND name = :qp3))', [':qp0' => 1, ':qp1' => 'foo', ':qp2' => 2, ':qp3' => 'bar']];
$conditions[52] = [ ['not in', ['id', 'name'], [['id' => 1, 'name' => 'foo'], ['id' => 2, 'name' => 'bar']]], '((id != :qp0 OR name != :qp1) AND (id != :qp2 OR name != :qp3))', [':qp0' => 1, ':qp1' => 'foo', ':qp2' => 2, ':qp3' => 'bar']];

return $conditions;
}
Expand Down Expand Up @@ -186,7 +186,7 @@ function (\yii\db\QueryBuilder $qb) use ($tableName, $name) {
},
],
'add' => [
"ALTER TABLE {{{$tableName}}} ALTER COLUMN [[$name]] SET DEFAULT 0",
"ALTER TABLE {{{$tableName}}} ALTER COLUMN [[C_default]] SET DEFAULT 0",
function (\yii\db\QueryBuilder $qb) use ($tableName, $name) {
return $qb->addDefaultValue($name, $tableName, 'C_default', 0);
},
Expand Down Expand Up @@ -505,4 +505,13 @@ public function testReplaceQuotes()
$this->assertEquals('"order".comment', $this->replaceQuotes('[[order]].[[comment]]'));
$this->assertEquals('"order"."time"', $this->replaceQuotes('[[order]].[[time]]'));
}

public function indexesProvider()
{
$tests = parent::indexesProvider();

$tests['drop'][0] = 'DROP INDEX [[CN_constraints_2_single]]';

return $tests;
}
}

0 comments on commit dee1f4e

Please sign in to comment.