Commit | Line | Data |
---|---|---|
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 | ||
12 | namespace 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 | */ | |
21 | abstract 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 | } |