diff --git a/src/Api/HL/Doc/Schema.php b/src/Api/HL/Doc/Schema.php index af6809be8d2..eea33bc780b 100644 --- a/src/Api/HL/Doc/Schema.php +++ b/src/Api/HL/Doc/Schema.php @@ -213,9 +213,9 @@ public static function getJoins(array $props, string $prefix = ''): array $joins = []; foreach ($props as $name => $prop) { if ($prop['type'] === self::TYPE_OBJECT && isset($prop['x-join'])) { - $joins[$name] = $prop['x-join'] + ['parent_type' => self::TYPE_OBJECT]; + $joins[$prefix . $name] = $prop['x-join'] + ['parent_type' => self::TYPE_OBJECT]; } else if ($prop['type'] === self::TYPE_ARRAY && isset($prop['items']['x-join'])) { - $joins[$name] = $prop['items']['x-join'] + ['parent_type' => self::TYPE_ARRAY]; + $joins[$prefix . $name] = $prop['items']['x-join'] + ['parent_type' => self::TYPE_ARRAY]; } else if ($prop['type'] === self::TYPE_OBJECT && isset($prop['properties'])) { $joins += self::getJoins($prop['properties'], $prefix . $name . '.'); } diff --git a/src/Api/HL/Search.php b/src/Api/HL/Search.php index 58289136fa0..170ff4010f6 100644 --- a/src/Api/HL/Search.php +++ b/src/Api/HL/Search.php @@ -82,18 +82,27 @@ private function __construct(array $schema, array $request_params) private function getSQLFieldForProperty(string $prop_name): string { - $prop = $this->flattened_properties[$prop_name]; - $is_join = str_contains($prop_name, '.') && array_key_exists(explode('.', $prop_name)[0], $this->joins); - $sql_field = $prop['x-field'] ?? $prop_name; + $real_prop_name = str_replace(chr(0x1F), '.', $prop_name); + $prop = $this->flattened_properties[$real_prop_name]; + $is_join = str_contains($real_prop_name, '.') && array_key_exists(explode('.', $real_prop_name)[0], $this->joins); + $sql_field = $prop['x-field'] ?? $real_prop_name; if (!$is_join) { // Only add the _. prefix if it isn't a join $sql_field = "_.$sql_field"; - } else if ($prop_name !== $sql_field) { + } else if ($real_prop_name !== $sql_field) { // If the property name is different from the SQL field name, we will need to add/change the table alias // $prop_name is a join where the part before the dot is the join alias (also the property on the main item), and the part after the dot is the property on the joined item - $join_alias = explode('.', $prop_name)[0]; + $parts = explode('.', $prop_name); + array_pop($parts); // Get rid of the field + $join_alias = implode('.', $parts); $sql_field = "{$join_alias}.{$sql_field}"; } + $parts = explode('.', $sql_field); + if (count($parts) > 2) { + $field = array_pop($parts); + $table = implode(chr(0x1F), $parts); + $sql_field = "{$table}.{$field}"; + } return $sql_field; } @@ -105,6 +114,7 @@ private function getSelectCriteriaForProperty(string $prop_name, bool $distinct_ { global $DB; + $prop_name = str_replace(chr(0x1F), '.', $prop_name); $prop = $this->flattened_properties[$prop_name]; if ($prop['x-writeonly'] ?? false) { // Do not expose write-only fields @@ -164,6 +174,7 @@ private static function getJoins(string $join_alias, array $join_definition): ar if (!isset($joins[$join_type])) { $joins[$join_type] = []; } + $join_alias = str_replace('.', chr(0x1F), $join_alias); $join_table = $join['table'] . ' AS ' . $join_alias; $join_parent = (isset($join['ref_join']) && $join['ref_join']) ? "{$join_alias}_ref" : '_'; if (isset($join['ref_join'])) { @@ -390,6 +401,7 @@ private function getMatchingRecords($ignore_pagination = false): array $criteria['GROUPBY'] = ['_itemtype', '_.id']; } else { foreach ($this->joins as $join_alias => $join) { + $join_alias = str_replace('.', chr(0x1F), $join_alias); $s = $this->getSelectCriteriaForProperty("$join_alias.id", true); if ($s !== null) { $criteria['SELECT'][] = $s; @@ -495,6 +507,11 @@ private function hydrateRecords(array $records): array } } else { foreach ($this->joins as $join_alias => $join) { + $parts = explode(chr(0x1F), $fkey); + if (count($parts) > 1) { + $field = array_pop($parts); + $fkey = implode('.', $parts) . chr(0x1F) . $field; + } if ($fkey === $join_alias . chr(0x1F) . 'id') { $fkey_tables[$fkey] = $join['table']; break; @@ -543,9 +560,12 @@ private function hydrateRecords(array $records): array $criteria['SELECT'][] = new QueryExpression($DB::quoteValue($schema_name), '_itemtype'); } } else { - $join_name = explode(chr(0x1F), $fkey)[0]; - $props_to_use = array_filter($this->flattened_properties, static function ($prop_name) use ($join_name) { - return str_starts_with($prop_name, $join_name . '.'); + $join_name_parts = explode(chr(0x1F), $fkey); + array_pop($join_name_parts); + $join_name = implode(chr(0x1F), $join_name_parts); + $join_name_dotted = str_replace(chr(0x1F), '.', $join_name); + $props_to_use = array_filter($this->flattened_properties, static function ($prop_name) use ($join_name_dotted) { + return str_starts_with($prop_name, $join_name_dotted . '.'); }, ARRAY_FILTER_USE_KEY); $criteria['FROM'] = "$table AS " . $DB::quoteName($join_name); @@ -553,7 +573,7 @@ private function hydrateRecords(array $records): array } $criteria['WHERE'] = [$id_field => $ids_to_fetch]; foreach ($props_to_use as $prop_name => $prop) { - if ($prop['x-writeonly'] ?? false) { + if ($prop['x-writeonly'] ?? false || isset($prop['x-mapped-from'])) { continue; } $sql_field = $this->getSQLFieldForProperty($prop_name);