fix(params): quote nested DateTime64 values - #341
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #341 +/- ##
==========================================
- Coverage 95.99% 95.83% -0.17%
==========================================
Files 42 42
Lines 849 864 +15
==========================================
+ Hits 815 828 +13
- Misses 34 36 +2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR fixes ClickHouse native-parameter encoding for DateTime64 values when they appear nested inside compound types (e.g., arrays/tuples), by emitting quoted ISO datetime literals for nested values while preserving the existing top-level numeric Unix timestamp behavior.
Changes:
- Update
DateTime64param conversion to emit quoted ISO datetime literals when the value is nested. - Add a multipart request-body regression test covering
Array(Tuple(DateTime64(6), UUID)).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| tests/Client/Http/RequestFactoryTest.php | Adds regression test asserting nested DateTime64 inside an array/tuple is emitted as a quoted ISO literal in the request body. |
| src/Param/ParamValueConverterRegistry.php | Updates the datetime64 converter to switch encoding based on the nested flag (ISO+quoted when nested; Unix timestamp when top-level). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| 'datetime64' => static function (mixed $value, Type|string|null $type = null, bool $nested = false) { | ||
| if ($value instanceof DateTimeInterface) { | ||
| return $value->format('U.u'); | ||
| $value = $nested | ||
| ? $value->format('Y-m-d H:i:s.u') | ||
| : $value->format('U.u'); | ||
| } | ||
|
|
||
| if (is_string($value) || is_float($value) || is_int($value)) { | ||
| return $value; | ||
| return $nested | ||
| ? "'" . Escaper::escape((string) $value) . "'" | ||
| : $value; | ||
| } |
fb02387 to
d447ea0
Compare
|
Addressed in d447ea0. Nullable and LowCardinality now preserve the nested flag and resolve parameterized inner types via Type::fromString(), with regression coverage for both wrappers around DateTime64(6). |
d447ea0 to
d2dc5d6
Compare
|
CI fix pushed in d2dc5d6: documented the UnsupportedParamType exception from convertWrappedValue. Focused PHPUnit, PHPCS, and syntax checks pass. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
Suppressed comments (1)
src/Param/ParamValueConverterRegistry.php:117
- When $nested is true, DateTime64 values are formatted using the DateTimeInterface instance’s local timezone (format('Y-m-d H:i:s.u')). This can shift the represented instant when the DateTime has a non-default timezone (the string literal has no offset), whereas the non-nested path uses an epoch-based representation (format('U.u')) that is timezone-safe. Consider normalizing nested DateTimeInterface values to a fixed timezone (e.g., UTC) before formatting so nested and top-level encodings represent the same instant.
if ($value instanceof DateTimeInterface) {
$value = $nested
? $value->format('Y-m-d H:i:s.u')
: $value->format('U.u');
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
Suppressed comments (2)
src/Param/ParamValueConverterRegistry.php:118
- When
$nestedis true,DateTimeInterfacevalues are formatted as a timezone-less local datetime (Y-m-d H:i:s.u). This can change the represented instant compared to the top-levelU.uencoding if theDateTimeInterfacecarries a non-UTC timezone, because ClickHouse will interpret the literal in the server/type timezone. Consider normalizing to UTC (or the type’s timezone, if applicable) before formatting so nested and top-level encodings represent the same moment.
if ($value instanceof DateTimeInterface) {
$value = $nested
? $value->format('Y-m-d H:i:s.u')
: $value->format('U.u');
}
src/Param/ParamValueConverterRegistry.php:123
- PR description says nested
DateTime64values should be emitted as quoted ISO datetime literals. In the$nestedbranch, non-DateTimeInterfaceinputs (string/float/int) are currently just quoted as-is (e.g.1675213323.123456becomes'1675213323.123456') rather than being converted to an ISO datetime string. If numeric/epoch inputs are expected to work for nestedDateTime64, this likely won’t meet the stated decision and may still be hard for ClickHouse to parse depending on context/scale.
if (is_string($value) || is_float($value) || is_int($value)) {
return $nested
? "'" . Escaper::escape((string) $value) . "'"
: $value;
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
Suppressed comments (2)
tests/Client/Http/RequestFactoryTest.php:186
- The expected Unix timestamp is hard-coded, but
new DateTimeImmutable('2026-07-30 12:00:00.123456')is interpreted in the runtime default timezone. If CI/default timezone changes, this assertion will fail even if the converter behavior is correct. Compute the expected value from the same literal usingformat('U.u')(or set an explicit timezone) to make the test deterministic.
self::assertStringContainsString('1785412800.123456', $request->getBody()->__toString());
src/Param/ParamValueConverterRegistry.php:117
- When
DateTime64is nested, DateTimeInterface values are formatted asY-m-d H:i:s.uwithout any timezone/offset. If the DateTime object has a non-default timezone (or ClickHouse server TZ differs), this can shift the represented instant compared to the top-level numeric encoding which preserves absolute time. Consider documenting this behavior, normalizing to a specific timezone, or including an offset in the formatted string if ClickHouse supports it.
'datetime64' => static function (mixed $value, Type|string|null $type = null, bool $nested = false) {
if ($value instanceof DateTimeInterface) {
$value = $nested
? $value->format('Y-m-d H:i:s.u')
: $value->format('U.u');
Context
Native ClickHouse parameters can contain
DateTime64values nested inside tuples and arrays.Root cause
The converter emitted fractional Unix timestamps without quoting when
DateTime64was nested. ClickHouse could not parse those values inside tuple input.Decision
Keep numeric Unix timestamp encoding for top-level
DateTime64parameters, but emit quoted ISO datetime literals for nested values. Add request-body regression coverage.Consequences
Nested
DateTime64parameters are valid for native tuple and array payloads while preserving existing top-level parameter behavior.