Skip to content

Commit

Permalink
Create a driver for postgres
Browse files Browse the repository at this point in the history
  • Loading branch information
duncan3dc committed Aug 20, 2017
1 parent 2cb13bd commit 3e37004
Show file tree
Hide file tree
Showing 7 changed files with 440 additions and 3 deletions.
4 changes: 3 additions & 1 deletion .appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ environment:

services:
- mysql
- postgresql

init:
- SET DUNCAN3DC_CACHE=c:\duncan3dc
- SET PATH=C:\Program Files\MySQL\MySQL Server 5.7\bin\;%PATH%;
- SET PATH=C:\Program Files\MySQL\MySQL Server 5.7\bin\;C:\Program Files\PostgreSQL\9.6\bin\;%PATH%;
- SET MYSQL_PASSWORD=Password12!
- SET POSTGRES_PASSWORD=Password12!

cache:
- '%DUNCAN3DC_CACHE%'
Expand Down
8 changes: 6 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@ matrix:
- php: nightly

env:
- COMPOSER_OPTS=""
- COMPOSER_OPTS="--prefer-lowest"
global:
- POSTGRES_PASSWORD=""
matrix:
- COMPOSER_OPTS=""
- COMPOSER_OPTS="--prefer-lowest"

install:
- composer self-update --snapshot
Expand All @@ -27,6 +30,7 @@ after_success:

services:
- mysql
- postgresql

git:
depth: 5
Expand Down
50 changes: 50 additions & 0 deletions src/Driver/Postgres/Result.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

namespace duncan3dc\Sql\Driver\Postgres;

use duncan3dc\Sql\Driver\ResultInterface;

class Result implements ResultInterface
{

/**
* Create a new instance.
*
* @param mixed $result Something returned by pg_query_params
*/
public function __construct($result)
{
$this->result = $result;
}


/**
* Fetch the next row from the result set.
*
* @return array|null
*/
public function getNextRow()
{
$row = pg_fetch_assoc($this->result);

if (is_array($row)) {
return $row;
}

return null;
}


/**
* Free the memory used by the result resource.
*
* @return void
*/
public function free()
{
if (is_resource($this->result)) {
pg_free_result($this->result);
$this->result = null;
}
}
}
204 changes: 204 additions & 0 deletions src/Driver/Postgres/Server.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
<?php

namespace duncan3dc\Sql\Driver\Postgres;

use duncan3dc\PhpIni\State;
use duncan3dc\Sql\Driver\ServerInterface;

class Server implements ServerInterface
{
/**
* @var resource $pg The connection to the database server.
*/
private $pg;

/**
* @var string $database The database to use.
*/
private $database;

/**
* @var string $hostname The host or ip address of the database server.
*/
private $hostname;

/**
* @var string $username The user to authenticate with.
*/
private $username;

/**
* @var string $password The password to authenticate with.
*/
private $password;

/**
* @var int $port The port to connect on.
*/
private $port;

/**
* @var State $ini An ini state object to supress warnings.
*/
private $ini;


/**
* Create a new instance.
*
* @param string $database The database to use
* @param string $hostname The host or ip address of the database server
* @param string $username The user to authenticate with
* @param string $password The password to authenticate with
*/
public function __construct(string $database, string $hostname, string $username, string $password)
{
$this->database = $database;
$this->hostname = $hostname;
$this->username = $username;
$this->password = $password;

$this->ini = new State;
$this->ini->set("error_reporting", error_reporting() ^ \E_WARNING);
}


/**
* Set the port to connect on.
*
* @param int $port The port number
*
* @return $this
*/
public function setPort(int $port): ServerInterface
{
$this->port = $port;
}


/**
* Connect to the database using the previously supplied credentials.
*
* @return bool
*/
public function connect(): bool
{
$options = [
"host" => $this->hostname,
"user" => $this->username,
"password" => $this->password,
"dbname" => $this->database,
"port" => $this->port,
];

$connect = "";
foreach ($options as $key => $val) {
if ($val === null) {
continue;
}
$connect .= "{$key}={$val} ";
}

$this->ini->call(function () use ($connect) {
$this->pg = pg_connect($connect, \PGSQL_CONNECT_FORCE_NEW);
});

if (!$this->pg) {
return false;
}

return true;
}


/**
* Run a query.
*
* @param string $query The query to run
* @param array $params The parameters to substitute in the query string
* @param string $preparedQuery A simulated prepared query (if the server doesn't support prepared statements)
*
* @return ResultInterface|null Successful statements should return a Result instance
*/
public function query(string $query, array $params, string $preparedQuery)
{
$tmpQuery = $query;
$query = "";

$i = 1;
while ($pos = strpos($tmpQuery, "?")) {
$query .= substr($tmpQuery, 0, $pos) . "\$" . $i++;
$tmpQuery = substr($tmpQuery, $pos + 1);
}
$query .= $tmpQuery;

$result = $this->ini->call(function () use ($query, $params) {
return pg_query_params($this->pg, $query, $params);
});

if ($result) {
return new Result($result);
}
}


/**
* Quote the supplied string with the relevant characters used by the database driver.
*
* @param string $value The string to quote
*
* @return string The quoted string
*/
public function quoteValue(string $value): string
{
return pg_escape_literal($this->pg, $value);
}


/**
* Get the error code of the last error.
*
* @return mixed
*/
public function getErrorCode()
{
return 0;
}


/**
* Get the error message text of the last error.
*
* @return string
*/
public function getErrorMessage(): string
{
if (!$this->pg) {
$error = error_get_last();
if (array_key_exists("message", $error)) {
return explode("\n", $error["message"])[0];
}
}

return pg_last_error($this->pg);
}


/**
* Close the sql connection.
*
* @return bool
*/
public function disconnect(): bool
{
if (!$this->pg) {
return true;
}

$result = pg_close($this->pg);

$this->pg = null;

return $result;
}
}
28 changes: 28 additions & 0 deletions tests/Driver/Postgres/AbstractTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace duncan3dc\SqlTests\Driver\Postgres;

use duncan3dc\Sql\Driver\Postgres\Server;
use PHPUnit\Framework\TestCase;

abstract class AbstractTest extends TestCase
{
private static $server;

public static function setUpBeforeClass()
{
if (!isset($_ENV["POSTGRES_PASSWORD"])) {
$_ENV["POSTGRES_PASSWORD"] = "pass";
}

self::$server = new Server("postgres", "127.0.0.1", "postgres", $_ENV["POSTGRES_PASSWORD"]);
self::$server->connect();
self::$server->query("CREATE DATABASE pgdb", [], "");
}


public static function tearDownAfterClass()
{
self::$server->query("DROP DATABASE pgdb", [], "");
}
}
56 changes: 56 additions & 0 deletions tests/Driver/Postgres/ResultTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

namespace duncan3dc\SqlTests\Driver\Postgres;

use duncan3dc\Sql\Driver\Postgres\Result;
use duncan3dc\Sql\Driver\Postgres\Server;

class ResultTest extends AbstractTest
{
private $server;


public function setUp()
{
$this->server = new Server("pgdb", "127.0.0.1", "postgres", $_ENV["POSTGRES_PASSWORD"]);
$this->server->connect();
}


public function tearDown()
{
$this->server->disconnect();
}


public function testGetNextRow()
{
$this->server->query("CREATE TEMPORARY TABLE bands (name VARCHAR(20), year INTEGER)", [], "");
$this->server->query("INSERT INTO bands VALUES ('metallica', 1981), ('protest the hero', 2002)", [], "");

$result = $this->server->query("SELECT name, year FROM bands", [], "");

$this->assertSame([
"name" => "metallica",
"year" => "1981",
], $result->getNextRow());

$this->assertSame([
"name" => "protest the hero",
"year" => "2002",
], $result->getNextRow());

$this->assertNull($result->getNextRow());
}


public function testFree()
{
$result = $this->server->query("SELECT current_database()", [], "");

$result->free();
$result->free();

$this->assertInstanceOf(Result::class, $result);
}
}

0 comments on commit 3e37004

Please sign in to comment.