Skip to content

Commit

Permalink
Create column mappings for custom fields’ sub fields
Browse files Browse the repository at this point in the history
Resolves #16157
  • Loading branch information
brandonkelly committed Nov 22, 2024
1 parent dc7323e commit a6c770d
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 16 deletions.
1 change: 1 addition & 0 deletions CHANGELOG-WIP.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
- Added support for fallback element partial templates, e.g. `_partials/entry.twig` as opposed to `_partials/entry/typeHandle.twig`. ([#16125](https://github.com/craftcms/cms/pull/16125))
- Added the `affiliatedSite` and `affiliatedSiteId` user query and GraphQL params. ([#16174](https://github.com/craftcms/cms/pull/16174))
- Added the `affiliatedSiteHandle` and `affiliatedSiteId` user GraphQL field. ([#16174](https://github.com/craftcms/cms/pull/16174))
- It’s now possible to pass nested custom field value keys into element queries’ `orderBy` and `select` params (e.g. `myDateField.tz`). ([#16157](https://github.com/craftcms/cms/discussions/16157))

### Extensibility
- Added `craft\base\conditions\BaseElementSelectConditionRule::allowMultiple()`.
Expand Down
68 changes: 52 additions & 16 deletions src/elements/db/ElementQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -1692,24 +1692,38 @@ public function prepare($builder): Query
}

// Map custom field handles to their content values
$isMysql = $db->getIsMysql();
foreach ($this->customFields as $field) {
$valueSql = $field->getValueSql();
if ($valueSql !== null) {
if (isset($this->_columnMap[$field->handle])) {
if (!is_array($this->_columnMap[$field->handle])) {
$this->_columnMap[$field->handle] = [$this->_columnMap[$field->handle]];
}
$this->_columnMap[$field->handle][] = $valueSql;
$dbTypes = $field::dbType();

if ($dbTypes !== null) {
if (is_string($dbTypes)) {
$dbTypes = ['*' => $dbTypes];
} else {
$this->_columnMap[$field->handle] = $valueSql;
$dbTypes = [
'*' => reset($dbTypes),
...$dbTypes,
];
}

// when preparing the query, we sometimes need to prep custom values some more
$dbType = $field::dbType();
// for mysql, we have to make sure text column type is cast to char, otherwise it won't be sorted correctly
// see https://github.com/craftcms/cms/issues/15609
if ($db->getIsMysql() && is_string($dbType) && Db::parseColumnType($dbType) === Schema::TYPE_TEXT) {
$this->_columnsToCast[$field->handle] = 'CHAR(255)';
foreach ($dbTypes as $key => $dbType) {
$alias = $field->handle . ($key !== '*' ? ".$key" : '');
$resolver = fn() => $field->getValueSql($key !== '*' ? $key : null);

if (isset($this->_columnMap[$alias])) {
if (!is_array($this->_columnMap[$alias])) {
$this->_columnMap[$alias] = [$this->_columnMap[$alias]];
}
$this->_columnMap[$alias][] = $resolver;
} else {
$this->_columnMap[$alias] = $resolver;
}

// for mysql, we have to make sure text column type is cast to char, otherwise it won't be sorted correctly
// see https://github.com/craftcms/cms/issues/15609
if ($isMysql && Db::parseColumnType($dbType) === Schema::TYPE_TEXT) {
$this->_columnsToCast[$alias] = 'CHAR(255)';
}
}
}
}
Expand Down Expand Up @@ -3306,11 +3320,13 @@ private function _applyOrderByParams(YiiConnection $db): void
// (yes this is awkward but we need to preserve the order of the keys!)
$orderByColumns = array_keys($orderBy);

foreach ($this->_columnMap as $orderValue => $columnName) {
foreach (array_keys($this->_columnMap) as $orderValue) {
// Are we ordering by this column name?
$pos = array_search($orderValue, $orderByColumns, true);

if ($pos !== false) {
$columnName = $this->_resolveColumnMapping($orderValue);

// Swap it with the mapped column name
if (is_array($columnName)) {
$params = [];
Expand Down Expand Up @@ -3391,7 +3407,7 @@ private function _applySelectParam(): void
} else {
// Is this a mapped column name?
if (is_string($column) && isset($this->_columnMap[$column])) {
$column = $this->_columnMap[$column];
$column = $this->_resolveColumnMapping($column);

// Completely ditch the mapped name if instantiated elements are going to be returned
if (!$this->asArray && is_string($column)) {
Expand Down Expand Up @@ -3577,4 +3593,24 @@ private function _createElements(array $rows): array

return $elements;
}

private function _resolveColumnMapping(string $key): string|array
{
if (!isset($this->_columnMap[$key])) {
throw new InvalidArgumentException("Invalid column map key: $key");
}

// make sure it's not still a callback
if (is_callable($this->_columnMap[$key])) {
$this->_columnMap[$key] = $this->_columnMap[$key]();
} elseif (is_array($this->_columnMap[$key])) {
foreach ($this->_columnMap[$key] as $i => $mapping) {
if (is_callable($mapping)) {
$this->_columnMap[$key][$i] = $mapping();
}
}
}

return $this->_columnMap[$key];
}
}

0 comments on commit a6c770d

Please sign in to comment.