+ can setup postgresql database

This commit is contained in:
maxpozdeev 2023-08-03 23:09:32 +03:00
parent ca3e332a2f
commit 772305dbed
4 changed files with 378 additions and 28 deletions

View file

@ -2,14 +2,14 @@
// Rename it to config.php before using in docker.
if (getenv('MTT_DB_TYPE') == 'mysql') {
define("MTT_DB_TYPE", "mysql");
define("MTT_DB_HOST", getenv('MTT_DB_HOST'));
define("MTT_DB_NAME", getenv('MTT_DB_NAME'));
define("MTT_DB_USER", getenv('MTT_DB_USER'));
define("MTT_DB_PASSWORD", getenv('MTT_DB_PASSWORD'));
define("MTT_DB_PREFIX", getenv('MTT_DB_PREFIX'));
define("MTT_DB_DRIVER", getenv('MTT_DB_DRIVER'));
if (getenv('MTT_DB_TYPE') == 'mysql' || getenv('MTT_DB_TYPE') == 'postgres') {
define("MTT_DB_TYPE", getenv('MTT_DB_TYPE'));
define("MTT_DB_HOST", getenv('MTT_DB_HOST') ?: "undefined_host");
define("MTT_DB_NAME", getenv('MTT_DB_NAME') ?: "undefined_db");
define("MTT_DB_USER", getenv('MTT_DB_USER') ?: "undefined_user");
define("MTT_DB_PASSWORD", getenv('MTT_DB_PASSWORD') ?: "");
define("MTT_DB_PREFIX", getenv('MTT_DB_PREFIX') ?: "mtt_");
define("MTT_DB_DRIVER", getenv('MTT_DB_DRIVER') ?: "");
}
else if (getenv('MTT_DB_TYPE') == 'sqlite') {
define("MTT_DB_TYPE", "sqlite");

View file

@ -0,0 +1,208 @@
<?php declare(strict_types=1);
/*
This file is a part of myTinyTodo.
(C) Copyright 2023 Max Pozdeev <maxpozdeev@gmail.com>
Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
*/
// ---------------------------------------------------------------------------- //
class DatabaseResult_Postgres extends DatabaseResult_Abstract
{
/** @var PDOStatement */
protected $q;
/** @var int */
protected $affected;
/** @var string const */
protected $schema = 'public';
function __construct(PDO $dbh, string $query, bool $resultless = false)
{
// use with DELETE, INSERT, UPDATE
if ($resultless)
{
$this->affected = (int) $dbh->exec($query); //throws PDOException
}
// SELECT
else
{
$this->q = $dbh->query($query); //throws PDOException
$this->affected = $this->q->rowCount();
}
}
function fetchRow(): ?array
{
$res = $this->q->fetch(PDO::FETCH_NUM);
if ($res === false || !is_array($res)) {
return null;
}
return $res;
}
function fetchAssoc(): ?array
{
$res = $this->q->fetch(PDO::FETCH_ASSOC);
if ($res === false || !is_array($res)) {
return null;
}
return $res;
}
function rowsAffected(): int
{
return $this->affected;
}
}
// ---------------------------------------------------------------------------- //
class Database_Postgres extends Database_Abstract
{
const DBTYPE = 'postgres';
/** @var PDO */
protected $dbh;
/** @var int */
protected $affected = 0;
protected $dbname;
function __construct()
{
}
function connect(array $params): void
{
$host = $params['host'];
$user = $params['user'];
$pass = $params['password'];
$db = $params['db'];
$options = array(
PDO::PGSQL_ATTR_DISABLE_PREPARES => 1,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
);
$this->dbname = $db;
$this->dbh = new PDO("pgsql:host=$host;dbname=$db", $user, $pass, $options);
}
/*
Returns single row of SELECT query as indexed array (FETCH_NUM).
Returns single field value if resulting array has only one field.
*/
function sq(string $query, ?array $values = null)
{
$q = $this->_dq($query, $values);
$res = $q->fetchRow();
if ($res === false || !is_array($res)) {
return null;
}
if (sizeof($res) > 1) return $res;
else return $res[0];
}
/*
Returns single row of SELECT query as dictionary array (FETCH_ASSOC).
*/
function sqa(string $query, ?array $values = null): ?array
{
$q = $this->_dq($query, $values);
$res = $q->fetchAssoc();
if ($res === false || !is_array($res)){
return null;
}
return $res;
}
function dq(string $query, ?array $values = null) : DatabaseResult_Abstract
{
return $this->_dq($query, $values);
}
/*
for resultless queries like INSERT,UPDATE,DELETE
*/
function ex(string $query, ?array $values = null): void
{
$this->_dq($query, $values, true);
}
private function _dq(string $query, ?array $values = null, bool $resultless = false) : DatabaseResult_Abstract
{
if (null !== $values && sizeof($values) > 0)
{
$m = explode('?', $query);
if (sizeof($m) < sizeof($values)+1) {
throw new Exception("params to set MORE than query params");
}
if (sizeof($m) > sizeof($values)+1) {
throw new Exception("params to set LESS than query params");
}
$query = "";
for ($i=0; $i<sizeof($m)-1; $i++) {
$query .= $m[$i]. $this->quote($values[$i]);
}
$query .= $m[$i];
}
$this->lastQuery = $query;
$dbr = new DatabaseResult_Postgres($this->dbh, $query, $resultless);
$this->affected = $dbr->rowsAffected();
return $dbr;
}
function affected(): int
{
return $this->affected;
}
function quote($value): string
{
if (null === $value) {
return 'null';
}
return $this->dbh->quote((string) $value);
}
function quoteForLike(string $format, string $string): string
{
$string = str_replace(array('\\','%','_'), array('\\\\','\%','\_'), $string);
return $this->dbh->quote(sprintf($format, $string)). " ESCAPE '\'";
}
function like(string $column, string $format, string $string): string
{
$column = str_replace('"', '""', $column);
return '"'. $column. '" ILIKE '. $this->quoteForLike($format, $string);
}
function lastInsertId(?string $name = null): ?string
{
$ret = $this->dbh->lastInsertId();
if (false === $ret) {
return null;
}
return (string) $ret;
}
function tableExists(string $table): bool
{
$r = $this->sq("SELECT 1 FROM information_schema.tables WHERE table_catalog = ? AND table_name = ?",
array($this->dbname, $table) );
if ($r === false || $r === null) return false;
return true;
}
function tableFieldExists(string $table, string $field): bool
{
$r = $this->sq("SELECT 1 FROM information_schema.columns WHERE table_name = ? AND column_name = ? AND table_schema = ?",
array($table, $field, $this->schema) );
if ($r === false || $r === null) return false;
return true;
}
}

View file

@ -116,6 +116,31 @@ function configureDbConnection()
$db->dq("SET NAMES utf8mb4");
}
# PostgreSQL Database
else if (MTT_DB_TYPE == 'postgres')
{
require_once(MTTINC. 'class.db.postgres.php');
$db = DBConnection::init(new Database_Postgres());
try {
$db->connect([
'host' => MTT_DB_HOST,
'user' => MTT_DB_USER,
'password' => MTT_DB_PASSWORD,
'db' => MTT_DB_NAME,
]);
}
catch(Exception $e) {
$errlog = "Failed to connect to PostgreSQL database: ". $e->getMessage();
if (MTT_DEBUG) {
logAndDie($errlog);
}
else {
logAndDie("Failed to connect to database", $errlog);
}
}
$db->dq("SET NAMES 'utf8'");
}
# SQLite3 Database
elseif (MTT_DB_TYPE == 'sqlite')
{

View file

@ -77,22 +77,28 @@ if ($configExists)
// Determine current installed db version
$ver = databaseVersion($db);
if ($ver == '1.4') {
if ($ver == '') {
// clean install
// will not load settings from database in init.php
Config::$noDatabase = true;
}
else if (version_compare($ver, '1.4') < 0) {
// Very old or previously failed while install
exitMessage(htmlspecialchars("Can not update. Unsupported database version ($ver)."));
}
else if ($ver == '1.4') {
// Need to upgrade. Do not ask for old password
require_once(MTTPATH. 'db/config.php');
Config::loadConfigV14($config);
unset($config);
DBConnection::init($db);
}
else {
if ($ver == '') {
Config::$noDatabase = true; //will not load settings from database in init.php
}
require_once('./init.php');
if ( !is_logged() ) {
die("Access denied!<br> Disable password protection or Log in.");
}
require_once('./init.php');
if ( !is_logged() ) {
die("Access denied!<br> Disable password protection or Log in.");
}
}
if ($ver == '')
@ -102,7 +108,7 @@ if ($ver == '')
if ($install == '' && $db !== null)
{
# We already have settings file and need to create tables.
exitMessage("<form method=post>Click next to create tables in '". htmlspecialchars($dbtype). "' database.<br><br>
exitMessage("<form method=post>Click next to create tables in ". htmlspecialchars(databaseTypeName($db)). " database.<br><br>
<input type=hidden name=stoken value='$setupToken'>
<input type=hidden name=install value=create>
<input type=submit value=' Next '></form>");
@ -114,9 +120,10 @@ if ($ver == '')
<form method=post>Select database type to use:<br><br>
<input type=hidden name=install value=config>
<input type=hidden name=stoken value='$setupToken'>
<label><input type=radio name=db_type value=sqlite checked=checked onclick=\"document.getElementById('mysqlsettings').style.display='none'\">SQLite</label><br><br>
<label><input type=radio name=db_type value=mysql onclick=\"document.getElementById('mysqlsettings').style.display=''\">MySQL</label><br>
<div id='mysqlsettings' style='display:none; margin-left:30px;'><br><table>
<label><input type=radio name=db_type value=sqlite checked=checked onclick=\"document.getElementById('dbsettings').style.display='none'\"> SQLite</label><br><br>
<label><input type=radio name=db_type value=mysql onclick=\"document.getElementById('dbsettings').style.display=''\"> MySQL</label><br><br>
<label><input type=radio name=db_type value=postgres onclick=\"document.getElementById('dbsettings').style.display=''\"> PostgreSQL (beta)</label><br>
<div id='dbsettings' style='display:none; margin-left:30px;'><br><table>
<tr><td>Host:</td><td><input name=db_host value=localhost></td></tr>
<tr><td>Database:</td><td><input name=db_name value=mytinytodo></td></tr>
<tr><td>User:</td><td><input name=db_user value=mtt></td></tr>
@ -129,9 +136,12 @@ if ($ver == '')
{
check_post_stoken();
# Save configuration
$dbtype = ($_POST['db_type'] == 'mysql') ? 'mysql' : 'sqlite';
$dbtype = $_POST['db_type'] ?? '';
if ($dbtype != 'mysql' && $dbtype != 'postgres' && $dbtype != 'sqlite') {
exitMessage("Unknown database type $dbtype");
}
Config::set('db.type', $dbtype);
if ($dbtype == 'mysql') {
if ($dbtype == 'mysql' || $dbtype == 'postgres') {
Config::set('db.host', _post('db_host'));
Config::set('db.name', _post('db_name'));
Config::set('db.user', _post('db_user'));
@ -240,12 +250,15 @@ function createAllTables($db, $dbtype)
if ($dbtype == 'mysql') {
createMysqlTables($db);
}
else if ($dbtype == 'postgres') {
createPostgresTables($db);
}
else {
createSqliteTables($db);
}
}
/* ===== mysql ========================================================= */
function createMysqlTables($db)
{
$db->ex(
@ -324,6 +337,78 @@ function createMysqlTables($db)
}
/* ===== postgres =============================================== */
function createPostgresTables($db)
{
$db->ex(
"CREATE TABLE {$db->prefix}lists (
id INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
uuid CHAR(36) NOT NULL default '',
ow INTEGER NOT NULL default 0,
name VARCHAR(250) NOT NULL default '',
d_created INTEGER NOT NULL default 0,
d_edited INTEGER NOT NULL default 0,
sorting SMALLINT NOT NULL default 0,
published SMALLINT NOT NULL default 0,
taskview INTEGER NOT NULL default 0,
extra TEXT
) ");
$db->ex("CREATE UNIQUE INDEX {$db->prefix}lists_uuid ON {$db->prefix}lists (uuid)");
$db->ex(
"CREATE TABLE {$db->prefix}todolist (
id INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
uuid CHAR(36) NOT NULL default '',
list_id INTEGER NOT NULL default 0,
d_created INTEGER NOT NULL default 0,
d_completed INTEGER NOT NULL default 0,
d_edited INTEGER NOT NULL default 0,
compl SMALLINT NOT NULL default 0,
title VARCHAR(250) NOT NULL default '',
note TEXT default NULL,
prio SMALLINT NOT NULL default 0,
ow INTEGER NOT NULL default 0,
duedate DATE default NULL
) ");
$db->ex("CREATE INDEX {$db->prefix}todo_list_id ON {$db->prefix}todolist (list_id)");
$db->ex("CREATE UNIQUE INDEX {$db->prefix}todo_uuid ON {$db->prefix}todolist (uuid)");
$db->ex(
"CREATE TABLE {$db->prefix}tags (
id INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
name VARCHAR(250) NOT NULL DEFAULT ''
) ");
$db->ex("CREATE UNIQUE INDEX {$db->prefix}tags_name ON {$db->prefix}tags (name)");
$db->ex(
"CREATE TABLE {$db->prefix}tag2task (
tag_id INTEGER NOT NULL,
task_id INTEGER NOT NULL,
list_id INTEGER NOT NULL
) ");
$db->ex("CREATE INDEX {$db->prefix}tag2task_tag_id ON {$db->prefix}tag2task (tag_id)");
$db->ex("CREATE INDEX {$db->prefix}tag2task_task_id ON {$db->prefix}tag2task (task_id)");
$db->ex("CREATE INDEX {$db->prefix}tag2task_list_id ON {$db->prefix}tag2task (list_id)");
$db->ex(
"CREATE TABLE {$db->prefix}settings (
param_key VARCHAR(250) NOT NULL default '',
param_value TEXT
) ");
$db->ex("CREATE UNIQUE INDEX {$db->prefix}settings_key ON {$db->prefix}settings (param_key)");
$db->ex(
"CREATE TABLE {$db->prefix}sessions (
id VARCHAR(64) NOT NULL default '',
data TEXT,
last_access INTEGER NOT NULL default 0,
expires INTEGER NOT NULL default 0
) ");
$db->ex("CREATE UNIQUE INDEX {$db->prefix}sessions_id ON {$db->prefix}sessions (id)");
}
/* ===== sqlite =============================================== */
function createSqliteTables($db)
{
$db->ex(
@ -364,7 +449,7 @@ function createSqliteTables($db)
$db->ex(
"CREATE TABLE {$db->prefix}tags (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR(50) NOT NULL DEFAULT '' COLLATE UTF8CI
name VARCHAR(250) NOT NULL DEFAULT '' COLLATE UTF8CI
) ");
$db->ex("CREATE INDEX tags_name ON {$db->prefix}tags (name)"); //NB: unique in mysql
@ -515,12 +600,31 @@ function testConnect(&$error)
if (!defined($c)) throw new Exception("$c is not defined");
}
$db->connect( array(
$db->connect([
'host' => MTT_DB_HOST,
'user' => MTT_DB_USER,
'password' => MTT_DB_PASSWORD,
'db' => MTT_DB_NAME
));
]);
}
else if (MTT_DB_TYPE == 'postgres')
{
if (!defined('PDO::PGSQL_ATTR_DISABLE_PREPARES')) {
throw new Exception("Required PHP extension 'PDO_PostgreSQL' is not installed.");
}
require_once(MTTINC. 'class.db.postgres.php');
foreach (['MTT_DB_HOST', 'MTT_DB_USER', 'MTT_DB_PASSWORD', 'MTT_DB_NAME', 'MTT_DB_PREFIX'] as $c) {
if (!defined($c)) throw new Exception("$c is not defined");
}
$db = new Database_Postgres;
$db->connect([
'host' => MTT_DB_HOST,
'user' => MTT_DB_USER,
'password' => MTT_DB_PASSWORD,
'db' => MTT_DB_NAME
]);
}
else if (MTT_DB_TYPE == 'sqlite')
{
@ -535,7 +639,9 @@ function testConnect(&$error)
}
require_once(MTTINC. 'class.db.sqlite3.php');
$db = new Database_Sqlite3;
$db->connect( array( 'filename' => MTTPATH. 'db/todolist.db' ) );
$db->connect([
'filename' => MTTPATH. 'db/todolist.db'
]);
}
else {
throw new Exception("Unsupported database type: ". MTT_DB_TYPE);
@ -566,6 +672,17 @@ function myExceptionHandler($e)
exit;
}
function databaseTypeName(Database_Abstract $db)
{
$s = get_class($db);
switch ($s) {
case "Database_Mysql": return "MySQL";
case "Database_Postgres": return "PostgreSQL";
case "Database_Sqlite3": return "SQLite";
default: return $s;
}
}
### update v1.4 to v1.7 ##########
function update_14_17(Database_Abstract $db, $dbtype)