Skip to content

Commit 420f982

Browse files
committed
Added new array_merge and format_date functions, renamed combine function to array_combine and recognizing datetime in result and create DateTimeImmutable object
1 parent 2b41604 commit 420f982

File tree

16 files changed

+290
-44
lines changed

16 files changed

+290
-44
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Changelog
22

3+
## [2.0.2]
4+
5+
- Rename `COMBINE` to `ARRAY_COMBINE`
6+
- Added new function `ARRAY_MERGE` for merging two arrays
7+
- Automatically recognize date from selected string and cast it to `\DateTimeImmutable`
8+
- When `\DateTimeImmutable` casting to the string, it will be formatted to `c` format (`Y-m-d\TH:i:sP`)
9+
- Added new function `DATE_FORMAT` for formatting `\DateTimeImmutable` to string
10+
311
## [2.0.1]
412

513
- Fixed issue with parsing `EXCLUDE` clause

README.md

Lines changed: 5 additions & 4 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_174-lightgreen)
11-
![Static Badge](https://img.shields.io/badge/PHPUnit-asserts%3A_599-lightgreen)
10+
![Static Badge](https://img.shields.io/badge/PHPUnit-tests%3A_180-lightgreen)
11+
![Static Badge](https://img.shields.io/badge/PHPUnit-asserts%3A_605-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

@@ -22,7 +22,7 @@ various sources, **F**i**Q**ue**L**a provides a seamless way to manipulate and e
2222
- 📂 **Supports multiple formats**: Work seamlessly with XML, CSV, JSON, NDJSON, YAML, and NEON.
2323
- 🛠️ **SQL-inspired syntax**: Perform `SELECT`, `JOIN`, `WHERE`, `GROUP BY`, `ORDER BY` and more.
2424
- ✍️ **Flexible Querying**: Write SQL-like strings or use the fluent API for maximum flexibility.
25-
- 📊 **Advanced functions**: Access features like `SUM`, `COUNT`, `AVG`, `GROUP_CONCAT`, `COMBINE`, `MD5` and many more.
25+
- 📊 **Advanced functions**: Access features like `SUM`, `COUNT`, `GROUP_CONCAT`, `ARRAY_MERGE`, `FORMAT_DATE` and many more.
2626
- 🚀 **Efficient with Large Files**: Optimized for processing JSON, XML, and CSV files with tens of thousands of rows using stream processing.
2727
- 🧑‍💻 **Developer-Friendly**: Map results to DTOs for easier data manipulation.
2828
-**Unified API across all supported formats**: Use a consistent API for all your data needs.
@@ -295,7 +295,8 @@ to load all data into memory. It may cause memory issues for large datasets. But
295295
## 8. Planning Features
296296

297297
- [ ] **Operator BETWEEN**: Add operator `BETWEEN` for filtering data and add support for dates and ranges.
298-
- [ ] **Next file formats**: Add next file formats [MessagePack](https://msgpack.org/), [INI](https://en.wikipedia.org/wiki/INI_file), [XLSX](https://en.wikipedia.org/wiki/Excel_Open_XML_file_formats) and [TOML](https://toml.io/en/)
298+
- [ ] **Next file formats**: Add next file formats [MessagePack](https://msgpack.org/), [XLSX](https://en.wikipedia.org/wiki/Excel_Open_XML_file_formats), [INI](https://en.wikipedia.org/wiki/INI_file) and [TOML](https://toml.io/en/)
299+
- [ ] **Custom cast type**: Add support for custom cast type for `SELECT` clause.
299300
- [ ] **Documentation**: Create detailed guides and examples for advanced use cases.
300301
- [ ] **Add explain method**: Add method `explain()` for explaining query execution from actual query debugger and provide more complex information about query.
301302
- [ ] **PHPStan 8**: Fix all PHPStan 8 errors.

docs/file-query-language.md

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -199,21 +199,25 @@ 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-
| `COMBINE` | Combine two arrays into a single array |
207-
| `RANDOM_BYTES` | Generates cryptographically secure random bytes. |
202+
| Function | Description |
203+
|-----------------|-----------------------------------------------------------------------|
204+
| `ARRAY_COMBINE` | Combine two array with keys and array with values into a single array |
205+
| `ARRAY_MERGE` | Merge two arrays into a single array |
206+
| `COALESCE` | Coalesce values (first non-null value) |
207+
| `COALESCE_NE` | Coalesce values when not empty (first non-empty value) |
208+
| `FORMAT_DATE` | Format date field to string |
209+
| `RANDOM_BYTES` | Generates cryptographically secure random bytes. |
208210

209211
**Examples:**
210212

211213
```sql
212214
SELECT
213-
COALESCE(NULL, 'Hello World') AS coalesce,
214-
COALESCE_NE(NULL, 'Hello World') AS coalesceNe,
215-
COMBINE('filedWitArrayKeys', 'FieldWithArrayValues') AS combined,
216-
RANDOM_BYTES(16) AS randomBytes
215+
ARRAY_COMBINE(filedWitArrayKeys, FieldWithArrayValues) AS arrayCombine,
216+
ARRAY_MERGE(fieldWithArray1, fieldWithArray2) AS arrayMerge,
217+
COALESCE(NULL, 'Hello World') AS coalesce,
218+
COALESCE_NE(NULL, 'Hello World') AS coalesceNe,
219+
FORMAT_DATE(dateField, 'Y-m-d') AS dateFormat,
220+
RANDOM_BYTES(16) AS randomBytes
217221
FROM [jsonFile](./examples/data/products.tmp).data.products
218222
```
219223

docs/fluent-api.md

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -114,19 +114,23 @@ $query->concat('ArticleNr', 'CatalogNr')->as('CONCAT')
114114

115115
### Utils functions
116116

117-
| Function | Description |
118-
|--------------------|--------------------------------------------------------|
119-
| `coalesce` | Coalesce values (first non-null value) |
120-
| `coalesceNotEmpty` | Coalesce values when not empty (first non-empty value) |
121-
| `combine` | Combine two arrays into a single array |
122-
| `randomBytes` | Generates cryptographically secure random bytes. |
117+
| Function | Description |
118+
|--------------------|------------------------------------------------------------------------|
119+
| `arrayCombine` | Combine two array with keys and array with values into a single array |
120+
| `arrayMerge` | Merge two arrays into a single array |
121+
| `coalesce` | Coalesce values (first non-null value) |
122+
| `coalesceNotEmpty` | Coalesce values when not empty (first non-empty value) |
123+
| `formatDate` | Format date field to string |
124+
| `randomBytes` | Generates cryptographically secure random bytes. |
123125

124126
**Example:**
125127

126128
```php
127-
$query->coalesce('whatever', 'ArticleNr')->as('COALESCE')
129+
$query->arrayCombine('fieldWithArrayKeys', 'fieldWithArrayValues')->as('ARRAY_COMBINE')
130+
->arrayMerge('fieldWithArray1', 'fieldWithArray2')->as('ARRAY_MERGE')
131+
->coalesce('whatever', 'ArticleNr')->as('COALESCE')
128132
->coalesceNotEmpty('whatever', 'ArticleNr')->as('COALESCE_NE')
129-
->combine('fieldWithArrayKeys', 'fieldWithArrayValues')
133+
->formatDate('dateField', 'Y-m-d')->as('FORMAT_DATE')
130134
->randomBytes(16)->as('RANDOM_BYTES');
131135
```
132136

examples/data/products.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<root>
3-
<item id="1" category="A">
3+
<item id="1" category="A" created="2025-04-08T12:29:38Z">
44
<name>Item 1</name>
55
<price>100</price>
66
<brand>
@@ -16,7 +16,7 @@
1616
<gallery_image>http://example.com/g/1.jpg</gallery_image>
1717
</gallery>
1818
</item>
19-
<item id="2" category="B">
19+
<item id="2" category="B" created="2025-04-03T11:43:21+02:00">
2020
<name>Item 2</name>
2121
<price>200</price>
2222
<brand>

src/Enum/Type.php

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ enum Type: string
2020
case ARRAY = 'array';
2121
case OBJECT = 'object';
2222

23+
case DATETIME = 'datetime';
24+
2325
case RESOURCE = 'resource';
2426
case RESOURCE_CLOSED = 'resource (closed)';
2527

@@ -34,6 +36,10 @@ public static function castValue(mixed $value, ?Type $type = null): mixed
3436
self::FLOAT => is_numeric($value) ? (float) $value : 0.0,
3537
self::BOOLEAN => (bool) filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE),
3638
self::NULL => null,
39+
self::DATETIME => match (true) {
40+
$value instanceof \DateTimeInterface => $value,
41+
default => \DateTimeImmutable::createFromFormat(DATE_ATOM, self::toString($value)),
42+
},
3743
self::ARRAY => is_array($value) ? $value : [$value],
3844
self::OBJECT => is_object($value) ? $value : null,
3945
default => throw new InvalidArgumentException(
@@ -50,7 +56,10 @@ public static function match(mixed $value): self
5056
'double' => self::FLOAT,
5157
'string' => self::STRING,
5258
'array' => self::ARRAY,
53-
'object' => self::OBJECT,
59+
'object' => match (true) {
60+
$value instanceof \DateTimeInterface => self::DATETIME,
61+
default => self::OBJECT,
62+
},
5463
'resource' => self::RESOURCE,
5564
'resource (closed)' => self::RESOURCE_CLOSED,
5665
'NULL' => self::NULL,
@@ -70,6 +79,16 @@ public static function matchByString(string $value): mixed
7079
return self::castValue(strtolower($value) === 'true' ? 1 : 0, self::BOOLEAN);
7180
}
7281

82+
// Datetime / Timestamp
83+
if (preg_match('/^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})?$/', $value)) {
84+
try {
85+
return self::castValue(new \DateTimeImmutable(self::toString($value)));
86+
} catch (\Exception) {
87+
// Invalid date format
88+
return self::castValue($value, self::STRING);
89+
}
90+
}
91+
7392
// Integer or Float
7493
if (self::isNumeric($value)) {
7594
$type = self::INTEGER;
@@ -124,13 +143,19 @@ private static function isNumeric(string $value): bool
124143
private static function toString(mixed $value): string
125144
{
126145
$type = self::match($value);
127-
return match ($type) {
128-
self::NULL => 'null',
129-
self::TRUE => 'true',
130-
self::FALSE => 'false',
131-
self::ARRAY => 'array',
132-
self::OBJECT => 'object',
133-
default => (string) $value,
134-
};
146+
try {
147+
return match ($type) {
148+
self::NULL => 'null',
149+
self::TRUE => 'true',
150+
self::FALSE => 'false',
151+
self::ARRAY => json_encode($value),
152+
self::DATETIME => $value instanceof \DateTimeInterface ? $value->format('c') : 'null',
153+
self::OBJECT => $value instanceof \Serializable ? $value->serialize() : 'object',
154+
self::STRING => $value,
155+
default => (string) $value,
156+
};
157+
} catch (\Exception) {
158+
return 'null';
159+
}
135160
}
136161
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
use FQL\Functions;
66
use FQL\Traits;
77

8-
class Combine extends Functions\Core\MultipleFieldsFunction
8+
class ArrayCombine extends Functions\Core\MultipleFieldsFunction
99
{
1010
use Traits\Helpers\StringOperations;
1111

src/Functions/Utils/ArrayMerge.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
namespace FQL\Functions\Utils;
4+
5+
use FQL\Functions;
6+
use FQL\Traits;
7+
8+
class ArrayMerge 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+
return array_merge($keys, $values);
34+
}
35+
}

src/Functions/Utils/DateFormat.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
namespace FQL\Functions\Utils;
4+
5+
use FQL\Functions;
6+
7+
class DateFormat extends Functions\Core\MultipleFieldsFunction
8+
{
9+
public function __construct(private readonly string $field, private readonly string $format = 'c')
10+
{
11+
parent::__construct($field, $format);
12+
}
13+
14+
public function __invoke(array $item, array $resultItem): ?string
15+
{
16+
$value = $this->getFieldValue($this->field, $item, $resultItem) ?? $this->field;
17+
if (!$value instanceof \DateTimeImmutable) {
18+
return null;
19+
}
20+
21+
return $value->format($this->format);
22+
}
23+
24+
public function __toString(): string
25+
{
26+
return sprintf(
27+
'%s(%s, "%s")',
28+
$this->getName(),
29+
$this->field,
30+
$this->format
31+
);
32+
}
33+
}

src/Interface/Query.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,9 @@ 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;
451+
public function arrayCombine(string $keysArrayField, string $valueArrayField): Query;
452+
public function arrayMerge(string $keysArrayField, string $valueArrayField): Query;
453+
public function formatDate(string $dateField, string $format = 'c'): Query;
452454

453455
/**
454456
* Specify a specific part of the data to select.

0 commit comments

Comments
 (0)