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 <kavirajkanagaraj@gmail.com>
Signed-off-by: Kaviraj <kavirajkanagaraj@gmail.com>
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 <kavirajkanagaraj@gmail.com>
1. Also fixes the tests for timezone related tweaks

Signed-off-by: Kaviraj <kavirajkanagaraj@gmail.com>
@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

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah thanks for the context. I will do it in follow up PR :) (this PR is already big enough I think)

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 <kavirajkanagaraj@gmail.com>
Signed-off-by: Kaviraj <kavirajkanagaraj@gmail.com>
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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's part of sql package. It think it is there to handle cases like

var s sql.NullTime
db.QueryRow("SELECT time FROM foo WHERE id=?", id).Scan(&s)

if s.Valid() { // non-null
  // do something
}

Similar to how we already handle for string type in clickhouse-go

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

TYPES.md Outdated
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

Copy link
Contributor Author

@kavirajk kavirajk Oct 2, 2025

Choose a reason for hiding this comment

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

The more I think about this, the more I realize, time.Time is not the correct Go type for this time and time64 CH type. Unwanted complexities with Date (year, month, day, timezone) aspect of it.

The ideal Go type would be I think is time.Duration. Which includes only time without date component.

  1. This exactly align with time and time64 docs
  2. Also Rust library uses time::Duration

also Go definition of time.Duration seems to reflect the same purpose.

const (
	Nanosecond  Duration = 1
	Microsecond          = 1000 * Nanosecond
	Millisecond          = 1000 * Microsecond
	Second               = 1000 * Millisecond
	Minute               = 60 * Second
	Hour                 = 60 * Minute
)

We only have to make sure that time has precision max up to only milliseconds. We don't have to fight with default values for year, month, day and timezone aspect of it. Which can leads to some subtle logical errors depends on how users use those apis.

I think sticking to "Make your APIs easy to use correctly, and make it hard to use in-correctly" philosophy will help here :)

wdyt @SpencerTorres ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Looks like it doesn't need lots of change(may be i'm wrong 😆 ). In fact may lead to removing lots of code from both ch-go and clickhouse-go. I'm going to try with time.Duration type and see what happens.

Copy link
Member

Choose a reason for hiding this comment

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

This sounds much more correct, let's get ch-go updated too then. ch-go technically allows for these breaking changes with its versioning.

I'd say take all the code from here and ch-go and make it use time.Duration, it is much more fitting as you said.

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.

Time type doesn't need parse, because it doesn't take
any additional params

Signed-off-by: Kaviraj <kavirajkanagaraj@gmail.com>
Signed-off-by: Kaviraj <kavirajkanagaraj@gmail.com>
Signed-off-by: Kaviraj <kavirajkanagaraj@gmail.com>
Signed-off-by: Kaviraj <kavirajkanagaraj@gmail.com>
Signed-off-by: Kaviraj <kavirajkanagaraj@gmail.com>
Changes:
1. Vendor the un-released version of ch-go to use the changes
2. Remove lots of helpers and parsing code that is unrelated to
   timezone, day, month and year
3. Update tests to validate the changes

Signed-off-by: Kaviraj <kavirajkanagaraj@gmail.com>
Signed-off-by: Kaviraj <kavirajkanagaraj@gmail.com>
Signed-off-by: Kaviraj <kavirajkanagaraj@gmail.com>
Signed-off-by: Kaviraj <kavirajkanagaraj@gmail.com>
go.mod Outdated

require (
github.com/ClickHouse/ch-go v0.68.0
github.com/ClickHouse/ch-go v0.68.1-0.20251003160956-f6cfd63ac24c
Copy link
Contributor Author

Choose a reason for hiding this comment

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

FYI: This points to this PR commit in ch-go. Once it's merged we can point main (or after releasing point to specific newer version)

Copy link
Collaborator

Choose a reason for hiding this comment

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

Merged

@kavirajk kavirajk requested a review from SpencerTorres October 3, 2025 22:23
Signed-off-by: Kaviraj <kavirajkanagaraj@gmail.com>
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.

4 participants