Skip to content

Commit

Permalink
Introduce singleton Icinga\Module\Icingadb\Common\Backend
Browse files Browse the repository at this point in the history
  • Loading branch information
nilmerg committed Dec 17, 2024
1 parent 91333a1 commit 7f944af
Showing 1 changed file with 168 additions and 0 deletions.
168 changes: 168 additions & 0 deletions library/Icingadb/Common/Backend.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
<?php

/* Icinga DB Web | (c) 2024 Icinga GmbH | GPLv2 */

namespace Icinga\Module\Icingadb\Common;

use Icinga\Application\Config as AppConfig;
use Icinga\Data\ResourceFactory;
use Icinga\Module\Icingadb\Model\Schema;
use ipl\Sql\Adapter\Pgsql;
use ipl\Sql\Config as SqlConfig;
use ipl\Sql\Connection;
use ipl\Sql\Expression;
use ipl\Sql\QueryBuilder;
use ipl\Sql\Select;
use PDO;

/**
* Singleton providing access to the Icinga DB and Redis
*/
final class Backend
{
/** @var ?Connection */
private static $db;

/** @var ?int */
private static $schemaVersion;

/** @var ?IcingaRedis */
private static $redis;

/**
* Set the connection to the Icinga DB
*
* Usually not required, as the connection is created on demand. Useful for testing.
*
* @param Connection $db
*
* @return void
*/
public static function setDb(Connection $db): void
{
self::$db = $db;
}

/**
* Get the connection to the Icinga DB
*
* @return Connection
*/
public static function getDb(): Connection
{
if (self::$db === null) {
$config = new SqlConfig(ResourceFactory::getResourceConfig(
AppConfig::module('icingadb')->get('icingadb', 'resource')
));

$config->options = [PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ];
if ($config->db === 'mysql') {
$config->options[PDO::MYSQL_ATTR_INIT_COMMAND] = "SET SESSION SQL_MODE='STRICT_TRANS_TABLES"
. ",NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'";
}

self::$db = new Connection($config);

$adapter = self::$db->getAdapter();
if ($adapter instanceof Pgsql) {
$quoted = $adapter->quoteIdentifier('user');
self::$db->getQueryBuilder()
->on(QueryBuilder::ON_SELECT_ASSEMBLED, function (&$sql) use ($quoted) {
// user is a reserved key word in PostgreSQL, so we need to quote it.
// TODO(lippserd): This is pretty hacky,
// reconsider how to properly implement identifier quoting.
$sql = str_replace(' user ', sprintf(' %s ', $quoted), $sql);
$sql = str_replace(' user.', sprintf(' %s.', $quoted), $sql);
$sql = str_replace('(user.', sprintf('(%s.', $quoted), $sql);
})
->on(QueryBuilder::ON_ASSEMBLE_SELECT, function (Select $select) {
// For SELECT DISTINCT, all ORDER BY columns must appear in SELECT list.
if (! $select->getDistinct() || ! $select->hasOrderBy()) {
return;
}

$candidates = [];
foreach ($select->getOrderBy() as list($columnOrAlias, $_)) {
if ($columnOrAlias instanceof Expression) {
// Expressions can be and include anything,
// also columns that aren't already part of the SELECT list,
// so we're not trying to guess anything here.
// Such expressions must be in the SELECT list if necessary and
// referenced manually with an alias in ORDER BY.
continue;
}

$candidates[$columnOrAlias] = true;
}

foreach ($select->getColumns() as $alias => $column) {
if (is_int($alias)) {
if ($column instanceof Expression) {
// This is the complement to the above consideration.
// If it is an unaliased expression, ignore it.
continue;
}
} else {
unset($candidates[$alias]);
}

if (! $column instanceof Expression) {
unset($candidates[$column]);
}
}

if (! empty($candidates)) {
$select->columns(array_keys($candidates));
}
});
}
}

return self::$db;
}

/**
* Get the schema version of the Icinga DB
*
* @return int
*/
public static function getDbSchemaVersion(): int
{
if (self::$schemaVersion === null) {
self::$schemaVersion = Schema::on(self::getDb())
->columns('version')
->first()
->version;
}

return self::$schemaVersion;
}

/**
* Set the connection to the Icinga Redis
*
* Usually not required, as the connection is created on demand. Useful for testing.
*
* @param IcingaRedis $redis
*
* @return void
*/
public static function setRedis(IcingaRedis $redis): void
{
self::$redis = $redis;
}

/**
* Get the connection to the Icinga Redis
*
* @return IcingaRedis
*/
public static function getRedis(): IcingaRedis
{
if (self::$redis === null) {
self::$redis = new IcingaRedis();
}

return self::$redis;
}
}

0 comments on commit 7f944af

Please sign in to comment.