mirror of
https://github.com/danog/postgres.git
synced 2024-11-26 20:15:02 +01:00
Automatically cast/encode statement parameters
Statement parameters are automatically cast to values expected by postgres, including encoding arrays.
This commit is contained in:
parent
cbbe6ff815
commit
225776c542
@ -2,13 +2,15 @@
|
||||
|
||||
namespace Amp\Postgres\Internal;
|
||||
|
||||
use function Amp\Postgres\cast;
|
||||
|
||||
const STATEMENT_PARAM_REGEX = <<<'REGEX'
|
||||
~(["'`])(?:\\(?:\\|\1)|(?!\1).)*+\1(*SKIP)(*F)|(\$(\d+)|\?)|:([a-zA-Z_]+)~ms
|
||||
REGEX;
|
||||
|
||||
/**
|
||||
* @param string $sql SQL statement with named placeholers.
|
||||
* @param array $names Array of parameter positions mapped to names.
|
||||
* @param string $sql SQL statement with named and unnamed placeholders.
|
||||
* @param array $names Array of parameter positions mapped to names and/or indexed locations.
|
||||
*
|
||||
* @return string SQL statement with Postgres-style placeholders
|
||||
*/
|
||||
@ -35,8 +37,8 @@ function parseNamedParams(string $sql, array &$names = null): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $params
|
||||
* @param mixed[] $names
|
||||
* @param mixed[] $params User-provided array of statement parameters.
|
||||
* @param mixed[] $names Array generated by parseNamedParams.
|
||||
*
|
||||
* @return mixed[]
|
||||
*
|
||||
@ -45,7 +47,7 @@ function parseNamedParams(string $sql, array &$names = null): string {
|
||||
function replaceNamedParams(array $params, array $names): array {
|
||||
$values = [];
|
||||
foreach ($names as $index => $name) {
|
||||
if (!isset($params[$name])) {
|
||||
if (!\array_key_exists($name, $params)) {
|
||||
if (\is_int($name)) {
|
||||
$message = \sprintf("Value for unnamed parameter at position %s missing", $name);
|
||||
} else {
|
||||
@ -55,7 +57,7 @@ function replaceNamedParams(array $params, array $names): array {
|
||||
throw new \Error($message);
|
||||
}
|
||||
|
||||
$values[$index] = $params[$name];
|
||||
$values[$index] = cast($params[$name]);
|
||||
}
|
||||
|
||||
return $values;
|
||||
|
@ -37,3 +37,72 @@ function connect(string $connectionString, CancellationToken $token = null): Pro
|
||||
function pool(string $connectionString, int $maxConnections = Pool::DEFAULT_MAX_CONNECTIONS): Pool {
|
||||
return new Pool($connectionString, $maxConnections);
|
||||
}
|
||||
|
||||
/**
|
||||
* Casts a PHP value to a representation that is understood by Postgres, including encoding arrays.
|
||||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return string|int|float|null
|
||||
*
|
||||
* @throws \Error If $value is an object without a __toString() method, a resource, or an unknown type.
|
||||
*/
|
||||
function cast($value) {
|
||||
switch ($type = \gettype($value)) {
|
||||
case "NULL":
|
||||
case "integer":
|
||||
case "double":
|
||||
case "string":
|
||||
return $value; // No casting necessary for numerics, strings, and null.
|
||||
|
||||
case "boolean":
|
||||
return $value ? 't' : 'f';
|
||||
|
||||
case "array":
|
||||
return encode($value);
|
||||
|
||||
case "object":
|
||||
if (!\method_exists($value, "__toString")) {
|
||||
throw new \Error("Object without a __toString() method included in parameter values");
|
||||
}
|
||||
|
||||
return (string) $value;
|
||||
|
||||
default:
|
||||
throw new \Error("Invalid value type '$type' in parameter values");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes an array into a PostgreSQL representation of the array.
|
||||
*
|
||||
* @param array $array
|
||||
*
|
||||
* @return string The serialized representation of the array.
|
||||
*
|
||||
* @throws \Error If $array contains an object without a __toString() method, a resource, or an unknown type.
|
||||
*/
|
||||
function encode(array $array): string {
|
||||
$array = \array_map(function ($value) {
|
||||
switch (\gettype($value)) {
|
||||
case "NULL":
|
||||
return "NULL";
|
||||
|
||||
case "object":
|
||||
if (!\method_exists($value, "__toString")) {
|
||||
throw new \Error("Object without a __toString() method in array");
|
||||
}
|
||||
|
||||
$value = (string) $value;
|
||||
// no break
|
||||
|
||||
case "string":
|
||||
return '"' . \str_replace(['\\', '"'], ['\\\\', '\\"'], $value) . '"';
|
||||
|
||||
default:
|
||||
return cast($value); // Recursively encodes arrays and errors on invalid values.
|
||||
}
|
||||
}, $array);
|
||||
|
||||
return '{' . \implode(',', $array) . '}';
|
||||
}
|
||||
|
79
test/EncodeTest.php
Normal file
79
test/EncodeTest.php
Normal file
@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace Amp\Postgres\Test;
|
||||
|
||||
use Amp\PHPUnit\TestCase;
|
||||
use function Amp\Postgres\encode;
|
||||
|
||||
class EncodeTest extends TestCase {
|
||||
public function testSingleDimensionalStringArray() {
|
||||
$array = ["one", "two", "three"];
|
||||
$string = '{"one","two","three"}';
|
||||
|
||||
$this->assertSame($string, encode($array));
|
||||
}
|
||||
|
||||
public function testMultiDimensionalStringArray() {
|
||||
$array = ["one", "two", ["three", "four"], "five"];
|
||||
$string = '{"one","two",{"three","four"},"five"}';
|
||||
|
||||
$this->assertSame($string, encode($array));
|
||||
}
|
||||
|
||||
public function testQuotedStrings() {
|
||||
$array = ["one", "two", ["three", "four"], "five"];
|
||||
$string = '{"one","two",{"three","four"},"five"}';
|
||||
|
||||
$this->assertSame($string, encode($array));
|
||||
}
|
||||
|
||||
public function testEscapedQuoteDelimiter() {
|
||||
$array = ['va"lue1', 'value"2'];
|
||||
$string = '{"va\\"lue1","value\\"2"}';
|
||||
|
||||
$this->assertSame($string, encode($array));
|
||||
}
|
||||
|
||||
public function testNullValue() {
|
||||
$array = ["one", null, "three"];
|
||||
$string = '{"one",NULL,"three"}';
|
||||
|
||||
$this->assertSame($string, encode($array));
|
||||
}
|
||||
|
||||
public function testSingleDimensionalIntegerArray() {
|
||||
$array = [1, 2, 3];
|
||||
$string = '{' . \implode(',', $array) . '}';
|
||||
|
||||
$this->assertSame($string, encode($array));
|
||||
}
|
||||
|
||||
public function testIntegerArrayWithNull() {
|
||||
$array = [1, 2, null, 3];
|
||||
$string = '{1,2,NULL,3}';
|
||||
|
||||
$this->assertSame($string, encode($array));
|
||||
}
|
||||
|
||||
public function testMultidimensionalIntegerArray() {
|
||||
$array = [1, 2, [3, 4], [5], 6, 7, [[8, 9], 10]];
|
||||
$string = '{1,2,{3,4},{5},6,7,{{8,9},10}}';
|
||||
|
||||
$this->assertSame($string, encode($array));
|
||||
}
|
||||
|
||||
public function testEscapedBackslashesInQuotedValue() {
|
||||
$array = ["test\\ing", "esca\\ped\\"];
|
||||
$string = '{"test\\\\ing","esca\\\\ped\\\\"}';
|
||||
|
||||
$this->assertSame($string, encode($array));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Error
|
||||
* @expectedExceptionMessage Object without a __toString() method in array
|
||||
*/
|
||||
public function testObjectWithoutToStringMethod() {
|
||||
encode([new \stdClass]);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user