Skip to content

fix(params): quote nested DateTime64 values - #341

Open
simPod wants to merge 1 commit into
masterfrom
fix/nested-datetime64-parameters
Open

fix(params): quote nested DateTime64 values#341
simPod wants to merge 1 commit into
masterfrom
fix/nested-datetime64-parameters

Conversation

@simPod

@simPod simPod commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Context

Native ClickHouse parameters can contain DateTime64 values nested inside tuples and arrays.

Root cause

The converter emitted fractional Unix timestamps without quoting when DateTime64 was nested. ClickHouse could not parse those values inside tuple input.

Decision

Keep numeric Unix timestamp encoding for top-level DateTime64 parameters, but emit quoted ISO datetime literals for nested values. Add request-body regression coverage.

Consequences

Nested DateTime64 parameters are valid for native tuple and array payloads while preserving existing top-level parameter behavior.

@codecov

codecov Bot commented Jul 31, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.47619% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 95.83%. Comparing base (6a2827c) to head (d2dc5d6).

Files with missing lines Patch % Lines
src/Param/ParamValueConverterRegistry.php 90.47% 2 Missing ⚠️
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 DateTime64 param 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.

Comment on lines +105 to 116
'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;
}
@simPod
simPod force-pushed the fix/nested-datetime64-parameters branch from fb02387 to d447ea0 Compare July 31, 2026 08:23
@simPod

simPod commented Jul 31, 2026

Copy link
Copy Markdown
Owner Author

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).

@simPod
simPod force-pushed the fix/nested-datetime64-parameters branch from d447ea0 to d2dc5d6 Compare July 31, 2026 08:29
@simPod

simPod commented Jul 31, 2026

Copy link
Copy Markdown
Owner Author

CI fix pushed in d2dc5d6: documented the UnsupportedParamType exception from convertWrappedValue. Focused PHPUnit, PHPCS, and syntax checks pass.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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');

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 $nested is true, DateTimeInterface values 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-level U.u encoding if the DateTimeInterface carries 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 DateTime64 values should be emitted as quoted ISO datetime literals. In the $nested branch, non-DateTimeInterface inputs (string/float/int) are currently just quoted as-is (e.g. 1675213323.123456 becomes '1675213323.123456') rather than being converted to an ISO datetime string. If numeric/epoch inputs are expected to work for nested DateTime64, 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;

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 using format('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 DateTime64 is nested, DateTimeInterface values are formatted as Y-m-d H:i:s.u without 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');

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants