Passed
Push — release-2.1 ( f28109...493a4c )
by Mathias
06:40
created

Postgres   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 185
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 63
c 0
b 0
f 0
dl 0
loc 185
rs 10
wmc 22

12 Methods

Rating   Name   Duplication   Size   Complexity  
A cleanCache() 0 5 1
A getVersion() 0 3 1
A housekeeping() 0 6 1
A deleteTempTable() 0 3 1
A prepareQueries() 0 18 4
A connect() 0 30 2
A __construct() 0 8 1
A createTempTable() 0 3 1
A retrieveData() 0 3 1
A isSupported() 0 14 4
A putData() 0 10 3
A getData() 0 10 2
1
<?php
2
3
/**
4
 * Simple Machines Forum (SMF)
5
 *
6
 * @package SMF
7
 * @author Simple Machines https://www.simplemachines.org
8
 * @copyright 2020 Simple Machines and individual contributors
9
 * @license https://www.simplemachines.org/about/smf/license.php BSD
10
 *
11
 * @version 2.1 RC3
12
 */
13
14
namespace SMF\Cache\APIs;
15
16
use SMF\Cache\CacheApi;
17
use SMF\Cache\CacheApiInterface;
18
19
if (!defined('SMF'))
20
	die('No direct access...');
21
22
/**
23
 * PostgreSQL Cache API class
24
 *
25
 * @package CacheAPI
26
 */
27
class Postgres extends CacheApi implements CacheApiInterface
28
{
29
	/** @var string */
30
	private $db_prefix;
31
32
	/** @var resource result of pg_connect. */
33
	private $db_connection;
34
35
	public function __construct()
36
	{
37
		global $db_prefix, $db_connection;
38
39
		$this->db_prefix = $db_prefix;
40
		$this->db_connection = $db_connection;
41
42
		parent::__construct();
43
	}
44
45
	/**
46
	 * {@inheritDoc}
47
	 */
48
	public function connect()
49
	{
50
		$result = pg_query_params($this->db_connection, 'SELECT 1
51
			FROM   pg_tables
52
			WHERE  schemaname = $1
53
			AND    tablename = $2',
54
			array(
55
				'public',
56
				$this->db_prefix . 'cache',
57
			)
58
		);
59
60
		if (pg_affected_rows($result) === 0)
61
			pg_query($this->db_connection, 'CREATE UNLOGGED TABLE ' . $this->db_prefix . 'cache (key text, value text, ttl bigint, PRIMARY KEY (key))');
62
63
		$this->prepareQueries(
64
			array(
65
				'smf_cache_get_data',
66
				'smf_cache_put_data',
67
				'smf_cache_delete_data',
68
			),
69
			array(
70
				'SELECT value FROM ' . $this->db_prefix . 'cache WHERE key = $1 AND ttl >= $2 LIMIT 1',
71
				'INSERT INTO ' . $this->db_prefix . 'cache(key,value,ttl) VALUES($1,$2,$3)
72
				ON CONFLICT(key) DO UPDATE SET value = $2, ttl = $3',
73
				'DELETE FROM ' . $this->db_prefix . 'cache WHERE key = $1',
74
			)
75
		);
76
77
		return true;
78
	}
79
80
	/**
81
	 * Stores a prepared SQL statement, ensuring that it's not done twice.
82
	 *
83
	 * @param array $stmtnames
84
	 * @param array $queries
85
	 */
86
	private function prepareQueries(array $stmtnames, array $queries)
87
	{
88
		$result = pg_query_params(
89
			$this->db_connection,
90
			'SELECT name FROM pg_prepared_statements WHERE name = ANY ($1)',
91
			array('{' . implode(', ', $stmtnames) . '}')
92
		);
93
94
		$arr = pg_num_rows($result) == 0 ? array() : array_map(
95
			function($el)
96
			{
97
				return $el['name'];
98
			},
99
			pg_fetch_all($result)
100
		);
101
		foreach ($stmtnames as $idx => $stmtname)
102
			if (!in_array($stmtname, $arr))
103
				pg_prepare($this->db_connection, $stmtname, $queries[$idx]);
104
	}
105
106
	/**
107
	 * {@inheritDoc}
108
	 */
109
	public function isSupported($test = false)
110
	{
111
		global $smcFunc;
112
113
		if ($smcFunc['db_title'] !== POSTGRE_TITLE)
114
			return false;
115
116
		$result = pg_query($this->db_connection, 'SHOW server_version_num');
117
		$res = pg_fetch_assoc($result);
118
119
		if ($res['server_version_num'] < 90500)
120
			return false;
121
122
		return $test ? true : parent::isSupported();
123
	}
124
125
	/**
126
	 * {@inheritDoc}
127
	 */
128
	public function getData($key, $ttl = null)
129
	{
130
		$result = pg_execute($this->db_connection, 'smf_cache_get_data', array($key, time()));
131
132
		if (pg_affected_rows($result) === 0)
133
			return null;
134
135
		$res = pg_fetch_assoc($result);
136
137
		return $res['value'];
138
	}
139
140
	/**
141
	 * {@inheritDoc}
142
	 */
143
	public function putData($key, $value, $ttl = null)
144
	{
145
		$ttl = time() + (int) ($ttl !== null ? $ttl : $this->ttl);
146
147
		if ($value === null)
148
			$result = pg_execute($this->db_connection, 'smf_cache_delete_data', array($key));
149
		else
150
			$result = pg_execute($this->db_connection, 'smf_cache_put_data', array($key, $value, $ttl));
151
152
		return pg_affected_rows($result) > 0;
153
	}
154
155
	/**
156
	 * {@inheritDoc}
157
	 */
158
	public function cleanCache($type = '')
159
	{
160
		pg_query($this->db_connection, 'TRUNCATE ' . $this->db_prefix . 'cache');
161
162
		return true;
163
	}
164
165
	/**
166
	 * {@inheritDoc}
167
	 */
168
	public function getVersion()
169
	{
170
		return pg_version()['server'];
171
	}
172
173
	/**
174
	 * {@inheritDoc}
175
	 */
176
	public function housekeeping()
177
	{
178
		$this->createTempTable();
179
		$this->cleanCache();
180
		$this->retrieveData();
181
		$this->deleteTempTable();
182
	}
183
184
	/**
185
	 * Create the temp table of valid data.
186
	 *
187
	 * @return void
188
	 */
189
	private function createTempTable()
190
	{
191
		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());
192
	}
193
194
	/**
195
	 * Delete the temp table.
196
	 *
197
	 * @return void
198
	 */
199
	private function deleteTempTable()
200
	{
201
		pg_query($this->db_connection, 'DROP TABLE IF EXISTS ' . $this->db_prefix . 'cache_tmp');
202
	}
203
204
	/**
205
	 * Retrieve the valid data from temp table.
206
	 *
207
	 * @return void
208
	 */
209
	private function retrieveData()
210
	{
211
		pg_query($this->db_connection, 'INSERT INTO ' . $this->db_prefix . 'cache SELECT * FROM ' . $this->db_prefix . 'cache_tmp ON CONFLICT DO NOTHING');
212
	}
213
}
214
215
?>