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 $transStatus = [];
|
||||
|
||||
abstract protected function lock(): bool;
|
||||
abstract protected function unlock(bool $rollback = false): bool;
|
||||
abstract protected function getError(): string;
|
||||
|
||||
/** @codeCoverageIgnore */
|
||||
public function schemaVersion(): int {
|
||||
// FIXME: generic schemaVersion() will need to be covered for database engines other than SQLite
|
||||
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) {
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -13,11 +13,10 @@ use JKingWeb\Arsse\Db\ExceptionInput;
|
|||
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, string $service = null) {
|
||||
// check to make sure required extension is loaded
|
||||
if (!static::requirementsMet()) {
|
||||
throw new Exception("extMissing", self::driverName()); // @codeCoverageIgnore
|
||||
throw new Exception("extMissing", static::driverName()); // @codeCoverageIgnore
|
||||
}
|
||||
$user = $user ?? Arsse::$conf->dbPostgreSQLUser;
|
||||
$pass = $pass ?? Arsse::$conf->dbPostgreSQLPass;
|
||||
|
@ -27,16 +26,9 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
|||
$schema = $schema ?? Arsse::$conf->dbPostgreSQLSchema;
|
||||
$service = $service ?? Arsse::$conf->dbPostgreSQLService;
|
||||
$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 {
|
||||
|
@ -73,7 +65,15 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
|||
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 */
|
||||
|
@ -96,48 +96,57 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
|||
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 {
|
||||
// stub
|
||||
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);
|
||||
return $this->query("SELECT pg_encoding_to_char(encoding) from pg_database where datname = current_database()")->getValue() == "UTF8";
|
||||
}
|
||||
|
||||
protected function lock(): bool {
|
||||
// stub
|
||||
$this->exec("BEGIN TRANSACTION");
|
||||
if ($this->schemaVersion()) {
|
||||
$this->exec("LOCK TABLE arsse_meta IN EXCLUSIVE MODE NOWAIT");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function unlock(bool $rollback = false): bool {
|
||||
// stub
|
||||
$this->exec((!$rollback) ? "COMMIT" : "ROLLBACK");
|
||||
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() {
|
||||
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, "");
|
||||
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() {
|
||||
|
@ -173,7 +177,6 @@ class TestStatement extends \JKingWeb\Arsse\Test\AbstractTest {
|
|||
$dateImmutable = new \DateTimeImmutable("Noon Today", new \DateTimezone("America/Toronto"));
|
||||
$dateUTC = new \DateTime("@".$dateMutable->getTimestamp(), new \DateTimezone("UTC"));
|
||||
$tests = [
|
||||
/* input, type, expected binding as SQL fragment */
|
||||
'Null as integer' => [null, "integer", "null"],
|
||||
'Null as float' => [null, "float", "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"));
|
||||
$dateUTC = new \DateTime("@".$dateMutable->getTimestamp(), new \DateTimezone("UTC"));
|
||||
$tests = [
|
||||
/* input, type, expected binding as SQL fragment */
|
||||
'Null as binary' => [null, "binary", "null"],
|
||||
'Null as strict binary' => [null, "strict binary", "x''"],
|
||||
'True as binary' => [true, "binary", "x'31'"],
|
||||
|
|
Loading…
Reference in a new issue