2017-01-18 18:05:05 +01:00
|
|
|
<?php
|
2016-09-14 16:27:39 +02:00
|
|
|
|
|
|
|
namespace Amp\Postgres\Test;
|
|
|
|
|
2017-03-17 16:17:24 +01:00
|
|
|
use Amp\{ Coroutine, Loop, Pause };
|
2016-09-14 16:27:39 +02:00
|
|
|
use Amp\Postgres\{ CommandResult, Connection, QueryError, Transaction, TransactionError, TupleResult };
|
|
|
|
|
|
|
|
abstract class AbstractConnectionTest extends \PHPUnit_Framework_TestCase {
|
|
|
|
/** @var \Amp\Postgres\Connection */
|
|
|
|
protected $connection;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return array Start test data for database.
|
|
|
|
*/
|
|
|
|
public function getData() {
|
|
|
|
return [
|
|
|
|
['amphp', 'org'],
|
|
|
|
['github', 'com'],
|
|
|
|
['google', 'com'],
|
|
|
|
['php', 'net'],
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
abstract public function createConnection(string $connectionString): Connection;
|
|
|
|
|
|
|
|
abstract public function getConnectCallable(): callable;
|
|
|
|
|
|
|
|
public function setUp() {
|
|
|
|
$this->connection = $this->createConnection('host=localhost user=postgres');
|
|
|
|
}
|
|
|
|
|
|
|
|
public function testQueryWithTupleResult() {
|
2017-03-17 16:17:24 +01:00
|
|
|
Loop::run(function () {
|
2016-09-14 16:27:39 +02:00
|
|
|
/** @var \Amp\Postgres\TupleResult $result */
|
|
|
|
$result = yield $this->connection->query("SELECT * FROM test");
|
|
|
|
|
|
|
|
$this->assertInstanceOf(TupleResult::class, $result);
|
|
|
|
|
|
|
|
$this->assertSame(2, $result->numFields());
|
|
|
|
|
|
|
|
$data = $this->getData();
|
|
|
|
|
2016-12-30 07:10:43 +01:00
|
|
|
for ($i = 0; yield $result->advance(); ++$i) {
|
2016-09-14 16:27:39 +02:00
|
|
|
$row = $result->getCurrent();
|
|
|
|
$this->assertSame($data[$i][0], $row['domain']);
|
|
|
|
$this->assertSame($data[$i][1], $row['tld']);
|
|
|
|
}
|
2017-03-17 16:17:24 +01:00
|
|
|
});
|
2016-09-14 16:27:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public function testQueryWithCommandResult() {
|
2017-03-17 16:17:24 +01:00
|
|
|
Loop::run(function () {
|
2016-09-14 16:27:39 +02:00
|
|
|
/** @var \Amp\Postgres\CommandResult $result */
|
|
|
|
$result = yield $this->connection->query("INSERT INTO test VALUES ('canon', 'jp')");
|
|
|
|
|
|
|
|
$this->assertInstanceOf(CommandResult::class, $result);
|
|
|
|
$this->assertSame(1, $result->affectedRows());
|
2017-03-17 16:17:24 +01:00
|
|
|
});
|
2016-09-14 16:27:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @expectedException \Amp\Postgres\QueryError
|
|
|
|
*/
|
|
|
|
public function testQueryWithEmptyQuery() {
|
2017-03-17 16:17:24 +01:00
|
|
|
Loop::run(function () {
|
2016-09-14 16:27:39 +02:00
|
|
|
/** @var \Amp\Postgres\CommandResult $result */
|
|
|
|
$result = yield $this->connection->query('');
|
2017-03-17 16:17:24 +01:00
|
|
|
});
|
2016-09-14 16:27:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @expectedException \Amp\Postgres\QueryError
|
|
|
|
*/
|
|
|
|
public function testQueryWithSyntaxError() {
|
2017-03-17 16:17:24 +01:00
|
|
|
Loop::run(function () {
|
2016-09-14 16:27:39 +02:00
|
|
|
/** @var \Amp\Postgres\CommandResult $result */
|
|
|
|
$result = yield $this->connection->query("SELECT & FROM test");
|
2017-03-17 16:17:24 +01:00
|
|
|
});
|
2016-09-14 16:27:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public function testPrepare() {
|
2017-03-17 16:17:24 +01:00
|
|
|
Loop::run(function () {
|
2016-09-14 16:27:39 +02:00
|
|
|
$query = "SELECT * FROM test WHERE domain=\$1";
|
|
|
|
|
|
|
|
/** @var \Amp\Postgres\Statement $statement */
|
|
|
|
$statement = yield $this->connection->prepare($query);
|
|
|
|
|
|
|
|
$this->assertSame($query, $statement->getQuery());
|
|
|
|
|
|
|
|
$data = $this->getData()[0];
|
|
|
|
|
|
|
|
/** @var \Amp\Postgres\TupleResult $result */
|
|
|
|
$result = yield $statement->execute($data[0]);
|
|
|
|
|
|
|
|
$this->assertInstanceOf(TupleResult::class, $result);
|
|
|
|
|
|
|
|
$this->assertSame(2, $result->numFields());
|
|
|
|
|
2016-12-30 07:10:43 +01:00
|
|
|
while (yield $result->advance()) {
|
2016-09-14 16:27:39 +02:00
|
|
|
$row = $result->getCurrent();
|
|
|
|
$this->assertSame($data[0], $row['domain']);
|
|
|
|
$this->assertSame($data[1], $row['tld']);
|
|
|
|
}
|
2017-03-17 16:17:24 +01:00
|
|
|
});
|
2016-09-14 16:27:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public function testExecute() {
|
2017-03-17 16:17:24 +01:00
|
|
|
Loop::run(function () {
|
2016-09-14 16:27:39 +02:00
|
|
|
$data = $this->getData()[0];
|
|
|
|
|
|
|
|
/** @var \Amp\Postgres\TupleResult $result */
|
|
|
|
$result = yield $this->connection->execute("SELECT * FROM test WHERE domain=\$1", $data[0]);
|
|
|
|
|
|
|
|
$this->assertInstanceOf(TupleResult::class, $result);
|
|
|
|
|
|
|
|
$this->assertSame(2, $result->numFields());
|
|
|
|
|
2016-12-30 07:10:43 +01:00
|
|
|
while (yield $result->advance()) {
|
2016-09-14 16:27:39 +02:00
|
|
|
$row = $result->getCurrent();
|
|
|
|
$this->assertSame($data[0], $row['domain']);
|
|
|
|
$this->assertSame($data[1], $row['tld']);
|
|
|
|
}
|
2017-03-17 16:17:24 +01:00
|
|
|
});
|
2016-09-14 16:27:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @depends testQueryWithTupleResult
|
|
|
|
*/
|
|
|
|
public function testSimultaneousQuery() {
|
|
|
|
$callback = \Amp\coroutine(function ($value) {
|
|
|
|
/** @var \Amp\Postgres\TupleResult $result */
|
|
|
|
$result = yield $this->connection->query("SELECT {$value} as value");
|
|
|
|
|
|
|
|
if ($value) {
|
|
|
|
yield new Pause(100);
|
|
|
|
}
|
|
|
|
|
2016-12-30 07:10:43 +01:00
|
|
|
while (yield $result->advance()) {
|
2016-09-14 16:27:39 +02:00
|
|
|
$row = $result->getCurrent();
|
|
|
|
$this->assertEquals($value, $row['value']);
|
|
|
|
}
|
|
|
|
});
|
2016-12-30 06:21:48 +01:00
|
|
|
|
2017-03-17 16:17:24 +01:00
|
|
|
Loop::run(function () use ($callback) {
|
|
|
|
yield \Amp\Promise\all([$callback(0), $callback(1)]);
|
|
|
|
});
|
2016-09-14 16:27:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @depends testSimultaneousQuery
|
|
|
|
*/
|
|
|
|
public function testSimultaneousQueryWithFirstFailing() {
|
|
|
|
$callback = \Amp\coroutine(function ($query) {
|
|
|
|
/** @var \Amp\Postgres\TupleResult $result */
|
|
|
|
$result = yield $this->connection->query($query);
|
|
|
|
|
|
|
|
$data = $this->getData();
|
|
|
|
|
2016-12-30 07:10:43 +01:00
|
|
|
for ($i = 0; yield $result->advance(); ++$i) {
|
2016-09-14 16:27:39 +02:00
|
|
|
$row = $result->getCurrent();
|
|
|
|
$this->assertSame($data[$i][0], $row['domain']);
|
|
|
|
$this->assertSame($data[$i][1], $row['tld']);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
try {
|
2017-03-17 16:17:24 +01:00
|
|
|
Loop::run(function () use ($callback) {
|
2016-09-14 16:27:39 +02:00
|
|
|
$failing = $callback("SELECT & FROM test");
|
|
|
|
$successful = $callback("SELECT * FROM test");
|
|
|
|
|
|
|
|
yield $successful;
|
|
|
|
yield $failing;
|
2017-03-17 16:17:24 +01:00
|
|
|
});
|
2016-09-14 16:27:39 +02:00
|
|
|
} catch (QueryError $exception) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->fail(\sprintf("Test did not throw an instance of %s", QueryError::class));
|
|
|
|
}
|
|
|
|
|
|
|
|
public function testSimultaneousQueryAndPrepare() {
|
2016-11-15 18:06:21 +01:00
|
|
|
$promises = [];
|
|
|
|
$promises[] = new Coroutine((function () {
|
2016-09-14 16:27:39 +02:00
|
|
|
/** @var \Amp\Postgres\TupleResult $result */
|
|
|
|
$result = yield $this->connection->query("SELECT * FROM test");
|
|
|
|
|
|
|
|
$data = $this->getData();
|
|
|
|
|
2016-12-30 07:10:43 +01:00
|
|
|
for ($i = 0; yield $result->advance(); ++$i) {
|
2016-09-14 16:27:39 +02:00
|
|
|
$row = $result->getCurrent();
|
|
|
|
$this->assertSame($data[$i][0], $row['domain']);
|
|
|
|
$this->assertSame($data[$i][1], $row['tld']);
|
|
|
|
}
|
|
|
|
})());
|
|
|
|
|
2016-11-15 18:06:21 +01:00
|
|
|
$promises[] = new Coroutine((function () {
|
2016-09-14 16:27:39 +02:00
|
|
|
/** @var \Amp\Postgres\Statement $statement */
|
|
|
|
$statement = (yield $this->connection->prepare("SELECT * FROM test"));
|
|
|
|
|
|
|
|
/** @var \Amp\Postgres\TupleResult $result */
|
|
|
|
$result = yield $statement->execute();
|
|
|
|
|
|
|
|
$data = $this->getData();
|
|
|
|
|
2016-12-30 07:10:43 +01:00
|
|
|
for ($i = 0; yield $result->advance(); ++$i) {
|
2016-09-14 16:27:39 +02:00
|
|
|
$row = $result->getCurrent();
|
|
|
|
$this->assertSame($data[$i][0], $row['domain']);
|
|
|
|
$this->assertSame($data[$i][1], $row['tld']);
|
|
|
|
}
|
|
|
|
})());
|
2016-12-30 06:21:48 +01:00
|
|
|
|
2017-03-17 16:17:24 +01:00
|
|
|
Loop::run(function () use ($promises) {
|
|
|
|
yield \Amp\Promise\all($promises);
|
|
|
|
});
|
2016-09-14 16:27:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public function testSimultaneousPrepareAndExecute() {
|
2016-11-15 18:06:21 +01:00
|
|
|
$promises[] = new Coroutine((function () {
|
2016-09-14 16:27:39 +02:00
|
|
|
/** @var \Amp\Postgres\Statement $statement */
|
|
|
|
$statement = yield $this->connection->prepare("SELECT * FROM test");
|
|
|
|
|
|
|
|
/** @var \Amp\Postgres\TupleResult $result */
|
|
|
|
$result = yield $statement->execute();
|
|
|
|
|
|
|
|
$data = $this->getData();
|
|
|
|
|
2016-12-30 07:10:43 +01:00
|
|
|
for ($i = 0; yield $result->advance(); ++$i) {
|
2016-09-14 16:27:39 +02:00
|
|
|
$row = $result->getCurrent();
|
|
|
|
$this->assertSame($data[$i][0], $row['domain']);
|
|
|
|
$this->assertSame($data[$i][1], $row['tld']);
|
|
|
|
}
|
|
|
|
})());
|
|
|
|
|
2016-11-15 18:06:21 +01:00
|
|
|
$promises[] = new Coroutine((function () {
|
2016-09-14 16:27:39 +02:00
|
|
|
/** @var \Amp\Postgres\TupleResult $result */
|
|
|
|
$result = yield $this->connection->execute("SELECT * FROM test");
|
|
|
|
|
|
|
|
$data = $this->getData();
|
|
|
|
|
2016-12-30 07:10:43 +01:00
|
|
|
for ($i = 0; yield $result->advance(); ++$i) {
|
2016-09-14 16:27:39 +02:00
|
|
|
$row = $result->getCurrent();
|
|
|
|
$this->assertSame($data[$i][0], $row['domain']);
|
|
|
|
$this->assertSame($data[$i][1], $row['tld']);
|
|
|
|
}
|
|
|
|
})());
|
2016-12-30 06:21:48 +01:00
|
|
|
|
2017-03-17 16:17:24 +01:00
|
|
|
Loop::run(function () use ($promises) {
|
|
|
|
yield \Amp\Promise\all($promises);
|
|
|
|
});
|
2016-09-14 16:27:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public function testTransaction() {
|
2017-03-17 16:17:24 +01:00
|
|
|
Loop::run(function () {
|
2016-09-14 16:27:39 +02:00
|
|
|
$isolation = Transaction::COMMITTED;
|
|
|
|
|
|
|
|
/** @var \Amp\Postgres\Transaction $transaction */
|
|
|
|
$transaction = yield $this->connection->transaction($isolation);
|
|
|
|
|
|
|
|
$this->assertInstanceOf(Transaction::class, $transaction);
|
|
|
|
|
|
|
|
$data = $this->getData()[0];
|
|
|
|
|
|
|
|
$this->assertTrue($transaction->isActive());
|
|
|
|
$this->assertSame($isolation, $transaction->getIsolationLevel());
|
|
|
|
|
|
|
|
yield $transaction->savepoint('test');
|
|
|
|
|
|
|
|
$result = yield $transaction->execute("SELECT * FROM test WHERE domain=\$1 FOR UPDATE", $data[0]);
|
|
|
|
|
|
|
|
yield $transaction->rollbackTo('test');
|
|
|
|
|
|
|
|
yield $transaction->commit();
|
|
|
|
|
|
|
|
$this->assertFalse($transaction->isActive());
|
|
|
|
|
|
|
|
try {
|
|
|
|
$result = yield $transaction->execute("SELECT * FROM test");
|
|
|
|
$this->fail('Query should fail after transaction commit');
|
|
|
|
} catch (TransactionError $exception) {
|
|
|
|
// Exception expected.
|
|
|
|
}
|
2017-03-17 16:17:24 +01:00
|
|
|
});
|
2016-09-14 16:27:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public function testConnect() {
|
2017-03-17 16:17:24 +01:00
|
|
|
Loop::run(function () {
|
2016-09-14 16:27:39 +02:00
|
|
|
$connect = $this->getConnectCallable();
|
|
|
|
$connection = yield $connect('host=localhost user=postgres');
|
|
|
|
$this->assertInstanceOf(Connection::class, $connection);
|
2017-03-17 16:17:24 +01:00
|
|
|
});
|
2016-09-14 16:27:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @expectedException \Amp\Postgres\FailureException
|
|
|
|
*/
|
|
|
|
public function testConnectInvalidUser() {
|
2017-03-17 16:17:24 +01:00
|
|
|
Loop::run(function () {
|
2016-09-14 16:27:39 +02:00
|
|
|
$connect = $this->getConnectCallable();
|
2017-02-16 00:36:10 +01:00
|
|
|
$connection = yield $connect('host=localhost user=invalid', 100);
|
2017-03-17 16:17:24 +01:00
|
|
|
});
|
2016-09-14 16:27:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @expectedException \Amp\Postgres\FailureException
|
|
|
|
*/
|
|
|
|
public function testConnectInvalidConnectionString() {
|
2017-03-17 16:17:24 +01:00
|
|
|
Loop::run(function () {
|
2016-09-14 16:27:39 +02:00
|
|
|
$connect = $this->getConnectCallable();
|
2017-02-16 00:36:10 +01:00
|
|
|
$connection = yield $connect('invalid connection string', 100);
|
2017-03-17 16:17:24 +01:00
|
|
|
});
|
2016-09-14 16:27:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @expectedException \Amp\Postgres\FailureException
|
|
|
|
*/
|
|
|
|
public function testConnectInvalidHost() {
|
2017-03-17 16:17:24 +01:00
|
|
|
Loop::run(function () {
|
2016-09-14 16:27:39 +02:00
|
|
|
$connect = $this->getConnectCallable();
|
2017-02-16 00:36:10 +01:00
|
|
|
$connection = yield $connect('hostaddr=invalid.host user=postgres', 100);
|
2017-03-17 16:17:24 +01:00
|
|
|
});
|
2016-09-14 16:27:39 +02:00
|
|
|
}
|
|
|
|
}
|