Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like Database_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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
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 Database_PostgreSQL, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
25 | class Database_PostgreSQL extends Database_Abstract |
||
26 | { |
||
27 | /** |
||
28 | * Holds current instance of the class |
||
29 | * @var Database_PostgreSQL |
||
30 | */ |
||
31 | private static $_db = null; |
||
32 | |||
33 | /** |
||
34 | * Holds last query result |
||
35 | * @var string |
||
36 | */ |
||
37 | private $_db_last_result = null; |
||
38 | |||
39 | /** |
||
40 | * Since PostgreSQL doesn't support INSERT REPLACE we are using this to remember |
||
41 | * the rows affected by the delete |
||
42 | * @var int |
||
43 | */ |
||
44 | private $_db_replace_result = null; |
||
45 | |||
46 | /** |
||
47 | * A variable to remember if a transaction was started already or not |
||
48 | * @var boolean |
||
49 | */ |
||
50 | private $_in_transaction = false; |
||
51 | |||
52 | /** |
||
53 | * Initializes a database connection. |
||
54 | * It returns the connection, if successful. |
||
55 | * |
||
56 | * @param string $db_server |
||
57 | * @param string $db_name |
||
58 | * @param string $db_user |
||
59 | * @param string $db_passwd |
||
60 | * @param string $db_prefix |
||
61 | * @param mixed[] $db_options |
||
62 | * |
||
63 | * @return resource |
||
64 | * @throws Elk_Exception |
||
65 | */ |
||
66 | public static function initiate($db_server, $db_name, $db_user, $db_passwd, $db_prefix, $db_options = array()) |
||
|
|||
67 | { |
||
68 | // initialize the instance... if not done already! |
||
69 | if (self::$_db === null) |
||
70 | self::$_db = new self(); |
||
71 | |||
72 | View Code Duplication | if (!empty($db_options['port'])) |
|
73 | $db_port = ' port=' . (int) $db_options['port']; |
||
74 | else |
||
75 | $db_port = ''; |
||
76 | |||
77 | if (!empty($db_options['persist'])) |
||
78 | $connection = @pg_pconnect('host=' . $db_server . $db_port . ' dbname=' . $db_name . ' user=\'' . $db_user . '\' password=\'' . $db_passwd . '\''); |
||
79 | else |
||
80 | $connection = @pg_connect('host=' . $db_server . $db_port . ' dbname=' . $db_name . ' user=\'' . $db_user . '\' password=\'' . $db_passwd . '\''); |
||
81 | |||
82 | // Something's wrong, show an error if its fatal (which we assume it is) |
||
83 | View Code Duplication | if (!$connection) |
|
84 | { |
||
85 | if (!empty($db_options['non_fatal'])) |
||
86 | return null; |
||
87 | else |
||
88 | Errors::instance()->display_db_error(); |
||
89 | } |
||
90 | |||
91 | self::$_db->_connection = $connection; |
||
92 | |||
93 | return $connection; |
||
94 | } |
||
95 | |||
96 | /** |
||
97 | * Fix the database prefix if necessary. |
||
98 | * Do nothing on postgreSQL |
||
99 | * |
||
100 | * @param string $db_prefix |
||
101 | * @param string $db_name |
||
102 | * |
||
103 | * @return string |
||
104 | */ |
||
105 | public function fix_prefix($db_prefix, $db_name) |
||
106 | { |
||
107 | return $db_prefix; |
||
108 | } |
||
109 | |||
110 | /** |
||
111 | * Do a query. Takes care of errors too. |
||
112 | * Special queries may need additional replacements to be appropriate |
||
113 | * for PostgreSQL. |
||
114 | * |
||
115 | * @param string $identifier |
||
116 | * @param string $db_string |
||
117 | * @param mixed[] $db_values |
||
118 | * @param resource|null $connection |
||
119 | * |
||
120 | * @return bool|resource|string |
||
121 | * @throws Elk_Exception |
||
122 | */ |
||
123 | 39 | public function query($identifier, $db_string, $db_values = array(), $connection = null) |
|
124 | { |
||
125 | 39 | global $db_show_debug, $time_start, $modSettings; |
|
126 | |||
127 | // Decide which connection to use. |
||
128 | 39 | $connection = $connection === null ? $this->_connection : $connection; |
|
129 | |||
130 | // Special queries that need processing. |
||
131 | $replacements = array( |
||
132 | 'alter_table' => array( |
||
133 | 39 | '~(.+)~' => '', |
|
134 | 39 | ), |
|
135 | 'ban_suggest_error_ips' => array( |
||
136 | 39 | '~RLIKE~' => '~', |
|
137 | 39 | '~\\.~' => '\.', |
|
138 | 39 | ), |
|
139 | 'ban_suggest_message_ips' => array( |
||
140 | 39 | '~RLIKE~' => '~', |
|
141 | 39 | '~\\.~' => '\.', |
|
142 | 39 | ), |
|
143 | 'consolidate_spider_stats' => array( |
||
144 | 39 | '~MONTH\(log_time\), DAYOFMONTH\(log_time\)~' => 'MONTH(CAST(CAST(log_time AS abstime) AS timestamp)), DAYOFMONTH(CAST(CAST(log_time AS abstime) AS timestamp))', |
|
145 | 39 | ), |
|
146 | 'display_get_post_poster' => array( |
||
147 | 39 | '~GROUP BY id_msg\s+HAVING~' => 'AND', |
|
148 | 39 | ), |
|
149 | 'attach_download_increase' => array( |
||
150 | 39 | '~LOW_PRIORITY~' => '', |
|
151 | 39 | ), |
|
152 | 'boardindex_fetch_boards' => array( |
||
153 | 39 | '~COALESCE\(lb.id_msg, 0\) >= b.id_msg_updated~' => 'CASE WHEN COALESCE(lb.id_msg, 0) >= b.id_msg_updated THEN 1 ELSE 0 END', |
|
154 | 39 | ), |
|
155 | 'get_random_number' => array( |
||
156 | 39 | '~RAND~' => 'RANDOM', |
|
157 | 39 | ), |
|
158 | 'insert_log_search_topics' => array( |
||
159 | 39 | '~NOT RLIKE~' => '!~', |
|
160 | 39 | ), |
|
161 | 'insert_log_search_results_no_index' => array( |
||
162 | 39 | '~NOT RLIKE~' => '!~', |
|
163 | 39 | ), |
|
164 | 'insert_log_search_results_subject' => array( |
||
165 | 39 | '~NOT RLIKE~' => '!~', |
|
166 | 39 | ), |
|
167 | 'pm_conversation_list' => array( |
||
168 | 39 | '~ORDER\\s+BY\\s+\\{raw:sort\\}~' => 'ORDER BY ' . (isset($db_values['sort']) ? ($db_values['sort'] === 'pm.id_pm' ? 'MAX(pm.id_pm)' : $db_values['sort']) : ''), |
|
169 | 39 | ), |
|
170 | 'top_topic_starters' => array( |
||
171 | 39 | '~ORDER BY FIND_IN_SET\(id_member,(.+?)\)~' => 'ORDER BY STRPOS(\',\' || $1 || \',\', \',\' || id_member|| \',\')', |
|
172 | 39 | ), |
|
173 | 'unread_replies' => array( |
||
174 | 39 | '~SELECT\\s+DISTINCT\\s+t.id_topic~' => 'SELECT t.id_topic, {raw:sort}', |
|
175 | 39 | ), |
|
176 | 'profile_board_stats' => array( |
||
177 | 39 | '~COUNT\(\*\) \/ MAX\(b.num_posts\)~' => 'CAST(COUNT(*) AS DECIMAL) / CAST(b.num_posts AS DECIMAL)', |
|
178 | 39 | ), |
|
179 | 39 | ); |
|
180 | |||
181 | 39 | if (isset($replacements[$identifier])) |
|
182 | 39 | $db_string = preg_replace(array_keys($replacements[$identifier]), array_values($replacements[$identifier]), $db_string); |
|
183 | |||
184 | // Limits need to be a little different. |
||
185 | 39 | $db_string = preg_replace('~\sLIMIT\s(\d+|{int:.+}),\s*(\d+|{int:.+})\s*$~i', 'LIMIT $2 OFFSET $1', $db_string); |
|
186 | |||
187 | 39 | if (trim($db_string) == '') |
|
188 | 39 | return false; |
|
189 | |||
190 | // Comments that are allowed in a query are preg_removed. |
||
191 | static $allowed_comments_from = array( |
||
192 | '~\s+~s', |
||
193 | '~/\*!40001 SQL_NO_CACHE \*/~', |
||
194 | '~/\*!40000 USE INDEX \([A-Za-z\_]+?\) \*/~', |
||
195 | '~/\*!40100 ON DUPLICATE KEY UPDATE id_msg = \d+ \*/~', |
||
196 | 39 | ); |
|
197 | static $allowed_comments_to = array( |
||
198 | ' ', |
||
199 | '', |
||
200 | '', |
||
201 | '', |
||
202 | 39 | ); |
|
203 | |||
204 | // One more query.... |
||
205 | 39 | $this->_query_count++; |
|
206 | 39 | $this->_db_replace_result = null; |
|
207 | |||
208 | 39 | View Code Duplication | if (empty($modSettings['disableQueryCheck']) && strpos($db_string, '\'') !== false && empty($db_values['security_override'])) |
209 | 39 | $this->error_backtrace('Hacking attempt...', 'Illegal character (\') used in query...', true, __FILE__, __LINE__); |
|
210 | |||
211 | 39 | View Code Duplication | if (empty($db_values['security_override']) && (!empty($db_values) || strpos($db_string, '{db_prefix}') !== false)) |
212 | 39 | { |
|
213 | // Store these values for use in the callback function. |
||
214 | 39 | $this->_db_callback_values = $db_values; |
|
215 | 39 | $this->_db_callback_connection = $connection; |
|
216 | |||
217 | // Inject the values passed to this function. |
||
218 | 39 | $db_string = preg_replace_callback('~{([a-z_]+)(?::([a-zA-Z0-9_-]+))?}~', array($this, 'replacement__callback'), $db_string); |
|
219 | |||
220 | // No need for them any longer. |
||
221 | 39 | $this->_db_callback_values = array(); |
|
222 | 39 | $this->_db_callback_connection = null; |
|
223 | 39 | } |
|
224 | |||
225 | // Debugging. |
||
226 | 39 | View Code Duplication | if ($db_show_debug === true) |
227 | 39 | { |
|
228 | $debug = Debug::instance(); |
||
229 | |||
230 | // Get the file and line number this function was called. |
||
231 | list ($file, $line) = $this->error_backtrace('', '', 'return', __FILE__, __LINE__); |
||
232 | |||
233 | if (!empty($_SESSION['debug_redirect'])) |
||
234 | { |
||
235 | $debug->merge_db($_SESSION['debug_redirect']); |
||
236 | // @todo this may be off by 1 |
||
237 | $this->_query_count += count($_SESSION['debug_redirect']); |
||
238 | $_SESSION['debug_redirect'] = array(); |
||
239 | } |
||
240 | |||
241 | // Don't overload it. |
||
242 | $st = microtime(true); |
||
243 | $db_cache = array(); |
||
244 | $db_cache['q'] = $this->_query_count < 50 ? $db_string : '...'; |
||
245 | $db_cache['f'] = $file; |
||
246 | $db_cache['l'] = $line; |
||
247 | $db_cache['s'] = $st - $time_start; |
||
248 | } |
||
249 | |||
250 | // First, we clean strings out of the query, reduce whitespace, lowercase, and trim - so we can check it over. |
||
251 | 39 | if (empty($modSettings['disableQueryCheck'])) |
|
252 | 39 | { |
|
253 | 39 | $clean = ''; |
|
254 | 39 | $old_pos = 0; |
|
255 | 39 | $pos = -1; |
|
256 | 39 | View Code Duplication | while (true) |
257 | { |
||
258 | 39 | $pos = strpos($db_string, '\'', $pos + 1); |
|
259 | 39 | if ($pos === false) |
|
260 | 39 | break; |
|
261 | 37 | $clean .= substr($db_string, $old_pos, $pos - $old_pos); |
|
262 | |||
263 | 37 | while (true) |
|
264 | { |
||
265 | 37 | $pos1 = strpos($db_string, '\'', $pos + 1); |
|
266 | 37 | $pos2 = strpos($db_string, '\'\'', $pos + 1); |
|
267 | |||
268 | 37 | if ($pos1 === false) |
|
269 | 37 | break; |
|
270 | 37 | elseif ($pos2 === false || $pos2 > $pos1) |
|
271 | { |
||
272 | 37 | $pos = $pos1; |
|
273 | 37 | break; |
|
274 | } |
||
275 | |||
276 | 1 | $pos = $pos2 + 1; |
|
277 | 1 | } |
|
278 | |||
279 | 37 | $clean .= ' %s '; |
|
280 | 37 | $old_pos = $pos + 1; |
|
281 | 37 | } |
|
282 | |||
283 | 39 | $clean .= substr($db_string, $old_pos); |
|
284 | 39 | $clean = trim(strtolower(preg_replace($allowed_comments_from, $allowed_comments_to, $clean))); |
|
285 | |||
286 | // Comments? We don't use comments in our queries, we leave 'em outside! |
||
287 | 39 | View Code Duplication | if (strpos($clean, '/*') > 2 || strpos($clean, '--') !== false || strpos($clean, ';') !== false) |
288 | 39 | $fail = true; |
|
289 | // Trying to change passwords, slow us down, or something? |
||
290 | 39 | elseif (strpos($clean, 'sleep') !== false && preg_match('~(^|[^a-z])sleep($|[^[_a-z])~s', $clean) != 0) |
|
291 | $fail = true; |
||
292 | 39 | elseif (strpos($clean, 'benchmark') !== false && preg_match('~(^|[^a-z])benchmark($|[^[a-z])~s', $clean) != 0) |
|
293 | $fail = true; |
||
294 | |||
295 | 39 | if (!empty($fail) && class_exists('Errors')) |
|
296 | 39 | $this->error_backtrace('Hacking attempt...', 'Hacking attempt...' . "\n" . $db_string, E_USER_ERROR, __FILE__, __LINE__); |
|
297 | |||
298 | // If we are updating something, better start a transaction so that indexes may be kept consistent |
||
299 | 39 | if (!$this->_in_transaction && strpos($clean, 'update') !== false) |
|
300 | 39 | $this->db_transaction('begin', $connection); |
|
301 | 39 | } |
|
302 | |||
303 | 39 | $this->_db_last_result = @pg_query($connection, $db_string); |
|
304 | |||
305 | // @deprecated since 1.1 - use skip_next_error method |
||
306 | 39 | if (!empty($db_values['db_error_skip'])) |
|
307 | 39 | { |
|
308 | 11 | $this->_skip_error = true; |
|
309 | 11 | } |
|
310 | |||
311 | 39 | if ($this->_db_last_result === false && !$this->_skip_error) |
|
312 | 39 | { |
|
313 | $this->error($db_string, $connection); |
||
314 | } |
||
315 | |||
316 | // Revert not to skip errors |
||
317 | 39 | if ($this->_skip_error === true) |
|
318 | 39 | { |
|
319 | 11 | $this->_skip_error = false; |
|
320 | 11 | } |
|
321 | |||
322 | 39 | if ($this->_in_transaction) |
|
323 | 39 | $this->db_transaction('commit', $connection); |
|
324 | |||
325 | // Debugging. |
||
326 | 39 | View Code Duplication | if ($db_show_debug === true) |
327 | 39 | { |
|
328 | $db_cache['t'] = microtime(true) - $st; |
||
329 | $debug->db_query($db_cache); |
||
330 | } |
||
331 | |||
332 | 39 | return $this->_db_last_result; |
|
333 | } |
||
334 | |||
335 | /** |
||
336 | * Affected rows from previous operation. |
||
337 | * |
||
338 | * @param resource|null $result |
||
339 | */ |
||
340 | 9 | public function affected_rows($result = null) |
|
341 | { |
||
342 | 9 | if ($this->_db_replace_result !== null) |
|
343 | 9 | return $this->_db_replace_result; |
|
344 | 9 | elseif ($result === null && !$this->_db_last_result) |
|
345 | return 0; |
||
346 | |||
347 | 9 | return pg_affected_rows($result === null ? $this->_db_last_result : $result); |
|
348 | } |
||
349 | |||
350 | /** |
||
351 | * Last inserted id. |
||
352 | * |
||
353 | * @param string $table |
||
354 | * @param string|null $field = null |
||
355 | * @param resource|null $connection = null |
||
356 | * @throws Elk_Exception |
||
357 | */ |
||
358 | 15 | public function insert_id($table, $field = null, $connection = null) |
|
359 | { |
||
360 | 15 | global $db_prefix; |
|
361 | |||
362 | 15 | $table = str_replace('{db_prefix}', $db_prefix, $table); |
|
363 | |||
364 | 15 | $connection = $connection === null ? $this->_connection : $connection; |
|
365 | |||
366 | // Try get the last ID for the auto increment field. |
||
367 | 15 | $request = $this->query('', 'SELECT CURRVAL(\'' . $table . '_seq\') AS insertID', |
|
368 | array( |
||
369 | 15 | ), |
|
370 | $connection |
||
371 | 15 | ); |
|
372 | |||
373 | 15 | if (!$request) |
|
374 | 15 | return false; |
|
375 | |||
376 | 15 | list ($lastID) = $this->fetch_row($request); |
|
377 | 15 | $this->free_result($request); |
|
378 | |||
379 | 15 | return $lastID; |
|
380 | } |
||
381 | |||
382 | /** |
||
383 | * Tracking the current row. |
||
384 | * Fetch a row from the resultset given as parameter. |
||
385 | * |
||
386 | * @param resource $request |
||
387 | * @param integer|bool $counter = false |
||
388 | */ |
||
389 | 23 | View Code Duplication | public function fetch_row($request, $counter = false) |
390 | { |
||
391 | 23 | global $db_row_count; |
|
392 | |||
393 | 23 | if ($counter !== false) |
|
394 | 23 | return pg_fetch_row($request, $counter); |
|
395 | |||
396 | // Reset the row counter... |
||
397 | 23 | if (!isset($db_row_count[(int) $request])) |
|
398 | 23 | $db_row_count[(int) $request] = 0; |
|
399 | |||
400 | // Return the right row. |
||
401 | 23 | return @pg_fetch_row($request, $db_row_count[(int) $request]++); |
|
402 | } |
||
403 | |||
404 | /** |
||
405 | * Free the resultset. |
||
406 | * |
||
407 | * @param resource $result |
||
408 | */ |
||
409 | 37 | public function free_result($result) |
|
410 | { |
||
411 | // Just delegate to the native function |
||
412 | 37 | pg_free_result($result); |
|
413 | 37 | } |
|
414 | |||
415 | /** |
||
416 | * Get the number of rows in the result. |
||
417 | * |
||
418 | * @param resource $result |
||
419 | */ |
||
420 | 19 | public function num_rows($result) |
|
421 | { |
||
422 | // simply delegate to the native function |
||
423 | 19 | return pg_num_rows($result); |
|
424 | } |
||
425 | |||
426 | /** |
||
427 | * Get the number of fields in the resultset. |
||
428 | * |
||
429 | * @param resource $request |
||
430 | */ |
||
431 | public function num_fields($request) |
||
435 | |||
436 | /** |
||
437 | * Reset the internal result pointer. |
||
438 | * |
||
439 | * @param boolean $request |
||
440 | * @param integer $counter |
||
441 | */ |
||
442 | public function data_seek($request, $counter) |
||
450 | |||
451 | /** |
||
452 | * Do a transaction. |
||
453 | * |
||
454 | * @param string $type - the step to perform (i.e. 'begin', 'commit', 'rollback') |
||
455 | * @param resource|null $connection = null |
||
456 | */ |
||
457 | 23 | public function db_transaction($type = 'commit', $connection = null) |
|
458 | { |
||
459 | // Decide which connection to use |
||
460 | 23 | $connection = $connection === null ? $this->_connection : $connection; |
|
461 | |||
462 | 23 | if ($type == 'begin') |
|
463 | 23 | { |
|
464 | 23 | $this->_in_transaction = true; |
|
465 | 23 | return @pg_query($connection, 'BEGIN'); |
|
466 | } |
||
467 | 23 | elseif ($type == 'rollback') |
|
468 | return @pg_query($connection, 'ROLLBACK'); |
||
469 | 23 | elseif ($type == 'commit') |
|
470 | { |
||
471 | 23 | $this->_in_transaction = false; |
|
472 | 23 | return @pg_query($connection, 'COMMIT'); |
|
473 | } |
||
474 | |||
475 | return false; |
||
476 | } |
||
477 | |||
478 | /** |
||
479 | * Return last error string from the database server |
||
480 | * |
||
481 | * @param resource|null $connection = null |
||
482 | */ |
||
483 | public function last_error($connection = null) |
||
484 | { |
||
485 | // Decide which connection to use |
||
486 | $connection = $connection === null ? $this->_connection : $connection; |
||
487 | |||
488 | if (is_resource($connection)) |
||
489 | return pg_last_error($connection); |
||
490 | } |
||
491 | |||
492 | /** |
||
493 | * Database error. |
||
494 | * Backtrace, log, try to fix. |
||
495 | * |
||
496 | * @param string $db_string |
||
497 | * @param resource|null $connection = null |
||
498 | * |
||
499 | * @throws Elk_Exception |
||
500 | */ |
||
501 | public function error($db_string, $connection = null) |
||
502 | { |
||
503 | global $txt, $context, $modSettings, $db_show_debug; |
||
504 | |||
505 | // We'll try recovering the file and line number the original db query was called from. |
||
506 | list ($file, $line) = $this->error_backtrace('', '', 'return', __FILE__, __LINE__); |
||
507 | |||
508 | // Decide which connection to use |
||
509 | $connection = $connection === null ? $this->_connection : $connection; |
||
510 | |||
511 | // This is the error message... |
||
512 | $query_error = @pg_last_error($connection); |
||
513 | |||
514 | // Log the error. |
||
515 | View Code Duplication | if (class_exists('Errors')) |
|
516 | { |
||
517 | Errors::instance()->log_error($txt['database_error'] . ': ' . $query_error . (!empty($modSettings['enableErrorQueryLogging']) ? "\n\n" . $db_string : ''), 'database', $file, $line); |
||
518 | } |
||
519 | |||
520 | // Nothing's defined yet... just die with it. |
||
521 | if (empty($context) || empty($txt)) |
||
522 | die($query_error); |
||
523 | |||
524 | // Show an error message, if possible. |
||
525 | $context['error_title'] = $txt['database_error']; |
||
526 | View Code Duplication | if (allowedTo('admin_forum')) |
|
527 | $context['error_message'] = nl2br($query_error) . '<br />' . $txt['file'] . ': ' . $file . '<br />' . $txt['line'] . ': ' . $line; |
||
528 | else |
||
529 | $context['error_message'] = $txt['try_again']; |
||
530 | |||
531 | // Add database version that we know of, for the admin to know. (and ask for support) |
||
532 | View Code Duplication | if (allowedTo('admin_forum')) |
|
533 | $context['error_message'] .= '<br /><br />' . sprintf($txt['database_error_versions'], $modSettings['elkVersion']); |
||
534 | |||
535 | View Code Duplication | if (allowedTo('admin_forum') && $db_show_debug === true) |
|
536 | $context['error_message'] .= '<br /><br />' . nl2br($db_string); |
||
537 | |||
538 | // It's already been logged... don't log it again. |
||
539 | throw new Elk_Exception($context['error_message'], false); |
||
540 | } |
||
541 | |||
542 | /** |
||
543 | * Insert data. |
||
544 | * |
||
545 | * @param string $method - options 'replace', 'ignore', 'insert' |
||
546 | * @param string $table |
||
547 | * @param mixed[] $columns |
||
548 | * @param mixed[] $data |
||
549 | * @param mixed[] $keys |
||
550 | * @param bool $disable_trans = false |
||
551 | * @param resource|null $connection = null |
||
552 | * @throws Elk_Exception |
||
553 | */ |
||
554 | 24 | public function insert($method = 'replace', $table, $columns, $data, $keys, $disable_trans = false, $connection = null) |
|
555 | { |
||
556 | 24 | global $db_prefix; |
|
557 | |||
558 | 24 | $connection = $connection === null ? $this->_connection : $connection; |
|
559 | |||
560 | // With nothing to insert, simply return. |
||
561 | 24 | if (empty($data)) |
|
562 | 24 | return; |
|
563 | |||
564 | // Inserting data as a single row can be done as a single array. |
||
565 | 24 | if (!is_array($data[array_rand($data)])) |
|
566 | 24 | $data = array($data); |
|
567 | |||
568 | // Replace the prefix holder with the actual prefix. |
||
569 | 24 | $table = str_replace('{db_prefix}', $db_prefix, $table); |
|
570 | |||
571 | 24 | $priv_trans = false; |
|
572 | 24 | if ((count($data) > 1 || $method == 'replace') && !$this->_in_transaction && !$disable_trans) |
|
573 | 24 | { |
|
574 | 20 | $this->db_transaction('begin', $connection); |
|
575 | 20 | $priv_trans = true; |
|
576 | 20 | } |
|
577 | |||
578 | // PostgreSQL doesn't support replace: we implement a MySQL-compatible behavior instead |
||
579 | 24 | if ($method == 'replace') |
|
580 | 24 | { |
|
581 | 16 | $count = 0; |
|
582 | 16 | $where = ''; |
|
583 | 16 | $db_replace_result = 0; |
|
584 | 16 | foreach ($columns as $columnName => $type) |
|
585 | { |
||
586 | // Are we restricting the length? |
||
587 | 16 | if (strpos($type, 'string-') !== false) |
|
588 | 16 | $actualType = sprintf($columnName . ' = SUBSTRING({string:%1$s}, 1, ' . substr($type, 7) . '), ', $count); |
|
589 | else |
||
590 | 5 | $actualType = sprintf($columnName . ' = {%1$s:%2$s}, ', $type, $count); |
|
591 | |||
592 | // A key? That's what we were looking for. |
||
593 | 16 | if (in_array($columnName, $keys)) |
|
594 | 16 | $where .= (empty($where) ? '' : ' AND ') . substr($actualType, 0, -2); |
|
595 | 16 | $count++; |
|
596 | 16 | } |
|
597 | |||
598 | // Make it so. |
||
599 | 16 | if (!empty($where) && !empty($data)) |
|
600 | 16 | { |
|
601 | 16 | foreach ($data as $k => $entry) |
|
602 | { |
||
603 | 16 | $this->query('', ' |
|
604 | 16 | DELETE FROM ' . $table . |
|
605 | 16 | ' WHERE ' . $where, |
|
606 | 16 | $entry, $connection |
|
607 | 16 | ); |
|
608 | 16 | $db_replace_result += (!$this->_db_last_result ? 0 : pg_affected_rows($this->_db_last_result)); |
|
609 | 16 | } |
|
610 | 16 | } |
|
611 | 16 | } |
|
612 | |||
613 | 24 | if (!empty($data)) |
|
614 | 24 | { |
|
615 | // Create the mold for a single row insert. |
||
616 | 24 | $insertData = '('; |
|
617 | 24 | View Code Duplication | foreach ($columns as $columnName => $type) |
618 | { |
||
619 | // Are we restricting the length? |
||
620 | 24 | if (strpos($type, 'string-') !== false) |
|
621 | 24 | $insertData .= sprintf('SUBSTRING({string:%1$s}, 1, ' . substr($type, 7) . '), ', $columnName); |
|
622 | else |
||
623 | 20 | $insertData .= sprintf('{%1$s:%2$s}, ', $type, $columnName); |
|
624 | 24 | } |
|
625 | 24 | $insertData = substr($insertData, 0, -2) . ')'; |
|
626 | |||
627 | // Create an array consisting of only the columns. |
||
628 | 24 | $indexed_columns = array_keys($columns); |
|
629 | |||
630 | // Here's where the variables are injected to the query. |
||
631 | 24 | $insertRows = array(); |
|
632 | 24 | foreach ($data as $dataRow) |
|
633 | 24 | $insertRows[] = $this->quote($insertData, $this->_array_combine($indexed_columns, $dataRow), $connection); |
|
634 | |||
635 | 24 | $inserted_results = 0; |
|
636 | 24 | $skip_error = $method == 'ignore' || $table === $db_prefix . 'log_errors'; |
|
637 | 24 | foreach ($insertRows as $entry) |
|
638 | { |
||
639 | 24 | $this->_skip_error = $skip_error; |
|
640 | |||
641 | // Do the insert. |
||
642 | 24 | $this->query('', ' |
|
643 | 24 | INSERT INTO ' . $table . '("' . implode('", "', $indexed_columns) . '") |
|
644 | VALUES |
||
645 | 24 | ' . $entry, |
|
646 | array( |
||
647 | 24 | 'security_override' => true, |
|
648 | 24 | ), |
|
649 | $connection |
||
650 | 24 | ); |
|
651 | 24 | $inserted_results += (!$this->_db_last_result ? 0 : pg_affected_rows($this->_db_last_result)); |
|
652 | 24 | } |
|
653 | 24 | if (isset($db_replace_result)) |
|
654 | 24 | $this->_db_replace_result = $db_replace_result + $inserted_results; |
|
655 | 24 | } |
|
656 | |||
657 | if ($priv_trans) |
||
658 | 24 | $this->db_transaction('commit', $connection); |
|
659 | 24 | } |
|
660 | |||
661 | /** |
||
662 | * Unescape an escaped string! |
||
663 | * |
||
664 | * @param string $string |
||
665 | */ |
||
666 | public function unescape_string($string) |
||
670 | |||
671 | /** |
||
672 | * Returns whether the database system supports ignore. |
||
673 | * |
||
674 | * @return false |
||
675 | */ |
||
676 | public function support_ignore() |
||
680 | |||
681 | /** |
||
682 | * Gets all the necessary INSERTs for the table named table_name. |
||
683 | * It goes in 250 row segments. |
||
684 | * |
||
685 | * @param string $tableName - the table to create the inserts for. |
||
686 | * @param bool $new_table |
||
687 | * |
||
688 | * @return string the query to insert the data back in, or an empty string if the table was empty. |
||
689 | * @throws Elk_Exception |
||
690 | */ |
||
691 | public function insert_sql($tableName, $new_table = false) |
||
692 | { |
||
693 | global $db_prefix; |
||
694 | |||
695 | static $start = 0, $num_rows, $fields, $limit; |
||
696 | |||
697 | View Code Duplication | if ($new_table) |
|
698 | { |
||
699 | $limit = strstr($tableName, 'log_') !== false ? 500 : 250; |
||
700 | $start = 0; |
||
701 | } |
||
702 | |||
703 | $data = ''; |
||
704 | $tableName = str_replace('{db_prefix}', $db_prefix, $tableName); |
||
705 | |||
706 | // This will be handy... |
||
707 | $crlf = "\r\n"; |
||
708 | |||
709 | $result = $this->query('', ' |
||
710 | SELECT * |
||
711 | FROM ' . $tableName . ' |
||
712 | LIMIT ' . $start . ', ' . $limit, |
||
713 | array( |
||
714 | 'security_override' => true, |
||
715 | ) |
||
716 | ); |
||
717 | |||
718 | // The number of rows, just for record keeping and breaking INSERTs up. |
||
719 | $num_rows = $this->num_rows($result); |
||
720 | |||
721 | if ($num_rows == 0) |
||
722 | return ''; |
||
723 | |||
724 | if ($new_table) |
||
725 | { |
||
726 | $fields = array_keys($this->fetch_assoc($result)); |
||
727 | $this->data_seek($result, 0); |
||
728 | } |
||
729 | |||
730 | // Start it off with the basic INSERT INTO. |
||
731 | $insert_msg = 'INSERT INTO ' . $tableName . $crlf . "\t" . '(' . implode(', ', $fields) . ')' . $crlf . 'VALUES ' . $crlf . "\t"; |
||
732 | |||
733 | // Loop through each row. |
||
734 | View Code Duplication | while ($row = $this->fetch_assoc($result)) |
|
735 | { |
||
736 | // Get the fields in this row... |
||
737 | $field_list = array(); |
||
738 | |||
739 | foreach ($row as $key => $item) |
||
740 | { |
||
741 | // Try to figure out the type of each field. (NULL, number, or 'string'.) |
||
742 | if (!isset($item)) |
||
743 | $field_list[] = 'NULL'; |
||
744 | elseif (is_numeric($item) && (int) $item == $item) |
||
745 | $field_list[] = $item; |
||
746 | else |
||
747 | $field_list[] = '\'' . $this->escape_string($item) . '\''; |
||
748 | } |
||
749 | |||
750 | // 'Insert' the data. |
||
751 | $data .= $insert_msg . '(' . implode(', ', $field_list) . ');' . $crlf; |
||
752 | } |
||
753 | $this->free_result($result); |
||
754 | |||
755 | $data .= $crlf; |
||
756 | |||
757 | $start += $limit; |
||
758 | |||
759 | return $data; |
||
760 | } |
||
761 | |||
762 | /** |
||
763 | * Dumps the schema (CREATE) for a table. |
||
764 | * |
||
765 | * @param string $tableName - the table |
||
766 | * |
||
767 | * @return string - the CREATE statement as string |
||
768 | * @throws Elk_Exception |
||
769 | */ |
||
770 | public function db_table_sql($tableName) |
||
771 | { |
||
772 | global $db_prefix; |
||
773 | |||
774 | $tableName = str_replace('{db_prefix}', $db_prefix, $tableName); |
||
775 | |||
776 | // This will be needed... |
||
777 | $crlf = "\r\n"; |
||
778 | |||
779 | // Start the create table... |
||
780 | $schema_create = 'CREATE TABLE ' . $tableName . ' (' . $crlf; |
||
781 | $index_create = ''; |
||
782 | $seq_create = ''; |
||
783 | |||
784 | // Find all the fields. |
||
785 | $result = $this->query('', ' |
||
786 | SELECT column_name, column_default, is_nullable, data_type, character_maximum_length |
||
787 | FROM information_schema.columns |
||
788 | WHERE table_name = {string:table} |
||
789 | ORDER BY ordinal_position', |
||
790 | array( |
||
791 | 'table' => $tableName, |
||
792 | ) |
||
793 | ); |
||
794 | while ($row = $this->fetch_assoc($result)) |
||
795 | { |
||
796 | if ($row['data_type'] == 'character varying') |
||
797 | $row['data_type'] = 'varchar'; |
||
798 | elseif ($row['data_type'] == 'character') |
||
799 | $row['data_type'] = 'char'; |
||
800 | |||
801 | if ($row['character_maximum_length']) |
||
802 | $row['data_type'] .= '(' . $row['character_maximum_length'] . ')'; |
||
803 | |||
804 | // Make the CREATE for this column. |
||
805 | $schema_create .= ' "' . $row['column_name'] . '" ' . $row['data_type'] . ($row['is_nullable'] != 'YES' ? ' NOT NULL' : ''); |
||
806 | |||
807 | // Add a default...? |
||
808 | if (trim($row['column_default']) != '') |
||
809 | { |
||
810 | $schema_create .= ' default ' . $row['column_default'] . ''; |
||
811 | |||
812 | // Auto increment? |
||
813 | if (preg_match('~nextval\(\'(.+?)\'(.+?)*\)~i', $row['column_default'], $matches) != 0) |
||
814 | { |
||
815 | // Get to find the next variable first! |
||
816 | $count_req = $this->query('', ' |
||
817 | SELECT MAX("{raw:column}") |
||
818 | FROM {raw:table}', |
||
819 | array( |
||
820 | 'column' => $row['column_name'], |
||
821 | 'table' => $tableName, |
||
822 | ) |
||
823 | ); |
||
824 | list ($max_ind) = $this->fetch_row($count_req); |
||
825 | $this->free_result($count_req); |
||
826 | |||
827 | // Get the right bloody start! |
||
828 | $seq_create .= 'CREATE SEQUENCE ' . $matches[1] . ' START WITH ' . ($max_ind + 1) . ';' . $crlf . $crlf; |
||
829 | } |
||
830 | } |
||
831 | |||
832 | $schema_create .= ',' . $crlf; |
||
833 | } |
||
834 | $this->free_result($result); |
||
835 | |||
836 | // Take off the last comma. |
||
837 | $schema_create = substr($schema_create, 0, -strlen($crlf) - 1); |
||
838 | |||
839 | $result = $this->query('', ' |
||
840 | SELECT CASE WHEN i.indisprimary THEN 1 ELSE 0 END AS is_primary, pg_get_indexdef(i.indexrelid) AS inddef |
||
841 | FROM pg_class AS c |
||
842 | INNER JOIN pg_index AS i ON (i.indrelid = c.oid) |
||
843 | INNER JOIN pg_class AS c2 ON (c2.oid = i.indexrelid) |
||
844 | WHERE c.relname = {string:table}', |
||
845 | array( |
||
846 | 'table' => $tableName, |
||
847 | ) |
||
848 | ); |
||
849 | |||
850 | while ($row = $this->fetch_assoc($result)) |
||
851 | { |
||
852 | if ($row['is_primary']) |
||
853 | { |
||
854 | if (preg_match('~\(([^\)]+?)\)~i', $row['inddef'], $matches) == 0) |
||
855 | continue; |
||
856 | |||
857 | $index_create .= $crlf . 'ALTER TABLE ' . $tableName . ' ADD PRIMARY KEY ("' . $matches[1] . '");'; |
||
858 | } |
||
859 | else |
||
860 | $index_create .= $crlf . $row['inddef'] . ';'; |
||
861 | } |
||
862 | $this->free_result($result); |
||
863 | |||
864 | // Finish it off! |
||
865 | $schema_create .= $crlf . ');'; |
||
866 | |||
867 | return $seq_create . $schema_create . $index_create; |
||
868 | } |
||
869 | |||
870 | /** |
||
871 | * {@inheritdoc} |
||
872 | */ |
||
873 | 1 | public function db_list_tables($db_name_str = false, $filter = false) |
|
874 | { |
||
875 | 1 | $request = $this->query('', ' |
|
876 | SELECT tablename |
||
877 | FROM pg_tables |
||
878 | 1 | WHERE schemaname = {string:schema_public}' . ($filter === false ? '' : ' |
|
879 | 1 | AND tablename LIKE {string:filter}') . ' |
|
880 | 1 | ORDER BY tablename', |
|
881 | array( |
||
882 | 1 | 'schema_public' => 'public', |
|
883 | 1 | 'filter' => $filter, |
|
884 | ) |
||
885 | 1 | ); |
|
886 | 1 | $tables = array(); |
|
887 | 1 | while ($row = $this->fetch_row($request)) |
|
888 | 1 | $tables[] = $row[0]; |
|
889 | 1 | $this->free_result($request); |
|
890 | |||
891 | 1 | return $tables; |
|
892 | } |
||
893 | |||
894 | /** |
||
895 | * Backup $table to $backup_table. |
||
896 | * |
||
897 | * @param string $table |
||
898 | * @param string $backup_table |
||
899 | * @throws Elk_Exception |
||
900 | */ |
||
901 | public function db_backup_table($table, $backup_table) |
||
902 | { |
||
903 | global $db_prefix; |
||
904 | |||
905 | $table = str_replace('{db_prefix}', $db_prefix, $table); |
||
906 | |||
907 | // Do we need to drop it first? |
||
908 | $db_table = db_table(); |
||
909 | $db_table->db_drop_table($backup_table); |
||
910 | |||
911 | // @todo Should we create backups of sequences as well? |
||
912 | $this->query('', ' |
||
913 | CREATE TABLE {raw:backup_table} |
||
914 | ( |
||
915 | LIKE {raw:table} |
||
916 | INCLUDING DEFAULTS |
||
917 | )', |
||
918 | array( |
||
919 | 'backup_table' => $backup_table, |
||
920 | 'table' => $table, |
||
921 | ) |
||
922 | ); |
||
923 | |||
924 | $this->query('', ' |
||
925 | INSERT INTO {raw:backup_table} |
||
926 | SELECT * FROM {raw:table}', |
||
927 | array( |
||
928 | 'backup_table' => $backup_table, |
||
929 | 'table' => $table, |
||
930 | ) |
||
931 | ); |
||
932 | } |
||
933 | |||
934 | /** |
||
935 | * Get the server version number. |
||
936 | * |
||
937 | * @return string - the version |
||
938 | */ |
||
939 | public function db_server_version() |
||
945 | |||
946 | /** |
||
947 | * Get the name (title) of the database system. |
||
948 | * |
||
949 | * @return string |
||
950 | */ |
||
951 | public function db_title() |
||
955 | |||
956 | /** |
||
957 | * Whether the database system is case sensitive. |
||
958 | * |
||
959 | * @return boolean |
||
960 | */ |
||
961 | public function db_case_sensitive() |
||
965 | |||
966 | /** |
||
967 | * Quotes identifiers for replacement__callback. |
||
968 | * |
||
969 | * @param mixed $replacement |
||
970 | * @return string |
||
971 | * @throws Elk_Exception |
||
972 | */ |
||
973 | View Code Duplication | protected function _replaceIdentifier($replacement) |
|
982 | |||
983 | /** |
||
984 | * Escape string for the database input |
||
985 | * |
||
986 | * @param string $string |
||
987 | */ |
||
988 | 34 | public function escape_string($string) |
|
989 | { |
||
990 | 34 | return pg_escape_string($string); |
|
991 | } |
||
992 | |||
993 | /** |
||
994 | * Fetch next result as association. |
||
995 | * |
||
996 | * @param resource $request |
||
997 | * @param int|bool $counter = false |
||
998 | */ |
||
999 | 31 | View Code Duplication | public function fetch_assoc($request, $counter = false) |
1000 | { |
||
1001 | 31 | global $db_row_count; |
|
1002 | |||
1003 | 31 | if ($counter !== false) |
|
1004 | 31 | return pg_fetch_assoc($request, $counter); |
|
1005 | |||
1006 | // Reset the row counter... |
||
1007 | 31 | if (!isset($db_row_count[(int) $request])) |
|
1008 | 31 | $db_row_count[(int) $request] = 0; |
|
1009 | |||
1010 | // Return the right row. |
||
1011 | 31 | return @pg_fetch_assoc($request, $db_row_count[(int) $request]++); |
|
1012 | } |
||
1013 | |||
1014 | /** |
||
1015 | * Return server info. |
||
1016 | * |
||
1017 | * @return string |
||
1018 | */ |
||
1019 | public function db_server_info() |
||
1026 | |||
1027 | /** |
||
1028 | * Return client version. |
||
1029 | * |
||
1030 | * @return string - the version |
||
1031 | */ |
||
1032 | public function db_client_version() |
||
1038 | |||
1039 | /** |
||
1040 | * Dummy function really. Doesn't do anything on PostgreSQL. |
||
1041 | * |
||
1042 | * @param string|null $db_name = null |
||
1043 | * @param resource|null $connection = null |
||
1044 | * |
||
1045 | * @return boolean |
||
1046 | */ |
||
1047 | public function select_db($db_name = null, $connection = null) |
||
1051 | |||
1052 | /** |
||
1053 | * Returns a reference to the existing instance |
||
1054 | */ |
||
1055 | 59 | public static function db() |
|
1056 | { |
||
1057 | 59 | return self::$_db; |
|
1058 | } |
||
1059 | |||
1060 | /** |
||
1061 | * Finds out if the connection is still valid. |
||
1062 | * |
||
1063 | * @param postgre|null $connection = null |
||
1064 | */ |
||
1065 | 39 | public function validConnection($connection = null) |
|
1066 | { |
||
1067 | 39 | return is_resource($connection); |
|
1068 | } |
||
1069 | } |
||
1070 |
This check looks from parameters that have been defined for a function or method, but which are not used in the method body.