Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/SystemVariableAssigner.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Mpyw\LaravelMySqlSystemVariableManager;

use Closure;
use Mpyw\LaravelMySqlSystemVariableManager\Value as BindingValue;
use Mpyw\LaravelPdoEmulationControl\EmulationController;
use Mpyw\Unclosure\Value;
use PDO;
Expand Down Expand Up @@ -86,7 +87,8 @@ protected static function withStatementFor(PDO $pdo, string $query, array $value
{
$statement = $pdo->prepare($query);
foreach (array_values($values) as $i => $value) {
$statement->bindValue($i + 1, $value, Grammar::paramTypeFor($value));
$value = BindingValue::wrap($value);
$statement->bindValue($i + 1, $value->getValue(), $value->getParamType());
}
$statement->execute();

Expand Down
35 changes: 1 addition & 34 deletions src/SystemVariableGrammar.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

namespace Mpyw\LaravelMySqlSystemVariableManager;

use PDO;

class SystemVariableGrammar
{
/**
Expand All @@ -23,42 +21,11 @@ public static function assignmentExpressions(array $values): array
{
$expressions = [];
foreach ($values as $name => $value) {
$expressions[] = static::escapeIdentifier($name) . '=' . static::placeholderFor($value);
$expressions[] = static::escapeIdentifier($name) . '=' . Value::wrap($value)->getPlaceholder();
}
return $expressions;
}

/**
* @param mixed $value
* @return int
*/
public static function paramTypeFor($value): int
{
switch (gettype($value)) {
case 'integer':
return PDO::PARAM_INT;
case 'boolean':
return PDO::PARAM_BOOL;
case 'NULL':
default:
return PDO::PARAM_STR;
}
}

/**
* @param mixed $value
* @return string
*/
public static function placeholderFor($value): string
{
switch (gettype($value)) {
case 'double':
return 'cast(? as decimal(65, 30))';
default:
return '?';
}
}

/**
* @param string $identifier
* @return string
Expand Down
172 changes: 172 additions & 0 deletions src/Value.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
<?php

namespace Mpyw\LaravelMySqlSystemVariableManager;

use InvalidArgumentException;
use PDO;

class Value implements ValueInterface
{
/**
* @var mixed
*/
protected $value;

/**
* @var string
*/
protected $type;

/**
* Create new int value for MySQL system variable.
*
* @param int $value
* @return static
*/
public static function int(int $value)
{
return new static($value, static::TYPE_INT);
}

/**
* Create new bool value for MySQL system variable.
*
* @param bool $value
* @return static
*/
public static function bool(bool $value)
{
return new static($value, static::TYPE_BOOL);
}

/**
* Create new float value for MySQL system variable.
*
* @param float $value
* @return static
*/
public static function float(float $value)
{
return new static($value, static::TYPE_FLOAT);
}

/**
* Create new string value for MySQL system variable.
*
* @param string $value
* @return static
*/
public static function str(string $value)
{
return new static($value, static::TYPE_STR);
}

/**
* Create new typed value for MySQL system variable.
*
* @param string $type
* @param bool|float|int|string $value
* @return \Mpyw\LaravelMySqlSystemVariableManager\ValueInterface
*/
public static function as(string $type, $value): ValueInterface
{
switch ($type) {
case static::TYPE_INT:
return static::int($value);
case static::TYPE_BOOL:
return static::bool($value);
case static::TYPE_FLOAT:
return static::float($value);
case static::TYPE_STR:
return static::str($value);
default:
throw new InvalidArgumentException('The type must be one of "integer", "boolean", "double" or "string".');
}
}

/**
* Automatically wrap a non-null value.
*
* @param mixed $value
* @return \Mpyw\LaravelMySqlSystemVariableManager\ValueInterface
*/
public static function wrap($value): ValueInterface
{
if ($value instanceof ValueInterface) {
return $value;
}
if (is_scalar($value)) {
return static::as(gettype($value), $value);
}
throw new InvalidArgumentException('The value must be a scalar or ' . ValueInterface::class . ' instance.');
}

/**
* Value constructor.
*
* @param mixed $value
* @param string $type
*/
protected function __construct($value, string $type)
{
$this->value = $value;
$this->type = $type;
}

/**
* Return original value.
*
* @return bool|float|int|string
*/
public function getValue()
{
return $this->value;
}

/**
* Return type.
*
* @return string
*/
public function getType(): string
{
return $this->type;
}

/**
* Return PDO::PARAM_* type.
*
* @return int
*/
public function getParamType(): int
{
switch ($this->type) {
case static::TYPE_INT:
return PDO::PARAM_INT;
case static::TYPE_BOOL:
return PDO::PARAM_BOOL;
case static::TYPE_FLOAT:
case static::TYPE_STR:
default:
return PDO::PARAM_STR;
}
}

/**
* Return a placeholder format.
*
* @return string
*/
public function getPlaceholder(): string
{
switch ($this->type) {
case static::TYPE_FLOAT:
return 'cast(? as decimal(65, 30))';
case static::TYPE_INT:
case static::TYPE_BOOL:
case static::TYPE_STR:
default:
return '?';
}
}
}
39 changes: 39 additions & 0 deletions src/ValueInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

namespace Mpyw\LaravelMySqlSystemVariableManager;

interface ValueInterface
{
public const TYPE_INT = 'integer';
public const TYPE_BOOL = 'boolean';
public const TYPE_FLOAT = 'double';
public const TYPE_STR = 'string';

/**
* Return original value.
*
* @return bool|float|int|string
*/
public function getValue();

/**
* Return type.
*
* @return string
*/
public function getType(): string;

/**
* Return PDO::PARAM_* type.
*
* @return int
*/
public function getParamType(): int;

/**
* Return placeholder for prepared statement.
*
* @return string
*/
public function getPlaceholder(): string;
}
32 changes: 25 additions & 7 deletions tests/BasicVariableAssignmentTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

namespace Mpyw\LaravelMySqlSystemVariableManager\Tests;

use InvalidArgumentException;
use Mpyw\LaravelMySqlSystemVariableManager\MySqlConnection;
use PDOException;
use Mpyw\LaravelMySqlSystemVariableManager\Value;

class BasicVariableAssignmentTest extends TestCase
{
Expand Down Expand Up @@ -39,13 +40,21 @@ public function provideBasicVariables(): array
'assigning boolean (emulated)' => ['foreign_key_checks', true, '1', false, '0'],
'assigning string (native)' => ['tx_isolation', false, 'REPEATABLE-READ', 'read-committed', 'READ-COMMITTED'],
'assigning string (emulated)' => ['tx_isolation', true, 'REPEATABLE-READ', 'read-committed', 'READ-COMMITTED'],
'assigning wrapped float (native)' => ['long_query_time', false, 10.0, Value::float(15.0), 15.0],
'assigning wrapped float (emulated)' => ['long_query_time', true, '10.000000', Value::float(15.0), '15.000000'],
'assigning wrapped integer (native)' => ['long_query_time', false, 10.0, Value::int(15), 15.0],
'assigning wrapped integer (emulated)' => ['long_query_time', true, '10.000000', Value::int(15), '15.000000'],
'assigning wrapped boolean (native)' => ['foreign_key_checks', false, 1, Value::bool(false), 0],
'assigning wrapped boolean (emulated)' => ['foreign_key_checks', true, '1', Value::bool(false), '0'],
'assigning wrapped string (native)' => ['tx_isolation', false, 'REPEATABLE-READ', Value::str('read-committed'), 'READ-COMMITTED'],
'assigning wrapped string (emulated)' => ['tx_isolation', true, 'REPEATABLE-READ', Value::str('read-committed'), 'READ-COMMITTED'],
];
}

public function testAssigningNullThrowsExceptionOnNative(): void
{
$this->expectException(PDOException::class);
$this->expectExceptionMessage("SQLSTATE[42000]: Syntax error or access violation: 1231 Variable 'foreign_key_checks' can't be set to the value of 'NULL'");
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('The value must be a scalar or Mpyw\LaravelMySqlSystemVariableManager\ValueInterface instance.');

$this->onNativeConnection(function (MySqlConnection $db) {
$db->setSystemVariable('foreign_key_checks', null);
Expand All @@ -55,24 +64,33 @@ public function testAssigningNullThrowsExceptionOnNative(): void

public function testAssigningNullThrowsExceptionOnEmulation(): void
{
$this->expectException(PDOException::class);
$this->expectExceptionMessage("SQLSTATE[42000]: Syntax error or access violation: 1231 Variable 'foreign_key_checks' can't be set to the value of 'NULL'");
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('The value must be a scalar or Mpyw\LaravelMySqlSystemVariableManager\ValueInterface instance.');

$this->onEmulatedConnection(function (MySqlConnection $db) {
$db->setSystemVariable('foreign_key_checks', null);
$db->getPdo();
});
}

public function testAssigningNullDoesNotThrowOnUnresolvedConnection(): void
public function testAssigningNullThrowsOnUnresolvedNativeConnection(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('The value must be a scalar or Mpyw\LaravelMySqlSystemVariableManager\ValueInterface instance.');

$this->onNativeConnection(function (MySqlConnection $db) {
$db->setSystemVariable('foreign_key_checks', null);
});
}

public function testAssigningNullThrowsOnUnresolvedEmulatedConnection(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('The value must be a scalar or Mpyw\LaravelMySqlSystemVariableManager\ValueInterface instance.');

$this->onEmulatedConnection(function (MySqlConnection $db) {
$db->setSystemVariable('foreign_key_checks', null);
});
$this->assertTrue(true);
}

public function testAssignmentPriorityOnLazilyResolvedConnection(): void
Expand Down