Completed
Push — master ( 265d0d...5b91fe )
by Nazar
07:12
created

PostgreSQL::server()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 2
rs 10
c 0
b 0
f 0
ccs 2
cts 2
cp 1
crap 1
1
<?php
2
/**
3
 * @package   CleverStyle Framework
4
 * @author    Nazar Mokrynskyi <[email protected]>
5
 * @copyright Copyright (c) 2011-2017, Nazar Mokrynskyi
6
 * @license   MIT License, see license.txt
7
 */
8
namespace cs\DB;
9
class PostgreSQL extends _Abstract {
10
	/**
11
	 * @var resource DB connection handler
12
	 */
13
	protected $handler;
14
	/**
15
	 * @var resource
16
	 */
17
	protected $query_result;
18
	/**
19
	 * @inheritdoc
20
	 */
21 32
	public function __construct ($database, $user = '', $password = '', $host = 'localhost', $prefix = '') {
22 32
		$start = microtime(true);
23
		/**
24
		 * Parsing of $host variable, detecting port and persistent connection
25
		 */
26 32
		list($host, $port, $persistent) = $this->get_host_port_and_persistent($host);
27 32
		$connection_string = "host=$host port=$port dbname=$database user=$user password=$password options='--client_encoding=UTF8'";
28 32
		$this->handler     = $persistent ? pg_connect($connection_string) : pg_pconnect($connection_string);
29 32
		if (!is_resource($this->handler)) {
30 1
			return;
31
		}
32 32
		$this->database        = $database;
33 32
		$this->connected       = true;
34 32
		$this->connecting_time = microtime(true) - $start;
35 32
		$this->db_type         = 'postgresql';
36 32
		$this->prefix          = $prefix;
37 32
	}
38
	/**
39
	 * Parse host string into host, port and persistent separately
40
	 *
41
	 * Understands `p:` prefix for persistent connections
42
	 *
43
	 * @param string $host_string
44
	 *
45
	 * @return array
46
	 */
47 32
	protected function get_host_port_and_persistent ($host_string) {
48 32
		$host       = explode(':', $host_string);
49 32
		$port       = 5432;
50 32
		$persistent = false;
51 32
		switch (count($host)) {
52 32
			case 1:
53 32
				$host = $host[0];
54 32
				break;
55 1
			case 2:
56 1
				if ($host[0] == 'p') {
57 1
					$persistent = true;
58 1
					$host       = $host[1];
59
				} else {
60 1
					list($host, $port) = $host;
61
				}
62 1
				break;
63 1
			case 3:
64 1
				$persistent = true;
65 1
				list(, $host, $port) = $host;
66
		}
67 32
		return [$host, $port, $persistent];
68
	}
69
	/**
70
	 * @inheritdoc
71
	 */
72 32
	public function q ($query, ...$params) {
73 32
		return parent::q(
74 32
			$this->convert_sql($query),
75 32
			...$params
76
		);
77
	}
78
	/**
79
	 * Convert small subset of MySQL queries into PostgreSQL-compatible syntax
80
	 *
81
	 * @param string $query
82
	 *
83
	 * @return string
84
	 */
85 32
	protected function convert_sql ($query) {
86 32
		$query = str_replace('`', '"', $query);
87 32
		return preg_replace_callback(
88 32
			'/(INSERT IGNORE INTO|REPLACE INTO)(.+)(;|$)/Uis',
89
			function ($matches) {
90
				// Only support simplest cases
91 23
				if (stripos($matches[2], 'on duplicate')) {
92
					return $matches[0];
93
				}
94 23
				switch (strtoupper($matches[1])) {
95
					case 'INSERT IGNORE INTO':
96 22
						return "INSERT INTO $matches[2] ON CONFLICT DO NOTHING$matches[3]";
97
					case 'REPLACE INTO':
98 16
						$table_name = substr(
99 16
							$matches[2],
100 16
							strpos($matches[2], '"') + 1
101
						);
102 16
						$table_name = substr(
103 16
							$table_name,
104 16
							0,
105 16
							strpos($table_name, '"')
106
						);
107 16
						$update     = preg_replace_callback(
108 16
							'/"([^"]+)"/',
109 16
							function ($matches) {
110 16
								return "\"$matches[1]\" = EXCLUDED.\"$matches[1]\"";
111 16
							},
112 16
							substr(
113 16
								strstr($matches[2], ')', true),
114 16
								strpos($matches[2], '(') + 1
115
							)
116
						);
117
						// Only support constraint named as table with `_primary` prefix
118 16
						return "INSERT INTO $matches[2] ON CONFLICT ON CONSTRAINT \"{$table_name}_primary\" DO UPDATE SET $update$matches[3]";
119
				}
120 32
			},
121 32
			$query
122
		);
123
	}
124
	/**
125
	 * @inheritdoc
126
	 *
127
	 * @return false|resource
128
	 */
129 32
	protected function q_internal ($query, $parameters = []) {
130 32
		if (!$query) {
131 1
			return false;
132
		}
133 32
		if ($parameters) {
134
			// Allows to provide more parameters for prepared statements than needed
135 27
			$local_parameters = array_slice($parameters, 0, substr_count($query, '?'));
136 27
			$query            = $this->convert_prepared_statements_syntax($query);
137 27
			return $this->query_result = pg_query_params($this->handler, $query, $local_parameters);
138
		}
139 32
		return $this->query_result = pg_query($this->handler, $query);
140
	}
141
	/**
142
	 * @param string $query
143
	 *
144
	 * @return string
145
	 */
146 27
	protected function convert_prepared_statements_syntax ($query) {
147 27
		$i = 1;
148 27
		while ($q_pos = strpos($query, '?')) {
149 27
			$query = substr($query, 0, $q_pos)."$$i".substr($query, $q_pos + 1);
150 27
			++$i;
151
		}
152 27
		return $query;
153
	}
154
	/**
155
	 * @inheritdoc
156
	 *
157
	 * @param false|resource $query_result
158
	 */
159 32
	public function f ($query_result, $single_column = false, $array = false, $indexed = false) {
160 32
		if (!is_resource($query_result)) {
161 1
			return false;
162
		}
163 32
		$result_type = $single_column || $indexed ? PGSQL_NUM : PGSQL_ASSOC;
164 32
		if ($array) {
165 27
			$result = [];
166 27
			while ($current = pg_fetch_array($query_result, null, $result_type)) {
167 26
				$result[] = $single_column ? $current[0] : $current;
168
			}
169 27
			$this->free($query_result);
170 27
			return $result;
171
		}
172 30
		$result = pg_fetch_array($query_result, null, $result_type);
173 30
		return $single_column && $result ? $result[0] : $result;
0 ignored issues
show
introduced by
The condition $single_column && $result can never be true.
Loading history...
174
	}
175
	/**
176
	 * @inheritdoc
177
	 */
178 24
	public function id () {
179 24
		return (int)$this->qfs('SELECT lastval()');
0 ignored issues
show
Bug introduced by
'SELECT lastval()' of type string is incompatible with the type string[] expected by parameter $query of cs\DB\_Abstract::qfs(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

179
		return (int)$this->qfs(/** @scrutinizer ignore-type */ 'SELECT lastval()');
Loading history...
180
	}
181
	/**
182
	 * @inheritdoc
183
	 */
184 1
	public function affected () {
185 1
		return is_resource($this->query_result) ? pg_affected_rows($this->query_result) : 0;
186
	}
187
	/**
188
	 * @inheritdoc
189
	 *
190
	 * @param false|resource $query_result
191
	 */
192 27
	public function free ($query_result) {
193 27
		if (is_resource($query_result)) {
194 27
			return pg_free_result($query_result);
195
		}
196 1
		return true;
197
	}
198
	/**
199
	 * @inheritdoc
200
	 */
201 3
	public function columns ($table, $like = false) {
202 3
		if (!$table) {
203 1
			return false;
204
		}
205 3
		if ($like) {
206 1
			$like    = $this->s($like);
207 1
			$columns = $this->qfas(
208
				"SELECT `column_name` 
0 ignored issues
show
Bug introduced by
EncapsedNode of type string is incompatible with the type string[] expected by parameter $query of cs\DB\_Abstract::qfas(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

208
				/** @scrutinizer ignore-type */ "SELECT `column_name` 
Loading history...
209
				FROM `information_schema`.`columns`
210
				WHERE
211 1
					`table_name` = '$table' AND
212 1
					`column_name` LIKE $like"
213 1
			) ?: [];
214
		} else {
215 3
			$columns = $this->qfas(
216
				"SELECT `column_name`
217
				FROM `information_schema`.`columns`
218 3
				WHERE `table_name` = '$table'"
219 3
			) ?: [];
220
		}
221 3
		return $columns;
222
	}
223
	/**
224
	 * @inheritdoc
225
	 */
226 1
	public function tables ($like = false) {
227 1
		if ($like) {
228 1
			$like = $this->s($like);
229 1
			return $this->qfas(
230
				"SELECT `table_name`
0 ignored issues
show
Bug introduced by
EncapsedNode of type string is incompatible with the type string[] expected by parameter $query of cs\DB\_Abstract::qfas(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

230
				/** @scrutinizer ignore-type */ "SELECT `table_name`
Loading history...
231
				FROM `information_schema`.`tables`
232
				WHERE
233
					`table_schema` = 'public' AND
234 1
					`table_name` LIKE $like
235
				ORDER BY `table_name` ASC"
236 1
			) ?: [];
237
		} else {
238 1
			return $this->qfas(
239 1
				"SELECT `table_name`
240
				FROM `information_schema`.`tables`
241
				WHERE `table_schema` = 'public'
242
				ORDER BY `table_name` ASC"
243 1
			) ?: [];
244
		}
245
	}
246
	/**
247
	 * @inheritdoc
248
	 */
249 29
	protected function s_internal ($string, $single_quotes_around) {
250 29
		return $single_quotes_around ? pg_escape_literal($this->handler, $string) : pg_escape_string($this->handler, $string);
251
	}
252
	/**
253
	 * @inheritdoc
254
	 */
255 1
	public function server () {
256 1
		return pg_version($this->handler)['server'];
257
	}
258
	/**
259
	 * @inheritdoc
260
	 */
261 1
	public function __destruct () {
262 1
		if ($this->connected && is_resource($this->handler)) {
263 1
			pg_close($this->handler);
264 1
			$this->connected = false;
265
		}
266 1
	}
267
}
268