simple-machines-forum/Sources/Cache/APIs/Postgres.php

220 lines
No EOL
4.7 KiB
PHP

<?php
/**
* Simple Machines Forum (SMF)
*
* @package SMF
* @author Simple Machines https://www.simplemachines.org
* @copyright 2022 Simple Machines and individual contributors
* @license https://www.simplemachines.org/about/smf/license.php BSD
*
* @version 2.1.0
*/
namespace SMF\Cache\APIs;
use SMF\Cache\CacheApi;
use SMF\Cache\CacheApiInterface;
if (!defined('SMF'))
die('No direct access...');
/**
* PostgreSQL Cache API class
*
* @package CacheAPI
*/
class Postgres extends CacheApi implements CacheApiInterface
{
/** @var string */
private $db_prefix;
/** @var resource result of pg_connect. */
private $db_connection;
public function __construct()
{
global $db_prefix, $db_connection;
$this->db_prefix = $db_prefix;
$this->db_connection = $db_connection;
parent::__construct();
}
/**
* {@inheritDoc}
*/
public function connect()
{
$result = pg_query_params($this->db_connection, 'SELECT 1
FROM pg_tables
WHERE schemaname = $1
AND tablename = $2',
array(
'public',
$this->db_prefix . 'cache',
)
);
if (pg_affected_rows($result) === 0)
pg_query($this->db_connection, 'CREATE UNLOGGED TABLE ' . $this->db_prefix . 'cache (key text, value text, ttl bigint, PRIMARY KEY (key))');
$this->prepareQueries(
array(
'smf_cache_get_data',
'smf_cache_put_data',
'smf_cache_delete_data',
),
array(
'SELECT value FROM ' . $this->db_prefix . 'cache WHERE key = $1 AND ttl >= $2 LIMIT 1',
'INSERT INTO ' . $this->db_prefix . 'cache(key,value,ttl) VALUES($1,$2,$3)
ON CONFLICT(key) DO UPDATE SET value = $2, ttl = $3',
'DELETE FROM ' . $this->db_prefix . 'cache WHERE key = $1',
)
);
return true;
}
/**
* Stores a prepared SQL statement, ensuring that it's not done twice.
*
* @param array $stmtnames
* @param array $queries
*/
private function prepareQueries(array $stmtnames, array $queries)
{
$result = pg_query_params(
$this->db_connection,
'SELECT name FROM pg_prepared_statements WHERE name = ANY ($1)',
array('{' . implode(', ', $stmtnames) . '}')
);
$arr = pg_num_rows($result) == 0 ? array() : array_map(
function($el)
{
return $el['name'];
},
pg_fetch_all($result)
);
foreach ($stmtnames as $idx => $stmtname)
if (!in_array($stmtname, $arr))
pg_prepare($this->db_connection, $stmtname, $queries[$idx]);
}
/**
* {@inheritDoc}
*/
public function isSupported($test = false)
{
global $smcFunc;
if ($smcFunc['db_title'] !== POSTGRE_TITLE)
return false;
$result = pg_query($this->db_connection, 'SHOW server_version_num');
$res = pg_fetch_assoc($result);
if ($res['server_version_num'] < 90500)
return false;
return $test ? true : parent::isSupported();
}
/**
* {@inheritDoc}
*/
public function getData($key, $ttl = null)
{
$result = pg_execute($this->db_connection, 'smf_cache_get_data', array($key, time()));
if (pg_affected_rows($result) === 0)
return null;
$res = pg_fetch_assoc($result);
return $res['value'];
}
/**
* {@inheritDoc}
*/
public function putData($key, $value, $ttl = null)
{
$ttl = time() + (int) ($ttl !== null ? $ttl : $this->ttl);
if ($value === null)
$result = pg_execute($this->db_connection, 'smf_cache_delete_data', array($key));
else
$result = pg_execute($this->db_connection, 'smf_cache_put_data', array($key, $value, $ttl));
return pg_affected_rows($result) > 0;
}
/**
* {@inheritDoc}
*/
public function cleanCache($type = '')
{
if ($type == 'expired')
pg_query($this->db_connection, 'DELETE FROM ' . $this->db_prefix . 'cache WHERE ttl < ' . time() . ';');
else
pg_query($this->db_connection, 'TRUNCATE ' . $this->db_prefix . 'cache');
$this->invalidateCache();
return true;
}
/**
* {@inheritDoc}
*/
public function getVersion()
{
return pg_version($this->db_connection)['server'];
}
/**
* {@inheritDoc}
*/
public function housekeeping()
{
$this->createTempTable();
$this->cleanCache();
$this->retrieveData();
$this->deleteTempTable();
}
/**
* Create the temp table of valid data.
*
* @return void
*/
private function createTempTable()
{
pg_query($this->db_connection, 'CREATE LOCAL TEMP TABLE IF NOT EXISTS ' . $this->db_prefix . 'cache_tmp AS SELECT * FROM ' . $this->db_prefix . 'cache WHERE ttl >= ' . time());
}
/**
* Delete the temp table.
*
* @return void
*/
private function deleteTempTable()
{
pg_query($this->db_connection, 'DROP TABLE IF EXISTS ' . $this->db_prefix . 'cache_tmp');
}
/**
* Retrieve the valid data from temp table.
*
* @return void
*/
private function retrieveData()
{
pg_query($this->db_connection, 'INSERT INTO ' . $this->db_prefix . 'cache SELECT * FROM ' . $this->db_prefix . 'cache_tmp ON CONFLICT DO NOTHING');
}
}
?>