mirror of
https://code.mensbeam.com/MensBeam/Arsse.git
synced 2024-12-22 13:12:41 +00:00
Nominally complete PostgreSQL driver
Connection error handling as well as uprade error handling still need to be implemented.
This commit is contained in:
parent
84b4cb7465
commit
c0c4810662
6 changed files with 245 additions and 53 deletions
|
@ -13,13 +13,13 @@ abstract class AbstractDriver implements Driver {
|
||||||
protected $transDepth = 0;
|
protected $transDepth = 0;
|
||||||
protected $transStatus = [];
|
protected $transStatus = [];
|
||||||
|
|
||||||
|
abstract protected function lock(): bool;
|
||||||
|
abstract protected function unlock(bool $rollback = false): bool;
|
||||||
abstract protected function getError(): string;
|
abstract protected function getError(): string;
|
||||||
|
|
||||||
/** @codeCoverageIgnore */
|
|
||||||
public function schemaVersion(): int {
|
public function schemaVersion(): int {
|
||||||
// FIXME: generic schemaVersion() will need to be covered for database engines other than SQLite
|
|
||||||
try {
|
try {
|
||||||
return (int) $this->query("SELECT value from arsse_meta where key is schema_version")->getValue();
|
return (int) $this->query("SELECT value from arsse_meta where key = 'schema_version'")->getValue();
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,11 +13,10 @@ use JKingWeb\Arsse\Db\ExceptionInput;
|
||||||
use JKingWeb\Arsse\Db\ExceptionTimeout;
|
use JKingWeb\Arsse\Db\ExceptionTimeout;
|
||||||
|
|
||||||
class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
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, string $service = 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
|
// check to make sure required extension is loaded
|
||||||
if (!static::requirementsMet()) {
|
if (!static::requirementsMet()) {
|
||||||
throw new Exception("extMissing", self::driverName()); // @codeCoverageIgnore
|
throw new Exception("extMissing", static::driverName()); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
$user = $user ?? Arsse::$conf->dbPostgreSQLUser;
|
$user = $user ?? Arsse::$conf->dbPostgreSQLUser;
|
||||||
$pass = $pass ?? Arsse::$conf->dbPostgreSQLPass;
|
$pass = $pass ?? Arsse::$conf->dbPostgreSQLPass;
|
||||||
|
@ -27,16 +26,9 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
||||||
$schema = $schema ?? Arsse::$conf->dbPostgreSQLSchema;
|
$schema = $schema ?? Arsse::$conf->dbPostgreSQLSchema;
|
||||||
$service = $service ?? Arsse::$conf->dbPostgreSQLService;
|
$service = $service ?? Arsse::$conf->dbPostgreSQLService;
|
||||||
$this->makeConnection($user, $pass, $db, $host, $port, $service);
|
$this->makeConnection($user, $pass, $db, $host, $port, $service);
|
||||||
}
|
foreach (static::makeSetupQueries($schema) as $q) {
|
||||||
|
$this->exec($q);
|
||||||
public static function requirementsMet(): bool {
|
}
|
||||||
// stub: native interface is not yet supported
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function makeConnection(string $user, string $pass, string $db, string $host, int $port, string $service) {
|
|
||||||
// stub: native interface is not yet supported
|
|
||||||
throw new \Exception;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function makeConnectionString(bool $pdo, string $user, string $pass, string $db, string $host, int $port, string $service): string {
|
public static function makeConnectionString(bool $pdo, string $user, string $pass, string $db, string $host, int $port, string $service): string {
|
||||||
|
@ -73,7 +65,15 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
||||||
return implode(" ", $out);
|
return implode(" ", $out);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __destruct() {
|
public static function makeSetupQueries(string $schema = ""): array {
|
||||||
|
$out = [
|
||||||
|
"SET TIME ZONE UTC",
|
||||||
|
"SET DateStyle = 'ISO, MDY'"
|
||||||
|
];
|
||||||
|
if (strlen($schema) > 0) {
|
||||||
|
$out[] = 'SET search_path = \'"'.str_replace('"', '""', $schema).'", "$user", public\'';
|
||||||
|
}
|
||||||
|
return $out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @codeCoverageIgnore */
|
/** @codeCoverageIgnore */
|
||||||
|
@ -96,48 +96,57 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
||||||
return "PostgreSQL";
|
return "PostgreSQL";
|
||||||
}
|
}
|
||||||
|
|
||||||
public function schemaVersion(): int {
|
|
||||||
// stub
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function schemaUpdate(int $to, string $basePath = null): bool {
|
|
||||||
// stub
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function charsetAcceptable(): bool {
|
public function charsetAcceptable(): bool {
|
||||||
// stub
|
return $this->query("SELECT pg_encoding_to_char(encoding) from pg_database where datname = current_database()")->getValue() == "UTF8";
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getError(): string {
|
|
||||||
// stub
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
public function exec(string $query): bool {
|
|
||||||
// stub
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function query(string $query): \JKingWeb\Arsse\Db\Result {
|
|
||||||
// stub
|
|
||||||
return new ResultEmpty;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function prepareArray(string $query, array $paramTypes): \JKingWeb\Arsse\Db\Statement {
|
|
||||||
// stub
|
|
||||||
return new Statement($this->db, $s, $paramTypes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function lock(): bool {
|
protected function lock(): bool {
|
||||||
// stub
|
$this->exec("BEGIN TRANSACTION");
|
||||||
|
if ($this->schemaVersion()) {
|
||||||
|
$this->exec("LOCK TABLE arsse_meta IN EXCLUSIVE MODE NOWAIT");
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function unlock(bool $rollback = false): bool {
|
protected function unlock(bool $rollback = false): bool {
|
||||||
// stub
|
$this->exec((!$rollback) ? "COMMIT" : "ROLLBACK");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function __destruct() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function requirementsMet(): bool {
|
||||||
|
// stub: native interface is not yet supported
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function makeConnection(string $user, string $pass, string $db, string $host, int $port, string $service) {
|
||||||
|
// stub: native interface is not yet supported
|
||||||
|
throw new \Exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @codeCoverageIgnore */
|
||||||
|
protected function getError(): string {
|
||||||
|
// stub: native interface is not yet supported
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @codeCoverageIgnore */
|
||||||
|
public function exec(string $query): bool {
|
||||||
|
// stub: native interface is not yet supported
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @codeCoverageIgnore */
|
||||||
|
public function query(string $query): \JKingWeb\Arsse\Db\Result {
|
||||||
|
// stub: native interface is not yet supported
|
||||||
|
return new ResultEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @codeCoverageIgnore */
|
||||||
|
public function prepareArray(string $query, array $paramTypes): \JKingWeb\Arsse\Db\Statement {
|
||||||
|
// stub: native interface is not yet supported
|
||||||
|
return new Statement($this->db, $s, $paramTypes);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
123
sql/PostgreSQL/0.sql
Normal file
123
sql/PostgreSQL/0.sql
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
-- SPDX-License-Identifier: MIT
|
||||||
|
-- Copyright 2017 J. King, Dustin Wilson et al.
|
||||||
|
-- See LICENSE and AUTHORS files for details
|
||||||
|
|
||||||
|
-- metadata
|
||||||
|
create table arsse_meta(
|
||||||
|
key text primary key,
|
||||||
|
value text
|
||||||
|
);
|
||||||
|
|
||||||
|
-- users
|
||||||
|
create table arsse_users(
|
||||||
|
id text primary key,
|
||||||
|
password text,
|
||||||
|
name text,
|
||||||
|
avatar_type text,
|
||||||
|
avatar_data bytea,
|
||||||
|
admin smallint default 0,
|
||||||
|
rights bigint not null default 0
|
||||||
|
);
|
||||||
|
|
||||||
|
-- extra user metadata
|
||||||
|
create table arsse_users_meta(
|
||||||
|
owner text not null references arsse_users(id) on delete cascade on update cascade,
|
||||||
|
key text not null,
|
||||||
|
value text,
|
||||||
|
primary key(owner,key)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- NextCloud News folders and TT-RSS categories
|
||||||
|
create table arsse_folders(
|
||||||
|
id bigserial primary key,
|
||||||
|
owner text not null references arsse_users(id) on delete cascade on update cascade,
|
||||||
|
parent bigint references arsse_folders(id) on delete cascade,
|
||||||
|
name text not null,
|
||||||
|
modified timestamp(0) with time zone not null default CURRENT_TIMESTAMP, --
|
||||||
|
unique(owner,name,parent)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- newsfeeds, deduplicated
|
||||||
|
create table arsse_feeds(
|
||||||
|
id bigserial primary key,
|
||||||
|
url text not null,
|
||||||
|
title text,
|
||||||
|
favicon text,
|
||||||
|
source text,
|
||||||
|
updated timestamp(0) with time zone,
|
||||||
|
modified timestamp(0) with time zone,
|
||||||
|
next_fetch timestamp(0) with time zone,
|
||||||
|
orphaned timestamp(0) with time zone,
|
||||||
|
etag text not null default '',
|
||||||
|
err_count bigint not null default 0,
|
||||||
|
err_msg text,
|
||||||
|
username text not null default '',
|
||||||
|
password text not null default '',
|
||||||
|
size bigint not null default 0,
|
||||||
|
scrape smallint not null default 0,
|
||||||
|
unique(url,username,password)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- users' subscriptions to newsfeeds, with settings
|
||||||
|
create table arsse_subscriptions(
|
||||||
|
id bigserial primary key,
|
||||||
|
owner text not null references arsse_users(id) on delete cascade on update cascade,
|
||||||
|
feed bigint not null references arsse_feeds(id) on delete cascade,
|
||||||
|
added timestamp(0) with time zone not null default CURRENT_TIMESTAMP,
|
||||||
|
modified timestamp(0) with time zone not null default CURRENT_TIMESTAMP,
|
||||||
|
title text,
|
||||||
|
order_type smallint not null default 0,
|
||||||
|
pinned smallint not null default 0,
|
||||||
|
folder bigint references arsse_folders(id) on delete cascade,
|
||||||
|
unique(owner,feed)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- entries in newsfeeds
|
||||||
|
create table arsse_articles(
|
||||||
|
id bigserial primary key,
|
||||||
|
feed bigint not null references arsse_feeds(id) on delete cascade,
|
||||||
|
url text,
|
||||||
|
title text,
|
||||||
|
author text,
|
||||||
|
published timestamp(0) with time zone,
|
||||||
|
edited timestamp(0) with time zone,
|
||||||
|
modified timestamp(0) with time zone not null default CURRENT_TIMESTAMP,
|
||||||
|
content text,
|
||||||
|
guid text,
|
||||||
|
url_title_hash text not null,
|
||||||
|
url_content_hash text not null,
|
||||||
|
title_content_hash text not null
|
||||||
|
);
|
||||||
|
|
||||||
|
-- enclosures associated with articles
|
||||||
|
create table arsse_enclosures(
|
||||||
|
article bigint not null references arsse_articles(id) on delete cascade,
|
||||||
|
url text,
|
||||||
|
type text
|
||||||
|
);
|
||||||
|
|
||||||
|
-- users' actions on newsfeed entries
|
||||||
|
create table arsse_marks(
|
||||||
|
article bigint not null references arsse_articles(id) on delete cascade,
|
||||||
|
subscription bigint not null references arsse_subscriptions(id) on delete cascade on update cascade,
|
||||||
|
read smallint not null default 0,
|
||||||
|
starred smallint not null default 0,
|
||||||
|
modified timestamp(0) with time zone not null default CURRENT_TIMESTAMP,
|
||||||
|
primary key(article,subscription)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- IDs for specific editions of articles (required for at least NextCloud News)
|
||||||
|
create table arsse_editions(
|
||||||
|
id bigserial primary key,
|
||||||
|
article bigint not null references arsse_articles(id) on delete cascade,
|
||||||
|
modified timestamp(0) with time zone not null default CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- author categories associated with newsfeed entries
|
||||||
|
create table arsse_categories(
|
||||||
|
article bigint not null references arsse_articles(id) on delete cascade,
|
||||||
|
name text
|
||||||
|
);
|
||||||
|
|
||||||
|
-- set version marker
|
||||||
|
insert into arsse_meta(key,value) values('schema_version','1');
|
36
sql/PostgreSQL/1.sql
Normal file
36
sql/PostgreSQL/1.sql
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
-- SPDX-License-Identifier: MIT
|
||||||
|
-- Copyright 2017 J. King, Dustin Wilson et al.
|
||||||
|
-- See LICENSE and AUTHORS files for details
|
||||||
|
|
||||||
|
-- Sessions for Tiny Tiny RSS (and possibly others)
|
||||||
|
create table arsse_sessions (
|
||||||
|
id text primary key,
|
||||||
|
created timestamp(0) with time zone not null default CURRENT_TIMESTAMP,
|
||||||
|
expires timestamp(0) with time zone not null,
|
||||||
|
user text not null references arsse_users(id) on delete cascade on update cascade
|
||||||
|
);
|
||||||
|
|
||||||
|
-- User-defined article labels for Tiny Tiny RSS
|
||||||
|
create table arsse_labels (
|
||||||
|
id bigserial primary key,
|
||||||
|
owner text not null references arsse_users(id) on delete cascade on update cascade,
|
||||||
|
name text not null,
|
||||||
|
modified timestamp(0) with time zone not null default CURRENT_TIMESTAMP,
|
||||||
|
unique(owner,name)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Labels assignments for articles
|
||||||
|
create table arsse_label_members (
|
||||||
|
label bigint not null references arsse_labels(id) on delete cascade,
|
||||||
|
article bigint not null references arsse_articles(id) on delete cascade,
|
||||||
|
subscription bigint not null references arsse_subscriptions(id) on delete cascade,
|
||||||
|
assigned smallint not null default 1,
|
||||||
|
modified timestamp(0) with time zone not null default CURRENT_TIMESTAMP,
|
||||||
|
primary key(label,article)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- alter marks table to add Tiny Tiny RSS' notes
|
||||||
|
alter table arsse_marks add column note text not null default '';
|
||||||
|
|
||||||
|
-- set version marker
|
||||||
|
update arsse_meta set value = '2' where key = 'schema_version';
|
22
sql/PostgreSQL/2.sql
Normal file
22
sql/PostgreSQL/2.sql
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
-- SPDX-License-Identifier: MIT
|
||||||
|
-- Copyright 2017 J. King, Dustin Wilson et al.
|
||||||
|
-- See LICENSE and AUTHORS files for details
|
||||||
|
|
||||||
|
-- create a case-insensitive generic collation sequence
|
||||||
|
create collation nocase(
|
||||||
|
provider = icu,
|
||||||
|
locale = '@kf=false'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Correct collation sequences
|
||||||
|
alter table arsse_users alter column id type text collate nocase;
|
||||||
|
alter table arsse_folders alter column name type text collate nocase;
|
||||||
|
alter table arsse_feeds alter column title type text collate nocase;
|
||||||
|
alter table arsse_subscriptions alter column title type text collate nocase;
|
||||||
|
alter table arsse_articles alter column title type text collate nocase;
|
||||||
|
alter table arsse_articles alter column author type text collate nocase;
|
||||||
|
alter table arsse_categories alter column name type text collate nocase;
|
||||||
|
alter table arsse_labels alter column name type text collate nocase;
|
||||||
|
|
||||||
|
-- set version marker
|
||||||
|
update arsse_meta set value = '3' where key = 'schema_version';
|
|
@ -28,7 +28,11 @@ class TestStatement extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$drvPgsql = (function() {
|
$drvPgsql = (function() {
|
||||||
if (\JKingWeb\Arsse\Db\PostgreSQL\PDODriver::requirementsMet()) {
|
if (\JKingWeb\Arsse\Db\PostgreSQL\PDODriver::requirementsMet()) {
|
||||||
$connString = \JKingWeb\Arsse\Db\PostgreSQL\Driver::makeConnectionString(true, Arsse::$conf->dbPostgreSQLUser, Arsse::$conf->dbPostgreSQLPass, Arsse::$conf->dbPostgreSQLDb, Arsse::$conf->dbPostgreSQLHost, Arsse::$conf->dbPostgreSQLPort, "");
|
$connString = \JKingWeb\Arsse\Db\PostgreSQL\Driver::makeConnectionString(true, Arsse::$conf->dbPostgreSQLUser, Arsse::$conf->dbPostgreSQLPass, Arsse::$conf->dbPostgreSQLDb, Arsse::$conf->dbPostgreSQLHost, Arsse::$conf->dbPostgreSQLPort, "");
|
||||||
return new \PDO("pgsql:".$connString, Arsse::$conf->dbPostgreSQLUser, Arsse::$conf->dbPostgreSQLPass, [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION]);
|
$c = new \PDO("pgsql:".$connString, Arsse::$conf->dbPostgreSQLUser, Arsse::$conf->dbPostgreSQLPass, [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION]);
|
||||||
|
foreach (\JKingWeb\Arsse\Db\PostgreSQL\PDODriver::makeSetupQueries(Arsse::$conf->dbPostgreSQLSchema) as $q) {
|
||||||
|
$c->exec($q);
|
||||||
|
}
|
||||||
|
return $c;
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
$drvPdo = (function() {
|
$drvPdo = (function() {
|
||||||
|
@ -173,7 +177,6 @@ class TestStatement extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$dateImmutable = new \DateTimeImmutable("Noon Today", new \DateTimezone("America/Toronto"));
|
$dateImmutable = new \DateTimeImmutable("Noon Today", new \DateTimezone("America/Toronto"));
|
||||||
$dateUTC = new \DateTime("@".$dateMutable->getTimestamp(), new \DateTimezone("UTC"));
|
$dateUTC = new \DateTime("@".$dateMutable->getTimestamp(), new \DateTimezone("UTC"));
|
||||||
$tests = [
|
$tests = [
|
||||||
/* input, type, expected binding as SQL fragment */
|
|
||||||
'Null as integer' => [null, "integer", "null"],
|
'Null as integer' => [null, "integer", "null"],
|
||||||
'Null as float' => [null, "float", "null"],
|
'Null as float' => [null, "float", "null"],
|
||||||
'Null as string' => [null, "string", "null"],
|
'Null as string' => [null, "string", "null"],
|
||||||
|
@ -321,7 +324,6 @@ class TestStatement extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$dateImmutable = new \DateTimeImmutable("Noon Today", new \DateTimezone("America/Toronto"));
|
$dateImmutable = new \DateTimeImmutable("Noon Today", new \DateTimezone("America/Toronto"));
|
||||||
$dateUTC = new \DateTime("@".$dateMutable->getTimestamp(), new \DateTimezone("UTC"));
|
$dateUTC = new \DateTime("@".$dateMutable->getTimestamp(), new \DateTimezone("UTC"));
|
||||||
$tests = [
|
$tests = [
|
||||||
/* input, type, expected binding as SQL fragment */
|
|
||||||
'Null as binary' => [null, "binary", "null"],
|
'Null as binary' => [null, "binary", "null"],
|
||||||
'Null as strict binary' => [null, "strict binary", "x''"],
|
'Null as strict binary' => [null, "strict binary", "x''"],
|
||||||
'True as binary' => [true, "binary", "x'31'"],
|
'True as binary' => [true, "binary", "x'31'"],
|
||||||
|
|
Loading…
Reference in a new issue