blob: 277248348ee0f1ecec2f9c7d6fe8b87f5ec342ab [file] [view]
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
# 快速上手 - Go
## 依赖与安装
Go 包 `github.com/apache/tsfile/go/tsfile` 是基于 C++ 实现的 cgo 绑定,因此必须先有一份 C/C++ 构建产物,Go 代码才能编译。
获取方式有两种:
- 安装我们发布的 C/C++ 包或 SDK,无需自行构建 C++,适用于 Linux、macOS 和 Windows。
- 从源码构建 C++ 库,适用于需要修改 TsFile 或所在平台没有发布包的情况。
两种方式下 Go 模块都从 GitHub 获取,目前尚未发布到模块代理。
### 依赖
- Go >= 1.22,且 `CGO_ENABLED=1`
- C/C++ 编译工具链和 CMake,仅在从源码构建原生库时需要
- JDK >= 17 和 Maven,仅在从源码构建原生库时需要
- Windows 上受支持的 cgo 工具链为 MinGW-w64
### 方式一:使用已发布的 C/C++ 包(Linux、macOS、Windows)
按[下载与安装页面](../../../Download/README.md#cpp-packages)安装运行库和开发包,可以是 SDK 压缩包、DEB/RPM 包、Homebrew Formula
或 Windows ZIP。头文件安装在 `include/tsfile` 子目录下,因此需要把 cgo 的头文件路径指向该目录,库路径指向安装前缀的库目录:
```bash
export TSFILE_HOME=/path/to/tsfile-sdk # 或:$(brew --prefix local/tsfile250/tsfile)
export CGO_CFLAGS="-I$TSFILE_HOME/include/tsfile"
export CGO_LDFLAGS="-L$TSFILE_HOME/lib -Wl,-rpath,$TSFILE_HOME/lib"
```
其中 `-rpath` 让编译出的程序在运行时能找到 `libtsfile`;如果共享库安装在系统标准目录下可以去掉。
对于安装在 `/usr` 下的系统包,链接器无需 `-L` 就能找到库,但头文件仍需要 `-I/usr/include/tsfile`。
这些环境变量会追加到包内自带的编译参数之后,而包内参数指向源码检出的 `cpp/target/build` 目录;使用已安装的包时
该目录并不存在,链接器会打印一条搜索路径警告,可以忽略。
Windows 上解压 ZIP 后,在 PowerShell 中设置同样的变量即可。ZIP 内含 `bin\tsfile.dll` 和 `lib\tsfile.lib` 导入库,
MinGW 的链接器可以直接使用它,`-ltsfile` 照常通过 `-L` 找到:
```powershell
$env:TSFILE_HOME = "C:\path\to\tsfile-2.5.0-windows-x86_64"
$env:CGO_CFLAGS = "-I$env:TSFILE_HOME\include\tsfile"
$env:CGO_LDFLAGS = "-L$env:TSFILE_HOME\lib -ltsfile"
$env:Path = "$env:TSFILE_HOME\bin;$env:Path"
```
Windows 没有运行时的库搜索路径,编译出的程序通过 `PATH` 查找 `tsfile.dll`:保持 `bin` 在 `PATH` 中,
或者把 `tsfile.dll` 复制到可执行文件旁边。该 DLL 静态链接了 C 运行库,只依赖系统库,因此不需要安装
Visual C++ 运行库。
cgo 只在编译该包时读取这些变量,因此需要在每个构建程序的终端里设置,或者用
`go env -w CGO_CFLAGS=... CGO_LDFLAGS=...` 记录下来。运行已经编译好的程序时不需要它们。
### 方式二:从源码构建原生库
克隆源码,并在仓库根目录构建 C++ 库:
```bash
git clone https://github.com/apache/tsfile.git
cd tsfile
./mvnw -P with-cpp -DskipTests package
```
构建完成后会在 `cpp/target/build/include` 生成头文件、在 `cpp/target/build/lib` 生成库,Go 包默认就链接这里。
Windows 上需要使用 MinGW 构建。MinGW-w64 既是本项目在 Windows 上默认的 C++ 生成器,也是 cgo 编译 Go 包所用的编译器,
因此 `-Dcpp.toolchain=mingw` 构建出的 DLL 和导入库可以直接配合 Go 工具链使用。源码构建会把 `tsfile.dll` 输出到
`cpp\target\build\lib`(而不是发布包的 `bin` 目录),因此除了编译器所在的 MinGW-w64 `bin` 目录外,还要把该目录加入
`PATH`(或把 DLL 复制到可执行文件旁边):
```powershell
.\mvnw.cmd -P with-cpp -Dcpp.toolchain=mingw -DskipTests package
$env:Path = "$PWD\cpp\target\build\lib;C:\mingw64\bin;$env:Path"
```
每打开一个新的终端,都要在构建或运行 Go 程序前执行上面的 `PATH` 设置。
### 在项目中使用该模块
使用已安装的包(方式一)时,可以直接在你的 Go 项目中拉取模块:
```bash
go get github.com/apache/tsfile/go@develop
go mod tidy
```
模块目前还没有发布 tag,`@develop` 会解析成分支头部的伪版本号。为了构建可复现,请把该伪版本号记录在 `go.mod` 中,
或者固定到某个具体 commit。
使用源码构建(方式二)时,用 `replace` 指向检出目录下的 `go` 目录,Go 包就会使用它旁边构建好的头文件和库:
```bash
go mod edit -replace github.com/apache/tsfile/go=/path/to/tsfile/go
go get github.com/apache/tsfile/go
```
如果想直接运行自带的示例,在检出目录的 `go` 目录下执行:
```bash
cd go
go run ./examples/table_read_write
```
## 写入流程
### 构造 Writer
```go
schema := tsfile.TableSchema{
Table: "table1",
Columns: []tsfile.ColumnSchema{
{Name: "id1", DataType: tsfile.DataTypeString, Category: tsfile.ColumnCategoryTag},
{Name: "id2", DataType: tsfile.DataTypeString, Category: tsfile.ColumnCategoryTag},
{Name: "s1", DataType: tsfile.DataTypeInt32, Category: tsfile.ColumnCategoryField},
{Name: "s2", DataType: tsfile.DataTypeBoolean, Category: tsfile.ColumnCategoryField},
},
}
writer, err := tsfile.NewWriter("test.tsfile", schema, tsfile.WithMemoryThreshold(10*1024*1024))
if err != nil {
return err
}
defer writer.Close()
```
`NewWriter` 会创建或截断文件。`WithMemoryThreshold` 是可选参数,默认为 128 MB。
### 写入数据
```go
tablet, err := tsfile.NewTablet([]tsfile.TabletColumn{
{Name: "id1", DataType: tsfile.DataTypeString},
{Name: "id2", DataType: tsfile.DataTypeString},
{Name: "s1", DataType: tsfile.DataTypeInt32},
{Name: "s2", DataType: tsfile.DataTypeBoolean},
}, 5)
if err != nil {
return err
}
defer tablet.Close()
for row := 0; row < 5; row++ {
if err := tablet.AddTimestamp(row, int64(row)); err != nil {
return err
}
tablet.SetString(row, 0, "id1_filed_1")
tablet.SetString(row, 1, "id2_filed_1")
tablet.SetInt32(row, 2, int32(row))
tablet.SetBool(row, 3, true)
}
if err := writer.WriteTableTablet(tablet); err != nil {
return err
}
```
Tablet 的行和列下标都从 0 开始。每个 setter 都会校验下标和列类型,当下标越界或写入值与声明类型不符时返回错误,应与
`AddTimestamp` 一样检查这些错误。
也可以通过 Arrow record batch 或 table 写入数据。其 schema 必须包含 `int64` 或 `timestamp[ns]`(不带时区)类型的
`time` 列,其后紧跟表 schema 中的全部列:
```go
arrowSchema := arrow.NewSchema([]arrow.Field{
{Name: "time", Type: arrow.PrimitiveTypes.Int64},
{Name: "id1", Type: arrow.BinaryTypes.String},
{Name: "id2", Type: arrow.BinaryTypes.String},
{Name: "s1", Type: arrow.PrimitiveTypes.Int32},
{Name: "s2", Type: arrow.FixedWidthTypes.Boolean},
}, nil)
builder := array.NewRecordBuilder(memory.DefaultAllocator, arrowSchema)
defer builder.Release()
timeBuilder := builder.Field(0).(*array.Int64Builder)
id1Builder := builder.Field(1).(*array.StringBuilder)
id2Builder := builder.Field(2).(*array.StringBuilder)
s1Builder := builder.Field(3).(*array.Int32Builder)
s2Builder := builder.Field(4).(*array.BooleanBuilder)
for row := 5; row < 10; row++ {
timeBuilder.Append(int64(row))
id1Builder.Append("id1_filed_1")
id2Builder.Append("id2_filed_1")
s1Builder.Append(int32(row))
s2Builder.Append(true)
}
record := builder.NewRecord()
defer record.Release()
if err := writer.WriteArrowBatch(record); err != nil {
return err
}
```
### 关闭文件
```go
writer.Close()
```
### 示例代码
使用这些接口的示例代码可以在以下链接中找到
<https://github.com/apache/tsfile/blob/develop/go/examples/table_read_write/main.go>
## 查询流程
### 构造 Reader
```go
reader, err := tsfile.NewReader("test.tsfile")
if err != nil {
return err
}
defer reader.Close()
```
### 构造 Query Request
```go
result, err := reader.Query(
"table1",
[]string{"id1", "id2", "s1", "s2"},
tsfile.WithTimeRange(2, 8),
tsfile.WithTagFilter(tsfile.And(tsfile.Eq("id1", "id1_filed_1"), tsfile.Eq("id2", "id2_filed_1"))),
tsfile.WithLimit(100),
)
if err != nil {
return err
}
defer result.Close()
```
时间范围、Tag 过滤、offset、limit 和批次大小都是可选参数,均会下推到原生读取器。
### 查询数据
```go
metadata := result.Metadata()
for _, column := range metadata {
fmt.Printf("%s(%d) ", column.Name, column.DataType)
}
fmt.Println()
for {
hasNext, err := result.Next()
if err != nil {
return err
}
if !hasNext {
break
}
for column := 1; column <= len(metadata); column++ {
isNull, err := result.IsNull(column)
if err != nil {
return err
}
if isNull {
fmt.Print("null ")
continue
}
switch metadata[column-1].DataType {
case tsfile.DataTypeBoolean:
value, err := result.Bool(column)
if err != nil {
return err
}
fmt.Print(value, " ")
case tsfile.DataTypeInt32, tsfile.DataTypeDate:
value, err := result.Int32(column)
if err != nil {
return err
}
fmt.Print(value, " ")
case tsfile.DataTypeInt64, tsfile.DataTypeTimestamp:
value, err := result.Int64(column)
if err != nil {
return err
}
fmt.Print(value, " ")
case tsfile.DataTypeFloat:
value, err := result.Float32(column)
if err != nil {
return err
}
fmt.Print(value, " ")
case tsfile.DataTypeDouble:
value, err := result.Float64(column)
if err != nil {
return err
}
fmt.Print(value, " ")
case tsfile.DataTypeString, tsfile.DataTypeText:
value, err := result.String(column)
if err != nil {
return err
}
fmt.Print(value, " ")
}
}
fmt.Println()
}
```
结果集的列下标从 1 开始:第 1 列是时间戳,其后按查询顺序排列所选列。类型化 getter(`Bool`、`Int32`、`Int64`、
`Float32`、`Float64`、`String`、`Bytes`)在列类型不匹配时返回错误,因此需要像上例一样按元数据分发。
### 以 Arrow 批次查询数据
当 `WithBatchSize` 为正数时,查询运行在批次模式下,数据以 Arrow record batch 的形式返回:
```go
result, err := reader.Query("table1", []string{"id1", "id2", "s1", "s2"}, tsfile.WithBatchSize(1024))
if err != nil {
return err
}
defer result.Close()
for {
record, err := result.ReadArrowRecordBatch()
if errors.Is(err, io.EOF) {
break
}
if err != nil {
return err
}
fmt.Println(record.NumRows())
record.Release()
}
```
### 关闭文件
```go
result.Close()
reader.Close()
```
关闭 reader 时也会关闭由它创建的所有结果集。
### 示例代码
使用这些接口的示例代码可以在以下链接中找到
<https://github.com/apache/tsfile/blob/develop/go/examples/table_read_write/main.go>
> 注意:以上读写示例均基于表模型接口,接口定义介绍可见[Go 接口定义](./InterfaceDefinition/InterfaceDefinition-Go.md)。
> 若需了解树模型相关内容,请联系我们。