Skip to content

Commit a5b5b4d

Browse files
committed
Merge remote-tracking branch 'origin/dev' into dev-l9
2 parents 283f779 + 6c42ed0 commit a5b5b4d

File tree

6 files changed

+329
-40
lines changed

6 files changed

+329
-40
lines changed

README.md

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -48,18 +48,6 @@ Next, [Configuration](https://elasticsearch.pdphilip.com/#configuration)
4848

4949
---
5050

51-
# New in version 3
52-
53-
- [whereExact()](https://elasticsearch.pdphilip.com/es-specific#where-exact)
54-
- [wherePhrase()](https://elasticsearch.pdphilip.com/es-specific#where-phrase)
55-
- [whereNestedObject()](https://elasticsearch.pdphilip.com/es-specific#where-nested-object)
56-
- [whereNotNestedObject()](https://elasticsearch.pdphilip.com/es-specific#where-not-nested-object)
57-
- [firstOrCreate()](https://elasticsearch.pdphilip.com/saving-models#first-or-create)
58-
- [firstOrCreateWithoutRefresh()](https://elasticsearch.pdphilip.com/saving-models#first-or-create-without-refresh)
59-
- [whereNested()](https://elasticsearch.pdphilip.com/querying-models#where-nested)
60-
61-
---
62-
6351
# Documentation Links
6452

6553
## Getting Started
@@ -90,3 +78,38 @@ Next, [Configuration](https://elasticsearch.pdphilip.com/#configuration)
9078

9179
- [Migrations](https://elasticsearch.pdphilip.com/migrations)
9280
- [Re-indexing Process](https://elasticsearch.pdphilip.com/re-indexing)
81+
82+
---
83+
84+
# New in Version 3
85+
86+
### Nested Queries [(see)](https://elasticsearch.pdphilip.com/nested-queries)
87+
88+
This update introduces support for querying, sorting and filtering nested data
89+
90+
- [Nested Object Queries](https://elasticsearch.pdphilip.com/nested-queries#where-nested-object)
91+
- [Order By Nested](https://elasticsearch.pdphilip.com/nested-queries#order-by-nested-field)
92+
- [Filter Nested Values](https://elasticsearch.pdphilip.com/nested-queries#filtering-nested-values): Filters nested values of the parent collection
93+
94+
### New `Where` clauses
95+
96+
- [Phrase Matching](https://elasticsearch.pdphilip.com/es-specific#where-phrase): The enhancement in phrase matching capabilities allows for refined search precision, facilitating the targeting of exact word sequences within textual
97+
fields, thus improving search specificity
98+
and relevance.
99+
- [Exact Matching](https://elasticsearch.pdphilip.com/es-specific#where-exact): Strengthening exact match queries enables more stringent search criteria, ensuring the retrieval of documents that precisely align with specified parameters.
100+
101+
### Sorting Enhancements
102+
103+
- [Ordering with ES features](https://elasticsearch.pdphilip.com/ordering-and-pagination#extending-ordering-for-elasticsearch-features): Includes modes and missing values for sorting fields.
104+
- [Order by Geo Distance](https://elasticsearch.pdphilip.com/ordering-and-pagination#order-by-geo-distance)
105+
106+
### Saving Updates
107+
108+
- [First Or Create](https://elasticsearch.pdphilip.com/saving-models#first-or-create)
109+
- [First Or Create without Refresh](https://elasticsearch.pdphilip.com/saving-models#first-or-create-without-refresh)
110+
111+
### Grouped Queries
112+
113+
- [Grouped Queries](https://elasticsearch.pdphilip.com/querying-models#grouped-queries): Queries can be grouped allowing multiple conditions to be nested within a single query block.
114+
115+
---

src/DSL/Bridge.php

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,13 @@ public function processDistinct($wheres, $options, $columns, $includeDocCount =
142142
unset($options['skip']);
143143
unset($options['limit']);
144144

145+
if ($sort) {
146+
$sortField = key($sort);
147+
$sortDir = $sort[$sortField]['order'] ?? 'asc';
148+
$sort = [$sortField => $sortDir];
149+
}
150+
151+
145152
$params = $this->buildParams($this->index, $wheres, $options);
146153
$params['body']['aggs'] = $this->createNestedAggs($columns, $sort);
147154

@@ -826,6 +833,7 @@ private function _sanitizeSearchResponse($response, $params, $queryTag)
826833
$meta['timed_out'] = $response['timed_out'];
827834
$meta['total'] = $response['hits']['total']['value'] ?? 0;
828835
$meta['max_score'] = $response['hits']['max_score'] ?? 0;
836+
$meta['sorts'] = [];
829837
$data = [];
830838
if (!empty($response['hits']['hits'])) {
831839
foreach ($response['hits']['hits'] as $hit) {
@@ -837,13 +845,55 @@ private function _sanitizeSearchResponse($response, $params, $queryTag)
837845
$datum[$key] = $value;
838846
}
839847
}
840-
$data[] = $datum;
848+
if (!empty($hit['inner_hits'])) {
849+
foreach ($hit['inner_hits'] as $innerKey => $innerHit) {
850+
$datum[$innerKey] = $this->_filterInnerHits($innerHit);
851+
}
852+
}
853+
854+
//------------------------ later, maybe ------------------------------
855+
// if (!empty($hit['sort'])) {
856+
// $datum['_meta']['sort'] = $this->_parseSort($hit['sort'], $params['body']['sort'] ?? []);
857+
// }
858+
//----------------------------------------------------------------------
859+
860+
861+
{
862+
$data[] = $datum;
863+
}
841864
}
842865
}
843866

844867
return $this->_return($data, $meta, $params, $queryTag);
845868
}
846869

870+
private function _filterInnerHits($innerHit)
871+
{
872+
$hits = [];
873+
foreach ($innerHit['hits']['hits'] as $inner) {
874+
$innerDatum = [];
875+
if (!empty($inner['_source'])) {
876+
foreach ($inner['_source'] as $innerSourceKey => $innerSourceValue) {
877+
$innerDatum[$innerSourceKey] = $innerSourceValue;
878+
}
879+
}
880+
$hits[] = $innerDatum;
881+
}
882+
883+
return $hits;
884+
}
885+
886+
887+
private function _parseSort($sort, $sortParams)
888+
{
889+
$sortValues = [];
890+
foreach ($sort as $key => $value) {
891+
$sortValues[array_key_first($sortParams[$key])] = $value;
892+
}
893+
894+
return $sortValues;
895+
}
896+
847897
private function _sanitizeDistinctResponse($response, $columns, $includeDocCount)
848898
{
849899
$keys = [];

src/DSL/ParameterBuilder.php

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,63 @@ public static function query($dsl): array
3333
}
3434

3535

36-
public static function fieldSort($field, $order = 'asc'): array
36+
public static function fieldSort($field, $payload): array
3737
{
38+
if (!empty($payload['is_geo'])) {
39+
return self::fieldSortGeo($field, $payload);
40+
}
41+
if (!empty($payload['is_nested'])) {
42+
return self::filterNested($field, $payload);
43+
}
44+
$sort = [];
45+
$sort['order'] = $payload['order'] ?? 'asc';
46+
if (!empty($payload['mode'])) {
47+
$sort['mode'] = $payload['mode'];
48+
}
49+
if (!empty($payload['missing'])) {
50+
$sort['missing'] = $payload['missing'];
51+
}
52+
3853
return [
39-
$field => [
40-
'order' => $order,
41-
],
54+
$field => $sort,
55+
];
56+
}
57+
58+
public static function fieldSortGeo($field, $payload): array
59+
{
60+
$sort = [];
61+
$sort[$field] = $payload['pin'];
62+
$sort['order'] = $payload['order'] ?? 'asc';
63+
$sort['unit'] = $payload['unit'] ?? 'km';
64+
65+
if (!empty($payload['mode'])) {
66+
$sort['mode'] = $payload['mode'];
67+
}
68+
if (!empty($payload['type'])) {
69+
$sort['distance_type'] = $payload['type'];
70+
}
71+
72+
return [
73+
'_geo_distance' => $sort,
74+
];
75+
}
76+
77+
public static function filterNested($field, $payload)
78+
{
79+
$sort = [];
80+
$pathParts = explode('.', $field);
81+
$path = $pathParts[0];
82+
$sort['order'] = $payload['order'] ?? 'asc';
83+
if (!empty($payload['mode'])) {
84+
$sort['mode'] = $payload['mode'];
85+
}
86+
$sort['nested'] = [
87+
'path' => $path,
88+
];
89+
90+
91+
return [
92+
$field => $sort,
4293
];
4394
}
4495

src/DSL/QueryBuilder.php

Lines changed: 66 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ public function buildParams($index, $wheres, $options = [], $columns = [], $_id
111111
$params = $this->_parseFilterParameter($params, self::$filter);
112112
self::$filter = [];
113113
}
114+
115+
// dd($params);
114116

115117
return $params;
116118
}
@@ -129,14 +131,14 @@ public function createNestedAggs($columns, $sort)
129131
if (!isset($terms['terms']['order'])) {
130132
$terms['terms']['order'] = [];
131133
}
132-
if ($sort['_count'] == 1) {
134+
if ($sort['_count'] == 'asc') {
133135
$terms['terms']['order'][] = ['_count' => 'asc'];
134136
} else {
135137
$terms['terms']['order'][] = ['_count' => 'desc'];
136138
}
137139
}
138140
if (isset($sort[$columns[0]])) {
139-
if ($sort[$columns[0]] == 1) {
141+
if ($sort[$columns[0]] == 'asc') {
140142
$terms['terms']['order'][] = ['_key' => 'asc'];
141143
} else {
142144
$terms['terms']['order'][] = ['_key' => 'desc'];
@@ -205,15 +207,15 @@ private function _buildQuery($wheres): array
205207
}
206208

207209

208-
public function _convertWheresToDSL($wheres): array
210+
public function _convertWheresToDSL($wheres, $parentField = false): array
209211
{
210212
$dsl = ['bool' => []];
211213
foreach ($wheres as $logicalOperator => $conditions) {
212214
switch ($logicalOperator) {
213215
case 'and':
214216
$dsl['bool']['must'] = [];
215217
foreach ($conditions as $condition) {
216-
$parsedCondition = $this->_parseCondition($condition);
218+
$parsedCondition = $this->_parseCondition($condition, $parentField);
217219
if (!empty($parsedCondition)) {
218220
$dsl['bool']['must'][] = $parsedCondition;
219221
}
@@ -225,7 +227,7 @@ public function _convertWheresToDSL($wheres): array
225227
$boolClause = ['bool' => ['must' => []]];
226228
foreach ($conditionGroup as $subConditions) {
227229
foreach ($subConditions as $subCondition) {
228-
$parsedCondition = $this->_parseCondition($subCondition);
230+
$parsedCondition = $this->_parseCondition($subCondition, $parentField);
229231
if (!empty($parsedCondition)) {
230232
$boolClause['bool']['must'][] = $parsedCondition;
231233
}
@@ -237,17 +239,22 @@ public function _convertWheresToDSL($wheres): array
237239
}
238240
break;
239241
default:
240-
return $this->_parseCondition($wheres);
242+
return $this->_parseCondition($wheres, $parentField);
241243
}
242244
}
243245

244246
return $dsl;
245247
}
246248

247-
private function _parseCondition($condition): array
249+
private function _parseCondition($condition, $parentField = null): array
248250
{
249-
// dd($condition);
250251
$field = key($condition);
252+
if ($parentField) {
253+
if (!str_starts_with($field, $parentField.'.')) {
254+
$field = $parentField.'.'.$field;
255+
}
256+
}
257+
251258
$value = current($condition);
252259

253260

@@ -345,7 +352,7 @@ private function _parseCondition($condition): array
345352
$queryPart = [
346353
'nested' => [
347354
'path' => $field,
348-
'query' => $this->_convertWheresToDSL($operand['wheres']),
355+
'query' => $this->_convertWheresToDSL($operand['wheres'], $field),
349356
'score_mode' => $operand['score_mode'],
350357
],
351358
];
@@ -365,6 +372,26 @@ private function _parseCondition($condition): array
365372
],
366373

367374
];
375+
break;
376+
case 'innerNested':
377+
$options = $this->_buildNestedOptions($operand['options'], $field);
378+
if (!$options) {
379+
$options['size'] = 100;
380+
}
381+
$query = ParameterBuilder::matchAll()['query'];
382+
if (!empty($operand['wheres'])) {
383+
$query = $this->_convertWheresToDSL($operand['wheres'], $field);
384+
// $options['query'] = $query;
385+
// dd($query);
386+
}
387+
$queryPart = [
388+
'nested' => [
389+
'path' => $field,
390+
'query' => $query,
391+
'inner_hits' => $options,
392+
],
393+
];
394+
368395
break;
369396
default:
370397
abort('400', 'Invalid operator ['.$operator.'] provided for condition.');
@@ -390,8 +417,8 @@ private function _buildOptions($options): array
390417
if (!isset($return['body']['sort'])) {
391418
$return['body']['sort'] = [];
392419
}
393-
foreach ($value as $field => $direction) {
394-
$return['body']['sort'][] = $this->_parseSortOrder($field, $direction);
420+
foreach ($value as $field => $sortPayload) {
421+
$return['body']['sort'][] = ParameterBuilder::fieldSort($field, $sortPayload);
395422
}
396423
break;
397424
case 'skip':
@@ -405,8 +432,10 @@ private function _buildOptions($options): array
405432
$this->_parseFilter($filterType, $filerValues);
406433
}
407434
break;
435+
408436
case 'multiple':
409437
case 'searchOptions':
438+
410439
//Pass through
411440
break;
412441
default:
@@ -418,6 +447,32 @@ private function _buildOptions($options): array
418447
return $return;
419448
}
420449

450+
private function _buildNestedOptions($options, $field)
451+
{
452+
$options = $this->_buildOptions($options);
453+
if (!empty($options['body'])) {
454+
$body = $options['body'];
455+
unset($options['body']);
456+
$options = array_merge($options, $body);
457+
}
458+
if (!empty($options['sort'])) {
459+
//ensure that the sort field is prefixed with the nested field
460+
$sorts = [];
461+
foreach ($options['sort'] as $sort) {
462+
foreach ($sort as $sortField => $sortPayload) {
463+
if (!str_starts_with($sortField, $field.'.')) {
464+
$sortField = $field.'.'.$sortField;
465+
}
466+
$sorts[] = [$sortField => $sortPayload];
467+
}
468+
}
469+
470+
$options['sort'] = $sorts;
471+
}
472+
473+
return $options;
474+
}
475+
421476
public function _parseFilter($filterType, $filterPayload): void
422477
{
423478
switch ($filterType) {
@@ -440,15 +495,6 @@ public function _parseFilter($filterType, $filterPayload): void
440495
}
441496
}
442497

443-
private function _parseSortOrder($field, $direction): array
444-
{
445-
$dir = 'desc';
446-
if ($direction == 1) {
447-
$dir = 'asc';
448-
}
449-
450-
return ParameterBuilder::fieldSort($field, $dir);
451-
}
452498

453499
public function _parseFilterParameter($params, $filer)
454500
{

0 commit comments

Comments
 (0)