Skip to content

Commit a3a88ab

Browse files
committed
Improved accessor for arrays with fixed parsing at FQL and added new COMBINE function
1 parent a51cde9 commit a3a88ab

File tree

10 files changed

+136
-9
lines changed

10 files changed

+136
-9
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
## [2.0.1] Unreleased
44

55
- Fixed issue with parsing `EXCLUDE` clause
6+
- Improved accessor `[]->key` now supports associative arrays by wrapping them into a single-item list, allowing uniform iteration behavior.
67
- Replace some string by constant at sql parser
8+
- Added new function `COMBINE` for combining two arrays
79

810
## [2.0.0]
911

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
![Packagist Dependency Version](https://img.shields.io/packagist/dependency-v/1biot/fiquela/php)
88
![Packagist License](https://img.shields.io/packagist/l/1biot/fiquela)
99

10-
![Static Badge](https://img.shields.io/badge/PHPUnit-tests%3A_171-lightgreen)
11-
![Static Badge](https://img.shields.io/badge/PHPUnit-asserts%3A_596-lightgreen)
10+
![Static Badge](https://img.shields.io/badge/PHPUnit-tests%3A_174-lightgreen)
11+
![Static Badge](https://img.shields.io/badge/PHPUnit-asserts%3A_599-lightgreen)
1212
![Static Badge](https://img.shields.io/badge/PHPStan_6-OK-lightgreen)
1313
![Static Badge](https://img.shields.io/badge/PHPStan_7|8-16_errors-orange)
1414

docs/file-query-language.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -199,18 +199,20 @@ ORDER BY _score DESC
199199

200200
### Utils functions
201201

202-
| Function | Description |
203-
|-----------------|--------------------------------------------------------|
204-
| `COALESCE` | Coalesce values (first non-null value) |
205-
| `COALESCE_NE` | Coalesce values when not empty (first non-empty value) |
206-
| `RANDOM_BYTES` | Generates cryptographically secure random bytes. |
202+
| Function | Description |
203+
|----------------|--------------------------------------------------------|
204+
| `COALESCE` | Coalesce values (first non-null value) |
205+
| `COALESCE_NE` | Coalesce values when not empty (first non-empty value) |
206+
| `COMBINE` | Combine two arrays into a single array |
207+
| `RANDOM_BYTES` | Generates cryptographically secure random bytes. |
207208

208209
**Examples:**
209210

210211
```sql
211212
SELECT
212213
COALESCE(NULL, 'Hello World') AS coalesce,
213214
COALESCE_NE(NULL, 'Hello World') AS coalesceNe,
215+
COMBINE('filedWitArrayKeys', 'FieldWithArrayValues') AS combined,
214216
RANDOM_BYTES(16) AS randomBytes
215217
FROM [jsonFile](./examples/data/products.tmp).data.products
216218
```

docs/fluent-api.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,15 @@ $query->concat('ArticleNr', 'CatalogNr')->as('CONCAT')
118118
|--------------------|--------------------------------------------------------|
119119
| `coalesce` | Coalesce values (first non-null value) |
120120
| `coalesceNotEmpty` | Coalesce values when not empty (first non-empty value) |
121+
| `combine` | Combine two arrays into a single array |
121122
| `randomBytes` | Generates cryptographically secure random bytes. |
122123

123124
**Example:**
124125

125126
```php
126127
$query->coalesce('whatever', 'ArticleNr')->as('COALESCE')
127128
->coalesceNotEmpty('whatever', 'ArticleNr')->as('COALESCE_NE')
129+
->combine('fieldWithArrayKeys', 'fieldWithArrayValues')
128130
->randomBytes(16)->as('RANDOM_BYTES');
129131
```
130132

src/Functions/Utils/Combine.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
namespace FQL\Functions\Utils;
4+
5+
use FQL\Functions;
6+
use FQL\Traits;
7+
8+
class Combine extends Functions\Core\MultipleFieldsFunction
9+
{
10+
use Traits\Helpers\StringOperations;
11+
12+
public function __construct(private string $keysArrayField, private string $valueArrayField)
13+
{
14+
parent::__construct($keysArrayField, $valueArrayField);
15+
}
16+
17+
/**
18+
* @inheritDoc
19+
* @return array<int|string, mixed>|null
20+
*/
21+
public function __invoke(array $item, array $resultItem): ?array
22+
{
23+
$keys = $this->getFieldValue($this->keysArrayField, $item, $resultItem);
24+
$values = $this->getFieldValue($this->valueArrayField, $item, $resultItem);
25+
26+
if (
27+
!is_array($keys)
28+
|| !is_array($values)
29+
) {
30+
return null;
31+
}
32+
33+
if ($this->isAssoc($keys)) {
34+
$keys = array_values($keys);
35+
}
36+
37+
if ($this->isAssoc($values)) {
38+
$values = array_values($values);
39+
}
40+
41+
return array_combine($keys, $values);
42+
}
43+
}

src/Interface/Query.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,8 @@ public function fulltext(array $fields, string $searchQuery): Query;
448448
/** @param string[] $fields */
449449
public function matchAgainst(array $fields, string $searchQuery, ?Fulltext $mode = null): Query;
450450

451+
public function combine(string $keysArrayField, string $valueArrayField): Query;
452+
451453
/**
452454
* Specify a specific part of the data to select.
453455
*

src/Sql/Sql.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ private function parseFields(Interface\Query $query): void
175175

176176
if (strtoupper($field) === Interface\Query::EXCLUDE) {
177177
$mode = 'selectOut';
178-
$field = $this->nextToken();
178+
continue;
179179
}
180180

181181
if ($mode === 'selectOut') {
@@ -184,6 +184,12 @@ private function parseFields(Interface\Query $query): void
184184
if ($this->isFunction($field)) {
185185
$this->applyFunctionToQuery($field, $query);
186186
} else {
187+
$nextToken = $this->nextToken();
188+
if (str_starts_with($nextToken, '[]->')) {
189+
$field .= $nextToken;
190+
} else {
191+
$this->rewindToken();
192+
}
187193
$query->select($field);
188194
}
189195

@@ -290,6 +296,7 @@ private function applyFunctionToQuery(string $field, Interface\Query $query): vo
290296
)
291297
),
292298
'RANDOM_BYTES' => $query->randomBytes((int) ($arguments[0] ?? 10)),
299+
'COMBINE' => $query->combine((string) ($arguments[0] ?? ''), (string) ($arguments[1] ?? '')),
293300
'MATCH' => $this->processMatchFunction($query, $arguments),
294301
default => throw new Exception\UnexpectedValueException("Unknown function: $functionName"),
295302
};

src/Traits/Helpers/NestedArrayAccessor.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,10 @@ public function accessNestedValue(array $data, string $field, bool $throwOnMissi
6060
$subKey = $matches[2];
6161

6262
$nestedArray = $this->accessNestedValue($data, $arrayPath, $throwOnMissing);
63-
6463
if (!is_array($nestedArray)) {
6564
throw new InvalidArgumentException(sprintf('Field "%s" is not iterable or does not exist', $arrayPath));
65+
} elseif ($this->isAssoc($nestedArray)) {
66+
$nestedArray = [$nestedArray];
6667
}
6768

6869
return array_map(fn($item) => $item[$subKey] ?? null, $nestedArray);
@@ -105,4 +106,13 @@ public function removeNestedValue(array &$data, string $field): void
105106

106107
unset($ref[$lastKey]);
107108
}
109+
110+
/**
111+
* @param array<int|string, mixed> $array
112+
* @return bool
113+
*/
114+
public function isAssoc(array $array): bool
115+
{
116+
return array_keys($array) !== range(0, count($array) - 1);
117+
}
108118
}

src/Traits/Select.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,11 @@ public function matchAgainst(array $fields, string $searchQuery, ?Enum\Fulltext
273273
return $this->fulltext($fields, $searchQuery);
274274
}
275275

276+
public function combine(string $keysArrayField, string $valueArrayField): Interface\Query
277+
{
278+
return $this->addFieldFunction(new Functions\Utils\Combine($keysArrayField, $valueArrayField));
279+
}
280+
276281
private function addFieldFunction(
277282
Core\BaseFunction|Core\AggregateFunction|Core\NoFieldFunction $function
278283
): Interface\Query {
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
namespace Functions\Utils;
4+
5+
use FQL\Functions\Utils\Combine;
6+
use PHPUnit\Framework\TestCase;
7+
8+
class CombineTest extends TestCase
9+
{
10+
public function testCombine(): void
11+
{
12+
$combine = new Combine('keys', 'values');
13+
$this->assertEquals(
14+
['a' => 1, 'b' => 2],
15+
$combine(
16+
[
17+
'keys' => ['a', 'b'],
18+
'values' => [1, 2],
19+
],
20+
[]
21+
)
22+
);
23+
}
24+
25+
public function testCombineWithAssociativeKeys(): void
26+
{
27+
$combine = new Combine('keys', 'values');
28+
$this->assertEquals(
29+
['x' => 1, 'y' => 2],
30+
$combine(
31+
[
32+
'keys' => ['a' => 'x', 'b' => 'y'],
33+
'values' => [1, 2],
34+
],
35+
[]
36+
)
37+
);
38+
}
39+
40+
public function testInvalidKeys(): void
41+
{
42+
$combine = new Combine('keys', 'values');
43+
$this->assertEquals(
44+
null,
45+
$combine(
46+
[
47+
'keys' => 'invalid',
48+
'values' => [1, 2],
49+
],
50+
[]
51+
)
52+
);
53+
}
54+
}

0 commit comments

Comments
 (0)