数据类型
类型转换
客户端力求在接受用于插入和响应编组的变量类型时尽可能灵活。在大多数情况下,ClickHouse 列类型都存在对应的 Golang 类型,例如,UInt64 对应 uint64。这类逻辑映射应始终受到支持。如果变量或接收到的数据能够先完成转换,你也可以使用可插入列中或可用于接收响应的变量类型。客户端致力于透明地支持这些转换,使用户在插入前无需严格调整数据类型,并在查询时提供灵活的编组能力。这种透明转换不允许精度损失。例如,不能使用 uint32 来接收 UInt64 列中的数据。相反,只要满足格式要求,就可以将 string 插入 datetime64 字段。
当前支持的原始类型转换汇总见此处。
这项工作仍在持续推进,目前可分为插入时 (Append/AppendRow) 和读取时 (通过 Scan) 两部分。如果你需要支持某种特定转换,请提交 issue。
标准 database/sql 接口应支持与 ClickHouse API 相同的类型。也有少数例外,主要涉及复杂类型,这些内容将在下方各节中说明。与 ClickHouse API 类似,客户端力求在接受用于插入和响应编组的变量类型时尽可能灵活。
复杂类型
Date/DateTime
ClickHouse Go 客户端支持 Date、Date32、DateTime 和 DateTime64 日期/日期时间类型。插入日期时,可以使用 2006-01-02 格式的字符串,也可以使用原生 Go 类型 time.Time{} 或 sql.NullTime。DateTime 同样支持后两种类型,但字符串必须采用 2006-01-02 15:04:05 格式,也可选择附带时区偏移,例如 2006-01-02 15:04:05 +08:00。读取时也支持 time.Time{} 和 sql.NullTime,以及任何实现了 sql.Scanner 接口的类型。
时区信息的处理方式取决于 ClickHouse 类型,以及该值是在插入时还是读取时处理:
- DateTime/DateTime64
- 在 insert 时,值会以 UNIX 时间戳格式发送到 ClickHouse。如果未提供时区,客户端会假定使用客户端所在的本地时区。
time.Time{}或sql.NullTime也会据此转换为 epoch。 - 在 select 时,如果返回的是
time.Time值且列设置了时区,则使用该列的时区;否则使用服务器的时区。
- 在 insert 时,值会以 UNIX 时间戳格式发送到 ClickHouse。如果未提供时区,客户端会假定使用客户端所在的本地时区。
- Date/Date32
- 在 insert 时,将日期转换为 unix 时间戳时会考虑其时区。也就是说,由于 Date 类型在 ClickHouse 中不包含 locale 信息,因此在按日期存储之前会先根据时区进行偏移。如果字符串值中未指定时区,则使用本地时区。
- 在 select 时,扫描到
time.Time{}或sql.NullTime{}实例中的日期,返回时将不包含时区信息。
Time/Time64 类型
Time 和 Time64 列类型用于存储不包含日期部分的时间值。两者都映射到 Go 的 time.Duration。
Time以秒级精度存储时间。Time64(precision)支持子秒级精度 (类似于DateTime64) ,其中precision的取值范围为 0–9。
数组
数组应以 切片 的形式插入。元素的类型规则与原始类型一致,也就是说,在可能的情况下会自动进行类型转换。
在 Scan 时,应传入指向 切片 的指针。
Map
应以 Golang map 的形式插入 Map,其中键和值都必须符合前文定义的类型规则。
使用 database/sql API 时,Map 值必须严格指定类型,不能使用 interface{} 作为值类型。例如,对于 Map(String,String) 字段,不能传入 map[string]interface{},而必须使用 map[string]string。不过,interface{} 类型的变量本身始终兼容,因此可用于更复杂的结构。
元组
元组 表示一组长度任意的列。这些列既可以显式命名,也可以仅指定类型,例如
在这些方法中,具名元组的灵活性更高。虽然未命名元组必须使用 切片 进行 insert 和读取,但具名元组也兼容 map。
注:支持类型化的 切片 和 map,前提是具名元组中的所有子列类型都相同。
Nested
Nested 字段等同于由具名元组构成的 数组。具体用法取决于用户是否将 flatten_nested 设置为 1 或 0。
将 flatten_nested 设置为 0 后,Nested 列会保持为单个元组数组。这样,您就可以使用 map 切片进行插入和读取,并支持任意层级的嵌套。map 的键必须与列名一致,如下例所示。
注意:由于这些 map 表示的是元组,因此它们必须为 map[string]interface{} 类型。目前这些值还不是强类型的。
如果 flatten_nested 使用默认值 1,嵌套列会被展平为独立的数组。这要求在插入和读取时使用嵌套切片。虽然任意层级的嵌套可能也能工作,但这并未获得官方支持。
注意:Nested 列的各个维度必须一致。例如,在上述示例中,Col_2_2 和 Col_2_1 必须包含相同数量的元素。
由于接口更简洁,并且官方支持嵌套,我们建议使用 flatten_nested=0。
地理空间类型
客户端支持 Point、Ring、LineString、Polygon、MultiPolygon 和 MultiLineString 等地理空间类型。这些类型在 Go 中通过 github.com/paulmach/orb 包表示。
UUID
github.com/google/uuid 包支持 UUID 类型。你也可以将 UUID 作为字符串进行发送和编组,或者使用任何实现了 sql.Scanner 或 Stringify 的类型来发送和编组 UUID。
Decimal
由于 Go 没有内置的 Decimal 类型,我们建议使用第三方包 github.com/shopspring/decimal,以便在不修改原始查询的情况下,原生处理 Decimal 类型。
你可能会想改用 Float 来避免引入第三方依赖。不过请注意,当需要精确值时,不建议在 ClickHouse 中使用 Float 类型。
如果你仍决定在客户端使用 Go 的内置 Float 类型,则必须在 ClickHouse 查询中使用 toFloat64() 函数 或其变体,将 Decimal 显式转换为 Float。请注意,这种转换可能会导致精度损失。
Nullable
Go 中的 Nil 值表示 ClickHouse 中的 NULL。如果字段被声明为 Nullable,则可以使用它。插入时,对于某列的普通版本和 Nullable 版本,都可以传入 Nil。对于前者,将持久化该类型的默认值,例如 string 类型的默认值是空字符串。对于 Nullable 版本,则会在 ClickHouse 中存储 NULL 值。
扫描时,用户必须传入一个支持 nil 的类型指针,例如 *string,以表示 Nullable 字段的 nil 值。在下面的示例中,col1 是 Nullable(String),因此接收一个 **string。这样即可表示 nil。
客户端还额外支持 sql.Null* 类型,例如 sql.NullInt64。这些类型与对应的 ClickHouse 类型兼容。
大整数
超过 64 位的数值类型使用 Go 原生的 big 包表示。
BFloat16
BFloat16 是一种 16 位 brain float 类型,用于机器学习工作负载。在 Go 中,BFloat16 值以 float32 类型进行插入和扫描。Nullable 变体使用 sql.NullFloat64。
QBit
QBit 是一种实验性列类型,用于以位切片格式存储向量嵌入,并针对向量相似性搜索进行了优化。它要求启用 allow_experimental_qbit_type 设置。
在 Go 中,QBit(Float32, N) 列以 []float32 的形式插入和扫描,其中 N 表示向量维度。