Skip to content

Commit

Permalink
Add PostgreSQL backend
Browse files Browse the repository at this point in the history
Co-authored-by: Kim Lidström <[email protected]>
  • Loading branch information
leso-kn and dxtr committed Jul 21, 2024
1 parent cc3eca1 commit b322aaa
Show file tree
Hide file tree
Showing 12 changed files with 451 additions and 50 deletions.
4 changes: 2 additions & 2 deletions Core/Frameworks/Baikal/Core/Tools.php
Original file line number Diff line number Diff line change
Expand Up @@ -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('<strong>Baikal Fatal Error</strong>: Both <strong>PDO::sqlite</strong> and <strong>PDO::mysql</strong> 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('<strong>Baikal Fatal Error</strong>: None of <strong>PDO::sqlite</strong>, <strong>PDO::mysql</strong> or <strong>PDO::pgsql</strong> are available. One of them at least is required by Baikal.');
}

# Assert that the temp folder is writable
Expand Down
4 changes: 2 additions & 2 deletions Core/Frameworks/Baikal/Model/Calendar.php
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ function isDefault() {

function hasInstances() {
$rSql = $GLOBALS["DB"]->exec_SELECTquery(
"count(*)",
"count(*) as count",
"calendarinstances",
"calendarid='" . $this->aData["calendarid"] . "'"
);
Expand All @@ -254,7 +254,7 @@ function hasInstances() {
} else {
reset($aRs);

return $aRs["count(*)"] > 1;
return $aRs["count"] > 1;
}
}

Expand Down
4 changes: 2 additions & 2 deletions Core/Frameworks/Baikal/Model/Calendar/Calendar.php
Original file line number Diff line number Diff line change
Expand Up @@ -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"] . "'"
);
Expand All @@ -48,7 +48,7 @@ function hasInstances() {
} else {
reset($aRs);

return $aRs["count(*)"] > 1;
return $aRs["count"] > 1;
}
}

Expand Down
42 changes: 34 additions & 8 deletions Core/Frameworks/Baikal/Model/Config/Database.php
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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",
Expand All @@ -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",
Expand All @@ -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 <strong>':portnumber'</strong> 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;
}

Expand Down
121 changes: 112 additions & 9 deletions Core/Frameworks/BaikalAdmin/Controller/Install/Database.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down Expand Up @@ -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();
Expand All @@ -129,7 +133,7 @@ function validateConnection($oForm, $oMorpho) {
$sMessage .= "<br /><p>Nothing has been saved. <strong>Please, add these tables to the database before pursuing Baïkal initialization.</strong></p>";

$oForm->declareError(
$oMorpho->element("mysql"),
$oMorpho->element("backend"),
$sMessage
);
} else {
Expand All @@ -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.<br />MySQL says: " . $e->getMessage());
$oForm->declareError($oMorpho->element("mysql_host"));
$oForm->declareError($oMorpho->element("mysql_dbname"));
Expand Down Expand Up @@ -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) {
Expand All @@ -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 = "<br /><p><strong>Database is not structurally complete.</strong></p>";
$sMessage .= "<p>Missing tables are: <strong>" . implode("</strong>, <strong>", $aMissingTables) . "</strong></p>";
$sMessage .= "<p>You will find the SQL definition of Baïkal tables in this file: <strong>Core/Resources/Db/PgSQL/db.sql</strong></p>";
$sMessage .= "<br /><p>Nothing has been saved. <strong>Please, add these tables to the database before pursuing Baïkal initialization.</strong></p>";

$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.<br />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");
}
}
}
8 changes: 6 additions & 2 deletions Core/Frameworks/BaikalAdmin/Controller/Install/Initialize.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand All @@ -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(<<<SQL
CREATE TABLE calendarinstances (
id integer primary key asc NOT NULL,
Expand Down
Loading

0 comments on commit b322aaa

Please sign in to comment.