Completed
Push — master ( dabf55...764bc6 )
by Nazar
14:29
created

PostgreSQL::convert_prepared_statements_syntax()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 3
nop 1
dl 0
loc 11
ccs 0
cts 11
cp 0
crap 12
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * @package   CleverStyle Framework
4
 * @author    Nazar Mokrynskyi <[email protected]>
5
 * @copyright Copyright (c) 2011-2016, 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
	public function __construct ($database, $user = '', $password = '', $host = 'localhost', $prefix = '') {
22
		$start = microtime(true);
23
		/**
24
		 * Parsing of $host variable, detecting port and persistent connection
25
		 */
26
		list($host, $port, $persistent) = $this->get_host_port_and_persistent($host);
27
		$connection_string = "host=$host port=$port dbname=$database user=$user password=$password options='--client_encoding=UTF8'";
28
		$this->handler     = $persistent ? pg_connect($connection_string) : pg_pconnect($connection_string);
29
		if (!is_resource($this->handler)) {
30
			return;
31
		}
32
		$this->database        = $database;
33
		$this->connected       = true;
34
		$this->connecting_time = microtime(true) - $start;
35
		$this->db_type         = 'postgresql';
36
		$this->prefix          = $prefix;
37
	}
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
	protected function get_host_port_and_persistent ($host_string) {
48
		$host       = explode(':', $host_string);
49
		$port       = 5432;
50
		$persistent = false;
51
		switch (count($host)) {
52
			case 1:
53
				$host = $host[0];
54
				break;
55
			case 2:
56
				if ($host[0] == 'p') {
57
					$persistent = true;
58
					$host       = $host[1];
59
				} else {
60
					list($host, $port) = $host;
61
				}
62
				break;
63
			case 3:
64
				$persistent = true;
65
				list(, $host, $port) = $host;
66
		}
67
		return [$host, $port, $persistent];
68
	}
69
	/**
70
	 * @inheritdoc
71
	 */
72
	public function q ($query, ...$params) {
73
		// Hack to convert small subset of MySQL queries into PostgreSQL-compatible syntax
74
		$query = str_replace('`', '"', $query);
75
		$query = preg_replace_callback(
76
			'/(INSERT IGNORE INTO|REPLACE INTO)(.+)(;|$)/Uis',
77
			function ($matches) {
78
				// Only support simplest cases
79
				if (stripos($matches[2], 'on duplicate')) {
80
					return $matches[0];
81
				}
82
				switch (strtoupper($matches[1])) {
83
					case 'INSERT IGNORE INTO':
84
						return "INSERT INTO $matches[2] ON CONFLICT DO NOTHING$matches[3]";
85
					case 'REPLACE INTO':
86
						$table_name = substr(
87
							$matches[2],
88
							strpos($matches[2], '"') + 1
89
						);
90
						$table_name = substr(
91
							$table_name,
92
							0,
93
							strpos($table_name, '"')
94
						);
95
						$update     = preg_replace_callback(
96
							'/"([^"]+)"/',
97
							function ($matches) {
98
								return "\"$matches[1]\" = EXCLUDED.\"$matches[1]\"";
99
							},
100
							substr(
101
								strstr($matches[2], ')', true),
102
								strpos($matches[2], '(') + 1
103
							)
104
						);
105
						// Only support constraint named as table with `_primary` prefix
106
						return "INSERT INTO $matches[2] ON CONFLICT ON CONSTRAINT \"{$table_name}_primary\" DO UPDATE SET $update$matches[3]";
107
				}
108
			},
109
			$query
110
		);
111
		// Hack: Prepared statements will be converted right before execution
112
		return parent::q($query, ...$params);
113
	}
114
	/**
115
	 * @inheritdoc
116
	 *
117
	 * @return false|resource
118
	 */
119
	protected function q_internal ($query, $parameters = []) {
120
		if (!$query) {
121
			return false;
122
		}
123
		if ($parameters) {
124
			$query = $this->convert_prepared_statements_syntax($query);
125
			return $this->query_result = pg_query_params($this->handler, $query, $parameters);
126
		}
127
		return $this->query_result = pg_query($this->handler, $query);
128
	}
129
	/**
130
	 * @param string|string[] $query
131
	 *
132
	 * @return string|string[]
1 ignored issue
show
Documentation introduced by
Should the return type not be array|string? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
133
	 */
134
	protected function convert_prepared_statements_syntax ($query) {
135
		if (is_array($query)) {
136
			return array_map([$this, 'convert_prepared_statements_syntax'], $query);
137
		}
138
		$i = 1;
139
		while ($q_pos = strpos($query, '?')) {
140
			$query = substr($query, 0, $q_pos).'$1'.substr($query, $q_pos + 1);
141
			++$i;
142
		}
143
		return $query;
144
	}
145
	/**
146
	 * @inheritdoc
147
	 *
148
	 * @param false|resource $query_result
149
	 */
150
	public function f ($query_result, $single_column = false, $array = false, $indexed = false) {
151
		if (!is_resource($query_result)) {
152
			return false;
153
		}
154
		$result_type = $single_column || $indexed ? PGSQL_NUM : PGSQL_ASSOC;
155
		if ($array) {
156
			$result = [];
157
			while ($current = pg_fetch_array($query_result, null, $result_type)) {
158
				$result[] = $single_column ? $current[0] : $current;
159
			}
160
			$this->free($query_result);
161
			return $result;
162
		}
163
		$result = pg_fetch_array($query_result, null, $result_type);
164
		return $single_column && $result ? $result[0] : $result;
165
	}
166
	/**
167
	 * @inheritdoc
168
	 */
169
	public function id () {
170
		return (int)$this->qfs('SELECT lastval()');
171
	}
172
	/**
173
	 * @inheritdoc
174
	 */
175
	public function affected () {
176
		return is_resource($this->query_result) ? pg_affected_rows($this->query_result) : 0;
177
	}
178
	/**
179
	 * @inheritdoc
180
	 *
181
	 * @param false|resource $query_result
182
	 */
183
	public function free ($query_result) {
184
		if (is_resource($query_result)) {
185
			return pg_free_result($query_result);
186
		}
187
		return true;
188
	}
189
	/**
190
	 * @inheritdoc
191
	 */
192
	public function columns ($table, $like = false) {
193
		if (!$table) {
194
			return false;
195
		}
196
		if ($like) {
197
			$like    = $this->s($like);
198
			$columns = $this->qfas(
199
				"SELECT `column_name` 
200
				FROM `information_schema`.`columns`
201
				WHERE
202
					`table_name` = '$table' AND
203
					`column_name` LIKE $like"
204
			) ?: [];
205
		} else {
206
			$columns = $this->qfas(
207
				"SELECT `column_name`
208
				FROM `information_schema`.`columns`
209
				WHERE `table_name` = '$table'"
210
			) ?: [];
211
		}
212
		return $columns;
213
	}
214
	/**
215
	 * @inheritdoc
216
	 */
217
	public function tables ($like = false) {
218
		if ($like) {
219
			$like = $this->s($like);
220
			return $this->qfas(
221
				"SELECT `table_name`
222
				FROM `information_schema`.`tables`
223
				WHERE
224
					`table_schema` = 'public' AND
225
					`table_name` LIKE $like
226
				ORDER BY `table_name` ASC"
227
			) ?: [];
228
		} else {
229
			return $this->qfas(
230
				"SELECT `table_name`
231
				FROM `information_schema`.`tables`
232
				WHERE `table_schema` = 'public'
233
				ORDER BY `table_name` ASC"
234
			) ?: [];
235
		}
236
	}
237
	/**
238
	 * @inheritdoc
239
	 */
240
	protected function s_internal ($string, $single_quotes_around) {
241
		return $single_quotes_around ? pg_escape_literal($this->handler, $string) : pg_escape_string($this->handler, $string);
242
	}
243
	/**
244
	 * @inheritdoc
245
	 */
246
	public function server () {
247
		return pg_version($this->handler)['server'];
248
	}
249
	/**
250
	 * @inheritdoc
251
	 */
252
	public function __destruct () {
253
		if ($this->connected && is_resource($this->handler)) {
254
			pg_close($this->handler);
255
			$this->connected = false;
256
		}
257
	}
258
}
259