Passed
Pull Request — release-2.1 (#6262)
by Jeremy
04:04
created

postgres_cache   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 185
Duplicated Lines 0 %

Importance

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

12 Methods

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