Skip to content

Commit b1e90bb

Browse files
committed
duckgo init
0 parents  commit b1e90bb

25 files changed

Lines changed: 2345 additions & 0 deletions

.github/workflows/go.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: Go
2+
3+
on:
4+
push:
5+
branches: [ "main" ]
6+
pull_request:
7+
branches: [ "main" ]
8+
9+
jobs:
10+
build:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
15+
- name: Set up Go
16+
uses: actions/setup-go@v5
17+
with:
18+
go-version: '1.24.4'
19+
check-latest: true
20+
21+
- name: Test
22+
run: go test -v -ldflags="-checklinkname=0" ./...
23+
24+
- name: Vet
25+
run: go vet ./...

.gitignore

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Binaries for programs and plugins
2+
*.exe
3+
*.exe~
4+
*.dll
5+
*.so
6+
*.dylib
7+
8+
# Test binary, built with `go test -c`
9+
*.test
10+
11+
# Output of the go coverage tool, specifically when used with LiteIDE
12+
*.out
13+
14+
# Go workspace file
15+
go.work
16+
go.work.sum
17+
18+
# Dependency directories (remove the comment below to include it)
19+
# vendor/
20+
21+
# Mac files
22+
.DS_Store

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2024 ma6174
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT of OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
test:
2+
go test -cover -v -ldflags="-checklinkname=0" ./...

README.cn.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# DuckGo: 轻松创建 Go/XGo 版 DuckDB UDF
2+
3+
[![Go Reference](https://pkg.go.dev/badge/github.com/ma6174/duckgo.svg)](https://pkg.go.dev/github.com/ma6174/duckgo)
4+
5+
`DuckGo` 是一个 Go 语言库,旨在极大地简化为 [DuckDB](https://duckdb.org/) 创建用户定义函数 (UDF) 的过程。它通过 Go 的反射机制,让您可以直接将一个普通的 Go 函数无缝转换为 DuckDB 的标量函数 (Scalar UDF),同时通过[ixgo](https://github.com/goplus/ixgo)支持从 Go/XGo 脚本动态创建加载 UDF,无需预先编译。
6+
7+
## 核心功能
8+
9+
- **从原生 Go 函数创建**: 直接将你的 Go 函数(例如 `func(a, b int) int`) 转换为 DuckDB UDF。
10+
- **自动类型映射**: 自动处理 Go 类型与 DuckDB 类型之间的转换,支持多种数据类型。
11+
- **支持可变参数**: 支持 Go 的可变参数函数 (variadic functions)。
12+
- **动态脚本加载**: 无需编译!可以直接从 `.go``.xgo` 源文件动态加载函数作为 UDF。
13+
- **SQL 内直接加载**: 提供一个辅助函数,允许您直接在 SQL 查询中加载和注册脚本中的 UDF。
14+
- **错误处理**: 妥善处理 UDF 执行过程中的 `panic`,并将其转换为 DuckDB 错误返回。
15+
16+
## 安装
17+
18+
```bash
19+
go get github.com/ma6174/duckgo
20+
```
21+
22+
## 重要提示:关于动态脚本功能
23+
24+
本项目的动态脚本加载功能(由 `script` 包提供)依赖于 [ixgo](https://github.com/goplus/ixgo)。由于 `ixgo` 的工作原理,当您编译或运行任何使用了 `script` 包的代码时,**必须**添加一个特定的链接器标志来禁用符号链接检查。
25+
26+
否则,您会遇到链接器错误。
27+
28+
**编译时:**
29+
```bash
30+
go build -ldflags="-checklinkname=0" .
31+
```
32+
33+
**运行时:**
34+
```bash
35+
go run -ldflags="-checklinkname=0" .
36+
```
37+
38+
如果您在自己的项目里引用了本库的 `script` 包,请务必在您的编译和运行命令中加入此标志。
39+
40+
## 使用方法
41+
42+
下面是几个例子,展示了如何使用 `DuckGo`。完整的可运行示例代码存放在 [`example`](./example) 目录下。
43+
44+
### 示例 1: 从原生 Go 函数创建 UDF
45+
46+
这是最基本的使用方式。任何普通的 Go 函数都可以被注册。
47+
48+
核心逻辑是使用 `udf.BuildScalarUDF` 将原生 Go 函数包装起来,然后通过 `duckdb.RegisterScalarUDF` 进行注册。
49+
50+
**完整代码请参见: [`example/simple_udf/main.go`](./example/simple_udf/main.go)**
51+
52+
### 示例 2: 从 Go 脚本动态加载 UDF
53+
54+
这是 `DuckGo` 最强大的功能之一。您无需编译,可以直接将一个 Go 源文件中的函数注册为 UDF。
55+
56+
通过 `script.AddIXGoUDFFromFile` 函数,您可以指定一个 `.go` 文件和需要加载的函数名。
57+
58+
**完整代码请参见: [`example/script_udf/`](./example/script_udf/)**
59+
60+
### 示例 3: 通过 SQL 直接加载脚本 UDF
61+
62+
为了让动态加载更加灵活,您甚至可以直接在 SQL 中调用一个辅助函数来加载 UDF。
63+
64+
首先调用 `script.EnableRegisterUDFFromSQL(db)` 启用该功能,之后就可以在 SQL 中使用 `add_ixgo_udf` 函数了。
65+
66+
**完整代码请参见: [`example/sql_load_udf/`](./example/sql_load_udf/)**
67+
68+
## 包概览
69+
70+
- **`udf`**: 核心包,负责将原生的 Go 函数转换为 DuckDB UDF。
71+
- **`script`**: 提供从 Go/XGo 脚本动态加载 UDF 的功能。
72+
73+
## 许可证
74+
75+
本项目采用 [MIT](LICENSE) 许可证。

README.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# DuckGo: Create DuckDB UDFs in Go/XGo with Ease
2+
3+
[![Go Reference](https://pkg.go.dev/badge/github.com/ma6174/duckgo.svg)](https://pkg.go.dev/github.com/ma6174/duckgo)
4+
5+
`DuckGo` is a Go library designed to dramatically simplify the process of creating User-Defined Functions (UDFs) for [DuckDB](https://duckdb.org/). It uses Go's reflection mechanism to seamlessly convert a native Go function into a DuckDB scalar UDF. Additionally, it leverages [ixgo](https://github.com/goplus/ixgo) to support dynamically loading UDFs from Go/XGo scripts without prior compilation.
6+
7+
## Core Features
8+
9+
- **Create from Native Go Functions**: Directly convert your Go functions (e.g., `func(a, b int) int`) into DuckDB UDFs.
10+
- **Automatic Type Mapping**: Automatically handles type conversions between Go and DuckDB, supporting a wide range of data types.
11+
- **Variadic Function Support**: Seamlessly supports Go's variadic functions.
12+
- **Dynamic Script Loading**: No compilation needed! Directly load functions from `.go` or `.xgo` source files as UDFs.
13+
- **Direct Loading from SQL**: Provides a helper function to load and register UDFs from scripts directly within SQL queries.
14+
- **Panic Handling**: Gracefully recovers from panics during UDF execution and converts them into DuckDB errors.
15+
16+
## Installation
17+
18+
```bash
19+
go get github.com/ma6174/duckgo
20+
```
21+
22+
## Important Note: Regarding Dynamic Scripting
23+
24+
The dynamic script loading feature (provided by the `script` package) relies on [ixgo](https://github.com/goplus/ixgo). Due to how `ixgo` works, you **must** add a specific linker flag to disable symbol name checking whenever you build or run code that uses the `script` package.
25+
26+
Failure to do so will result in a linker error.
27+
28+
**Build:**
29+
```bash
30+
go build -ldflags="-checklinkname=0" .
31+
```
32+
33+
**Run:**
34+
```bash
35+
go run -ldflags="-checklinkname=0" .
36+
```
37+
38+
If you import the `script` package in your own project, make sure to include this flag in your build and run commands.
39+
40+
## Usage
41+
42+
Here are a few examples of how to use `DuckGo`. For complete, runnable code, please see the [`example`](./example) directory.
43+
44+
### Example 1: Creating a UDF from a Native Go Function
45+
46+
This is the most basic use case. Any regular Go function can be registered.
47+
48+
The core logic involves wrapping the native Go function with `udf.BuildScalarUDF` and then registering it using `duckdb.RegisterScalarUDF`.
49+
50+
**For the full code, see: [`example/simple_udf/main.go`](./example/simple_udf/main.go)**
51+
52+
### Example 2: Loading a UDF from a Go Script
53+
54+
This is one of `DuckGo`'s most powerful features. You can register a function from a Go source file as a UDF without compiling it first.
55+
56+
The `script.AddIXGoUDFFromFile` function allows you to specify a `.go` file and the names of the functions you want to load.
57+
58+
**For the full code, see: [`example/script_udf/`](./example/script_udf/)**
59+
60+
### Example 3: Loading a UDF Directly via SQL
61+
62+
For even greater flexibility, you can call a helper function from within SQL to load UDFs.
63+
64+
First, enable the feature by calling `script.EnableRegisterUDFFromSQL(db)`. You can then use the `add_ixgo_udf` function in your SQL queries.
65+
66+
**For the full code, see: [`example/sql_load_udf/`](./example/sql_load_udf/)**
67+
68+
## Package Overview
69+
70+
- **`udf`**: The core package, responsible for converting native Go functions into DuckDB UDFs.
71+
- **`script`**: Provides the functionality for dynamically loading UDFs from Go/XGo scripts.
72+
73+
## License
74+
75+
This project is licensed under the [MIT](LICENSE) License.

example/script_udf/README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Example: Dynamic UDF from Script
2+
3+
This example demonstrates how to dynamically load a User-Defined Function (UDF) into DuckDB from a Go source file (`my_udfs.go`) without prior compilation.
4+
5+
## How to Run
6+
7+
Due to the use of `ixgo` for dynamic code interpretation, you need to provide a specific linker flag to disable symbol name checking when running this example.
8+
9+
Use the following command from within this directory (`example/script_udf`):
10+
11+
```bash
12+
go run -ldflags="-checklinkname=0" .
13+
```
14+
15+
This will execute the `main.go` program, which dynamically loads the `my_multiply` function from `my_udfs.go` and then calls it from within a SQL query.

example/script_udf/main.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package main
2+
3+
import (
4+
"database/sql"
5+
"fmt"
6+
"log"
7+
8+
"github.com/ma6174/duckgo/script" // 导入 script 包
9+
_ "github.com/marcboeker/go-duckdb/v2"
10+
)
11+
12+
func main() {
13+
db, err := sql.Open("duckdb", "")
14+
if err != nil {
15+
log.Fatal(err)
16+
}
17+
defer db.Close()
18+
19+
// 从脚本文件加载 "multiply" 函数
20+
err = script.AddIXGoUDFFromFile(db, "my_udfs.go", "my_multiply")
21+
if err != nil {
22+
log.Fatal(err)
23+
}
24+
25+
// 在 SQL 中使用它
26+
var result int
27+
err = db.QueryRow("SELECT my_multiply(7, 6)").Scan(&result)
28+
if err != nil {
29+
log.Fatal(err)
30+
}
31+
fmt.Printf("my_multiply(7, 6) = %d\n", result)
32+
}

example/script_udf/my_udfs.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// my_udfs.go
2+
package main
3+
4+
func my_multiply(a, b int) int {
5+
return a * b
6+
}

example/simple_udf/main.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"fmt"
7+
"log"
8+
9+
"github.com/ma6174/duckgo/udf"
10+
"github.com/marcboeker/go-duckdb/v2"
11+
)
12+
13+
func main() {
14+
db, err := sql.Open("duckdb", "")
15+
if err != nil {
16+
log.Fatal(err)
17+
}
18+
defer db.Close()
19+
20+
// 1. 定义一个你想要在 SQL 中使用的 Go 函数
21+
add := func(a, b int) int {
22+
return a + b
23+
}
24+
25+
// 2. 使用 udf.BuildScalarUDF 将其包装成 DuckDB UDF
26+
scalarFunc, err := udf.BuildScalarUDF(add)
27+
if err != nil {
28+
log.Fatal(err)
29+
}
30+
31+
// 3. 获取连接并注册 UDF
32+
conn, err := db.Conn(context.Background())
33+
if err != nil {
34+
log.Fatal(err)
35+
}
36+
defer conn.Close()
37+
38+
err = duckdb.RegisterScalarUDF(conn, "go_add", scalarFunc)
39+
if err != nil {
40+
log.Fatal(err)
41+
}
42+
43+
// 4. 现在你可以在 SQL 中调用它了!
44+
var result int
45+
err = db.QueryRow("SELECT go_add(5, 3)").Scan(&result)
46+
if err != nil {
47+
log.Fatal(err)
48+
}
49+
fmt.Printf("go_add(5, 3) = %d\n", result)
50+
}

0 commit comments

Comments
 (0)