diff --git a/src/Typesense/Index.php b/src/Typesense/Index.php index 733f225..b54bc4c 100644 --- a/src/Typesense/Index.php +++ b/src/Typesense/Index.php @@ -160,6 +160,12 @@ public function getOrCreateIndex() return $collection; } + public function getTypesenseSchemaFields(): Collection + { + return collect(Arr::get($this->getOrCreateIndex()->retrieve(), 'fields', [])) + ->pluck('type', 'name'); + } + private function getDefaultFields(Searchable $entry): array { return [ diff --git a/src/Typesense/Query.php b/src/Typesense/Query.php index 7403ad4..2cf75b3 100644 --- a/src/Typesense/Query.php +++ b/src/Typesense/Query.php @@ -24,9 +24,90 @@ public function whereStatus($string) return $this->where('status', $string); } + private function wheresToFilter(array $wheres): string + { + $filterBy = ''; + + $schemaFields = $this->index->getTypesenseSchemaFields(); + + foreach ($this->wheres as $where) { + if ($filterBy != '') { + $filterBy .= $where['boolean'] == 'and' ? ' && ' : ' || '; + } + + if ($where['type'] == 'Nested') { + $filterBy .= ' ( '.$this->wheresToFilter($where->query['wheres']).' ) '; + + continue; + } + + // if its not in our typesense schema, we cant filter on it + if (! $schemaType = $schemaFields->get($where['column'])) { + continue; + } + + $filterBy .= ' ( '; + + switch ($where['type']) { + case 'JsonContains': + case 'JsonOverlaps': + case 'WhereIn': + $filterBy .= $where['column'].':'.$this->transformArrayOfValuesForTypeSense($schemaType, $where['values']); + break; + + case 'JsonDoesnContain': + case 'JsonDoesntOverlap': + case 'WhereNotIn': + $filterBy .= $where['column'].':!='.$this->transformArrayOfValuesForTypeSense($schemaType, $where['values']); + break; + + default: + if ($where['operator'] == 'like') { + $where['value'] = str_replace(['%"', '"%'], '', $where['value']); + } + + $filterBy .= $where['column'].':'.(! in_array($where['operator'], ['like']) ? $where['operator'] : '').$this->transformValueForTypeSense($schemaType, $where['value']); + break; + } + + $filterBy .= ' ) '; + + } + + return $filterBy; + } + + private function transformArrayOfValuesForTypeSense(string $schemaType, array $values): string + { + return json_encode( + collect($values) + ->map(fn ($value) => $this->transformValueForTypeSense($schemaType, $value)) + ->values() + ->all() + ); + } + + private function transformValueForTypeSense(string $schemaType, mixed $value): mixed + { + return match (str_replace('[]', '', $schemaType)) { + 'int32', 'int64' => (int) $value, + 'float' => (float) $value, + 'bool' => $value ? 'true' : 'false', + default => '`'.$value.'`' + }; + } + private function getApiResults() { - return $this->index->searchUsingApi($this->query ?? '', ['per_page' => $this->perPage, 'page' => $this->page]); + $options = ['per_page' => $this->perPage, 'page' => $this->page]; + + $filterBy = $this->wheresToFilter($this->wheres); + + if ($filterBy) { + $options['filter_by'] = $filterBy; + } + + return $this->index->searchUsingApi($this->query ?? '', $options); } public function forPage($page, $perPage = null)