1:   2:   3:   4:   5:   6:   7:   8:   9:  10:  11:  12:  13:  14:  15:  16:  17:  18:  19:  20:  21:  22:  23:  24:  25:  26:  27:  28:  29:  30:  31:  32:  33:  34:  35:  36:  37:  38:  39:  40:  41:  42:  43:  44:  45:  46:  47:  48:  49:  50:  51:  52:  53:  54:  55:  56:  57:  58:  59:  60:  61:  62:  63:  64:  65:  66:  67:  68:  69:  70:  71:  72:  73:  74:  75:  76:  77:  78:  79:  80:  81:  82:  83:  84:  85:  86:  87:  88:  89:  90:  91:  92:  93:  94:  95:  96:  97:  98:  99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 
<?php

/**
 * Simple Machines Forum (SMF)
 *
 * @package SMF
 * @author Simple Machines http://www.simplemachines.org
 * @copyright 2019 Simple Machines and individual contributors
 * @license http://www.simplemachines.org/about/smf/license.php BSD
 *
 * @version 2.1 RC1
 */

if (!defined('SMF'))
    die('Hacking attempt...');

/**
 * PostgreSQL Cache API class
 *
 * @package cacheAPI
 */
class postgres_cache extends cache_api
{
    /**
     * @var false|resource of the pg_prepare from get_data.
     */
    private $pg_get_data_prep;

    /**
     * @var false|resource of the pg_prepare from put_data.
     */
    private $pg_put_data_prep;

    public function __construct()
    {
        parent::__construct();
    }

    /**
     * {@inheritDoc}
     */
    public function connect()
    {
        global $db_prefix, $db_connection;

        pg_prepare($db_connection, '', 'SELECT 1
            FROM   pg_tables
            WHERE  schemaname = $1
            AND    tablename = $2');

        $result = pg_execute($db_connection, '', array('public', $db_prefix . 'cache'));

        if (pg_affected_rows($result) === 0)
            pg_query($db_connection, 'CREATE UNLOGGED TABLE ' . $db_prefix . 'cache (key text, value text, ttl bigint, PRIMARY KEY (key))');
    }

    /**
     * {@inheritDoc}
     */
    public function isSupported($test = false)
    {
        global $smcFunc, $db_connection;

        if ($smcFunc['db_title'] !== 'PostgreSQL')
            return false;

        $result = pg_query($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)
    {
        global $db_prefix, $db_connection;

        $ttl = time() - $ttl;

        if (empty($this->pg_get_data_prep))
            $this->pg_get_data_prep = pg_prepare($db_connection, 'smf_cache_get_data', 'SELECT value FROM ' . $db_prefix . 'cache WHERE key = $1 AND ttl >= $2 LIMIT 1');

        $result = pg_execute($db_connection, 'smf_cache_get_data', array($key, $ttl));

        if (pg_affected_rows($result) === 0)
            return null;

        $res = pg_fetch_assoc($result);

        return $res['value'];
    }

    /**
     * {@inheritDoc}
     */
    public function putData($key, $value, $ttl = null)
    {
        global $db_prefix, $db_connection;

        if (!isset($value))
            $value = '';

        $ttl = time() + $ttl;

        if (empty($this->pg_put_data_prep))
            $this->pg_put_data_prep = pg_prepare($db_connection, 'smf_cache_put_data',
                'INSERT INTO ' . $db_prefix . 'cache(key,value,ttl) VALUES($1,$2,$3)
                ON CONFLICT(key) DO UPDATE SET value = excluded.value, ttl = excluded.ttl'
            );

        $result = pg_execute($db_connection, 'smf_cache_put_data', array($key, $value, $ttl));

        if (pg_affected_rows($result) > 0)
            return true;
        else
            return false;
    }

    /**
     * {@inheritDoc}
     */
    public function cleanCache($type = '')
    {
        global $smcFunc;

        $smcFunc['db_query']('', '
            TRUNCATE TABLE {db_prefix}cache',
            array()
        );

        return true;
    }

    /**
     * {@inheritDoc}
     */
    public function getVersion()
    {
        global $smcFunc;

        return $smcFunc['db_server_info']();
    }

    /**
     * {@inheritDoc}
     */
    public function housekeeping()
    {
        $this->createTempTable();
        $this->cleanCache();
        $this->retrieveData();
        $this->deleteTempTable();
    }

    /**
     * Create the temp table of valid data.
     *
     * @return void
     */
    private function createTempTable()
    {
        global $db_connection, $db_prefix;

        pg_query($db_connection, 'CREATE LOCAL TEMP TABLE IF NOT EXISTS ' . $db_prefix . 'cache_tmp AS SELECT * FROM ' . $db_prefix . 'cache WHERE ttl >= ' . time());
    }

    /**
     * Delete the temp table.
     *
     * @return void
     */
    private function deleteTempTable()
    {
        global $db_connection, $db_prefix;

        pg_query($db_connection, 'DROP TABLE IF EXISTS ' . $db_prefix . 'cache_tmp');
    }

    /**
     * Retrieve the valid data from temp table.
     *
     * @return void
     */
    private function retrieveData()
    {
        global $db_connection, $db_prefix;

        pg_query($db_connection, 'INSERT INTO ' . $db_prefix . 'cache SELECT * FROM ' . $db_prefix . 'cache_tmp ON CONFLICT DO NOTHING');
    }
}

?>