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
17 changes: 15 additions & 2 deletions src/Internal/ConnectionProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -483,12 +483,25 @@ public function execute(int $stmtId, string $query, array $params, array $prebou
$paramType = $params[$paramId]->getType();

if (isset($prebound[$paramId])) {
$types[] = MysqlDataType::encodeInt16(MysqlDataType::VarString->value);
$preboundType = match ($paramType) {
MysqlDataType::TinyBlob,
MysqlDataType::Blob,
MysqlDataType::MediumBlob,
MysqlDataType::LongBlob => MysqlDataType::LongBlob,
default => MysqlDataType::VarString,
};

$types[] = MysqlDataType::encodeInt16($preboundType->value);

continue;
}

$encodedValue = match ($paramType) {
MysqlDataType::Json => MysqlEncodedValue::fromJson($param),
MysqlDataType::TinyBlob,
MysqlDataType::Blob,
MysqlDataType::MediumBlob,
MysqlDataType::LongBlob => MysqlEncodedValue::forTargetType(MysqlDataType::LongBlob, $param),
MysqlDataType::Json => MysqlEncodedValue::forTargetType(MysqlDataType::Json, $param),
default => MysqlEncodedValue::fromValue($param),
};

Expand Down
20 changes: 16 additions & 4 deletions src/Internal/MysqlEncodedValue.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public static function fromValue(mixed $param): self
{
switch (\get_debug_type($param)) {
case "string":
return new self(MysqlDataType::LongBlob, MysqlDataType::encodeInt(\strlen($param)) . $param);
return new self(MysqlDataType::VarString, MysqlDataType::encodeInt(\strlen($param)) . $param);

case "int":
if ($param >= -(1 << 7) && $param < (1 << 7)) {
Expand Down Expand Up @@ -51,13 +51,25 @@ public static function fromValue(mixed $param): self
}
}

public static function fromJson(?string $json): self
public static function forTargetType(MysqlDataType $targetType, mixed $data): self
{
if ($json === null) {
if ($data === null) {
return new self(MysqlDataType::Null, "");
}

return new self(MysqlDataType::Json, MysqlDataType::encodeInt(\strlen($json)) . $json);
if ($data instanceof \Stringable) {
$data = (string) $data;
}

if (!\is_string($data)) {
throw new \TypeError(\sprintf(
"Expected string or null for %s column data, got %s",
$targetType->name,
\get_debug_type($data),
));
}

return new self($targetType, MysqlDataType::encodeInt(\strlen($data)) . $data);
}

private function __construct(
Expand Down
38 changes: 38 additions & 0 deletions test/MysqlEncodedValueTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php declare(strict_types=1);

namespace Amp\Mysql\Test;

use Amp\Mysql\Internal\MysqlEncodedValue;
use Amp\Mysql\MysqlDataType;
use PHPUnit\Framework\TestCase;

class MysqlEncodedValueTest extends TestCase
{
public function testStringable(): void
{
$stringable = new class implements \Stringable {
public function __toString(): string
{
return 'cast me';
}
};

$encoded = MysqlEncodedValue::forTargetType(MysqlDataType::LongBlob, $stringable);

self::assertSame(MysqlDataType::LongBlob, $encoded->getType());
}

public function testNullEncodingIgnoresTarget(): void
{
$encoded = MysqlEncodedValue::forTargetType(MysqlDataType::LongBlob, null);

self::assertSame(MysqlDataType::Null, $encoded->getType());
}

public function testNonStringValueThrows(): void
{
self::expectException(\TypeError::class);

MysqlEncodedValue::forTargetType(MysqlDataType::LongBlob, 1);
}
}
135 changes: 125 additions & 10 deletions test/MysqlLinkTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ public function testPrepared(): void
$stmt = $db->prepare("SELECT * FROM main WHERE a = ? OR b = ?");
$result = $stmt->execute([1, 8]);
$this->assertInstanceOf(MysqlResult::class, $result);
$this->assertSame(5, $result->getColumnCount());
$this->assertSame(EXPECTED_COLUMN_COUNT, $result->getColumnCount());
$got = [];
foreach ($result as $row) {
$got[] = \array_values($row);
Expand All @@ -243,7 +243,7 @@ public function testPrepared(): void
$stmt = $db->prepare("SELECT * FROM main WHERE a = :a OR b = ?");
$result = $stmt->execute(["a" => 2, 5]);
$this->assertInstanceOf(MysqlResult::class, $result);
$this->assertSame(5, $result->getColumnCount());
$this->assertSame(EXPECTED_COLUMN_COUNT, $result->getColumnCount());
$got = [];
foreach ($result as $row) {
$got[] = \array_values($row);
Expand Down Expand Up @@ -317,7 +317,7 @@ public function testExecute(): void
{
$db = $this->getLink();

$result = $db->execute("SELECT * FROM test.main WHERE a = ? OR b = ?", [2, 5]);
$result = $db->execute("SELECT id, a, b, c, d FROM test.main WHERE a = ? OR b = ?", [2, 5]);
$this->assertInstanceOf(MysqlResult::class, $result);
$got = [];
foreach ($result as $row) {
Expand Down Expand Up @@ -444,17 +444,132 @@ public function testInsertSelect(): void
$db->close();
}

public function testBindJson(): void
public function provideJsonData(): array
{
$json = '{"key": "value"}';
return \array_map(
fn (mixed $data) => [$data, \json_encode($data, \JSON_THROW_ON_ERROR)],
[
'object' => (object) ['key' => 'value'],
'array' => [1, 2, 3],
'string' => 'string',
'integer' => 123,
'float' => 3.14159,
'boolean' => true,
'null' => null,
],
);
}

$statement = $this->getLink()->prepare("SELECT CAST(? AS JSON) AS json_data");
$statement->bind(0, $json);
/**
* @dataProvider provideJsonData
*/
public function testJsonData(mixed $data, string $json): void
{
$db = $this->getLink();

$result = $statement->execute();
$transaction = $db->beginTransaction();

foreach ($result as $row) {
self::assertSame($json, $row['json_data']);
try {
$result = $transaction->execute("INSERT INTO main SET f = :json", ['json' => $json]);

self::assertSame($result->getRowCount(), 1);
$id = $result->getLastInsertId();
self::assertNotEmpty($id);

$result = $transaction->execute("SELECT f FROM main WHERE id = :id", ['id' => $id]);
self::assertEquals($data, \json_decode($result->fetchRow()['f'], flags: \JSON_THROW_ON_ERROR));
} finally {
$transaction->rollback();
}

$db->close();
}

/**
* @dataProvider provideJsonData
*/
public function testBindJsonData(mixed $data, string $json): void
{
$db = $this->getLink();

$transaction = $db->beginTransaction();

try {
$statement = $transaction->prepare("INSERT INTO main SET f = ?");
$statement->bind(0, $json);

$result = $statement->execute();

self::assertSame($result->getRowCount(), 1);
$id = $result->getLastInsertId();
self::assertNotEmpty($id);

$result = $transaction->execute("SELECT f FROM main WHERE id = :id", ['id' => $id]);
self::assertEquals($data, \json_decode($result->fetchRow()['f'], flags: \JSON_THROW_ON_ERROR));
} finally {
$transaction->rollback();
}

$db->close();
}

public function provideBlobData(): iterable
{
foreach (\range(0, 9) as $i) {
yield 'blob-data-' . $i => [\random_bytes(10)];
}
}

/**
* @dataProvider provideBlobData
*/
public function testBlobData(string $data): void
{
$db = $this->getLink();

$transaction = $db->beginTransaction();

try {
$result = $transaction->execute("INSERT INTO main SET e = :data", ['data' => $data]);

self::assertSame($result->getRowCount(), 1);
$id = $result->getLastInsertId();
self::assertNotEmpty($id);

$result = $transaction->execute("SELECT e FROM main WHERE id = :id", ['id' => $id]);
self::assertSame($data, $result->fetchRow()['e']);
} finally {
$transaction->rollback();
}

$db->close();
}

/**
* @dataProvider provideBlobData
*/
public function testBindBlobData(string $data): void
{
$db = $this->getLink();

$transaction = $db->beginTransaction();

try {
$statement = $transaction->prepare("INSERT INTO main SET e = ?");
$statement->bind(0, $data);

$result = $statement->execute();

self::assertSame($result->getRowCount(), 1);
$id = $result->getLastInsertId();
self::assertNotEmpty($id);

$result = $transaction->execute("SELECT e FROM main WHERE id = :id", ['id' => $id]);
self::assertSame($data, $result->fetchRow()['e']);
} finally {
$transaction->rollback();
}

$db->close();
}
}
26 changes: 24 additions & 2 deletions test/initialize.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,36 @@

namespace Amp\Mysql\Test;

const EXPECTED_COLUMN_COUNT = 7;

function initialize(\mysqli $db): void
{
$db->query("CREATE DATABASE test");

$db->query("CREATE TABLE test.main (id INT NOT NULL AUTO_INCREMENT, a INT, b INT, c DATETIME, d VARCHAR(255), PRIMARY KEY (id))");
$db->query(<<<SQL
CREATE TABLE test.main (
id INT NOT NULL AUTO_INCREMENT,
a INT NULL,
b INT NULL,
c DATETIME NULL,
d VARCHAR(255) NULL,
e BLOB NULL,
f JSON NULL,
PRIMARY KEY (id)
);
SQL);

$epoch = MysqlLinkTest::EPOCH;
$db->query("INSERT INTO test.main (a, b, c, d) VALUES (1, 2, '$epoch', 'a'), (2, 3, '$epoch', 'b'), (3, 4, '$epoch', 'c'), (4, 5, '$epoch', 'd'), (5, 6, '$epoch', 'e')");
$db->query(<<<SQL
INSERT INTO
test.main (a, b, c, d)
VALUES
(1, 2, '$epoch', 'a'),
(2, 3, '$epoch', 'b'),
(3, 4, '$epoch', 'c'),
(4, 5, '$epoch', 'd'),
(5, 6, '$epoch', 'e')
SQL);

$db->close();
}