diff --git a/lib/Conf.php b/lib/Conf.php index 9173d3c..f15926c 100644 --- a/lib/Conf.php +++ b/lib/Conf.php @@ -35,8 +35,10 @@ class Conf { public $dbPostgreSQLPort = 5432; /** @var string Database name on PostgreSQL database server (if using PostgreSQL) */ public $dbPostgreSQLDb = "arsse"; - /** @var string Schema name on PostgreSQL database server (if using PostgreSQL) */ + /** @var string Schema name in PostgreSQL database (if using PostgreSQL) */ public $dbPostgreSQLSchema = ""; + /** @var string Service file entry to use (if using PostgreSQL); if using a service entry all above parameters except schema are ignored */ + public $dbPostgreSQLService = ""; /** @var string Class of the user management driver in use (Internal by default) */ public $userDriver = User\Internal\Driver::class; diff --git a/lib/Db/PostgreSQL/Driver.php b/lib/Db/PostgreSQL/Driver.php index 2ba5baa..7e763e8 100644 --- a/lib/Db/PostgreSQL/Driver.php +++ b/lib/Db/PostgreSQL/Driver.php @@ -14,7 +14,7 @@ use JKingWeb\Arsse\Db\ExceptionTimeout; class Driver extends \JKingWeb\Arsse\Db\AbstractDriver { - public function __construct(string $user = null, string $pass = null, string $db = null, string $host = null, int $port = null, string $schema = null) { + public function __construct(string $user = null, string $pass = null, string $db = null, string $host = null, int $port = null, string $schema = null, string $service = null) { // check to make sure required extension is loaded if (!static::requirementsMet()) { throw new Exception("extMissing", self::driverName()); // @codeCoverageIgnore @@ -25,7 +25,8 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver { $host = $host ?? Arsse::$conf->dbPostgreSQLHost; $port = $port ?? Arsse::$conf->dbPostgreSQLPort; $schema = $schema ?? Arsse::$conf->dbPostgreSQLSchema; - $this->makeConnection($user, $pass, $db, $host, $port); + $service = $service ?? Arsse::$conf->dbPostgreSQLService; + $this->makeConnection($user, $pass, $db, $host, $port, $service); } public static function requirementsMet(): bool { @@ -38,20 +39,38 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver { throw new \Exception; } - protected function makeConnectionString(bool $pdo, string $user, string $pass, string $db, string $host, int $port): string { - $out = ['dbname' => $db]; - if ($host != "") { - $out['host'] = $host; - $out['port'] = (string) $port; - } - if (!$pdo) { - $out['user'] = $user; - $out['password'] = $pass; + public static function makeConnectionString(bool $pdo, string $user, string $pass, string $db, string $host, int $port, string $service): string { + $base = [ + 'client_encoding' => "UTF8", + 'application_name' => "arsse", + ]; + $out = []; + if ($service != "") { + $out['service'] = $service; + } else { + if ($host != "") { + $out['host'] = $host; + } + if ($port != 5432 && !($host != "" && $host[0] == "/")) { + $out['port'] = (string) $port; + } + if ($db != "") { + $out['dbname'] = $db; + } + if (!$pdo) { + $out['user'] = $user; + if ($pass != "") { + $out['password'] = $pass; + } + } } + ksort($out); + ksort($base); + $out = array_merge($out, $base); $out = array_map(function($v, $k) { return "$k='".str_replace("'", "\\'", str_replace("\\", "\\\\", $v))."'"; }, $out, array_keys($out)); - return implode(($pdo ? ";" : " "), $out); + return implode(" ", $out); } public function __destruct() { diff --git a/lib/Db/PostgreSQL/PDODriver.php b/lib/Db/PostgreSQL/PDODriver.php index fd43780..9bb630e 100644 --- a/lib/Db/PostgreSQL/PDODriver.php +++ b/lib/Db/PostgreSQL/PDODriver.php @@ -20,8 +20,8 @@ class PDODriver extends Driver { return class_exists("PDO") && in_array("pgsql", \PDO::getAvailableDrivers()); } - protected function makeConnection(string $user, string $pass, string $db, string $host, int $port) { - $dsn = $this->makeconnectionString(true, $user, $pass, $db, $host, $port); + protected function makeConnection(string $user, string $pass, string $db, string $host, int $port, string $service) { + $dsn = $this->makeconnectionString(true, $user, $pass, $db, $host, $port, $service); $this->db = new \PDO("pgsql:$dsn", $user, $pass); } diff --git a/locale/en.php b/locale/en.php index 55a0bd3..50b8b5f 100644 --- a/locale/en.php +++ b/locale/en.php @@ -20,6 +20,8 @@ return [ 'Driver.Db.SQLite3.Name' => 'SQLite 3', 'Driver.Db.SQLite3PDO.Name' => 'SQLite 3 (PDO)', + 'Driver.Db.PostgreSQL.Name' => 'PostgreSQL', + 'Driver.Db.PostgreSQLPDO.Name' => 'PostgreSQL (PDO)', 'Driver.Service.Curl.Name' => 'HTTP (curl)', 'Driver.Service.Internal.Name' => 'Internal', 'Driver.User.Internal.Name' => 'Internal', diff --git a/tests/cases/Db/PostgreSQL/TestDriver.php b/tests/cases/Db/PostgreSQL/TestDriver.php new file mode 100644 index 0000000..59e113b --- /dev/null +++ b/tests/cases/Db/PostgreSQL/TestDriver.php @@ -0,0 +1,54 @@ + */ +class TestDriver extends \JKingWeb\Arsse\Test\AbstractTest { + /** @dataProvider provideConnectionStrings */ + public function testGenerateConnectionString(bool $pdo, string $user, string $pass, string $db, string $host, int $port, string $service, string $exp) { + $postfix = "application_name='arsse' client_encoding='UTF8'"; + $act = Driver::makeConnectionString($pdo, $user, $pass, $db, $host, $port, $service); + if ($act==$postfix) { + $this->assertSame($exp, ""); + } else { + $test = substr($act, 0, strlen($act) - (strlen($postfix) + 1) ); + $check = substr($act, strlen($test) + 1); + $this->assertSame($postfix, $check); + $this->assertSame($exp, $test); + } + } + + public function provideConnectionStrings() { + return [ + [false, "arsse", "secret", "arsse", "", 5432, "", "dbname='arsse' password='secret' user='arsse'"], + [false, "arsse", "p word", "arsse", "", 5432, "", "dbname='arsse' password='p word' user='arsse'"], + [false, "arsse", "p'word", "arsse", "", 5432, "", "dbname='arsse' password='p\\'word' user='arsse'"], + [false, "arsse user", "secret", "arsse db", "", 5432, "", "dbname='arsse db' password='secret' user='arsse user'"], + [false, "arsse", "secret", "", "", 5432, "", "password='secret' user='arsse'"], + [false, "arsse", "secret", "arsse", "localhost", 5432, "", "dbname='arsse' host='localhost' password='secret' user='arsse'"], + [false, "arsse", "secret", "arsse", "", 9999, "", "dbname='arsse' password='secret' port='9999' user='arsse'"], + [false, "arsse", "secret", "arsse", "localhost", 9999, "", "dbname='arsse' host='localhost' password='secret' port='9999' user='arsse'"], + [false, "arsse", "secret", "arsse", "/socket", 9999, "", "dbname='arsse' host='/socket' password='secret' user='arsse'"], + [false, "T'Pau of Vulcan", "", "", "", 5432, "", "user='T\\'Pau of Vulcan'"], + [false, "T'Pau of Vulcan", "superman", "datumbase", "somehost", 2112, "arsse", "service='arsse'"], + [true, "arsse", "secret", "arsse", "", 5432, "", "dbname='arsse'"], + [true, "arsse", "p word", "arsse", "", 5432, "", "dbname='arsse'"], + [true, "arsse", "p'word", "arsse", "", 5432, "", "dbname='arsse'"], + [true, "arsse user", "secret", "arsse db", "", 5432, "", "dbname='arsse db'"], + [true, "arsse", "secret", "", "", 5432, "", ""], + [true, "arsse", "secret", "arsse", "localhost", 5432, "", "dbname='arsse' host='localhost'"], + [true, "arsse", "secret", "arsse", "", 9999, "", "dbname='arsse' port='9999'"], + [true, "arsse", "secret", "arsse", "localhost", 9999, "", "dbname='arsse' host='localhost' port='9999'"], + [true, "arsse", "secret", "arsse", "/socket", 9999, "", "dbname='arsse' host='/socket'"], + [true, "T'Pau of Vulcan", "", "", "", 5432, "", ""], + [true, "T'Pau of Vulcan", "superman", "datumbase", "somehost", 2112, "arsse", "service='arsse'"], + ]; + } +} diff --git a/tests/phpunit.xml b/tests/phpunit.xml index f2a4967..564ced7 100644 --- a/tests/phpunit.xml +++ b/tests/phpunit.xml @@ -55,6 +55,8 @@ cases/Db/SQLite3PDO/TestCreation.php cases/Db/SQLite3PDO/TestDriver.php cases/Db/SQLite3PDO/TestUpdate.php + + cases/Db/PostgreSQL/TestDriver.php cases/Db/SQLite3/Database/TestMiscellany.php