Skip to content

Commit

Permalink
Add prepareValue() methods to QueryBuilderInterface (#902)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tigrov authored Nov 23, 2024
1 parent ba043b6 commit 6b4bbd4
Show file tree
Hide file tree
Showing 19 changed files with 211 additions and 81 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@
- Chg #889: Update `AbstractDMLQueryBuilder::insertBatch()` method (@Tigrov)
- Enh #890: Add properties of `AbstractColumnSchema` class to constructor (@Tigrov)
- New #899: Add `ColumnSchemaInterface::hasDefaultValue()` and `ColumnSchemaInterface::null()` methods (@Tigrov)
- New #902: Add `QueryBuilderInterface::prepareParam()` and `QueryBuilderInterface::prepareValue()` methods (@Tigrov)
- Enh #902: Refactor `Quoter::quoteValue()` method (@Tigrov)

## 1.3.0 March 21, 2024

Expand Down
5 changes: 4 additions & 1 deletion UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,9 @@ Each table column has its own class in the `Yiisoft\Db\Schema\Column` namespace

- `QuoterInterface::getRawTableName()` - returns the raw table name without quotes;
- `SchemaInterface::getColumnFactory()` - returns the column factory object for concrete DBMS;
- `QueryBuilderInterface::buildColumnDefinition()` - builds column definition for `CREATE TABLE` statement.
- `QueryBuilderInterface::buildColumnDefinition()` - builds column definition for `CREATE TABLE` statement;
- `QueryBuilderInterface::prepareParam()` - converts a `ParamInterface` object to its SQL representation;
- `QueryBuilderInterface::prepareValue()` - converts a value to its SQL representation;

### Remove methods

Expand Down Expand Up @@ -146,3 +148,4 @@ Each table column has its own class in the `Yiisoft\Db\Schema\Column` namespace
- Allow `ExpressionInterface` for `$alias` parameter of `QueryPartsInterface::withQuery()` method;
- Allow `QueryInterface::one()` to return an object;
- Allow `QueryInterface::all()` to return array of objects;
- Change `Quoter::quoteValue()` parameter type and return type from `mixed` to `string`;
24 changes: 3 additions & 21 deletions src/Command/AbstractCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
use Closure;
use Throwable;
use Yiisoft\Db\Exception\Exception;
use Yiisoft\Db\Expression\Expression;
use Yiisoft\Db\Constant\GettypeResult;
use Yiisoft\Db\Query\Data\DataReaderInterface;
use Yiisoft\Db\Query\QueryInterface;
use Yiisoft\Db\QueryBuilder\DMLQueryBuilderInterface;
Expand All @@ -17,14 +15,12 @@

use function explode;
use function get_resource_type;
use function gettype;
use function is_array;
use function is_int;
use function is_resource;
use function is_scalar;
use function is_string;
use function preg_replace_callback;
use function str_starts_with;
use function stream_get_contents;

/**
Expand Down Expand Up @@ -351,28 +347,14 @@ public function getRawSql(): string
}

$params = [];
$quoter = $this->getQueryBuilder()->quoter();
$queryBuilder = $this->getQueryBuilder();

foreach ($this->params as $name => $param) {
if (is_string($name) && !str_starts_with($name, ':')) {
if (is_string($name) && $name[0] !== ':') {
$name = ':' . $name;
}

$value = $param->getValue();

$params[$name] = match ($param->getType()) {
DataType::INTEGER => (string) (int) $value,
DataType::STRING, DataType::LOB => match (gettype($value)) {
GettypeResult::RESOURCE => $name,
GettypeResult::DOUBLE => (string) $value,
default => $value instanceof Expression
? (string) $value
: $quoter->quoteValue((string) $value),
},
DataType::BOOLEAN => $value ? 'TRUE' : 'FALSE',
DataType::NULL => 'NULL',
default => $name,
};
$params[$name] = $queryBuilder->prepareParam($param);
}

/** @var string[] $params */
Expand Down
4 changes: 4 additions & 0 deletions src/Command/CommandInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,8 @@ public function insertBatch(string $table, iterable $rows, array $columns = []):
* @param int|null $length The length of the data type.
* @param mixed|null $driverOptions The driver-specific options.
*
* @psalm-param DataType::*|null $dataType
*
* @throws Exception
*/
public function bindParam(
Expand Down Expand Up @@ -244,6 +246,8 @@ public function addUnique(string $table, string $name, array|string $columns): s
* @param mixed $value The value to bind to the parameter.
* @param int|null $dataType The {@see DataType SQL data type} of the parameter. If null, the type is determined
* by the PHP type of the value.
*
* @psalm-param DataType::*|null $dataType
*/
public function bindValue(int|string $name, mixed $value, int $dataType = null): static;

Expand Down
6 changes: 6 additions & 0 deletions src/Command/Param.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,16 @@
*/
final class Param implements ParamInterface, ExpressionInterface
{
/**
* @psalm-param DataType::* $type
*/
public function __construct(private mixed $value, private int $type)
{
}

/**
* @psalm-return DataType::*
*/
public function getType(): int
{
return $this->type;
Expand Down
4 changes: 4 additions & 0 deletions src/Command/ParamInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,15 @@ interface ParamInterface
* @param mixed $value The value to bind to the parameter.
* @param int $type The SQL data type of the parameter.
* If `null`, the type is determined by the PHP type of the value.
*
* @psalm-param DataType::* $type
*/
public function __construct(mixed $value, int $type);

/**
* @return int The SQL data type of the parameter.
*
* @psalm-return DataType::*
*/
public function getType(): int;

Expand Down
4 changes: 4 additions & 0 deletions src/Constant/GettypeResult.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ final class GettypeResult
* Define the php type as `resource`.
*/
public const RESOURCE = 'resource';
/**
* Define the php type as `resource (closed)`.
*/
public const RESOURCE_CLOSED = 'resource (closed)';
/**
* Define the php type as `string`.
*/
Expand Down
38 changes: 6 additions & 32 deletions src/QueryBuilder/AbstractColumnDefinitionBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,8 @@
namespace Yiisoft\Db\QueryBuilder;

use Yiisoft\Db\Constant\ColumnType;
use Yiisoft\Db\Constant\GettypeResult;
use Yiisoft\Db\Expression\ExpressionInterface;
use Yiisoft\Db\Schema\Column\ColumnSchemaInterface;

use function gettype;
use function in_array;
use function strtolower;

Expand Down Expand Up @@ -125,45 +122,22 @@ protected function buildDefault(ColumnSchemaInterface $column): string
return ' DEFAULT ' . static::GENERATE_UUID_EXPRESSION;
}

if ($column->isAutoIncrement() && $column->getType() !== ColumnType::UUID) {
if ($column->isAutoIncrement() && $column->getType() !== ColumnType::UUID
|| !$column->hasDefaultValue()
) {
return '';
}

$defaultValue = $this->buildDefaultValue($column);
$defaultValue = $column->dbTypecast($column->getDefaultValue());
$defaultValue = $this->queryBuilder->prepareValue($defaultValue);

if ($defaultValue === null) {
if ($defaultValue === '') {
return '';
}

return " DEFAULT $defaultValue";
}

/**
* Return the default value for the column.
*
* @return string|null string with default value of column.
*/
protected function buildDefaultValue(ColumnSchemaInterface $column): string|null
{
if (!$column->hasDefaultValue()) {
return null;
}

$value = $column->dbTypecast($column->getDefaultValue());

/** @var string */
return match (gettype($value)) {
GettypeResult::INTEGER => (string) $value,
GettypeResult::DOUBLE => (string) $value,
GettypeResult::BOOLEAN => $value ? 'TRUE' : 'FALSE',
GettypeResult::NULL => $column->isNotNull() !== true ? 'NULL' : null,
GettypeResult::OBJECT => $value instanceof ExpressionInterface
? $this->queryBuilder->buildExpression($value)
: $this->queryBuilder->quoter()->quoteValue((string) $value),
default => $this->queryBuilder->quoter()->quoteValue((string) $value),
};
}

/**
* Builds the custom string that's appended to column definition.
*
Expand Down
8 changes: 3 additions & 5 deletions src/QueryBuilder/AbstractDDLQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,15 @@ public function addCommentOnColumn(string $table, string $column, string $commen
. '.'
. $this->quoter->quoteColumnName($column)
. ' IS '
. (string) $this->quoter->quoteValue($comment);
. $this->quoter->quoteValue($comment);
}

public function addCommentOnTable(string $table, string $comment): string
{
return 'COMMENT ON TABLE '
. $this->quoter->quoteTableName($table)
. ' IS '
. (string) $this->quoter->quoteValue($comment);
. $this->quoter->quoteValue($comment);
}

public function addDefaultValue(string $table, string $name, string $column, mixed $value): string
Expand Down Expand Up @@ -195,10 +195,8 @@ public function createView(string $viewName, QueryInterface|string $subQuery): s
if ($subQuery instanceof QueryInterface) {
[$rawQuery, $params] = $this->queryBuilder->build($subQuery);

/** @psalm-var mixed $value */
foreach ($params as $key => $value) {
/** @psalm-var mixed */
$params[$key] = $this->quoter->quoteValue($value);
$params[$key] = $this->queryBuilder->prepareValue($value);
}

$subQuery = strtr($rawQuery, $params);
Expand Down
71 changes: 71 additions & 0 deletions src/QueryBuilder/AbstractQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@
namespace Yiisoft\Db\QueryBuilder;

use Yiisoft\Db\Command\CommandInterface;
use Yiisoft\Db\Command\DataType;
use Yiisoft\Db\Command\ParamInterface;
use Yiisoft\Db\Connection\ConnectionInterface;
use Yiisoft\Db\Constant\GettypeResult;
use Yiisoft\Db\Exception\InvalidArgumentException;
use Yiisoft\Db\Expression\Expression;
use Yiisoft\Db\Expression\ExpressionInterface;
use Yiisoft\Db\Query\QueryInterface;
use Yiisoft\Db\QueryBuilder\Condition\Interface\ConditionInterface;
Expand All @@ -14,10 +19,14 @@
use Yiisoft\Db\Schema\QuoterInterface;
use Yiisoft\Db\Schema\SchemaInterface;

use function bin2hex;
use function count;
use function get_resource_type;
use function gettype;
use function is_string;
use function preg_match;
use function preg_replace;
use function stream_get_contents;

/**
* Builds a SELECT SQL statement based on the specification given as a {@see QueryInterface} object.
Expand All @@ -37,6 +46,16 @@ abstract class AbstractQueryBuilder implements QueryBuilderInterface
* The prefix for automatically generated query binding parameters.
*/
public const PARAM_PREFIX = ':qp';

/**
* @var string SQL value of the PHP `false` value.
*/
protected const FALSE_VALUE = 'FALSE';
/**
* @var string SQL value of the PHP `true` value.
*/
protected const TRUE_VALUE = 'TRUE';

/**
* @psalm-var string[] The abstract column types mapped to physical column types.
*
Expand Down Expand Up @@ -382,6 +401,36 @@ public function quoter(): QuoterInterface
return $this->quoter;
}

public function prepareParam(ParamInterface $param): string
{
return match ($param->getType()) {
DataType::BOOLEAN => $param->getValue() ? static::TRUE_VALUE : static::FALSE_VALUE,
DataType::INTEGER => (string) (int) $param->getValue(),
DataType::LOB => $this->prepareBinary((string) $param->getValue()),
DataType::NULL => 'NULL',
default => $this->prepareValue($param->getValue()),
};
}

public function prepareValue(mixed $value): string
{
/** @psalm-suppress MixedArgument */
return match (gettype($value)) {
GettypeResult::BOOLEAN => $value ? static::TRUE_VALUE : static::FALSE_VALUE,
GettypeResult::DOUBLE => (string) $value,
GettypeResult::INTEGER => (string) $value,
GettypeResult::NULL => 'NULL',
GettypeResult::OBJECT => match (true) {
$value instanceof Expression => (string) $value,
$value instanceof ParamInterface => $this->prepareParam($value),
default => $this->quoter->quoteValue((string) $value),
},
GettypeResult::RESOURCE => $this->prepareResource($value),
GettypeResult::RESOURCE_CLOSED => throw new InvalidArgumentException('Resource is closed.'),
default => $this->quoter->quoteValue((string) $value),
};
}

public function renameColumn(string $table, string $oldName, string $newName): string
{
return $this->ddlBuilder->renameColumn($table, $oldName, $newName);
Expand Down Expand Up @@ -435,4 +484,26 @@ public function upsert(
): string {
return $this->dmlBuilder->upsert($table, $insertColumns, $updateColumns, $params);
}

/**
* Converts a resource value to its SQL representation or throws an exception if conversion is not possible.
*
* @param resource $value
*/
protected function prepareResource(mixed $value): string
{
if (get_resource_type($value) !== 'stream') {
throw new InvalidArgumentException('Supported only stream resource type.');
}

return $this->prepareBinary(stream_get_contents($value));
}

/**
* Converts a binary value to its SQL representation using hexadecimal encoding.
*/
protected function prepareBinary(string $binary): string
{
return '0x' . bin2hex($binary);
}
}
13 changes: 13 additions & 0 deletions src/QueryBuilder/QueryBuilderInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Yiisoft\Db\QueryBuilder;

use Yiisoft\Db\Command\ParamInterface;
use Yiisoft\Db\Connection\ConnectionInterface;
use Yiisoft\Db\Exception\InvalidArgumentException;
use Yiisoft\Db\Expression\ExpressionBuilderInterface;
Expand Down Expand Up @@ -112,4 +113,16 @@ public function getExpressionBuilder(ExpressionInterface $expression): object;
* @return QuoterInterface The quoter instance.
*/
public function quoter(): QuoterInterface;

/**
* Converts a {@see ParamInterface} object to its SQL representation and quotes it if necessary.
* Used when the bind parameter cannot be used in the SQL query.
*/
public function prepareParam(ParamInterface $param): string;

/**
* Converts a value to its SQL representation and quotes it if necessary.
* Used when the bind parameter cannot be used in the SQL query.
*/
public function prepareValue(mixed $value): string;
}
6 changes: 1 addition & 5 deletions src/Schema/Quoter.php
Original file line number Diff line number Diff line change
Expand Up @@ -207,12 +207,8 @@ public function quoteTableName(string $name): string
return implode('.', $parts);
}

public function quoteValue(mixed $value): mixed
public function quoteValue(string $value): string
{
if (!is_string($value)) {
return $value;
}

return "'" . str_replace("'", "''", addcslashes($value, "\000\032")) . "'";
}

Expand Down
Loading

0 comments on commit 6b4bbd4

Please sign in to comment.