Skip to content

Conversation

kavirajk
Copy link
Contributor

Summary

This is built on top of other PR preserving original authors commits.

Checklist

Delete items not relevant to your PR:

  • Unit and integration tests covering the common scenarios were added
  • A human-readable description of the changes was provided to include in CHANGELOG
  • For significant changes, documentation in https://github.com/ClickHouse/clickhouse-docs was updated with further explanations or tutorials

shivanshuraj1333 and others added 11 commits July 20, 2025 13:56
1. Populate column_gen.go
2. Removed unused the interface implementations

Signed-off-by: Kaviraj <[email protected]>
1. Removed the need for timezone information from these types (server
   explicitly states these types don't have timezone info)
2. Handle prefix correctly with time64(precision)
3. Handle default value for precision if not given
4. Update tests

Signed-off-by: Kaviraj <[email protected]>
1. Also fixes the tests for timezone related tweaks

Signed-off-by: Kaviraj <[email protected]>
@kavirajk kavirajk marked this pull request as ready for review September 26, 2025 12:03
Copy link

@Copilot Copilot AI left a comment

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 adds support for ClickHouse Time and Time64 datatypes to the Go driver. It introduces new column types that can handle time-of-day values with different precision levels, where Time stores seconds since midnight and Time64 supports precision up to nanoseconds.

  • Implementation of Time and Time64 column types with full encoding/decoding support
  • Addition of comprehensive test coverage for both types including array handling
  • Updates to documentation and type support matrix

Reviewed Changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
lib/column/time.go New Time column type implementation with second precision
lib/column/time64.go New Time64 column type implementation with configurable nanosecond precision
lib/column/column_gen.go Registration of Time and Time64 types in the column factory
lib/proto/block.go Allow custom serialization for Time and Time64 columns
tests/time_test.go Comprehensive test suite for Time and Time64 functionality
TYPES.md Documentation updates showing Time/Time64 support in type matrix
lib/column/codegen/*.tpl Template updates for code generation (license header removal)
conn_http_test.go License header removal

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

"enable_time_time64_type": 1,
}, nil, nil)
require.NoError(t, err)
if !CheckMinServerServerVersion(conn, 25, 6, 0) {
Copy link
Preview

Copilot AI Sep 28, 2025

Choose a reason for hiding this comment

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

Function name has a duplicate 'Server' - should be 'CheckMinServerVersion' instead of 'CheckMinServerServerVersion'.

Suggested change
if !CheckMinServerServerVersion(conn, 25, 6, 0) {
if !CheckMinServerVersion(conn, 25, 6, 0) {

Copilot uses AI. Check for mistakes.

Copy link
Member

Choose a reason for hiding this comment

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

I think I've seen this before but was too deep in a different PR to fix it. Let's correct this typo at some point. Should be a quick and easy refactor

Clickhouse server returns error if client tries to make any queries with
unsupported settings.

In HTTP protocol, to find server version (ServerVersion() api) we were
using `enable_time_time64_type` as connection level settings and causing
the server to return error (any version before 25.6)

This PR don't pass any connection level settings to very the server
version and only if server version is > 25.6 it uses the settings to do
actual testing on time and time64 types

Signed-off-by: Kaviraj <[email protected]>
Copy link
Member

@SpencerTorres SpencerTorres left a comment

Choose a reason for hiding this comment

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

Overall looks good, left some comments for things I had questions on. I understand this PR was copied from a different author. I appreciate your help ✨ enhancing ✨ it.

Let me know if you have any questions 👌

return &SharedVariant{name: name}, nil
case "Object('json')":
return &JSONObject{name: name, root: true, sc: sc}, nil
case "Time":
Copy link
Member

Choose a reason for hiding this comment

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

It looks like the changes here are inconsistent with what was generated. Was column_gen.go manually edited?

Also in column_gen.go it looks like Time has a prefix check, does it need a prefix check? I looked at the docs and it looks like Time is just Time. The only one that needs a prefix is Time64 since it has the precision defined

return (&DateTime{name: name}).parse(t, sc.Timezone)
case strings.HasPrefix(strType, "Time64"):
return (&Time64{name: name}).parse(t)
case strings.HasPrefix(strType, "Time"):
Copy link
Member

Choose a reason for hiding this comment

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

See comment on column.tpl


func (col *Time) parse(t Type) (_ Interface, err error) {
col.chType = t
if string(t) == "Time" {
Copy link
Member

Choose a reason for hiding this comment

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

Not sure if Time needs a parse function at all, you can probably initialize it without parsing. The reason we have parsing is for types like Tuple and Time64 where other syntax is included within the type string.

We don't need to return unsupported type error here since the column initialization function would never call parse if the string isn't already equal to Time

*d = new(int64)
t := col.row(row)
**d = timeToSeconds(t)
case *sql.NullTime:
Copy link
Member

Choose a reason for hiding this comment

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

I see NullTime here but it makes me wonder if there's a regular SQL Time type or if that's just the Go type

switch v := v.(type) {
case int64:
seconds := v
hours := seconds / 3600
Copy link
Member

Choose a reason for hiding this comment

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

It looks like this math is copied around a bunch. Can we make a function or struct that does it? Maybe a function with multiple return values?

return time.Time{}, nil
}

formats := []string{
Copy link
Member

Choose a reason for hiding this comment

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

Is this list/function duplicated from Time? Perhaps Time64 can borrow some helper functions from Time

return &BlockError{
Op: "Decode",
Err: errors.New(fmt.Sprintf("custom serialization for column %s. not supported by clickhouse-go driver", columnName)),
// Allow Time and Time64 columns with custom serialization
Copy link
Member

Choose a reason for hiding this comment

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

I am so incredibly suspicious of this line, I saw it in the first PR and was concerned. Is this really the first column type that has this hasCustom flag set?

I don't recall if I wrote it in a comment on that PR but perhaps we can have an interface on the column type to declare whether it hasCustom or not, because I don't like the idea of these types being hardcoded here.

Does this even match on Time64(3) ? Is this necessary? Let's ask the core team to confirm, or maybe try a hexdump of Native format to see if this flag is really being set.

"enable_time_time64_type": 1,
}, nil, nil)
require.NoError(t, err)
if !CheckMinServerServerVersion(conn, 25, 6, 0) {
Copy link
Member

Choose a reason for hiding this comment

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

I think I've seen this before but was too deep in a different PR to fix it. Let's correct this typo at some point. Should be a quick and easy refactor

colTime64.col.WithPrecision(9)

// Append values
colTime.AppendRow(time.Date(2024, 7, 11, 12, 34, 56, 0, time.UTC))
Copy link
Member

Choose a reason for hiding this comment

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

Let's make sure the docs are super clear about how the date component + timezone behaves. It's not immediately clear from here that it starts at 1970 or whatever we talked about

return ctx, func() {}, conn
}

func TestTimeAndTime64(t *testing.T) {
Copy link
Member

Choose a reason for hiding this comment

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

For the sake of test coverage and isolating issues with these types, I would suggest splitting these into separate tests, maybe even separate files since Time is different than Time64. I would use Date/DateTime/DateTime64 as reference (unless those are all in one test too, haven't looked recently)

Also considering the feature is experimental, the setup function is the correct way to go, glad to see it being used.

Also also, and I know it's annoying to add, but we need to add coverage for the standard library database/sql tests. These are in a different folder and are very similar to these tests. Be aware that if you're testing HTTP with the standard library connector it may need you to set the session_id if you're modifying the experimental setting.

I would reference Variant/Dynamic/JSON as well as DateTime/DateTime64 for these. You can copy/paste/rework the stdlib tests from that folder. Make sure we have all the right sanity checks for things like arrays, matching input/output, etc.

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.

3 participants