X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;f=Civi%2FApi4%2FQuery%2FSqlFunction.php;h=fd10f80ccabaff338cf374f723ed79d1eafe458f;hb=f4138bc4aee38bd264d3b2f800bc6a3703d62c29;hp=44923c6a5626a83345b6a85620900e28c2b999c4;hpb=11b0c2bca80122649d4f8a1d9989faba12284a27;p=civicrm-core.git diff --git a/Civi/Api4/Query/SqlFunction.php b/Civi/Api4/Query/SqlFunction.php index 44923c6a56..fd10f80cca 100644 --- a/Civi/Api4/Query/SqlFunction.php +++ b/Civi/Api4/Query/SqlFunction.php @@ -18,11 +18,6 @@ namespace Civi\Api4\Query; */ abstract class SqlFunction extends SqlExpression { - /** - * @var array - */ - protected static $params = []; - /** * @var array[] */ @@ -47,115 +42,55 @@ abstract class SqlFunction extends SqlExpression { protected function initialize() { $arg = trim(substr($this->expr, strpos($this->expr, '(') + 1, -1)); foreach ($this->getParams() as $idx => $param) { - $prefix = $this->captureKeyword($param['prefix'], $arg); + $prefix = NULL; + if ($param['name']) { + $prefix = $this->captureKeyword([$param['name']], $arg); + // Supply api_default + if (!$prefix && isset($param['api_default'])) { + $this->args[$idx] = [ + 'prefix' => [$param['name']], + 'expr' => array_map([parent::class, 'convert'], $param['api_default']['expr']), + 'suffix' => [], + ]; + continue; + } + if (!$prefix && !$param['optional']) { + throw new \API_Exception("Missing {$param['name']} for SQL function " . static::getName()); + } + } + elseif ($param['flag_before']) { + $prefix = $this->captureKeyword(array_keys($param['flag_before']), $arg); + } $this->args[$idx] = [ - 'prefix' => $prefix, + 'prefix' => (array) $prefix, 'expr' => [], - 'suffix' => NULL, + 'suffix' => [], ]; - if ($param['max_expr'] && isset($prefix) || in_array('', $param['prefix']) || !$param['optional']) { - $exprs = $this->captureExpressions($arg, $param['must_be'], $param['cant_be']); + if ($param['max_expr'] && (!$param['name'] || $param['name'] === $prefix)) { + $exprs = $this->captureExpressions($arg, $param['must_be'], TRUE); if (count($exprs) < $param['min_expr'] || count($exprs) > $param['max_expr']) { throw new \API_Exception('Incorrect number of arguments for SQL function ' . static::getName()); } $this->args[$idx]['expr'] = $exprs; - $this->args[$idx]['suffix'] = $this->captureKeyword($param['suffix'], $arg); - } - } - } - /** - * Shift a keyword off the beginning of the argument string and return it. - * - * @param array $keywords - * Whitelist of keywords - * @param string $arg - * @return mixed|null - */ - private function captureKeyword($keywords, &$arg) { - foreach (array_filter($keywords) as $key) { - if (strpos($arg, $key . ' ') === 0) { - $arg = ltrim(substr($arg, strlen($key))); - return $key; - } - } - return NULL; - } - - /** - * Shifts 0 or more expressions off the argument string and returns them - * - * @param string $arg - * @param array $mustBe - * @param array $cantBe - * @return array - * @throws \API_Exception - */ - private function captureExpressions(&$arg, $mustBe, $cantBe) { - $captured = []; - $arg = ltrim($arg); - while ($arg) { - $item = $this->captureExpression($arg); - $arg = ltrim(substr($arg, strlen($item))); - $expr = SqlExpression::convert($item, FALSE, $mustBe, $cantBe); - $this->fields = array_merge($this->fields, $expr->getFields()); - $captured[] = $expr; - // Keep going if we have a comma indicating another expression follows - if (substr($arg, 0, 1) === ',') { - $arg = ltrim(substr($arg, 1)); - } - else { - break; + $this->args[$idx]['suffix'] = (array) $this->captureKeyword(array_keys($param['flag_after']), $arg); } } - return $captured; } /** - * Scans the beginning of a string for an expression; stops when it hits delimiter + * Change $dataType according to output of function * - * @param $arg + * @see \Civi\Api4\Utils\FormattingUtil::formatOutputValues + * @param string $value + * @param string $dataType * @return string */ - private function captureExpression($arg) { - $chars = str_split($arg); - $isEscaped = $quote = NULL; - $item = ''; - $quotes = ['"', "'"]; - $brackets = [ - ')' => '(', - ]; - $enclosures = array_fill_keys($brackets, 0); - foreach ($chars as $index => $char) { - if (!$isEscaped && in_array($char, $quotes, TRUE)) { - // Open quotes - we'll ignore everything inside - if (!$quote) { - $quote = $char; - } - // Close quotes - elseif ($char === $quote) { - $quote = NULL; - } - } - if (!$quote) { - // Delineates end of expression - if (($char == ',' || $char == ' ') && !array_filter($enclosures)) { - return $item; - } - // Open brackets - we'll ignore delineators inside - if (isset($enclosures[$char])) { - $enclosures[$char]++; - } - // Close brackets - if (isset($brackets[$char]) && $enclosures[$brackets[$char]]) { - $enclosures[$brackets[$char]]--; - } - } - $item .= $char; - // We are escaping the next char if this is a backslash not preceded by an odd number of backslashes - $isEscaped = $char === '\\' && ((strlen($item) - strlen(rtrim($item, '\\'))) % 2); + public function formatOutputValue($value, &$dataType) { + if (static::$dataType) { + $dataType = static::$dataType; } - return $item; + return $value; } /** @@ -166,9 +101,8 @@ abstract class SqlFunction extends SqlExpression { */ public function render(array $fieldList): string { $output = ''; - $params = $this->getParams(); - foreach ($this->args as $index => $arg) { - $rendered = $this->renderArg($arg, $params[$index], $fieldList); + foreach ($this->args as $arg) { + $rendered = $this->renderArg($arg, $fieldList); if (strlen($rendered)) { $output .= (strlen($output) ? ' ' : '') . $rendered; } @@ -178,28 +112,19 @@ abstract class SqlFunction extends SqlExpression { /** * @param array $arg - * @param array $param * @param array $fieldList * @return string */ - private function renderArg($arg, $param, $fieldList): string { - // Supply api_default - if (!isset($arg['prefix']) && !isset($arg['suffix']) && empty($arg['expr']) && !empty($param['api_default'])) { - $arg = [ - 'prefix' => $param['api_default']['prefix'] ?? reset($param['prefix']), - 'expr' => array_map([parent::class, 'convert'], $param['api_default']['expr'] ?? []), - 'suffix' => $param['api_default']['suffix'] ?? reset($param['suffix']), - ]; - } - $rendered = $arg['prefix'] ?? ''; + private function renderArg($arg, $fieldList): string { + $rendered = implode(' ', $arg['prefix']); foreach ($arg['expr'] ?? [] as $idx => $expr) { if (strlen($rendered) || $idx) { $rendered .= $idx ? ', ' : ' '; } $rendered .= $expr->render($fieldList); } - if (isset($arg['suffix'])) { - $rendered .= (strlen($rendered) ? ' ' : '') . $arg['suffix']; + if ($arg['suffix']) { + $rendered .= (strlen($rendered) ? ' ' : '') . implode(' ', $arg['suffix']); } return $rendered; } @@ -224,24 +149,26 @@ abstract class SqlFunction extends SqlExpression { * Get the param metadata for this sql function. * @return array */ - public static function getParams(): array { + final public static function getParams(): array { $params = []; - foreach (static::$params as $param) { + foreach (static::params() as $param) { // Merge in defaults to ensure each param has these properties $params[] = $param + [ - 'prefix' => [], + 'name' => NULL, 'min_expr' => 1, 'max_expr' => 1, - 'suffix' => [], + 'flag_before' => [], + 'flag_after' => [], 'optional' => FALSE, - 'must_be' => [], - 'cant_be' => ['SqlWild'], + 'must_be' => ['SqlField', 'SqlFunction', 'SqlString', 'SqlNumber', 'SqlNull'], 'api_default' => NULL, ]; } return $params; } + abstract protected static function params(): array; + /** * Get the arguments passed to this sql function instance. * @return array[] @@ -262,4 +189,9 @@ abstract class SqlFunction extends SqlExpression { */ abstract public static function getTitle(): string; + /** + * @return string + */ + abstract public static function getDescription(): string; + }