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[] |
|
|
|
|
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
|
|
|
|
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[]
orarray<String>
.