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 CW |
39 | |
40 | /** | |
41 | * SqlFunction constructor. | |
42 | * @param string $expr | |
43 | * @param string|null $alias | |
44 | */ | |
45 | public function __construct(string $expr, $alias = NULL) { | |
46 | $this->expr = $expr; | |
47 | $this->alias = $alias; | |
48 | $this->initialize(); | |
49 | } | |
50 | ||
51 | abstract protected function initialize(); | |
52 | ||
53 | /** | |
54 | * Converts a string to a SqlExpression object. | |
55 | * | |
56 | * E.g. the expression "SUM(foo)" would return a SqlFunctionSUM object. | |
57 | * | |
58 | * @param string $expression | |
59 | * @param bool $parseAlias | |
60 | * @param array $mustBe | |
61 | * @param array $cantBe | |
62 | * @return SqlExpression | |
63 | * @throws \API_Exception | |
64 | */ | |
65 | public static function convert(string $expression, $parseAlias = FALSE, $mustBe = [], $cantBe = ['SqlWild']) { | |
66 | $as = $parseAlias ? strrpos($expression, ' AS ') : FALSE; | |
67 | $expr = $as ? substr($expression, 0, $as) : $expression; | |
68 | $alias = $as ? \CRM_Utils_String::munge(substr($expression, $as + 4)) : NULL; | |
69 | $bracketPos = strpos($expr, '('); | |
70 | $firstChar = substr($expr, 0, 1); | |
71 | $lastChar = substr($expr, -1); | |
72 | // If there are brackets but not the first character, we have a function | |
73 | if ($bracketPos && $lastChar === ')') { | |
74 | $fnName = substr($expr, 0, $bracketPos); | |
75 | if ($fnName !== strtoupper($fnName)) { | |
76 | throw new \API_Exception('Sql function must be uppercase.'); | |
77 | } | |
78 | $className = 'SqlFunction' . $fnName; | |
79 | } | |
80 | // String expression | |
81 | elseif ($firstChar === $lastChar && in_array($firstChar, ['"', "'"], TRUE)) { | |
82 | $className = 'SqlString'; | |
83 | } | |
84 | elseif ($expr === 'NULL') { | |
85 | $className = 'SqlNull'; | |
86 | } | |
87 | elseif ($expr === '*') { | |
88 | $className = 'SqlWild'; | |
89 | } | |
90 | elseif (is_numeric($expr)) { | |
91 | $className = 'SqlNumber'; | |
92 | } | |
93 | // If none of the above, assume it's a field name | |
94 | else { | |
95 | $className = 'SqlField'; | |
96 | } | |
97 | $className = __NAMESPACE__ . '\\' . $className; | |
98 | if (!class_exists($className)) { | |
99 | throw new \API_Exception('Unable to parse sql expression: ' . $expression); | |
100 | } | |
101 | $sqlExpression = new $className($expr, $alias); | |
102 | foreach ($cantBe as $cant) { | |
103 | if (is_a($sqlExpression, __NAMESPACE__ . '\\' . $cant)) { | |
104 | throw new \API_Exception('Illegal sql expression.'); | |
105 | } | |
106 | } | |
107 | if ($mustBe) { | |
108 | foreach ($mustBe as $must) { | |
109 | if (is_a($sqlExpression, __NAMESPACE__ . '\\' . $must)) { | |
110 | return $sqlExpression; | |
111 | } | |
112 | } | |
113 | throw new \API_Exception('Illegal sql expression.'); | |
114 | } | |
115 | return $sqlExpression; | |
116 | } | |
117 | ||
118 | /** | |
119 | * Returns the field names of all sql columns that are arguments to this expression. | |
120 | * | |
121 | * @return array | |
122 | */ | |
123 | public function getFields(): array { | |
124 | return $this->fields; | |
125 | } | |
126 | ||
127 | /** | |
128 | * Renders expression to a sql string, replacing field names with column names. | |
129 | * | |
130 | * @param array $fieldList | |
131 | * @return string | |
132 | */ | |
133 | abstract public function render(array $fieldList): string; | |
134 | ||
135 | /** | |
136 | * @return string | |
137 | */ | |
138 | public function getExpr(): string { | |
139 | return $this->expr; | |
140 | } | |
141 | ||
142 | /** | |
143 | * Returns the alias to use for SELECT AS. | |
144 | * | |
145 | * @return string | |
146 | */ | |
147 | public function getAlias(): string { | |
148 | return $this->alias ?? $this->fields[0] ?? \CRM_Utils_String::munge($this->expr); | |
149 | } | |
150 | ||
16f5a13d CW |
151 | /** |
152 | * Returns the name of this sql expression class. | |
153 | * | |
154 | * @return string | |
155 | */ | |
156 | public function getType(): string { | |
157 | $className = get_class($this); | |
158 | return substr($className, strrpos($className, '\\') + 1); | |
159 | } | |
160 | ||
f0acec37 | 161 | } |