Elgg  Version master
QueryBuilder.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg\Database;
4 
14 use Elgg\Values;
15 
19 abstract class QueryBuilder extends DbalQueryBuilder {
20 
21  const CALCULATIONS = [
22  'avg',
23  'count',
24  'greatest',
25  'least',
26  'max',
27  'min',
28  'sum',
29  ];
30 
31  protected array $joins = [];
32 
33  protected int $join_index = 0;
34 
35  protected ?string $table_name = null;
36 
37  protected ?string $table_alias = null;
38 
44  public function __construct(protected readonly Connection $backup_connection) {
45  parent::__construct($backup_connection);
46  }
47 
55  public function getConnection(): Connection {
56  return $this->backup_connection;
57  }
58 
67  public function subquery(string $table, string $alias = null): Select {
68  $qb = new Select($this->getConnection());
69  $qb->from($table, $alias);
70 
71  return $qb;
72  }
73 
82  public function addClause(Clause $clause, string $alias = null): static {
83  if (!isset($alias)) {
84  $alias = $this->getTableAlias();
85  }
86 
87  $expr = $clause->prepare($this, $alias);
88  if ($clause instanceof WhereClause && ($expr instanceof CompositeExpression || is_string($expr))) {
89  $this->andWhere($expr);
90  }
91 
92  return $this;
93  }
94 
102  public function prefix(string $table): string {
103  $prefix = _elgg_services()->db->prefix;
104  if ($prefix === '') {
105  return $table;
106  }
107 
108  if (!str_starts_with($table, $prefix)) {
109  return "{$prefix}{$table}";
110  }
111 
112  return $table;
113  }
114 
120  public function getTableName(): string {
121  return $this->table_name;
122  }
123 
129  public function getTableAlias(): ?string {
130  return $this->table_alias;
131  }
132 
143  public function param($value, string $type = ELGG_VALUE_STRING, string $key = null): string {
144  if (!$key) {
145  $parameters = $this->getParameters();
146  $key = ':qb' . (count($parameters) + 1);
147  }
148 
149  switch ($type) {
150  case ELGG_VALUE_GUID:
152  $type = ParameterType::INTEGER;
153 
154  break;
155  case ELGG_VALUE_ID:
157  $type = ParameterType::INTEGER;
158 
159  break;
160  case ELGG_VALUE_INTEGER:
161  $type = ParameterType::INTEGER;
162 
163  break;
164  case ELGG_VALUE_BOOL:
165  $type = ParameterType::INTEGER;
166  $value = (int) $value;
167 
168  break;
169  case ELGG_VALUE_STRING:
170  $type = ParameterType::STRING;
171 
172  break;
175  $type = ParameterType::INTEGER;
176 
177  break;
178  }
179 
180  // convert array value or type based on array
181  if (is_array($value)) {
182  if (count($value) === 1) {
183  $value = array_shift($value);
184  } else {
185  if ($type === ParameterType::INTEGER) {
186  $type = ArrayParameterType::INTEGER;
187  } elseif ($type === ParameterType::STRING) {
188  $type = ArrayParameterType::STRING;
189  }
190  }
191  }
192 
193  return $this->createNamedParameter($value, $type, $key);
194  }
195 
201  public function execute(bool $track_query = true) {
202  if (!$track_query) {
203  if ($this instanceof Select) {
204  return parent::executeQuery();
205  } else {
206  return parent::executeStatement();
207  }
208  }
209 
210  return _elgg_services()->db->trackQuery($this, function() {
211  if ($this instanceof Select) {
212  return parent::executeQuery();
213  } else {
214  return parent::executeStatement();
215  }
216  });
217  }
218 
224  public function from(string $table, ?string $alias = null): self {
225  $this->table_name = $table;
226  $this->table_alias = $alias;
227 
228  return parent::from($this->prefix($table), $alias);
229  }
230 
236  public function insert(string $table): self {
237  $this->table_name = $table;
238 
239  return parent::insert($this->prefix($table));
240  }
241 
247  public function update(string $table): self {
248  $this->table_name = $table;
249 
250  return parent::update($this->prefix($table));
251  }
252 
258  public function delete(string $table): self {
259  $this->table_name = $table;
260 
261  return parent::delete($this->prefix($table));
262  }
263 
267  public function join(string $fromAlias, string $join, string $alias, ?string $condition = null): self {
268  return parent::join($fromAlias, $this->prefix($join), $alias, $condition);
269  }
270 
274  public function innerJoin(string $fromAlias, string $join, string $alias, ?string $condition = null): self {
275  return parent::innerJoin($fromAlias, $this->prefix($join), $alias, $condition);
276  }
277 
281  public function leftJoin(string $fromAlias, string $join, string $alias, ?string $condition = null): self {
282  return parent::leftJoin($fromAlias, $this->prefix($join), $alias, $condition);
283  }
284 
288  public function rightJoin(string $fromAlias, string $join, string $alias, ?string $condition = null): self {
289  return parent::rightJoin($fromAlias, $this->prefix($join), $alias, $condition);
290  }
291 
300  public function merge($parts = null, $boolean = 'AND') {
301  if (empty($parts)) {
302  return null;
303  }
304 
305  $parts = (array) $parts;
306 
307  $parts = array_filter($parts, function ($e) {
308  if (empty($e)) {
309  return false;
310  }
311 
312  if (!$e instanceof CompositeExpression && !is_string($e)) {
313  return false;
314  }
315 
316  return true;
317  });
318  if (empty($parts)) {
319  return null;
320  }
321 
322  if (count($parts) === 1) {
323  return array_shift($parts);
324  }
325 
326  // PHP 8 can use named arguments in call_user_func_array(), this causes issues
327  // @see: https://www.php.net/manual/en/function.call-user-func-array.php#125953
328  $parts = array_values($parts);
329  if (strtoupper($boolean) === 'OR') {
330  return call_user_func_array([$this->expr(), 'or'], $parts);
331  }
332 
333  return call_user_func_array([$this->expr(), 'and'], $parts);
334  }
335 
352  public function compare(string $x, string $comparison, $y = null, string $type = null, bool $case_sensitive = null) {
353  return (new ComparisonClause($x, $comparison, $y, $type, $case_sensitive))->prepare($this);
354  }
355 
366  public function between(string $x, $lower = null, $upper = null, string $type = null) {
367  $wheres = [];
368  if ($lower) {
369  $wheres[] = $this->compare($x, '>=', $lower, $type);
370  }
371 
372  if ($upper) {
373  $wheres[] = $this->compare($x, '<=', $upper, $type);
374  }
375 
376  return $this->merge($wheres);
377  }
378 
384  public function getNextJoinAlias(): string {
385  $this->join_index++;
386 
387  return "qbt{$this->join_index}";
388  }
389 
400  public function joinEntitiesTable(string $from_alias = '', string $from_column = 'guid', ?string $join_type = 'inner', string $joined_alias = null): string {
401  if (in_array($joined_alias, $this->joins)) {
402  return $joined_alias;
403  }
404 
405  if ($from_alias) {
406  $from_column = "{$from_alias}.{$from_column}";
407  }
408 
409  $hash = sha1(serialize([
410  $join_type,
412  $from_column,
413  ]));
414 
415  if (!isset($joined_alias) && !empty($this->joins[$hash])) {
416  return $this->joins[$hash];
417  }
418 
419  $condition = function (QueryBuilder $qb, $joined_alias) use ($from_column) {
420  return $qb->compare("{$joined_alias}.guid", '=', $from_column);
421  };
422 
423  $clause = new JoinClause(EntityTable::TABLE_NAME, $joined_alias, $condition, $join_type);
424  $joined_alias = $clause->prepare($this, $from_alias);
425 
426  $this->joins[$hash] = $joined_alias;
427 
428  return $joined_alias;
429  }
430 
442  public function joinMetadataTable(string $from_alias = '', string $from_column = 'guid', $name = null, ?string $join_type = 'inner', string $joined_alias = null): string {
443  if (in_array($joined_alias, $this->joins)) {
444  return $joined_alias;
445  }
446 
447  if ($from_alias) {
448  $from_column = "{$from_alias}.{$from_column}";
449  }
450 
451  $hash = sha1(serialize([
452  $join_type,
454  $from_column,
455  (array) $name,
456  ]));
457 
458  if (!isset($joined_alias) && !empty($this->joins[$hash])) {
459  return $this->joins[$hash];
460  }
461 
462  $condition = function (QueryBuilder $qb, $joined_alias) use ($from_column, $name) {
463  return $qb->merge([
464  $qb->compare("{$joined_alias}.entity_guid", '=', $from_column),
465  $qb->compare("{$joined_alias}.name", '=', $name, ELGG_VALUE_STRING),
466  ]);
467  };
468 
469  $clause = new JoinClause(MetadataTable::TABLE_NAME, $joined_alias, $condition, $join_type);
470 
471  $joined_alias = $clause->prepare($this, $from_alias);
472 
473  $this->joins[$hash] = $joined_alias;
474 
475  return $joined_alias;
476  }
477 
489  public function joinAnnotationTable(string $from_alias = '', string $from_column = 'guid', $name = null, ?string $join_type = 'inner', string $joined_alias = null): string {
490  if (in_array($joined_alias, $this->joins)) {
491  return $joined_alias;
492  }
493 
494  if ($from_alias) {
495  $from_column = "{$from_alias}.{$from_column}";
496  }
497 
498  $hash = sha1(serialize([
499  $join_type,
501  $from_column,
502  (array) $name,
503  ]));
504 
505  if (!isset($joined_alias) && !empty($this->joins[$hash])) {
506  return $this->joins[$hash];
507  }
508 
509  $condition = function (QueryBuilder $qb, $joined_alias) use ($from_column, $name) {
510  return $qb->merge([
511  $qb->compare("{$joined_alias}.entity_guid", '=', $from_column),
512  $qb->compare("{$joined_alias}.name", '=', $name, ELGG_VALUE_STRING),
513  ]);
514  };
515 
516  $clause = new JoinClause(AnnotationsTable::TABLE_NAME, $joined_alias, $condition, $join_type);
517 
518  $joined_alias = $clause->prepare($this, $from_alias);
519 
520  $this->joins[$hash] = $joined_alias;
521 
522  return $joined_alias;
523  }
524 
537  public function joinRelationshipTable(string $from_alias = '', string $from_column = 'guid', $name = null, bool $inverse = false, ?string $join_type = 'inner', string $joined_alias = null): string {
538  if (in_array($joined_alias, $this->joins)) {
539  return $joined_alias;
540  }
541 
542  if ($from_alias) {
543  $from_column = "{$from_alias}.{$from_column}";
544  }
545 
546  $hash = sha1(serialize([
547  $join_type,
549  $from_column,
550  $inverse,
551  (array) $name,
552  ]));
553 
554  if (!isset($joined_alias) && !empty($this->joins[$hash])) {
555  return $this->joins[$hash];
556  }
557 
558  $condition = function (QueryBuilder $qb, $joined_alias) use ($from_column, $name, $inverse) {
559  $parts = [];
560  if ($inverse) {
561  $parts[] = $qb->compare("{$joined_alias}.guid_one", '=', $from_column);
562  } else {
563  $parts[] = $qb->compare("{$joined_alias}.guid_two", '=', $from_column);
564  }
565 
566  $parts[] = $qb->compare("{$joined_alias}.relationship", '=', $name, ELGG_VALUE_STRING);
567  return $qb->merge($parts);
568  };
569 
570  $clause = new JoinClause(RelationshipsTable::TABLE_NAME, $joined_alias, $condition, $join_type);
571 
572  $joined_alias = $clause->prepare($this, $from_alias);
573 
574  $this->joins[$hash] = $joined_alias;
575 
576  return $joined_alias;
577  }
578 }
getTableName()
Returns the name of the primary table.
joinAnnotationTable(string $from_alias= '', string $from_column= 'guid', $name=null,?string $join_type= 'inner', string $joined_alias=null)
Join annotations table from alias and return joined table alias.
Utility class for building composite comparison expression.
static normalizeGuids(...$args)
Flatten an array of data into an array of GUIDs.
Definition: Values.php:141
from(string $table,?string $alias=null)
{}
innerJoin(string $fromAlias, string $join, string $alias,?string $condition=null)
{}
param($value, string $type=ELGG_VALUE_STRING, string $key=null)
Sets a new parameter assigning it a unique parameter key/name if none provided Returns the name of th...
Saves user notification settings.
if(!$user||!$user->canDelete()) $name
Definition: delete.php:22
subquery(string $table, string $alias=null)
Creates a new SelectQueryBuilder for join/where sub queries using the DB connection of the primary Qu...
Interface that allows resolving statements and/or extending query builder.
Definition: Clause.php:14
const ELGG_VALUE_INTEGER
Value types.
Definition: constants.php:111
addClause(Clause $clause, string $alias=null)
Apply clause to this instance.
Extends QueryBuilder with JOIN clauses.
Definition: JoinClause.php:11
const ELGG_VALUE_GUID
Definition: constants.php:113
Database abstraction query builder.
$type
Definition: delete.php:21
__construct(protected readonly Connection $backup_connection)
Initializes a new QueryBuilder.
getConnection()
Returns the connection.
const ELGG_VALUE_ID
Definition: constants.php:114
insert(string $table)
{}
if($item instanceof\ElggEntity) elseif($item instanceof\ElggRiverItem) elseif($item instanceof\ElggRelationship) elseif(is_callable([$item, 'getType']))
Definition: item.php:48
$value
Definition: generic.php:51
prepare(QueryBuilder $qb, $table_alias=null)
Build an expression and/or apply it to an instance of query builder.
const ELGG_VALUE_BOOL
Definition: constants.php:116
static normalizeTimestamp($time)
Returns timestamp value of the time representation.
Definition: Values.php:63
compare(string $x, string $comparison, $y=null, string $type=null, bool $case_sensitive=null)
Build value comparison clause.
static normalizeIds(...$args)
Prepare IDs.
Definition: Values.php:104
joinEntitiesTable(string $from_alias= '', string $from_column= 'guid',?string $join_type= 'inner', string $joined_alias=null)
Join entity table from alias and return joined table alias.
$table
Definition: user.php:37
update(string $table)
{}
joinRelationshipTable(string $from_alias= '', string $from_column= 'guid', $name=null, bool $inverse=false,?string $join_type= 'inner', string $joined_alias=null)
Join relationship table from alias and return joined table alias.
getNextJoinAlias()
Get an index of the next available join alias.
const ELGG_VALUE_TIMESTAMP
Definition: constants.php:115
if($container instanceof ElggGroup &&$container->guid!=elgg_get_page_owner_guid()) $key
Definition: summary.php:44
joinMetadataTable(string $from_alias= '', string $from_column= 'guid', $name=null,?string $join_type= 'inner', string $joined_alias=null)
Join metadata table from alias and return joined table alias.
getTableAlias()
Returns the alias of the primary table.
Builds a clause from closure or composite expression.
Definition: WhereClause.php:11
merge($parts=null, $boolean= 'AND')
Merges multiple composite expressions with a boolean.
const ELGG_VALUE_STRING
Definition: constants.php:112
prefix(string $table)
Prefixes the table name with installation DB prefix.
execute(bool $track_query=true)
rightJoin(string $fromAlias, string $join, string $alias,?string $condition=null)
{}
_elgg_services()
Get the global service provider.
Definition: elgglib.php:351
join(string $fromAlias, string $join, string $alias,?string $condition=null)
{}
leftJoin(string $fromAlias, string $join, string $alias,?string $condition=null)
{}
$qb
Definition: queue.php:12
between(string $x, $lower=null, $upper=null, string $type=null)
Build a between clause.
Query builder for fetching data from the database.
Definition: Select.php:8