Completed
Push — master ( be40dc...524dea )
by Nazar
06:35
created

PostgreSQL   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 256
Duplicated Lines 0 %

Test Coverage

Coverage 71.35%

Importance

Changes 0
Metric Value
dl 0
loc 256
rs 8.439
c 0
b 0
f 0
ccs 127
cts 178
cp 0.7135
wmc 47

15 Methods

Rating   Name   Duplication   Size   Complexity  
A q_internal() 0 11 3
A id() 0 2 1
A s_internal() 0 2 2
A __construct() 0 16 3
A affected() 0 2 2
A tables() 0 18 4
A __destruct() 0 4 3
A free() 0 5 2
B get_host_port_and_persistent() 0 21 5
A convert_prepared_statements_syntax() 0 7 2
A q() 0 4 1
B f() 0 15 9
A server() 0 2 1
B columns() 0 21 5
B convert_sql() 0 37 4

How to fix   Complexity   

Complex Class

Complex classes like PostgreSQL often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use PostgreSQL, and based on these observations, apply Extract Interface, too.

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

178
		return (int)$this->qfs(/** @scrutinizer ignore-type */ 'SELECT lastval()');
Loading history...
179
	}
180
	/**
181
	 * @inheritdoc
182
	 */
183 1
	public function affected () {
184 1
		return is_resource($this->query_result) ? pg_affected_rows($this->query_result) : 0;
185
	}
186
	/**
187
	 * @inheritdoc
188
	 *
189
	 * @param false|resource $query_result
190
	 */
191 27
	public function free ($query_result) {
192 27
		if (is_resource($query_result)) {
193 27
			return pg_free_result($query_result);
194
		}
195 1
		return true;
196
	}
197
	/**
198
	 * @inheritdoc
199
	 */
200 3
	public function columns ($table, $like = false) {
201 3
		if (!$table) {
202 1
			return false;
203
		}
204 3
		if ($like) {
205 1
			$like    = $this->s($like);
206 1
			$columns = $this->qfas(
207
				"SELECT `column_name` 
0 ignored issues
show
Bug introduced by
'SELECT `column_name` ...lumn_name` LIKE '.$like 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

207
				/** @scrutinizer ignore-type */ "SELECT `column_name` 
Loading history...
208
				FROM `information_schema`.`columns`
209
				WHERE
210 1
					`table_name` = '$table' AND
211 1
					`column_name` LIKE $like"
212 1
			) ?: [];
213
		} else {
214 3
			$columns = $this->qfas(
215
				"SELECT `column_name`
216
				FROM `information_schema`.`columns`
217 3
				WHERE `table_name` = '$table'"
218 3
			) ?: [];
219
		}
220 3
		return $columns;
221
	}
222
	/**
223
	 * @inheritdoc
224
	 */
225 1
	public function tables ($like = false) {
226 1
		if ($like) {
227 1
			$like = $this->s($like);
228 1
			return $this->qfas(
229
				"SELECT `table_name`
0 ignored issues
show
Bug introduced by
'SELECT `table_name` ...ER BY `table_name` ASC' 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

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