APIv4 Search: Improve GROUP_CONCAT with :label prefix
[civicrm-core.git] / Civi / Api4 / Query / SqlExpression.php
CommitLineData
f0acec37
CW
1<?php
2/*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
10 */
11
12namespace Civi\Api4\Query;
13
14/**
15 * Base class for SqlColumn, SqlString, SqlBool, and SqlFunction classes.
16 *
17 * These are used to validate and format sql expressions in Api4 select queries.
18 *
19 * @package Civi\Api4\Query
20 */
21abstract class SqlExpression {
22
23 /**
24 * @var array
25 */
26 protected $fields = [];
27
28 /**
29 * The SELECT alias (if null it will be calculated by getAlias)
30 * @var string|null
31 */
32 protected $alias;
33
34 /**
35 * The raw expression, minus the alias.
36 * @var string
37 */
16f5a13d 38 public $expr = '';
f0acec37 39
7ce7b1cd
CW
40 /**
41 * Whether or not pseudoconstant suffixes should be evaluated during output.
42 *
43 * @var bool
44 * @see \Civi\Api4\Utils\FormattingUtil::formatOutputValues
45 */
46 public $supportsExpansion = FALSE;
47
f0acec37
CW
48 /**
49 * SqlFunction constructor.
50 * @param string $expr
51 * @param string|null $alias
52 */
53 public function __construct(string $expr, $alias = NULL) {
54 $this->expr = $expr;
55 $this->alias = $alias;
56 $this->initialize();
57 }
58
59 abstract protected function initialize();
60
61 /**
62 * Converts a string to a SqlExpression object.
63 *
64 * E.g. the expression "SUM(foo)" would return a SqlFunctionSUM object.
65 *
66 * @param string $expression
67 * @param bool $parseAlias
68 * @param array $mustBe
69 * @param array $cantBe
70 * @return SqlExpression
71 * @throws \API_Exception
72 */
73 public static function convert(string $expression, $parseAlias = FALSE, $mustBe = [], $cantBe = ['SqlWild']) {
74 $as = $parseAlias ? strrpos($expression, ' AS ') : FALSE;
75 $expr = $as ? substr($expression, 0, $as) : $expression;
76 $alias = $as ? \CRM_Utils_String::munge(substr($expression, $as + 4)) : NULL;
77 $bracketPos = strpos($expr, '(');
78 $firstChar = substr($expr, 0, 1);
79 $lastChar = substr($expr, -1);
80 // If there are brackets but not the first character, we have a function
81 if ($bracketPos && $lastChar === ')') {
82 $fnName = substr($expr, 0, $bracketPos);
83 if ($fnName !== strtoupper($fnName)) {
84 throw new \API_Exception('Sql function must be uppercase.');
85 }
86 $className = 'SqlFunction' . $fnName;
87 }
88 // String expression
89 elseif ($firstChar === $lastChar && in_array($firstChar, ['"', "'"], TRUE)) {
90 $className = 'SqlString';
91 }
92 elseif ($expr === 'NULL') {
93 $className = 'SqlNull';
94 }
95 elseif ($expr === '*') {
96 $className = 'SqlWild';
97 }
98 elseif (is_numeric($expr)) {
99 $className = 'SqlNumber';
100 }
101 // If none of the above, assume it's a field name
102 else {
103 $className = 'SqlField';
104 }
105 $className = __NAMESPACE__ . '\\' . $className;
106 if (!class_exists($className)) {
107 throw new \API_Exception('Unable to parse sql expression: ' . $expression);
108 }
109 $sqlExpression = new $className($expr, $alias);
110 foreach ($cantBe as $cant) {
111 if (is_a($sqlExpression, __NAMESPACE__ . '\\' . $cant)) {
112 throw new \API_Exception('Illegal sql expression.');
113 }
114 }
115 if ($mustBe) {
116 foreach ($mustBe as $must) {
117 if (is_a($sqlExpression, __NAMESPACE__ . '\\' . $must)) {
118 return $sqlExpression;
119 }
120 }
121 throw new \API_Exception('Illegal sql expression.');
122 }
123 return $sqlExpression;
124 }
125
126 /**
127 * Returns the field names of all sql columns that are arguments to this expression.
128 *
129 * @return array
130 */
131 public function getFields(): array {
132 return $this->fields;
133 }
134
135 /**
136 * Renders expression to a sql string, replacing field names with column names.
137 *
138 * @param array $fieldList
139 * @return string
140 */
141 abstract public function render(array $fieldList): string;
142
143 /**
144 * @return string
145 */
146 public function getExpr(): string {
147 return $this->expr;
148 }
149
150 /**
151 * Returns the alias to use for SELECT AS.
152 *
153 * @return string
154 */
155 public function getAlias(): string {
156 return $this->alias ?? $this->fields[0] ?? \CRM_Utils_String::munge($this->expr);
157 }
158
16f5a13d
CW
159 /**
160 * Returns the name of this sql expression class.
161 *
162 * @return string
163 */
164 public function getType(): string {
165 $className = get_class($this);
166 return substr($className, strrpos($className, '\\') + 1);
167 }
168
f0acec37 169}