APIv4 - Add support for sql equations
[civicrm-core.git] / Civi / Api4 / Query / SqlFunction.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 all Sql functions.
16 *
17 * @package Civi\Api4\Query
18 */
19abstract class SqlFunction extends SqlExpression {
20
7ce7b1cd
CW
21 /**
22 * @var array[]
23 */
f0acec37
CW
24 protected $args = [];
25
e7f6def6
CW
26 /**
27 * Used for categorizing functions in the UI
28 *
29 * @var string
30 */
31 protected static $category;
32
33 const CATEGORY_AGGREGATE = 'aggregate',
34 CATEGORY_COMPARISON = 'comparison',
35 CATEGORY_DATE = 'date',
36 CATEGORY_MATH = 'math',
37 CATEGORY_STRING = 'string';
38
f0acec37
CW
39 /**
40 * Parse the argument string into an array of function arguments
41 */
42 protected function initialize() {
43 $arg = trim(substr($this->expr, strpos($this->expr, '(') + 1, -1));
7ce7b1cd 44 foreach ($this->getParams() as $idx => $param) {
1b6a82ee 45 $prefix = NULL;
0dc0821c
CW
46 if ($param['name']) {
47 $prefix = $this->captureKeyword([$param['name']], $arg);
1b6a82ee
CW
48 // Supply api_default
49 if (!$prefix && isset($param['api_default'])) {
50 $this->args[$idx] = [
0dc0821c 51 'prefix' => [$param['name']],
1b6a82ee 52 'expr' => array_map([parent::class, 'convert'], $param['api_default']['expr']),
0dc0821c 53 'suffix' => [],
1b6a82ee
CW
54 ];
55 continue;
56 }
57 if (!$prefix && !$param['optional']) {
0dc0821c 58 throw new \API_Exception("Missing {$param['name']} for SQL function " . static::getName());
1b6a82ee
CW
59 }
60 }
61 elseif ($param['flag_before']) {
62 $prefix = $this->captureKeyword(array_keys($param['flag_before']), $arg);
63 }
7ce7b1cd 64 $this->args[$idx] = [
1b6a82ee 65 'prefix' => (array) $prefix,
7ce7b1cd 66 'expr' => [],
1b6a82ee 67 'suffix' => [],
7ce7b1cd 68 ];
0dc0821c 69 if ($param['max_expr'] && (!$param['name'] || $param['name'] === $prefix)) {
f4138bc4 70 $exprs = $this->captureExpressions($arg, $param['must_be'], TRUE);
fa7465e4
CW
71 if (count($exprs) < $param['min_expr'] || count($exprs) > $param['max_expr']) {
72 throw new \API_Exception('Incorrect number of arguments for SQL function ' . static::getName());
73 }
74 $this->args[$idx]['expr'] = $exprs;
1b6a82ee
CW
75
76 $this->args[$idx]['suffix'] = (array) $this->captureKeyword(array_keys($param['flag_after']), $arg);
f0acec37
CW
77 }
78 }
79 }
80
b0aa3463
CW
81 /**
82 * Change $dataType according to output of function
83 *
84 * @see \Civi\Api4\Utils\FormattingUtil::formatOutputValues
85 * @param string $value
86 * @param string $dataType
87 * @return string
88 */
89 public function formatOutputValue($value, &$dataType) {
90 if (static::$dataType) {
91 $dataType = static::$dataType;
92 }
93 return $value;
94 }
95
7ce7b1cd
CW
96 /**
97 * Render the expression for insertion into the sql query
98 *
99 * @param array $fieldList
100 * @return string
101 */
f0acec37 102 public function render(array $fieldList): string {
7ce7b1cd 103 $output = '';
0dc0821c
CW
104 foreach ($this->args as $arg) {
105 $rendered = $this->renderArg($arg, $fieldList);
7ce7b1cd
CW
106 if (strlen($rendered)) {
107 $output .= (strlen($output) ? ' ' : '') . $rendered;
f0acec37 108 }
7ce7b1cd
CW
109 }
110 return $this->getName() . '(' . $output . ')';
111 }
112
113 /**
114 * @param array $arg
7ce7b1cd
CW
115 * @param array $fieldList
116 * @return string
117 */
0dc0821c 118 private function renderArg($arg, $fieldList): string {
1b6a82ee 119 $rendered = implode(' ', $arg['prefix']);
7ce7b1cd
CW
120 foreach ($arg['expr'] ?? [] as $idx => $expr) {
121 if (strlen($rendered) || $idx) {
122 $rendered .= $idx ? ', ' : ' ';
f0acec37 123 }
7ce7b1cd
CW
124 $rendered .= $expr->render($fieldList);
125 }
1b6a82ee
CW
126 if ($arg['suffix']) {
127 $rendered .= (strlen($rendered) ? ' ' : '') . implode(' ', $arg['suffix']);
f0acec37 128 }
7ce7b1cd 129 return $rendered;
f0acec37
CW
130 }
131
132 /**
133 * @inheritDoc
134 */
135 public function getAlias(): string {
136 return $this->alias ?? $this->getName() . ':' . implode('_', $this->fields);
137 }
138
139 /**
140 * Get the name of this sql function.
141 * @return string
142 */
143 public static function getName(): string {
144 $className = static::class;
145 return substr($className, strrpos($className, 'SqlFunction') + 11);
146 }
147
148 /**
149 * Get the param metadata for this sql function.
150 * @return array
151 */
f19a0f00 152 final public static function getParams(): array {
f0acec37 153 $params = [];
f19a0f00 154 foreach (static::params() as $param) {
f0acec37
CW
155 // Merge in defaults to ensure each param has these properties
156 $params[] = $param + [
0dc0821c 157 'name' => NULL,
fa7465e4
CW
158 'min_expr' => 1,
159 'max_expr' => 1,
1b6a82ee
CW
160 'flag_before' => [],
161 'flag_after' => [],
f0acec37 162 'optional' => FALSE,
173405e2 163 'must_be' => ['SqlField', 'SqlFunction', 'SqlString', 'SqlNumber', 'SqlNull'],
7ce7b1cd 164 'api_default' => NULL,
f0acec37
CW
165 ];
166 }
167 return $params;
168 }
169
f19a0f00
CW
170 abstract protected static function params(): array;
171
7ce7b1cd
CW
172 /**
173 * Get the arguments passed to this sql function instance.
174 * @return array[]
175 */
176 public function getArgs(): array {
177 return $this->args;
178 }
179
e7f6def6
CW
180 /**
181 * @return string
182 */
183 public static function getCategory(): string {
184 return static::$category;
185 }
186
9cae8a07
CW
187 /**
188 * @return string
189 */
190 abstract public static function getTitle(): string;
191
1fe4682d
CW
192 /**
193 * @return string
194 */
195 abstract public static function getDescription(): string;
196
f0acec37 197}