From 3dbd39269b7fedd100e7a96061e174ca4910d245 Mon Sep 17 00:00:00 2001 From: Coleman Watts Date: Wed, 13 Apr 2022 19:12:21 -0400 Subject: [PATCH] APIv4 - Pass entire Api4SelectQuery object to expression when rendering This permits SearchSegment pseudo-fields to call $query->getField() on multiple fields, including custom fields, while rendering. --- Civi/Api4/Query/Api4SelectQuery.php | 14 +++++++------- Civi/Api4/Query/SqlEquation.php | 8 ++++---- Civi/Api4/Query/SqlExpression.php | 4 ++-- Civi/Api4/Query/SqlField.php | 19 ++++++------------- Civi/Api4/Query/SqlFunction.php | 12 ++++++------ Civi/Api4/Query/SqlNull.php | 2 +- Civi/Api4/Query/SqlNumber.php | 2 +- Civi/Api4/Query/SqlString.php | 2 +- Civi/Api4/Query/SqlWild.php | 2 +- 9 files changed, 29 insertions(+), 36 deletions(-) diff --git a/Civi/Api4/Query/Api4SelectQuery.php b/Civi/Api4/Query/Api4SelectQuery.php index 7b111afba9..c1cd9cbc35 100644 --- a/Civi/Api4/Query/Api4SelectQuery.php +++ b/Civi/Api4/Query/Api4SelectQuery.php @@ -271,7 +271,7 @@ class Api4SelectQuery { throw new \API_Exception('Cannot use existing field name as alias'); } $this->selectAliases[$alias] = $expr->getExpr(); - $this->query->select($expr->render($this->apiFieldSpec) . " AS `$alias`"); + $this->query->select($expr->render($this) . " AS `$alias`"); } } } @@ -465,7 +465,7 @@ class Api4SelectQuery { ]; FormattingUtil::formatInputValue($value, NULL, $fauxField, $operator); } - $fieldAlias = $expr->render($this->apiFieldSpec); + $fieldAlias = $expr->render($this); } // For HAVING, expr must be an item in the SELECT clause elseif ($type === 'HAVING') { @@ -508,7 +508,7 @@ class Api4SelectQuery { elseif ($type === 'ON' || ($type === 'WHERE' && $isExpression)) { $expr = $this->getExpression($expr); $fieldName = count($expr->getFields()) === 1 ? $expr->getFields()[0] : NULL; - $fieldAlias = $expr->render($this->apiFieldSpec); + $fieldAlias = $expr->render($this); if (is_string($value)) { $valExpr = $this->getExpression($value); if ($expr->getType() === 'SqlField' && $valExpr->getType() === 'SqlString') { @@ -517,7 +517,7 @@ class Api4SelectQuery { return $this->createSQLClause($fieldAlias, $operator, $value, $this->apiFieldSpec[$fieldName], $depth); } else { - $value = $valExpr->render($this->apiFieldSpec); + $value = $valExpr->render($this); return sprintf('%s %s %s', $fieldAlias, $operator, $value); } } @@ -992,13 +992,13 @@ class Api4SelectQuery { // If this condition makes an explicit link between the bridge and another entity if ($op === '=' && $sideB && ($sideA === "$alias.{$baseRef->getReferenceKey()}" || $sideB === "$alias.{$baseRef->getReferenceKey()}")) { $expr = $sideA === "$alias.{$baseRef->getReferenceKey()}" ? $sideB : $sideA; - $bridgeConditions[] = "`$bridgeAlias`.`{$baseRef->getReferenceKey()}` = " . $this->getExpression($expr)->render($this->apiFieldSpec); + $bridgeConditions[] = "`$bridgeAlias`.`{$baseRef->getReferenceKey()}` = " . $this->getExpression($expr)->render($this); return FALSE; } // Explicit link with dynamic "entity_table" column elseif ($op === '=' && $baseRef->getTypeColumn() && ($sideA === "$alias.{$baseRef->getTypeColumn()}" || $sideB === "$alias.{$baseRef->getTypeColumn()}")) { $expr = $sideA === "$alias.{$baseRef->getTypeColumn()}" ? $sideB : $sideA; - $bridgeConditions[] = "`$bridgeAlias`.`{$baseRef->getTypeColumn()}` = " . $this->getExpression($expr)->render($this->apiFieldSpec); + $bridgeConditions[] = "`$bridgeAlias`.`{$baseRef->getTypeColumn()}` = " . $this->getExpression($expr)->render($this); return FALSE; } return TRUE; @@ -1302,7 +1302,7 @@ class Api4SelectQuery { return "`$alias`"; } } - return $expr->render($this->apiFieldSpec); + return $expr->render($this); } /** diff --git a/Civi/Api4/Query/SqlEquation.php b/Civi/Api4/Query/SqlEquation.php index caaba24fb4..0b2f22334f 100644 --- a/Civi/Api4/Query/SqlEquation.php +++ b/Civi/Api4/Query/SqlEquation.php @@ -64,10 +64,10 @@ class SqlEquation extends SqlExpression { /** * Render the expression for insertion into the sql query * - * @param array $fieldList + * @param Civi\Api4\Query\Api4SelectQuery $query * @return string */ - public function render(array $fieldList): string { + public function render(Api4SelectQuery $query): string { $output = []; foreach ($this->args as $arg) { // Just an operator @@ -76,10 +76,10 @@ class SqlEquation extends SqlExpression { } // Surround fields with COALESCE to handle null values elseif (is_a($arg, SqlField::class)) { - $output[] = 'COALESCE(' . $arg->render($fieldList) . ', 0)'; + $output[] = 'COALESCE(' . $arg->render($query) . ', 0)'; } else { - $output[] = $arg->render($fieldList); + $output[] = $arg->render($query); } } return '(' . implode(' ', $output) . ')'; diff --git a/Civi/Api4/Query/SqlExpression.php b/Civi/Api4/Query/SqlExpression.php index 46a81c0f1d..fca26025b3 100644 --- a/Civi/Api4/Query/SqlExpression.php +++ b/Civi/Api4/Query/SqlExpression.php @@ -140,10 +140,10 @@ abstract class SqlExpression { /** * Renders expression to a sql string, replacing field names with column names. * - * @param array $fieldList + * @param Civi\Api4\Query\Api4SelectQuery $query * @return string */ - abstract public function render(array $fieldList): string; + abstract public function render(Api4SelectQuery $query): string; /** * @return string diff --git a/Civi/Api4/Query/SqlField.php b/Civi/Api4/Query/SqlField.php index a665f68609..0dde50583b 100644 --- a/Civi/Api4/Query/SqlField.php +++ b/Civi/Api4/Query/SqlField.php @@ -11,8 +11,6 @@ namespace Civi\Api4\Query; -use Civi\API\Exception\UnauthorizedException; - /** * Sql column expression */ @@ -27,18 +25,13 @@ class SqlField extends SqlExpression { $this->fields[] = $this->expr; } - public function render(array $fieldList): string { - if (!isset($fieldList[$this->expr])) { - throw new \API_Exception("Invalid field '{$this->expr}'"); - } - if ($fieldList[$this->expr] === FALSE) { - throw new UnauthorizedException("Unauthorized field '{$this->expr}'"); - } - if (!empty($fieldList[$this->expr]['sql_renderer'])) { - $renderer = $fieldList[$this->expr]['sql_renderer']; - return $renderer($fieldList[$this->expr]); + public function render(Api4SelectQuery $query): string { + $field = $query->getField($this->expr, TRUE); + if (!empty($field['sql_renderer'])) { + $renderer = $field['sql_renderer']; + return $renderer($field, $query); } - return $fieldList[$this->expr]['sql_name']; + return $field['sql_name']; } public static function getTitle(): string { diff --git a/Civi/Api4/Query/SqlFunction.php b/Civi/Api4/Query/SqlFunction.php index 5b146ee979..7cdb358aac 100644 --- a/Civi/Api4/Query/SqlFunction.php +++ b/Civi/Api4/Query/SqlFunction.php @@ -106,13 +106,13 @@ abstract class SqlFunction extends SqlExpression { /** * Render the expression for insertion into the sql query * - * @param array $fieldList + * @param Civi\Api4\Query\Api4SelectQuery $query * @return string */ - public function render(array $fieldList): string { + public function render(Api4SelectQuery $query): string { $output = ''; foreach ($this->args as $arg) { - $rendered = $this->renderArg($arg, $fieldList); + $rendered = $this->renderArg($arg, $query); if (strlen($rendered)) { $output .= (strlen($output) ? ' ' : '') . $rendered; } @@ -122,16 +122,16 @@ abstract class SqlFunction extends SqlExpression { /** * @param array $arg - * @param array $fieldList + * @param Civi\Api4\Query\Api4SelectQuery $query * @return string */ - private function renderArg($arg, $fieldList): string { + private function renderArg($arg, Api4SelectQuery $query): string { $rendered = implode(' ', $arg['prefix']); foreach ($arg['expr'] ?? [] as $idx => $expr) { if (strlen($rendered) || $idx) { $rendered .= $idx ? ', ' : ' '; } - $rendered .= $expr->render($fieldList); + $rendered .= $expr->render($query); } if ($arg['suffix']) { $rendered .= (strlen($rendered) ? ' ' : '') . implode(' ', $arg['suffix']); diff --git a/Civi/Api4/Query/SqlNull.php b/Civi/Api4/Query/SqlNull.php index 5b804ba7e9..a56720f3d5 100644 --- a/Civi/Api4/Query/SqlNull.php +++ b/Civi/Api4/Query/SqlNull.php @@ -19,7 +19,7 @@ class SqlNull extends SqlExpression { protected function initialize() { } - public function render(array $fieldList): string { + public function render(Api4SelectQuery $query): string { return 'NULL'; } diff --git a/Civi/Api4/Query/SqlNumber.php b/Civi/Api4/Query/SqlNumber.php index 04e7424aa0..14025f707c 100644 --- a/Civi/Api4/Query/SqlNumber.php +++ b/Civi/Api4/Query/SqlNumber.php @@ -22,7 +22,7 @@ class SqlNumber extends SqlExpression { \CRM_Utils_Type::validate($this->expr, 'Float'); } - public function render(array $fieldList): string { + public function render(Api4SelectQuery $query): string { return $this->expr; } diff --git a/Civi/Api4/Query/SqlString.php b/Civi/Api4/Query/SqlString.php index 53228498d3..51b5422c98 100644 --- a/Civi/Api4/Query/SqlString.php +++ b/Civi/Api4/Query/SqlString.php @@ -27,7 +27,7 @@ class SqlString extends SqlExpression { $this->expr = str_replace(['\\\\', "\\$quot", $backslash], [$backslash, $quot, '\\\\'], $str); } - public function render(array $fieldList): string { + public function render(Api4SelectQuery $query): string { return '"' . \CRM_Core_DAO::escapeString($this->expr) . '"'; } diff --git a/Civi/Api4/Query/SqlWild.php b/Civi/Api4/Query/SqlWild.php index f0679f58a2..090f864f5f 100644 --- a/Civi/Api4/Query/SqlWild.php +++ b/Civi/Api4/Query/SqlWild.php @@ -19,7 +19,7 @@ class SqlWild extends SqlExpression { protected function initialize() { } - public function render(array $fieldList): string { + public function render(Api4SelectQuery $query): string { return '*'; } -- 2.25.1