From b322aaa08196797407137a68149d3c6bd931dcd0 Mon Sep 17 00:00:00 2001 From: leso-kn Date: Sun, 21 Jul 2024 17:38:31 +0200 Subject: [PATCH] Add PostgreSQL backend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kim Lidström --- Core/Frameworks/Baikal/Core/Tools.php | 4 +- Core/Frameworks/Baikal/Model/Calendar.php | 4 +- .../Baikal/Model/Calendar/Calendar.php | 4 +- .../Baikal/Model/Config/Database.php | 42 +++++- .../Controller/Install/Database.php | 121 +++++++++++++-- .../Controller/Install/Initialize.php | 8 +- .../Controller/Install/VersionUpgrade.php | 8 +- .../Controller/Settings/Database.php | 58 ++++--- Core/Frameworks/Flake/Core/Database/Pgsql.php | 68 +++++++++ Core/Frameworks/Flake/Framework.php | 36 ++++- Core/Resources/Db/PgSQL/db.sql | 142 ++++++++++++++++++ config/baikal.yaml.dist | 6 +- 12 files changed, 451 insertions(+), 50 deletions(-) create mode 100644 Core/Frameworks/Flake/Core/Database/Pgsql.php create mode 100644 Core/Resources/Db/PgSQL/db.sql diff --git a/Core/Frameworks/Baikal/Core/Tools.php b/Core/Frameworks/Baikal/Core/Tools.php index 25f780827..73bb28eaf 100644 --- a/Core/Frameworks/Baikal/Core/Tools.php +++ b/Core/Frameworks/Baikal/Core/Tools.php @@ -45,8 +45,8 @@ static function assertEnvironmentIsOk() { # Asserting PDO::SQLite or PDO::MySQL $aPDODrivers = \PDO::getAvailableDrivers(); - if (!in_array('sqlite', $aPDODrivers, true) && !in_array('mysql', $aPDODrivers, true)) { - exit('Baikal Fatal Error: Both PDO::sqlite and PDO::mysql are unavailable. One of them at least is required by Baikal.'); + if (!in_array('sqlite', $aPDODrivers, true) && !in_array('mysql', $aPDODrivers, true) && !in_array('pgsql', $aPDODrivers, true)) { + exit('Baikal Fatal Error: None of PDO::sqlite, PDO::mysql or PDO::pgsql are available. One of them at least is required by Baikal.'); } # Assert that the temp folder is writable diff --git a/Core/Frameworks/Baikal/Model/Calendar.php b/Core/Frameworks/Baikal/Model/Calendar.php index 9b429b1a1..8b55b3be1 100644 --- a/Core/Frameworks/Baikal/Model/Calendar.php +++ b/Core/Frameworks/Baikal/Model/Calendar.php @@ -244,7 +244,7 @@ function isDefault() { function hasInstances() { $rSql = $GLOBALS["DB"]->exec_SELECTquery( - "count(*)", + "count(*) as count", "calendarinstances", "calendarid='" . $this->aData["calendarid"] . "'" ); @@ -254,7 +254,7 @@ function hasInstances() { } else { reset($aRs); - return $aRs["count(*)"] > 1; + return $aRs["count"] > 1; } } diff --git a/Core/Frameworks/Baikal/Model/Calendar/Calendar.php b/Core/Frameworks/Baikal/Model/Calendar/Calendar.php index 9c75adefb..980a318e8 100644 --- a/Core/Frameworks/Baikal/Model/Calendar/Calendar.php +++ b/Core/Frameworks/Baikal/Model/Calendar/Calendar.php @@ -38,7 +38,7 @@ class Calendar extends \Flake\Core\Model\Db { function hasInstances() { $rSql = $GLOBALS["DB"]->exec_SELECTquery( - "count(*)", + "count(*) as count", "calendarinstances", "calendarid='" . $this->aData["id"] . "'" ); @@ -48,7 +48,7 @@ function hasInstances() { } else { reset($aRs); - return $aRs["count(*)"] > 1; + return $aRs["count"] > 1; } } diff --git a/Core/Frameworks/Baikal/Model/Config/Database.php b/Core/Frameworks/Baikal/Model/Config/Database.php index e0035d59b..4ce20c088 100644 --- a/Core/Frameworks/Baikal/Model/Config/Database.php +++ b/Core/Frameworks/Baikal/Model/Config/Database.php @@ -31,12 +31,16 @@ class Database extends \Baikal\Model\Config { # Default values protected $aData = [ "sqlite_file" => PROJECT_PATH_SPECIFIC . "db/db.sqlite", - "mysql" => false, + "backend" => "", "mysql_host" => "", "mysql_dbname" => "", "mysql_username" => "", "mysql_password" => "", "encryption_key" => "", + "pgsql_host" => "", + "pgsql_dbname" => "", + "pgsql_username" => "", + "pgsql_password" => "", ]; function __construct() { @@ -46,6 +50,14 @@ function __construct() { function formMorphologyForThisModelInstance() { $oMorpho = new \Formal\Form\Morphology(); + $oMorpho->add(new \Formal\Element\Listbox([ + "prop" => "backend", + "label" => "Database Backend", + "validation" => "required", + "options" => ['sqlite', 'mysql', 'pgsql'], + "refreshonchange" => true, + ])); + $oMorpho->add(new \Formal\Element\Text([ "prop" => "sqlite_file", "label" => "SQLite file path", @@ -54,13 +66,6 @@ function formMorphologyForThisModelInstance() { "help" => "The absolute server path to the SQLite file", ])); - $oMorpho->add(new \Formal\Element\Checkbox([ - "prop" => "mysql", - "label" => "Use MySQL", - "help" => "If checked, Baïkal will use MySQL instead of SQLite.", - "refreshonchange" => true, - ])); - $oMorpho->add(new \Formal\Element\Text([ "prop" => "mysql_host", "label" => "MySQL host", @@ -82,6 +87,27 @@ function formMorphologyForThisModelInstance() { "label" => "MySQL password", ])); + $oMorpho->add(new \Formal\Element\Text([ + "prop" => "pgsql_host", + "label" => "PostgreSQL host", + "help" => "Host ip or name, including ':portnumber' if port is not the default one (?)", + ])); + + $oMorpho->add(new \Formal\Element\Text([ + "prop" => "pgsql_dbname", + "label" => "PostgreSQL database name", + ])); + + $oMorpho->add(new \Formal\Element\Text([ + "prop" => "pgsql_username", + "label" => "PostgreSQL username", + ])); + + $oMorpho->add(new \Formal\Element\Password([ + "prop" => "pgsql_password", + "label" => "PostgreSQL password", + ])); + return $oMorpho; } diff --git a/Core/Frameworks/BaikalAdmin/Controller/Install/Database.php b/Core/Frameworks/BaikalAdmin/Controller/Install/Database.php index 4a57584f5..fc17997b3 100644 --- a/Core/Frameworks/BaikalAdmin/Controller/Install/Database.php +++ b/Core/Frameworks/BaikalAdmin/Controller/Install/Database.php @@ -38,18 +38,22 @@ function execute() { if (file_exists(PROJECT_PATH_SPECIFIC . "config.system.php")) { require_once PROJECT_PATH_SPECIFIC . "config.system.php"; $this->oModel->set('sqlite_file', PROJECT_SQLITE_FILE); - $this->oModel->set('mysql', PROJECT_DB_MYSQL); + $this->oModel->set('backend', PROJECT_DB_BACKEND); $this->oModel->set('mysql_host', PROJECT_DB_MYSQL_HOST); $this->oModel->set('mysql_dbname', PROJECT_DB_MYSQL_DBNAME); $this->oModel->set('mysql_username', PROJECT_DB_MYSQL_USERNAME); $this->oModel->set('mysql_password', PROJECT_DB_MYSQL_PASSWORD); + $this->oModel->set('pgsql_host', PROJECT_DB_PGSQL_HOST); + $this->oModel->set('pgsql_dbname', PROJECT_DB_PGSQL_DBNAME); + $this->oModel->set('pgsql_username', PROJECT_DB_PGSQL_USERNAME); + $this->oModel->set('pgsql_password', PROJECT_DB_PGSQL_PASSWORD); $this->oModel->set('encryption_key', BAIKAL_ENCRYPTION_KEY); } $this->oForm = $this->oModel->formForThisModelInstance([ "close" => false, - "hook.validation" => [$this, "validateConnection"], - "hook.morphology" => [$this, "hideMySQLFieldWhenNeeded"], + "hook.validation" => [$this, "validateSQLConnection"], + "hook.morphology" => [$this, "hideSQLFieldWhenNeeded"], ]); if ($this->oForm->submitted()) { @@ -99,11 +103,11 @@ function render() { return $oView->render(); } - function validateConnection($oForm, $oMorpho) { + function validateMySQLConnection($oForm, $oMorpho) { if ($oForm->refreshed()) { return true; } - $bMySQLEnabled = $oMorpho->element("mysql")->value(); + $bMySQLEnabled = $oMorpho->element("backend")->value() == 'mysql'; if ($bMySQLEnabled) { $sHost = $oMorpho->element("mysql_host")->value(); @@ -129,7 +133,7 @@ function validateConnection($oForm, $oMorpho) { $sMessage .= "

Nothing has been saved. Please, add these tables to the database before pursuing Baïkal initialization.

"; $oForm->declareError( - $oMorpho->element("mysql"), + $oMorpho->element("backend"), $sMessage ); } else { @@ -142,7 +146,7 @@ function validateConnection($oForm, $oMorpho) { return true; } catch (\Exception $e) { - $oForm->declareError($oMorpho->element("mysql"), + $oForm->declareError($oMorpho->element("backend"), "Baïkal was not able to establish a connexion to the MySQL database as configured.
MySQL says: " . $e->getMessage()); $oForm->declareError($oMorpho->element("mysql_host")); $oForm->declareError($oMorpho->element("mysql_dbname")); @@ -211,10 +215,10 @@ function validateConnection($oForm, $oMorpho) { function hideMySQLFieldWhenNeeded(\Formal\Form $oForm, \Formal\Form\Morphology $oMorpho) { if ($oForm->submitted()) { - $bMySQL = (intval($oForm->postValue("mysql")) === 1); + $bMySQL = ($oForm->postValue("backend") == 'mysql'); } else { // oMorpho won't have the values from the model set on it yet - $bMySQL = $this->oModel->get("mysql"); + $bMySQL = $this->oModel->get("backend") == 'mysql'; } if ($bMySQL === true) { @@ -226,4 +230,103 @@ function hideMySQLFieldWhenNeeded(\Formal\Form $oForm, \Formal\Form\Morphology $ $oMorpho->remove("mysql_password"); } } + + function validatePgSQLConnection($oForm, $oMorpho) { + $bPgSqlEnabled = $oMorpho->element("backend")->value() == 'pgsql'; + + if ($bPgSqlEnabled) { + $sHost = $oMorpho->element("pgsql_host")->value(); + $sDbname = $oMorpho->element("pgsql_dbname")->value(); + $sUsername = $oMorpho->element("pgsql_username")->value(); + $sPassword = $oMorpho->element("pgsql_password")->value(); + + try { + $oDb = new \Flake\Core\Database\Pgsql( + $sHost, + $sDbname, + $sUsername, + $sPassword + ); + + if (($aMissingTables = \Baikal\Core\Tools::isDBStructurallyComplete($oDb)) !== true) { + # Checking if all tables are missing + $aRequiredTables = \Baikal\Core\Tools::getRequiredTablesList(); + if (count($aRequiredTables) !== count($aMissingTables)) { + $sMessage = "

Database is not structurally complete.

"; + $sMessage .= "

Missing tables are: " . implode(", ", $aMissingTables) . "

"; + $sMessage .= "

You will find the SQL definition of Baïkal tables in this file: Core/Resources/Db/PgSQL/db.sql

"; + $sMessage .= "

Nothing has been saved. Please, add these tables to the database before pursuing Baïkal initialization.

"; + + $oForm->declareError( + $oMorpho->element("backend"), + $sMessage + ); + } else { + # All tables are missing + # We add these tables ourselves to the database, to initialize Baïkal + $sSqlDefinition = file_get_contents(PROJECT_PATH_CORERESOURCES . "Db/PgSQL/db.sql"); + $oDb->getPDO()->exec($sSqlDefinition); + } + } + + return true; + } catch (\Exception $e) { + $oForm->declareError( + $oMorpho->element("backend"), + "Baïkal was not able to establish a connexion to the PostgreSQL database as configured.
PostgreSQL says: " . $e->getMessage() + ); + + $oForm->declareError( + $oMorpho->element("pgsql_host") + ); + + $oForm->declareError( + $oMorpho->element("pgsql_dbname") + ); + + $oForm->declareError( + $oMorpho->element("pgsql_username") + ); + + $oForm->declareError( + $oMorpho->element("pgsql_password") + ); + } + } + } + + public function validateSQLConnection($oForm, $oMorpho) { + if ($oMorpho->element("backend")->value() == 'mysql') { + $this->validateMySQLConnection($oForm, $oMorpho); + } elseif ($oMorpho->element("backend")->value() == 'pgsql') { + $this->validatePgSQLConnection($oForm, $oMorpho); + } + } + + public function hideSqlFieldWhenNeeded(\Formal\Form $oForm, \Formal\Form\Morphology $oMorpho) { + if ($oMorpho->element("backend")->value() == 'mysql') { + $this->hideMySQLFieldWhenNeeded($oForm, $oMorpho); + } elseif ($oMorpho->element("backend")->value() == 'pgsql') { + $this->hidePgSQLFieldWhenNeeded($oForm, $oMorpho); + } + } + + public function hidePgSQLFieldWhenNeeded(\Formal\Form $oForm, \Formal\Form\Morphology $oMorpho) { + if ($oForm->submitted()) { + $bPgSQL = ($oForm->postValue("backend")) == 'pgsql'; + } else { + // oMorpho won't have the values from the model set on it yet + $bPgSQL = $this->oModel->get("backend") == 'pgsql'; + } + + if ($bPgSQL === true) { + $oMorpho->remove("sqlite_file"); + $this->hideMySQLFieldWhenNeeded($oForm, $oMorpho); + } else { + $oMorpho->remove("pgsql_host"); + $oMorpho->remove("pgsql_dbname"); + $oMorpho->remove("pgsql_username"); + $oMorpho->remove("pgsql_password"); + } + } } diff --git a/Core/Frameworks/BaikalAdmin/Controller/Install/Initialize.php b/Core/Frameworks/BaikalAdmin/Controller/Install/Initialize.php index f1cc64393..6246bc1d9 100644 --- a/Core/Frameworks/BaikalAdmin/Controller/Install/Initialize.php +++ b/Core/Frameworks/BaikalAdmin/Controller/Install/Initialize.php @@ -82,8 +82,12 @@ function execute() { # Default: PDO::SQLite or PDO::MySQL ? $aPDODrivers = \PDO::getAvailableDrivers(); - if (!in_array('sqlite', $aPDODrivers)) { # PDO::MySQL is already asserted in \Baikal\Core\Tools::assertEnvironmentIsOk() - $oDatabaseConfig->set("mysql", true); + if (in_array('sqlite', $aPDODrivers)) { # PDO::MySQL is already asserted in \Baikal\Core\Tools::assertEnvironmentIsOk() + $oDatabaseConfig->set("backend", 'sqlite'); + } elseif (in_array('mysql', $aPDODrivers)) { + $oDatabaseConfig->set("backend", 'mysql'); + } else { + $oDatabaseConfig->set("backend", 'pgsql'); } $oDatabaseConfig->persist(); diff --git a/Core/Frameworks/BaikalAdmin/Controller/Install/VersionUpgrade.php b/Core/Frameworks/BaikalAdmin/Controller/Install/VersionUpgrade.php index d4cd91ebd..e62386696 100644 --- a/Core/Frameworks/BaikalAdmin/Controller/Install/VersionUpgrade.php +++ b/Core/Frameworks/BaikalAdmin/Controller/Install/VersionUpgrade.php @@ -99,7 +99,7 @@ protected function upgrade($databaseConfig, $sVersionFrom, $sVersionTo) { if (version_compare($sVersionFrom, '0.3.0', '<')) { // Upgrading from sabre/dav 1.8 schema to 3.1 schema. - if ($databaseConfig['mysql'] === true) { + if ($databaseConfig['backend'] == 'mysql') { // MySQL upgrade // sabre/dav 2.0 changes @@ -313,7 +313,7 @@ protected function upgrade($databaseConfig, $sVersionFrom, $sVersionTo) { // The sqlite schema had issues with both the calendar and // addressbooks tables. The tables didn't have a DEFAULT '1' for // the synctoken column. So we're adding it now. - if ($databaseConfig['mysql'] === false) { + if ($databaseConfig['backend'] === 'sqlite') { $pdo->exec('UPDATE calendars SET synctoken = 1 WHERE synctoken IS NULL'); $tmpTable = '_' . time(); @@ -343,7 +343,7 @@ protected function upgrade($databaseConfig, $sVersionFrom, $sVersionTo) { // Similar to upgrading from older than 0.4.5, there were still // issues with a missing DEFAULT 1 for sthe synctoken field in the // addressbook. - if ($databaseConfig['mysql'] === false) { + if ($databaseConfig['backend'] === 'sqlite') { $pdo->exec('UPDATE addressbooks SET synctoken = 1 WHERE synctoken IS NULL'); $tmpTable = '_' . time(); @@ -365,7 +365,7 @@ protected function upgrade($databaseConfig, $sVersionFrom, $sVersionTo) { } } if (version_compare($sVersionFrom, '0.5.1', '<')) { - if ($databaseConfig['mysql'] === false) { + if ($databaseConfig['backend'] == 'sqlite') { $pdo->exec(<<submitted()) { - $bMySQL = (intval($oForm->postValue("mysql")) === 1); + $bMySQL = ($oForm->postValue("backend") == 'mysql'); + $bPgSQL = ($oForm->postValue("backend") == 'pgsql'); } else { try { $config = Yaml::parseFile(PROJECT_PATH_CONFIG . "baikal.yaml"); } catch (\Exception $e) { error_log('Error reading baikal.yaml file : ' . $e->getMessage()); } - $bMySQL = $config['database']['mysql'] ?? true; + $bMySQL = $config['database']['backend'] == 'mysql'; + $bPgSQL = $config['database']['backend'] == 'pgsql'; } - if ($bMySQL === true) { + if ($bMySQL === true || $bPgSQL === true) { $oMorpho->remove("sqlite_file"); - } else { + } + + if (!$bMySQL) { $oMorpho->remove("mysql_host"); $oMorpho->remove("mysql_dbname"); $oMorpho->remove("mysql_username"); $oMorpho->remove("mysql_password"); } + + if (!$bPgSQL) { + $oMorpho->remove("pgsql_host"); + $oMorpho->remove("pgsql_dbname"); + $oMorpho->remove("pgsql_username"); + $oMorpho->remove("pgsql_password"); + } } function validationHook(\Formal\Form $oForm, \Formal\Form\Morphology $oMorpho) { if ($oForm->refreshed()) { return true; } - if (intval($oForm->modelInstance()->get("mysql")) === 1) { - # We have to check the MySQL connection - $sHost = $oForm->modelInstance()->get("mysql_host"); - $sDbName = $oForm->modelInstance()->get("mysql_dbname"); - $sUsername = $oForm->modelInstance()->get("mysql_username"); - $sPassword = $oForm->modelInstance()->get("mysql_password"); + if ($oForm->modelInstance()->get("backend") == 'mysql' || $oForm->modelInstance()->get("backend") == 'pgsql') { + $dbBackendName = $oForm->modelInstance()->get("backend") == 'pgsql' ? 'PostgreSQL' : 'MySQL'; + $dbBackendPrefix = $oForm->modelInstance()->get("backend"); + + # We have to check the MySQL or PostgreSQL connection + $sHost = $oForm->modelInstance()->get("{$dbBackendPrefix}_host"); + $sDbName = $oForm->modelInstance()->get("{$dbBackendPrefix}_dbname"); + $sUsername = $oForm->modelInstance()->get("{$dbBackendPrefix}_username"); + $sPassword = $oForm->modelInstance()->get("{$dbBackendPrefix}_password"); try { - $oDB = new \Flake\Core\Database\Mysql( + $oDB = (($oForm->modelInstance()->get("backend")) == 'pgsql' + ) ? new \Flake\Core\Database\Pgsql( + $sHost, + $sDbName, + $sUsername, + $sPassword + ) : new \Flake\Core\Database\Mysql( $sHost, $sDbName, $sUsername, $sPassword ); } catch (\Exception $e) { - $sMessage = "MySQL error: " . $e->getMessage(); + $sMessage = "{$dbBackendName} error: " . $e->getMessage(); $sMessage .= "
Nothing has been saved"; - $oForm->declareError($oMorpho->element("mysql_host"), $sMessage); - $oForm->declareError($oMorpho->element("mysql_dbname")); - $oForm->declareError($oMorpho->element("mysql_username")); - $oForm->declareError($oMorpho->element("mysql_password")); + $oForm->declareError($oMorpho->element("{$dbBackendPrefix}_host"), $sMessage); + $oForm->declareError($oMorpho->element("{$dbBackendPrefix}_dbname")); + $oForm->declareError($oMorpho->element("{$dbBackendPrefix}_username")); + $oForm->declareError($oMorpho->element("{$dbBackendPrefix}_password")); return; } if (($aMissingTables = \Baikal\Core\Tools::isDBStructurallyComplete($oDB)) !== true) { - $sMessage = "MySQL error: These tables, required by Baïkal, are missing: " . implode(", ", $aMissingTables) . "
"; - $sMessage .= "You may want create these tables using the file Core/Resources/Db/MySQL/db.sql"; + $sMessage = "{$dbBackendName} error: These tables, required by Baïkal, are missing: " . implode(", ", $aMissingTables) . "
"; + $sMessage .= "You may want create these tables using the file Core/Resources/Db/{$dbBackendName}/db.sql"; $sMessage .= "

Nothing has been saved"; - $oForm->declareError($oMorpho->element("mysql"), $sMessage); + $oForm->declareError($oMorpho->element("backend"), $sMessage); return; } diff --git a/Core/Frameworks/Flake/Core/Database/Pgsql.php b/Core/Frameworks/Flake/Core/Database/Pgsql.php new file mode 100644 index 000000000..8d6505407 --- /dev/null +++ b/Core/Frameworks/Flake/Core/Database/Pgsql.php @@ -0,0 +1,68 @@ + +# All rights reserved +# +# http://flake.codr.fr +# +# This script is part of the Flake project. The Flake +# project is free software; you can redistribute it +# and/or modify it under the terms of the GNU General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# The GNU General Public License can be found at +# http://www.gnu.org/copyleft/gpl.html. +# +# This script is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# This copyright notice MUST APPEAR in all copies of the script! +################################################################# + +namespace Flake\Core\Database; + +class Pgsql extends \Flake\Core\Database { + protected $oDb = false; // current DB link + protected $debugOutput = false; + protected $store_lastBuiltQuery = true; + protected $debug_lastBuiltQuery = ""; + protected $sHost = ""; + protected $sDbName = ""; + protected $sUsername = ""; + protected $sPassword = ""; + + public function __construct($sHost, $sDbName, $sUsername, $sPassword) { + $this->sHost = $sHost; + $this->sDbName = $sDbName; + $this->sUsername = $sUsername; + $this->sPassword = $sPassword; + + $this->oDb = new \PDO( + 'pgsql:host=' . $this->sHost . ';dbname=' . $this->sDbName, + $this->sUsername, + $this->sPassword + ); + } + + public function tables() { + $aTables = []; + + $sSql = "SELECT tablename FROM pg_catalog.pg_tables WHERE schemaname = 'public'"; + $oStmt = $this->query($sSql); + + while (($aRs = $oStmt->fetch()) !== false) { + $aTables[] = array_shift($aRs); + } + + asort($aTables); + reset($aTables); + + return $aTables; + } +} diff --git a/Core/Frameworks/Flake/Framework.php b/Core/Frameworks/Flake/Framework.php index 347ad67d6..a6fae0c20 100644 --- a/Core/Frameworks/Flake/Framework.php +++ b/Core/Frameworks/Flake/Framework.php @@ -273,8 +273,10 @@ protected static function initDb() { if (defined("BAIKAL_CONTEXT_INSTALL") && (!isset($config['system']['configured_version']) || $config['system']['configured_version'] === BAIKAL_VERSION)) { return true; } - if ($config['database']['mysql'] === true) { + if ($config['database']['backend'] === 'mysql') { self::initDbMysql($config); + } elseif ($config['database']['backend'] === 'pgsql') { + self::initDbPgsql($config); } else { self::initDbSqlite($config); } @@ -339,6 +341,38 @@ protected static function initDbMysql(array $config) { return true; } + protected static function initDbPgsql(array $config) { + if (!$config['database']['pgsql_host']) { + exit("

The constant PROJECT_DB_PGSQL_HOST, containing the PostgreSQL host name, is not set.
You should set it in config/baikal.yaml

"); + } + + if (!$config['database']['pgsql_dbname']) { + exit("

The constant PROJECT_DB_PGSQL_DBNAME, containing the PostgreSQL database name, is not set.
You should set it in config/baikal.yaml

"); + } + + try { + $GLOBALS["DB"] = new \Flake\Core\Database\Pgsql( + $config['database']['pgsql_host'], + $config['database']['pgsql_dbname'], + $config['database']['pgsql_username'], + $config['database']['pgsql_password'] + ); + + $GLOBALS["DB"]->query("SET NAMES 'UTF8'"); + } catch (\Exception $e) { + $message = "Baïkal was not able to establish a connection to the configured PostgreSQL database (as configured in config/baikal.yaml)."; + if (!$config['database']['pgsql_username']) { + exit("

$message Note: The constant PROJECT_DB_PGSQL_USERNAME, containing the PostgreSQL database username, is not set. If your database requires a username you should set it in config/baikal.yaml.

"); + } + + if ($config['database']['pgsql_password'] === null) { + exit("

$message Note: The constant PROJECT_DB_PGSQL_PASSWORD, containing the PostgreSQL database password, is not set. If your database requires a password you should set it in config/baikal.yaml.

"); + } + + exit("

$message

"); + } + } + static function isDBInitialized() { return isset($GLOBALS["DB"]) && \Flake\Util\Tools::is_a($GLOBALS["DB"], "\Flake\Core\Database"); } diff --git a/Core/Resources/Db/PgSQL/db.sql b/Core/Resources/Db/PgSQL/db.sql new file mode 100644 index 000000000..7335dbb2a --- /dev/null +++ b/Core/Resources/Db/PgSQL/db.sql @@ -0,0 +1,142 @@ + +CREATE TABLE addressbooks ( + id SERIAL PRIMARY KEY, + principaluri TEXT, + displayname VARCHAR(255), + uri TEXT, + description TEXT, + synctoken INT CHECK (synctoken > 0) NOT NULL DEFAULT '1' +); + +CREATE TABLE cards ( + id SERIAL PRIMARY KEY, + addressbookid INT CHECK (addressbookid > 0) NOT NULL, + carddata TEXT, + uri TEXT, + lastmodified INT CHECK (lastmodified > 0), + etag TEXT, + size INT CHECK (size > 0) NOT NULL +); + +CREATE TABLE addressbookchanges ( + id SERIAL PRIMARY KEY, + uri TEXT NOT NULL, + synctoken INT CHECK (synctoken > 0) NOT NULL, + addressbookid INT CHECK (addressbookid > 0) NOT NULL, + operation SMALLINT NOT NULL +); + +CREATE INDEX addressbookid_synctoken ON addressbookchanges (addressbookid, synctoken); + +CREATE TABLE calendarobjects ( + id SERIAL PRIMARY KEY, + calendardata TEXT, + uri TEXT, + calendarid INTEGER CHECK (calendarid > 0) NOT NULL, + lastmodified INT CHECK (lastmodified > 0), + etag TEXT, + size INT CHECK (size > 0) NOT NULL, + componenttype TEXT, + firstoccurence INT CHECK (firstoccurence > 0), + lastoccurence INT CHECK (lastoccurence > 0), + uid TEXT +); + +CREATE TABLE calendars ( + id SERIAL PRIMARY KEY, + synctoken INTEGER CHECK (synctoken > 0) NOT NULL DEFAULT '1', + components TEXT +); + +CREATE TABLE calendarinstances ( + id SERIAL PRIMARY KEY, + calendarid INTEGER CHECK (calendarid > 0) NOT NULL, + principaluri TEXT, + access SMALLINT NOT NULL DEFAULT '1', + displayname VARCHAR(100), + uri TEXT, + description TEXT, + calendarorder INT CHECK (calendarorder >= 0) NOT NULL DEFAULT '0', + calendarcolor TEXT, + timezone TEXT, + transparent SMALLINT NOT NULL DEFAULT '0', + share_href TEXT, + share_displayname VARCHAR(100), + share_invitestatus SMALLINT NOT NULL DEFAULT '2' +); + +CREATE TABLE calendarchanges ( + id SERIAL PRIMARY KEY, + uri TEXT NOT NULL, + synctoken INT CHECK (synctoken > 0) NOT NULL, + calendarid INT CHECK (calendarid > 0) NOT NULL, + operation SMALLINT NOT NULL +); + +CREATE INDEX calendarid_synctoken ON calendarchanges (calendarid, synctoken); + +CREATE TABLE calendarsubscriptions ( + id SERIAL PRIMARY KEY, + uri TEXT NOT NULL, + principaluri TEXT NOT NULL, + source TEXT, + displayname VARCHAR(100), + refreshrate VARCHAR(10), + calendarorder INT CHECK (calendarorder >= 0) NOT NULL DEFAULT '0', + calendarcolor TEXT, + striptodos SMALLINT NULL, + stripalarms SMALLINT NULL, + stripattachments SMALLINT NULL, + lastmodified INT CHECK (lastmodified > 0) +); + +CREATE TABLE schedulingobjects ( + id SERIAL PRIMARY KEY, + principaluri TEXT, + calendardata TEXT, + uri TEXT, + lastmodified INT CHECK (lastmodified > 0), + etag TEXT, + size INT CHECK (size > 0) NOT NULL +); +CREATE TABLE locks ( + id SERIAL PRIMARY KEY, + owner VARCHAR(100), + timeout INTEGER CHECK (timeout > 0), + created INTEGER, + token TEXT, + scope SMALLINT, + depth SMALLINT, + uri TEXT +); + +CREATE INDEX ON locks (token); +CREATE INDEX ON locks (uri); + +CREATE TABLE principals ( + id SERIAL PRIMARY KEY, + uri TEXT NOT NULL, + email TEXT, + displayname VARCHAR(80) +); + +CREATE TABLE groupmembers ( + id SERIAL PRIMARY KEY, + principal_id INTEGER CHECK (principal_id > 0) NOT NULL, + member_id INTEGER CHECK (member_id > 0) NOT NULL +); + +CREATE TABLE propertystorage ( + id SERIAL PRIMARY KEY, + path TEXT NOT NULL, + name TEXT NOT NULL, + valuetype INT CHECK (valuetype > 0), + value TEXT +); + +CREATE UNIQUE INDEX path_property ON propertystorage (path, name); +CREATE TABLE users ( + id SERIAL PRIMARY KEY, + username TEXT, + digesta1 TEXT +); diff --git a/config/baikal.yaml.dist b/config/baikal.yaml.dist index 3fd772b59..e592f47b3 100644 --- a/config/baikal.yaml.dist +++ b/config/baikal.yaml.dist @@ -11,9 +11,13 @@ system: base_uri: '' database: encryption_key: 5d3f0fa0192e3058ea70f1bb20924add + backend: 'mysql' sqlite_file: "absolute/path/to/Specific/db/db.sqlite" - mysql: true mysql_host: 'localhost' mysql_dbname: 'baikal' mysql_username: 'baikal' mysql_password: 'baikal' + pgsql_host: 'localhost' + pgsql_dbname: 'baikal' + pgsql_username: 'baikal' + pgsql_password: 'baikal'