1
1
Fork 0
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:
J. King 2018-11-21 11:06:12 -05:00
parent 84b4cb7465
commit c0c4810662
6 changed files with 245 additions and 53 deletions

View file

@ -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;
} }

View file

@ -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
View 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
View 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
View 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';

View file

@ -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'"],