1 <?php
2 /**
3 * @package FrameworkOnFramework
4 * @subpackage database
5 * @copyright Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
6 * @license GNU General Public License version 2 or later; see LICENSE.txt
7 *
8 * This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning FOFTable objects
9 * instead of plain stdClass objects
10 */
11
12 // Protect from unauthorized access
13 defined('FOF_INCLUDED') or die;
14
15 /**
16 * Query Building Class.
17 *
18 * @since 11.3
19 */
20 class FOFDatabaseQueryPostgresql extends FOFDatabaseQuery implements FOFDatabaseQueryLimitable
21 {
22 /**
23 * @var object The FOR UPDATE element used in "FOR UPDATE" lock
24 * @since 11.3
25 */
26 protected $forUpdate = null;
27
28 /**
29 * @var object The FOR SHARE element used in "FOR SHARE" lock
30 * @since 11.3
31 */
32 protected $forShare = null;
33
34 /**
35 * @var object The NOWAIT element used in "FOR SHARE" and "FOR UPDATE" lock
36 * @since 11.3
37 */
38 protected $noWait = null;
39
40 /**
41 * @var object The LIMIT element
42 * @since 11.3
43 */
44 protected $limit = null;
45
46 /**
47 * @var object The OFFSET element
48 * @since 11.3
49 */
50 protected $offset = null;
51
52 /**
53 * @var object The RETURNING element of INSERT INTO
54 * @since 11.3
55 */
56 protected $returning = null;
57
58 /**
59 * Magic function to convert the query to a string, only for postgresql specific query
60 *
61 * @return string The completed query.
62 *
63 * @since 11.3
64 */
65 public function __toString()
66 {
67 $query = '';
68
69 switch ($this->type)
70 {
71 case 'select':
72 $query .= (string) $this->select;
73 $query .= (string) $this->from;
74
75 if ($this->join)
76 {
77 // Special case for joins
78 foreach ($this->join as $join)
79 {
80 $query .= (string) $join;
81 }
82 }
83
84 if ($this->where)
85 {
86 $query .= (string) $this->where;
87 }
88
89 if ($this->group)
90 {
91 $query .= (string) $this->group;
92 }
93
94 if ($this->having)
95 {
96 $query .= (string) $this->having;
97 }
98
99 if ($this->order)
100 {
101 $query .= (string) $this->order;
102 }
103
104 if ($this->forUpdate)
105 {
106 $query .= (string) $this->forUpdate;
107 }
108 else
109 {
110 if ($this->forShare)
111 {
112 $query .= (string) $this->forShare;
113 }
114 }
115
116 if ($this->noWait)
117 {
118 $query .= (string) $this->noWait;
119 }
120
121 break;
122
123 case 'update':
124 $query .= (string) $this->update;
125 $query .= (string) $this->set;
126
127 if ($this->join)
128 {
129 $onWord = ' ON ';
130
131 // Workaround for special case of JOIN with UPDATE
132 foreach ($this->join as $join)
133 {
134 $joinElem = $join->getElements();
135
136 $joinArray = explode($onWord, $joinElem[0]);
137
138 $this->from($joinArray[0]);
139 $this->where($joinArray[1]);
140 }
141
142 $query .= (string) $this->from;
143 }
144
145 if ($this->where)
146 {
147 $query .= (string) $this->where;
148 }
149
150 break;
151
152 case 'insert':
153 $query .= (string) $this->insert;
154
155 if ($this->values)
156 {
157 if ($this->columns)
158 {
159 $query .= (string) $this->columns;
160 }
161
162 $elements = $this->values->getElements();
163
164 if (!($elements[0] instanceof $this))
165 {
166 $query .= ' VALUES ';
167 }
168
169 $query .= (string) $this->values;
170
171 if ($this->returning)
172 {
173 $query .= (string) $this->returning;
174 }
175 }
176
177 break;
178
179 default:
180 $query = parent::__toString();
181 break;
182 }
183
184 if ($this instanceof FOFDatabaseQueryLimitable)
185 {
186 $query = $this->processLimit($query, $this->limit, $this->offset);
187 }
188
189 return $query;
190 }
191
192 /**
193 * Clear data from the query or a specific clause of the query.
194 *
195 * @param string $clause Optionally, the name of the clause to clear, or nothing to clear the whole query.
196 *
197 * @return FOFDatabaseQueryPostgresql Returns this object to allow chaining.
198 *
199 * @since 11.3
200 */
201 public function clear($clause = null)
202 {
203 switch ($clause)
204 {
205 case 'limit':
206 $this->limit = null;
207 break;
208
209 case 'offset':
210 $this->offset = null;
211 break;
212
213 case 'forUpdate':
214 $this->forUpdate = null;
215 break;
216
217 case 'forShare':
218 $this->forShare = null;
219 break;
220
221 case 'noWait':
222 $this->noWait = null;
223 break;
224
225 case 'returning':
226 $this->returning = null;
227 break;
228
229 case 'select':
230 case 'update':
231 case 'delete':
232 case 'insert':
233 case 'from':
234 case 'join':
235 case 'set':
236 case 'where':
237 case 'group':
238 case 'having':
239 case 'order':
240 case 'columns':
241 case 'values':
242 parent::clear($clause);
243 break;
244
245 default:
246 $this->type = null;
247 $this->limit = null;
248 $this->offset = null;
249 $this->forUpdate = null;
250 $this->forShare = null;
251 $this->noWait = null;
252 $this->returning = null;
253 parent::clear($clause);
254 break;
255 }
256
257 return $this;
258 }
259
260 /**
261 * Casts a value to a char.
262 *
263 * Ensure that the value is properly quoted before passing to the method.
264 *
265 * Usage:
266 * $query->select($query->castAsChar('a'));
267 *
268 * @param string $value The value to cast as a char.
269 *
270 * @return string Returns the cast value.
271 *
272 * @since 11.3
273 */
274 public function castAsChar($value)
275 {
276 return $value . '::text';
277 }
278
279 /**
280 * Concatenates an array of column names or values.
281 *
282 * Usage:
283 * $query->select($query->concatenate(array('a', 'b')));
284 *
285 * @param array $values An array of values to concatenate.
286 * @param string $separator As separator to place between each value.
287 *
288 * @return string The concatenated values.
289 *
290 * @since 11.3
291 */
292 public function concatenate($values, $separator = null)
293 {
294 if ($separator)
295 {
296 return implode(' || ' . $this->quote($separator) . ' || ', $values);
297 }
298 else
299 {
300 return implode(' || ', $values);
301 }
302 }
303
304 /**
305 * Gets the current date and time.
306 *
307 * @return string Return string used in query to obtain
308 *
309 * @since 11.3
310 */
311 public function currentTimestamp()
312 {
313 return 'NOW()';
314 }
315
316 /**
317 * Sets the FOR UPDATE lock on select's output row
318 *
319 * @param string $table_name The table to lock
320 * @param string $glue The glue by which to join the conditions. Defaults to ',' .
321 *
322 * @return FOFDatabaseQueryPostgresql FOR UPDATE query element
323 *
324 * @since 11.3
325 */
326 public function forUpdate($table_name, $glue = ',')
327 {
328 $this->type = 'forUpdate';
329
330 if (is_null($this->forUpdate))
331 {
332 $glue = strtoupper($glue);
333 $this->forUpdate = new FOFDatabaseQueryElement('FOR UPDATE', 'OF ' . $table_name, "$glue ");
334 }
335 else
336 {
337 $this->forUpdate->append($table_name);
338 }
339
340 return $this;
341 }
342
343 /**
344 * Sets the FOR SHARE lock on select's output row
345 *
346 * @param string $table_name The table to lock
347 * @param string $glue The glue by which to join the conditions. Defaults to ',' .
348 *
349 * @return FOFDatabaseQueryPostgresql FOR SHARE query element
350 *
351 * @since 11.3
352 */
353 public function forShare($table_name, $glue = ',')
354 {
355 $this->type = 'forShare';
356
357 if (is_null($this->forShare))
358 {
359 $glue = strtoupper($glue);
360 $this->forShare = new FOFDatabaseQueryElement('FOR SHARE', 'OF ' . $table_name, "$glue ");
361 }
362 else
363 {
364 $this->forShare->append($table_name);
365 }
366
367 return $this;
368 }
369
370 /**
371 * Used to get a string to extract year from date column.
372 *
373 * Usage:
374 * $query->select($query->year($query->quoteName('dateColumn')));
375 *
376 * @param string $date Date column containing year to be extracted.
377 *
378 * @return string Returns string to extract year from a date.
379 *
380 * @since 12.1
381 */
382 public function year($date)
383 {
384 return 'EXTRACT (YEAR FROM ' . $date . ')';
385 }
386
387 /**
388 * Used to get a string to extract month from date column.
389 *
390 * Usage:
391 * $query->select($query->month($query->quoteName('dateColumn')));
392 *
393 * @param string $date Date column containing month to be extracted.
394 *
395 * @return string Returns string to extract month from a date.
396 *
397 * @since 12.1
398 */
399 public function month($date)
400 {
401 return 'EXTRACT (MONTH FROM ' . $date . ')';
402 }
403
404 /**
405 * Used to get a string to extract day from date column.
406 *
407 * Usage:
408 * $query->select($query->day($query->quoteName('dateColumn')));
409 *
410 * @param string $date Date column containing day to be extracted.
411 *
412 * @return string Returns string to extract day from a date.
413 *
414 * @since 12.1
415 */
416 public function day($date)
417 {
418 return 'EXTRACT (DAY FROM ' . $date . ')';
419 }
420
421 /**
422 * Used to get a string to extract hour from date column.
423 *
424 * Usage:
425 * $query->select($query->hour($query->quoteName('dateColumn')));
426 *
427 * @param string $date Date column containing hour to be extracted.
428 *
429 * @return string Returns string to extract hour from a date.
430 *
431 * @since 12.1
432 */
433 public function hour($date)
434 {
435 return 'EXTRACT (HOUR FROM ' . $date . ')';
436 }
437
438 /**
439 * Used to get a string to extract minute from date column.
440 *
441 * Usage:
442 * $query->select($query->minute($query->quoteName('dateColumn')));
443 *
444 * @param string $date Date column containing minute to be extracted.
445 *
446 * @return string Returns string to extract minute from a date.
447 *
448 * @since 12.1
449 */
450 public function minute($date)
451 {
452 return 'EXTRACT (MINUTE FROM ' . $date . ')';
453 }
454
455 /**
456 * Used to get a string to extract seconds from date column.
457 *
458 * Usage:
459 * $query->select($query->second($query->quoteName('dateColumn')));
460 *
461 * @param string $date Date column containing second to be extracted.
462 *
463 * @return string Returns string to extract second from a date.
464 *
465 * @since 12.1
466 */
467 public function second($date)
468 {
469 return 'EXTRACT (SECOND FROM ' . $date . ')';
470 }
471
472 /**
473 * Sets the NOWAIT lock on select's output row
474 *
475 * @return FOFDatabaseQueryPostgresql NO WAIT query element
476 *
477 * @since 11.3
478 */
479 public function noWait ()
480 {
481 $this->type = 'noWait';
482
483 if (is_null($this->noWait))
484 {
485 $this->noWait = new FOFDatabaseQueryElement('NOWAIT', null);
486 }
487
488 return $this;
489 }
490
491 /**
492 * Set the LIMIT clause to the query
493 *
494 * @param integer $limit An int of how many row will be returned
495 *
496 * @return FOFDatabaseQueryPostgresql Returns this object to allow chaining.
497 *
498 * @since 11.3
499 */
500 public function limit($limit = 0)
501 {
502 if (is_null($this->limit))
503 {
504 $this->limit = new FOFDatabaseQueryElement('LIMIT', (int) $limit);
505 }
506
507 return $this;
508 }
509
510 /**
511 * Set the OFFSET clause to the query
512 *
513 * @param integer $offset An int for skipping row
514 *
515 * @return FOFDatabaseQueryPostgresql Returns this object to allow chaining.
516 *
517 * @since 11.3
518 */
519 public function offset($offset = 0)
520 {
521 if (is_null($this->offset))
522 {
523 $this->offset = new FOFDatabaseQueryElement('OFFSET', (int) $offset);
524 }
525
526 return $this;
527 }
528
529 /**
530 * Add the RETURNING element to INSERT INTO statement.
531 *
532 * @param mixed $pkCol The name of the primary key column.
533 *
534 * @return FOFDatabaseQueryPostgresql Returns this object to allow chaining.
535 *
536 * @since 11.3
537 */
538 public function returning($pkCol)
539 {
540 if (is_null($this->returning))
541 {
542 $this->returning = new FOFDatabaseQueryElement('RETURNING', $pkCol);
543 }
544
545 return $this;
546 }
547
548 /**
549 * Sets the offset and limit for the result set, if the database driver supports it.
550 *
551 * Usage:
552 * $query->setLimit(100, 0); (retrieve 100 rows, starting at first record)
553 * $query->setLimit(50, 50); (retrieve 50 rows, starting at 50th record)
554 *
555 * @param integer $limit The limit for the result set
556 * @param integer $offset The offset for the result set
557 *
558 * @return FOFDatabaseQueryPostgresql Returns this object to allow chaining.
559 *
560 * @since 12.1
561 */
562 public function setLimit($limit = 0, $offset = 0)
563 {
564 $this->limit = (int) $limit;
565 $this->offset = (int) $offset;
566
567 return $this;
568 }
569
570 /**
571 * Method to modify a query already in string format with the needed
572 * additions to make the query limited to a particular number of
573 * results, or start at a particular offset.
574 *
575 * @param string $query The query in string format
576 * @param integer $limit The limit for the result set
577 * @param integer $offset The offset for the result set
578 *
579 * @return string
580 *
581 * @since 12.1
582 */
583 public function processLimit($query, $limit, $offset = 0)
584 {
585 if ($limit > 0)
586 {
587 $query .= ' LIMIT ' . $limit;
588 }
589
590 if ($offset > 0)
591 {
592 $query .= ' OFFSET ' . $offset;
593 }
594
595 return $query;
596 }
597
598 /**
599 * Add to the current date and time in Postgresql.
600 * Usage:
601 * $query->select($query->dateAdd());
602 * Prefixing the interval with a - (negative sign) will cause subtraction to be used.
603 *
604 * @param datetime $date The date to add to
605 * @param string $interval The string representation of the appropriate number of units
606 * @param string $datePart The part of the date to perform the addition on
607 *
608 * @return string The string with the appropriate sql for addition of dates
609 *
610 * @since 13.1
611 * @note Not all drivers support all units. Check appropriate references
612 * @link http://www.postgresql.org/docs/9.0/static/functions-datetime.html.
613 */
614 public function dateAdd($date, $interval, $datePart)
615 {
616 if (substr($interval, 0, 1) != '-')
617 {
618 return "timestamp '" . $date . "' + interval '" . $interval . " " . $datePart . "'";
619 }
620 else
621 {
622 return "timestamp '" . $date . "' - interval '" . ltrim($interval, '-') . " " . $datePart . "'";
623 }
624 }
625
626 /**
627 * Return correct regexp operator for Postgresql.
628 *
629 * Ensure that the regexp operator is Postgresql compatible.
630 *
631 * Usage:
632 * $query->where('field ' . $query->regexp($search));
633 *
634 * @param string $value The regex pattern.
635 *
636 * @return string Returns the regex operator.
637 *
638 * @since 11.3
639 */
640 public function regexp($value)
641 {
642 return ' ~* ' . $value;
643 }
644
645 /**
646 * Return correct rand() function for Postgresql.
647 *
648 * Ensure that the rand() function is Postgresql compatible.
649 *
650 * Usage:
651 * $query->Rand();
652 *
653 * @return string The correct rand function.
654 *
655 * @since 3.5
656 */
657 public function Rand()
658 {
659 return ' RANDOM() ';
660 }
661 }
662