diff --git a/src/docker-config.php b/src/docker-config.php index 7b810be..8d6dd9d 100644 --- a/src/docker-config.php +++ b/src/docker-config.php @@ -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"); diff --git a/src/includes/class.db.postgres.php b/src/includes/class.db.postgres.php new file mode 100644 index 0000000..776c557 --- /dev/null +++ b/src/includes/class.db.postgres.php @@ -0,0 +1,208 @@ + + 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; $iquote($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; + } +} diff --git a/src/init.php b/src/init.php index ea8a145..89ca472 100644 --- a/src/init.php +++ b/src/init.php @@ -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') { diff --git a/src/setup.php b/src/setup.php index 51a854b..ea3bccc 100644 --- a/src/setup.php +++ b/src/setup.php @@ -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!
Disable password protection or Log in."); - } + + require_once('./init.php'); + if ( !is_logged() ) { + die("Access denied!
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("
Click next to create tables in '". htmlspecialchars($dbtype). "' database.

+ exitMessage("Click next to create tables in ". htmlspecialchars(databaseTypeName($db)). " database.

"); @@ -114,9 +120,10 @@ if ($ver == '')
Select database type to use:

-

-
-