697 lines
18 KiB
PHP
697 lines
18 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Part of the Joomla Framework Database Package
|
|
*
|
|
* @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved.
|
|
* @license GNU General Public License version 2 or later; see LICENSE
|
|
*/
|
|
|
|
namespace Joomla\Database\Query;
|
|
|
|
/**
|
|
* Trait for PostgreSQL Query Building.
|
|
*
|
|
* @since 2.0.0
|
|
*/
|
|
trait PostgresqlQueryBuilder
|
|
{
|
|
/**
|
|
* The FOR UPDATE element used in "FOR UPDATE" lock
|
|
*
|
|
* @var QueryElement
|
|
* @since 2.0.0
|
|
*/
|
|
protected $forUpdate;
|
|
|
|
/**
|
|
* The FOR SHARE element used in "FOR SHARE" lock
|
|
*
|
|
* @var QueryElement
|
|
* @since 2.0.0
|
|
*/
|
|
protected $forShare;
|
|
|
|
/**
|
|
* The NOWAIT element used in "FOR SHARE" and "FOR UPDATE" lock
|
|
*
|
|
* @var QueryElement
|
|
* @since 2.0.0
|
|
*/
|
|
protected $noWait;
|
|
|
|
/**
|
|
* The LIMIT element
|
|
*
|
|
* @var QueryElement
|
|
* @since 2.0.0
|
|
*/
|
|
protected $limit;
|
|
|
|
/**
|
|
* The OFFSET element
|
|
*
|
|
* @var QueryElement
|
|
* @since 2.0.0
|
|
*/
|
|
protected $offset;
|
|
|
|
/**
|
|
* The RETURNING element of INSERT INTO
|
|
*
|
|
* @var QueryElement
|
|
* @since 2.0.0
|
|
*/
|
|
protected $returning;
|
|
|
|
/**
|
|
* Magic function to convert the query to a string, only for PostgreSQL specific queries
|
|
*
|
|
* @return string The completed query.
|
|
*
|
|
* @since 2.0.0
|
|
*/
|
|
public function __toString()
|
|
{
|
|
$query = '';
|
|
|
|
switch ($this->type) {
|
|
case 'select':
|
|
$query .= (string) $this->select;
|
|
$query .= (string) $this->from;
|
|
|
|
if ($this->join) {
|
|
// Special case for joins
|
|
foreach ($this->join as $join) {
|
|
$query .= (string) $join;
|
|
}
|
|
}
|
|
|
|
if ($this->where) {
|
|
$query .= (string) $this->where;
|
|
}
|
|
|
|
if ($this->selectRowNumber) {
|
|
if ($this->order) {
|
|
$query .= (string) $this->order;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if ($this->group) {
|
|
$query .= (string) $this->group;
|
|
}
|
|
|
|
if ($this->having) {
|
|
$query .= (string) $this->having;
|
|
}
|
|
|
|
if ($this->merge) {
|
|
// Special case for merge
|
|
foreach ($this->merge as $element) {
|
|
$query .= (string) $element;
|
|
}
|
|
}
|
|
|
|
if ($this->order) {
|
|
$query .= (string) $this->order;
|
|
}
|
|
|
|
if ($this->forUpdate) {
|
|
$query .= (string) $this->forUpdate;
|
|
} else {
|
|
if ($this->forShare) {
|
|
$query .= (string) $this->forShare;
|
|
}
|
|
}
|
|
|
|
if ($this->noWait) {
|
|
$query .= (string) $this->noWait;
|
|
}
|
|
|
|
$query = $this->processLimit($query, $this->limit, $this->offset);
|
|
|
|
break;
|
|
|
|
case 'update':
|
|
$query .= (string) $this->update;
|
|
$query .= (string) $this->set;
|
|
|
|
if ($this->join) {
|
|
$tmpFrom = $this->from;
|
|
$tmpWhere = $this->where ? clone $this->where : null;
|
|
$this->from = null;
|
|
|
|
// Workaround for special case of JOIN with UPDATE
|
|
foreach ($this->join as $join) {
|
|
$joinElem = $join->getElements();
|
|
|
|
$this->from($joinElem[0]);
|
|
|
|
if (isset($joinElem[1])) {
|
|
$this->where($joinElem[1]);
|
|
}
|
|
}
|
|
|
|
$query .= (string) $this->from;
|
|
|
|
if ($this->where) {
|
|
$query .= (string) $this->where;
|
|
}
|
|
|
|
$this->from = $tmpFrom;
|
|
$this->where = $tmpWhere;
|
|
} elseif ($this->where) {
|
|
$query .= (string) $this->where;
|
|
}
|
|
|
|
$query = $this->processLimit($query, $this->limit, $this->offset);
|
|
|
|
break;
|
|
|
|
case 'insert':
|
|
$query .= (string) $this->insert;
|
|
|
|
if ($this->values) {
|
|
if ($this->columns) {
|
|
$query .= (string) $this->columns;
|
|
}
|
|
|
|
$elements = $this->values->getElements();
|
|
|
|
if (!($elements[0] instanceof $this)) {
|
|
$query .= ' VALUES ';
|
|
}
|
|
|
|
$query .= (string) $this->values;
|
|
|
|
if ($this->returning) {
|
|
$query .= (string) $this->returning;
|
|
}
|
|
}
|
|
|
|
$query = $this->processLimit($query, $this->limit, $this->offset);
|
|
|
|
break;
|
|
|
|
default:
|
|
$query = parent::__toString();
|
|
|
|
break;
|
|
}
|
|
|
|
if ($this->type === 'select' && $this->alias !== null) {
|
|
$query = '(' . $query . ') AS ' . $this->alias;
|
|
}
|
|
|
|
return $query;
|
|
}
|
|
|
|
/**
|
|
* Clear data from the query or a specific clause of the query.
|
|
*
|
|
* @param string $clause Optionally, the name of the clause to clear, or nothing to clear the whole query.
|
|
*
|
|
* @return $this
|
|
*
|
|
* @since 2.0.0
|
|
*/
|
|
public function clear($clause = null)
|
|
{
|
|
switch ($clause) {
|
|
case 'limit':
|
|
$this->limit = null;
|
|
|
|
break;
|
|
|
|
case 'offset':
|
|
$this->offset = null;
|
|
|
|
break;
|
|
|
|
case 'forUpdate':
|
|
$this->forUpdate = null;
|
|
|
|
break;
|
|
|
|
case 'forShare':
|
|
$this->forShare = null;
|
|
|
|
break;
|
|
|
|
case 'noWait':
|
|
$this->noWait = null;
|
|
|
|
break;
|
|
|
|
case 'returning':
|
|
$this->returning = null;
|
|
|
|
break;
|
|
|
|
case 'select':
|
|
case 'update':
|
|
case 'delete':
|
|
case 'insert':
|
|
case 'querySet':
|
|
case 'from':
|
|
case 'join':
|
|
case 'set':
|
|
case 'where':
|
|
case 'group':
|
|
case 'having':
|
|
case 'merge':
|
|
case 'order':
|
|
case 'columns':
|
|
case 'values':
|
|
parent::clear($clause);
|
|
|
|
break;
|
|
|
|
default:
|
|
$this->forUpdate = null;
|
|
$this->forShare = null;
|
|
$this->noWait = null;
|
|
$this->returning = null;
|
|
|
|
parent::clear($clause);
|
|
|
|
break;
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Casts a value to a char.
|
|
*
|
|
* Ensure that the value is properly quoted before passing to the method.
|
|
*
|
|
* Usage:
|
|
* $query->select($query->castAs('CHAR', 'a'));
|
|
*
|
|
* @param string $type The type of string to cast as.
|
|
* @param string $value The value to cast as a char.
|
|
* @param ?string $length The value to cast as a char.
|
|
*
|
|
* @return string SQL statement to cast the value as a char type.
|
|
*
|
|
* @since 1.0
|
|
*/
|
|
public function castAs(string $type, string $value, ?string $length = null)
|
|
{
|
|
switch (strtoupper($type)) {
|
|
case 'CHAR':
|
|
if (!$length) {
|
|
return $value . '::text';
|
|
} else {
|
|
return 'CAST(' . $value . ' AS CHAR(' . $length . '))';
|
|
}
|
|
|
|
// No break
|
|
case 'INT':
|
|
return 'CAST(' . $value . ' AS INTEGER)';
|
|
}
|
|
|
|
return parent::castAs($type, $value, $length);
|
|
}
|
|
|
|
/**
|
|
* Concatenates an array of column names or values.
|
|
*
|
|
* Usage:
|
|
* $query->select($query->concatenate(array('a', 'b')));
|
|
*
|
|
* @param string[] $values An array of values to concatenate.
|
|
* @param string|null $separator As separator to place between each value.
|
|
*
|
|
* @return string The concatenated values.
|
|
*
|
|
* @since 2.0.0
|
|
*/
|
|
public function concatenate($values, $separator = null)
|
|
{
|
|
if ($separator !== null) {
|
|
return implode(' || ' . $this->quote($separator) . ' || ', $values);
|
|
}
|
|
|
|
return implode(' || ', $values);
|
|
}
|
|
|
|
/**
|
|
* Gets the current date and time.
|
|
*
|
|
* @return string Return string used in query to obtain
|
|
*
|
|
* @since 2.0.0
|
|
*/
|
|
public function currentTimestamp()
|
|
{
|
|
return 'NOW()';
|
|
}
|
|
|
|
/**
|
|
* Sets the FOR UPDATE lock on select's output row
|
|
*
|
|
* @param string $tableName The table to lock
|
|
* @param string $glue The glue by which to join the conditions. Defaults to ',' .
|
|
*
|
|
* @return $this
|
|
*
|
|
* @since 2.0.0
|
|
*/
|
|
public function forUpdate($tableName, $glue = ',')
|
|
{
|
|
$this->type = 'forUpdate';
|
|
|
|
if ($this->forUpdate === null) {
|
|
$glue = strtoupper($glue);
|
|
$this->forUpdate = new QueryElement('FOR UPDATE', 'OF ' . $tableName, "$glue ");
|
|
} else {
|
|
$this->forUpdate->append($tableName);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Sets the FOR SHARE lock on select's output row
|
|
*
|
|
* @param string $tableName The table to lock
|
|
* @param string $glue The glue by which to join the conditions. Defaults to ',' .
|
|
*
|
|
* @return $this
|
|
*
|
|
* @since 2.0.0
|
|
*/
|
|
public function forShare($tableName, $glue = ',')
|
|
{
|
|
$this->type = 'forShare';
|
|
|
|
if ($this->forShare === null) {
|
|
$glue = strtoupper($glue);
|
|
$this->forShare = new QueryElement('FOR SHARE', 'OF ' . $tableName, "$glue ");
|
|
} else {
|
|
$this->forShare->append($tableName);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Aggregate function to get input values concatenated into a string, separated by delimiter
|
|
*
|
|
* Usage:
|
|
* $query->groupConcat('id', ',');
|
|
*
|
|
* @param string $expression The expression to apply concatenation to, this may be a column name or complex SQL statement.
|
|
* @param string $separator The delimiter of each concatenated value
|
|
*
|
|
* @return string Input values concatenated into a string, separated by delimiter
|
|
*
|
|
* @since 2.0.0
|
|
*/
|
|
public function groupConcat($expression, $separator = ',')
|
|
{
|
|
return 'string_agg(' . $expression . ', ' . $this->quote($separator) . ')';
|
|
}
|
|
|
|
/**
|
|
* Used to get a string to extract year from date column.
|
|
*
|
|
* Usage:
|
|
* $query->select($query->year($query->quoteName('dateColumn')));
|
|
*
|
|
* @param string $date Date column containing year to be extracted.
|
|
*
|
|
* @return string Returns string to extract year from a date.
|
|
*
|
|
* @since 2.0.0
|
|
*/
|
|
public function year($date)
|
|
{
|
|
return 'EXTRACT (YEAR FROM ' . $date . ')';
|
|
}
|
|
|
|
/**
|
|
* Used to get a string to extract month from date column.
|
|
*
|
|
* Usage:
|
|
* $query->select($query->month($query->quoteName('dateColumn')));
|
|
*
|
|
* @param string $date Date column containing month to be extracted.
|
|
*
|
|
* @return string Returns string to extract month from a date.
|
|
*
|
|
* @since 2.0.0
|
|
*/
|
|
public function month($date)
|
|
{
|
|
return 'EXTRACT (MONTH FROM ' . $date . ')';
|
|
}
|
|
|
|
/**
|
|
* Used to get a string to extract day from date column.
|
|
*
|
|
* Usage:
|
|
* $query->select($query->day($query->quoteName('dateColumn')));
|
|
*
|
|
* @param string $date Date column containing day to be extracted.
|
|
*
|
|
* @return string Returns string to extract day from a date.
|
|
*
|
|
* @since 2.0.0
|
|
*/
|
|
public function day($date)
|
|
{
|
|
return 'EXTRACT (DAY FROM ' . $date . ')';
|
|
}
|
|
|
|
/**
|
|
* Used to get a string to extract hour from date column.
|
|
*
|
|
* Usage:
|
|
* $query->select($query->hour($query->quoteName('dateColumn')));
|
|
*
|
|
* @param string $date Date column containing hour to be extracted.
|
|
*
|
|
* @return string Returns string to extract hour from a date.
|
|
*
|
|
* @since 2.0.0
|
|
*/
|
|
public function hour($date)
|
|
{
|
|
return 'EXTRACT (HOUR FROM ' . $date . ')';
|
|
}
|
|
|
|
/**
|
|
* Used to get a string to extract minute from date column.
|
|
*
|
|
* Usage:
|
|
* $query->select($query->minute($query->quoteName('dateColumn')));
|
|
*
|
|
* @param string $date Date column containing minute to be extracted.
|
|
*
|
|
* @return string Returns string to extract minute from a date.
|
|
*
|
|
* @since 2.0.0
|
|
*/
|
|
public function minute($date)
|
|
{
|
|
return 'EXTRACT (MINUTE FROM ' . $date . ')';
|
|
}
|
|
|
|
/**
|
|
* Used to get a string to extract seconds from date column.
|
|
*
|
|
* Usage:
|
|
* $query->select($query->second($query->quoteName('dateColumn')));
|
|
*
|
|
* @param string $date Date column containing second to be extracted.
|
|
*
|
|
* @return string Returns string to extract second from a date.
|
|
*
|
|
* @since 2.0.0
|
|
*/
|
|
public function second($date)
|
|
{
|
|
return 'EXTRACT (SECOND FROM ' . $date . ')';
|
|
}
|
|
|
|
/**
|
|
* Sets the NOWAIT lock on select's output row
|
|
*
|
|
* @return $this
|
|
*
|
|
* @since 2.0.0
|
|
*/
|
|
public function noWait()
|
|
{
|
|
$this->type = 'noWait';
|
|
|
|
if ($this->noWait === null) {
|
|
$this->noWait = new QueryElement('NOWAIT', null);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Set the LIMIT clause to the query
|
|
*
|
|
* @param integer $limit Number of rows to return
|
|
*
|
|
* @return $this
|
|
*
|
|
* @since 2.0.0
|
|
*/
|
|
public function limit($limit = 0)
|
|
{
|
|
if ($this->limit === null) {
|
|
$this->limit = new QueryElement('LIMIT', (int) $limit);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Set the OFFSET clause to the query
|
|
*
|
|
* @param integer $offset An integer for skipping rows
|
|
*
|
|
* @return $this
|
|
*
|
|
* @since 2.0.0
|
|
*/
|
|
public function offset($offset = 0)
|
|
{
|
|
if ($this->offset === null) {
|
|
$this->offset = new QueryElement('OFFSET', (int) $offset);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Add the RETURNING element to INSERT INTO statement.
|
|
*
|
|
* @param mixed $pkCol The name of the primary key column.
|
|
*
|
|
* @return $this
|
|
*
|
|
* @since 2.0.0
|
|
*/
|
|
public function returning($pkCol)
|
|
{
|
|
if ($this->returning === null) {
|
|
$this->returning = new QueryElement('RETURNING', $pkCol);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Method to modify a query already in string format with the needed additions to make the query limited to a particular number of
|
|
* results, or start at a particular offset.
|
|
*
|
|
* @param string $query The query in string format
|
|
* @param integer $limit The limit for the result set
|
|
* @param integer $offset The offset for the result set
|
|
*
|
|
* @return string
|
|
*
|
|
* @since 2.0.0
|
|
*/
|
|
public function processLimit($query, $limit, $offset = 0)
|
|
{
|
|
if ($limit > 0) {
|
|
$query .= ' LIMIT ' . $limit;
|
|
}
|
|
|
|
if ($offset > 0) {
|
|
$query .= ' OFFSET ' . $offset;
|
|
}
|
|
|
|
return $query;
|
|
}
|
|
|
|
/**
|
|
* Add to the current date and time.
|
|
*
|
|
* Usage:
|
|
* $query->select($query->dateAdd());
|
|
*
|
|
* Prefixing the interval with a - (negative sign) will cause subtraction to be used.
|
|
*
|
|
* @param string $date The db quoted string representation of the date to add to
|
|
* @param string $interval The string representation of the appropriate number of units
|
|
* @param string $datePart The part of the date to perform the addition on
|
|
*
|
|
* @return string The string with the appropriate sql for addition of dates
|
|
*
|
|
* @since 2.0.0
|
|
* @link http://www.postgresql.org/docs/9.0/static/functions-datetime.html.
|
|
*/
|
|
public function dateAdd($date, $interval, $datePart)
|
|
{
|
|
if (substr($interval, 0, 1) !== '-') {
|
|
return 'timestamp ' . $date . " + interval '" . $interval . ' ' . $datePart . "'";
|
|
}
|
|
|
|
return 'timestamp ' . $date . " - interval '" . ltrim($interval, '-') . ' ' . $datePart . "'";
|
|
}
|
|
|
|
/**
|
|
* Get the regular expression operator
|
|
*
|
|
* Usage:
|
|
* $query->where('field ' . $query->regexp($search));
|
|
*
|
|
* @param string $value The regex pattern.
|
|
*
|
|
* @return string
|
|
*
|
|
* @since 2.0.0
|
|
*/
|
|
public function regexp($value)
|
|
{
|
|
return ' ~* ' . $value;
|
|
}
|
|
|
|
/**
|
|
* Get the function to return a random floating-point value
|
|
*
|
|
* Usage:
|
|
* $query->rand();
|
|
*
|
|
* @return string
|
|
*
|
|
* @since 2.0.0
|
|
*/
|
|
public function rand()
|
|
{
|
|
return ' RANDOM() ';
|
|
}
|
|
|
|
/**
|
|
* Find a value in a varchar used like a set.
|
|
*
|
|
* Ensure that the value is an integer before passing to the method.
|
|
*
|
|
* Usage:
|
|
* $query->findInSet((int) $parent->id, 'a.assigned_cat_ids')
|
|
*
|
|
* @param string $value The value to search for.
|
|
* @param string $set The set of values.
|
|
*
|
|
* @return string A representation of the MySQL find_in_set() function for the driver.
|
|
*
|
|
* @since 2.0.0
|
|
*/
|
|
public function findInSet($value, $set)
|
|
{
|
|
return " $value = ANY (string_to_array($set, ',')::integer[]) ";
|
|
}
|
|
}
|