3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
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 +--------------------------------------------------------------------+
12 namespace Civi\Api4\Query
;
15 * Base class for SqlColumn, SqlString, SqlBool, and SqlFunction classes.
17 * These are used to validate and format sql expressions in Api4 select queries.
19 * @package Civi\Api4\Query
21 abstract class SqlExpression
{
26 protected $fields = [];
29 * The SELECT alias (if null it will be calculated by getAlias)
35 * The raw expression, minus the alias.
41 * SqlFunction constructor.
43 * @param string|null $alias
45 public function __construct(string $expr, $alias = NULL) {
47 $this->alias
= $alias;
51 abstract protected function initialize();
54 * Converts a string to a SqlExpression object.
56 * E.g. the expression "SUM(foo)" would return a SqlFunctionSUM object.
58 * @param string $expression
59 * @param bool $parseAlias
60 * @param array $mustBe
61 * @param array $cantBe
62 * @return SqlExpression
63 * @throws \API_Exception
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.');
78 $className = 'SqlFunction' . $fnName;
81 elseif ($firstChar === $lastChar && in_array($firstChar, ['"', "'"], TRUE)) {
82 $className = 'SqlString';
84 elseif ($expr === 'NULL') {
85 $className = 'SqlNull';
87 elseif ($expr === '*') {
88 $className = 'SqlWild';
90 elseif (is_numeric($expr)) {
91 $className = 'SqlNumber';
93 // If none of the above, assume it's a field name
95 $className = 'SqlField';
97 $className = __NAMESPACE__
. '\\' . $className;
98 if (!class_exists($className)) {
99 throw new \
API_Exception('Unable to parse sql expression: ' . $expression);
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.');
108 foreach ($mustBe as $must) {
109 if (is_a($sqlExpression, __NAMESPACE__
. '\\' . $must)) {
110 return $sqlExpression;
113 throw new \
API_Exception('Illegal sql expression.');
115 return $sqlExpression;
119 * Returns the field names of all sql columns that are arguments to this expression.
123 public function getFields(): array {
124 return $this->fields
;
128 * Renders expression to a sql string, replacing field names with column names.
130 * @param array $fieldList
133 abstract public function render(array $fieldList): string;
138 public function getExpr(): string {
143 * Returns the alias to use for SELECT AS.
147 public function getAlias(): string {
148 return $this->alias ??
$this->fields
[0] ?? \CRM_Utils_String
::munge($this->expr
);