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 * Whether or not pseudoconstant suffixes should be evaluated during output.
44 * @see \Civi\Api4\Utils\FormattingUtil::formatOutputValues
46 public $supportsExpansion = FALSE;
49 * SqlFunction constructor.
51 * @param string|null $alias
53 public function __construct(string $expr, $alias = NULL) {
55 $this->alias
= $alias;
59 abstract protected function initialize();
62 * Converts a string to a SqlExpression object.
64 * E.g. the expression "SUM(foo)" would return a SqlFunctionSUM object.
66 * @param string $expression
67 * @param bool $parseAlias
68 * @param array $mustBe
69 * @param array $cantBe
70 * @return SqlExpression
71 * @throws \API_Exception
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.');
86 $className = 'SqlFunction' . $fnName;
89 elseif ($firstChar === $lastChar && in_array($firstChar, ['"', "'"], TRUE)) {
90 $className = 'SqlString';
92 elseif ($expr === 'NULL') {
93 $className = 'SqlNull';
95 elseif ($expr === '*') {
96 $className = 'SqlWild';
98 elseif (is_numeric($expr)) {
99 $className = 'SqlNumber';
101 // If none of the above, assume it's a field name
103 $className = 'SqlField';
105 $className = __NAMESPACE__
. '\\' . $className;
106 if (!class_exists($className)) {
107 throw new \
API_Exception('Unable to parse sql expression: ' . $expression);
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.');
116 foreach ($mustBe as $must) {
117 if (is_a($sqlExpression, __NAMESPACE__
. '\\' . $must)) {
118 return $sqlExpression;
121 throw new \
API_Exception('Illegal sql expression.');
123 return $sqlExpression;
127 * Returns the field names of all sql columns that are arguments to this expression.
131 public function getFields(): array {
132 return $this->fields
;
136 * Renders expression to a sql string, replacing field names with column names.
138 * @param array $fieldList
141 abstract public function render(array $fieldList): string;
146 public function getExpr(): string {
151 * Returns the alias to use for SELECT AS.
155 public function getAlias(): string {
156 return $this->alias ??
$this->fields
[0] ?? \CRM_Utils_String
::munge($this->expr
);
160 * Returns the name of this sql expression class.
164 public function getType(): string {
165 $className = get_class($this);
166 return substr($className, strrpos($className, '\\') +
1);