Skip to content

Commit 8e0f649

Browse files
authored
Merge pull request #5 from chdb-io/chdbv2
Use chdb stable ABI v2
2 parents 102e19d + b5e3bce commit 8e0f649

File tree

19 files changed

+211
-456
lines changed

19 files changed

+211
-456
lines changed

README.md

Lines changed: 37 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -30,40 +30,11 @@
3030
./chdb-go --path /tmp/chdb # interactive persistent mode
3131
```
3232

33-
3. Shortcuts
34-
35-
- `\l` to list databases;
36-
- `\dt dbname` to list tables in database;
37-
3833
```bash
3934
chdb-io/chdb-go [main] » ./chdb-go
4035
Enter your SQL commands; type 'exit' to quit.
4136
:) CREATE DATABASE IF NOT EXISTS testdb;
4237

43-
:) \l
44-
┏━━━━━━━━━━━━━━━━━━━━┓
45-
┃ name ┃
46-
┡━━━━━━━━━━━━━━━━━━━━┩
47-
│ INFORMATION_SCHEMA │
48-
├────────────────────┤
49-
│ _local │
50-
├────────────────────┤
51-
│ information_schema │
52-
├────────────────────┤
53-
│ system │
54-
├────────────────────┤
55-
│ testdb │
56-
└────────────────────┘
57-
58-
:) CREATE TABLE IF NOT EXISTS testdb.testtable (id UInt32) ENGINE = MergeTree()
59-
:-] ORDER BY id;
60-
61-
:) \dt testdb
62-
┏━━━━━━━━━━━┓
63-
┃ name ┃
64-
┡━━━━━━━━━━━┩
65-
│ testtable │
66-
└───────────┘
6738

6839
```
6940

@@ -72,34 +43,50 @@ Enter your SQL commands; type 'exit' to quit.
7243
package main
7344

7445
import (
75-
"fmt"
76-
"github.com/chdb-io/chdb-go/chdb"
46+
"fmt"
47+
"os"
48+
"path/filepath"
49+
50+
"github.com/chdb-io/chdb-go/chdb"
7751
)
7852

7953
func main() {
80-
// Stateless Query (ephemeral)
81-
result := chdb.Query("SELECT version()", "CSV")
82-
fmt.Println(result)
83-
84-
// Stateful Query (persistent)
85-
session, _ := NewSession(path)
86-
defer session.Cleanup()
87-
88-
session.Query("CREATE DATABASE IF NOT EXISTS testdb; " +
89-
"CREATE TABLE IF NOT EXISTS testdb.testtable (id UInt32) ENGINE = MergeTree() ORDER BY id;")
90-
91-
session.Query("USE testdb; INSERT INTO testtable VALUES (1), (2), (3);")
92-
93-
ret := session.Query("SELECT * FROM testtable;")
94-
fmt.Println(ret)
54+
// Stateless Query (ephemeral)
55+
result, err := chdb.Query("SELECT version()", "CSV")
56+
if err != nil {
57+
fmt.Println(err)
58+
}
59+
fmt.Println(result)
60+
61+
tmp_path := filepath.Join(os.TempDir(), "chdb_test")
62+
defer os.RemoveAll(tmp_path)
63+
// Stateful Query (persistent)
64+
session, _ := chdb.NewSession(tmp_path)
65+
defer session.Cleanup()
66+
67+
_, err = session.Query("CREATE DATABASE IF NOT EXISTS testdb; " +
68+
"CREATE TABLE IF NOT EXISTS testdb.testtable (id UInt32) ENGINE = MergeTree() ORDER BY id;")
69+
if err != nil {
70+
fmt.Println(err)
71+
return
72+
}
73+
74+
_, err = session.Query("USE testdb; INSERT INTO testtable VALUES (1), (2), (3);")
75+
if err != nil {
76+
fmt.Println(err)
77+
return
78+
}
79+
80+
ret, err := session.Query("SELECT * FROM testtable;")
81+
if err != nil {
82+
fmt.Println(err)
83+
} else {
84+
fmt.Println(ret)
85+
}
9586
}
9687
```
9788

9889
### Golang API docs
9990

10091
- See [lowApi.md](lowApi.md) for the low level APIs.
10192
- See [chdb.md](chdb.md) for high level APIs.
102-
103-
### Thanks
104-
105-
- cli implementation is based on [clickhouse-cli](https://github.com/memlimit/clickhouse-cli)

chdb/driver/driver.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func init() {
2727
sql.Register("chdb", Driver{})
2828
}
2929

30-
type queryHandle func(string, ...string) *chdbstable.LocalResult
30+
type queryHandle func(string, ...string) (*chdbstable.LocalResult, error)
3131

3232
type connector struct {
3333
udfPath string
@@ -127,7 +127,10 @@ func (c *conn) Query(query string, values []driver.Value) (driver.Rows, error) {
127127
}
128128

129129
func (c *conn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
130-
result := c.QueryFun(query, "Arrow", c.udfPath)
130+
result, err := c.QueryFun(query, "Arrow", c.udfPath)
131+
if err != nil {
132+
return nil, err
133+
}
131134
buf := result.Buf()
132135
if buf == nil {
133136
return nil, fmt.Errorf("result is nil")

chdb/driver/driver_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,10 @@ func TestDbWithSession(t *testing.T) {
9090

9191
session.Query("USE testdb; INSERT INTO testtable VALUES (1), (2), (3);")
9292

93-
ret := session.Query("SELECT * FROM testtable;")
93+
ret, err := session.Query("SELECT * FROM testtable;")
94+
if err != nil {
95+
t.Fatalf("Query fail, err: %s", err)
96+
}
9497
if string(ret.Buf()) != "1\n2\n3\n" {
9598
t.Errorf("Query result should be 1\n2\n3\n, got %s", string(ret.Buf()))
9699
}

chdb/session.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ func NewSession(paths ...string) (*Session, error) {
3636
}
3737

3838
// Query calls queryToBuffer with a default output format of "CSV" if not provided.
39-
func (s *Session) Query(queryStr string, outputFormats ...string) *chdbstable.LocalResult {
39+
func (s *Session) Query(queryStr string, outputFormats ...string) (result *chdbstable.LocalResult, err error) {
4040
outputFormat := "CSV" // Default value
4141
if len(outputFormats) > 0 {
4242
outputFormat = outputFormats[0]

chdb/session_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,10 @@ func TestQuery(t *testing.T) {
6464

6565
session.Query("USE testdb; INSERT INTO testtable VALUES (1), (2), (3);")
6666

67-
ret := session.Query("SELECT * FROM testtable;")
67+
ret, err := session.Query("SELECT * FROM testtable;")
68+
if err != nil {
69+
t.Errorf("Query failed: %s", err)
70+
}
6871
if string(ret.Buf()) != "1\n2\n3\n" {
6972
t.Errorf("Query result should be 1\n2\n3\n, got %s", string(ret.Buf()))
7073
}

chdb/wrapper.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import (
55
)
66

77
// Query calls queryToBuffer with a default output format of "CSV" if not provided.
8-
func Query(queryStr string, outputFormats ...string) *chdbstable.LocalResult {
8+
func Query(queryStr string, outputFormats ...string) (result *chdbstable.LocalResult, err error) {
99
outputFormat := "CSV" // Default value
1010
if len(outputFormats) > 0 {
1111
outputFormat = outputFormats[0]
@@ -14,7 +14,7 @@ func Query(queryStr string, outputFormats ...string) *chdbstable.LocalResult {
1414
}
1515

1616
// queryToBuffer constructs the arguments for QueryStable and calls it.
17-
func queryToBuffer(queryStr, outputFormat, path, udfPath string) *chdbstable.LocalResult {
17+
func queryToBuffer(queryStr, outputFormat, path, udfPath string) (result *chdbstable.LocalResult, err error) {
1818
argv := []string{"clickhouse", "--multiquery"}
1919

2020
// Handle output format

chdb/wrapper_test.go

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
11
package chdb
22

33
import (
4-
"io/ioutil"
54
"os"
5+
"path/filepath"
66
"testing"
77
)
88

99
func TestQueryToBuffer(t *testing.T) {
1010
// Create a temporary directory
11-
tempDir, err := ioutil.TempDir("", "example")
12-
if err != nil {
13-
t.Fatalf("Failed to create temporary directory: %v", err)
14-
}
11+
tempDir := filepath.Join(os.TempDir(), "chdb_test")
1512
defer os.RemoveAll(tempDir)
1613

1714
// Define test cases
@@ -21,6 +18,7 @@ func TestQueryToBuffer(t *testing.T) {
2118
outputFormat string
2219
path string
2320
udfPath string
21+
expectedErrMsg string
2422
expectedResult string
2523
}{
2624
{
@@ -29,6 +27,7 @@ func TestQueryToBuffer(t *testing.T) {
2927
outputFormat: "CSV",
3028
path: "",
3129
udfPath: "",
30+
expectedErrMsg: "",
3231
expectedResult: "123\n",
3332
},
3433
// Session
@@ -39,35 +38,59 @@ func TestQueryToBuffer(t *testing.T) {
3938
outputFormat: "CSV",
4039
path: tempDir,
4140
udfPath: "",
41+
expectedErrMsg: "",
4242
expectedResult: "",
4343
},
44+
// {
45+
// name: "Session Query 2",
46+
// queryStr: "USE testdb; INSERT INTO testtable VALUES (1), (2), (3);",
47+
// outputFormat: "CSV",
48+
// path: tempDir,
49+
// udfPath: "",
50+
// expectedErrMsg: "",
51+
// expectedResult: "",
52+
// },
53+
// {
54+
// name: "Session Query 3",
55+
// queryStr: "SELECT * FROM testtable;",
56+
// outputFormat: "CSV",
57+
// path: tempDir,
58+
// udfPath: "",
59+
// expectedErrMsg: "",
60+
// expectedResult: "1\n2\n3\n",
61+
// },
4462
{
45-
name: "Session Query 2",
46-
queryStr: "USE testdb; INSERT INTO testtable VALUES (1), (2), (3);",
63+
name: "Error Query",
64+
queryStr: "SELECT * FROM nonexist; ",
4765
outputFormat: "CSV",
4866
path: tempDir,
4967
udfPath: "",
68+
expectedErrMsg: "Code: 60. DB::Exception: Table _local.nonexist does not exist. (UNKNOWN_TABLE)",
5069
expectedResult: "",
5170
},
52-
{
53-
name: "Session Query 3",
54-
queryStr: "SELECT * FROM testtable;",
55-
outputFormat: "CSV",
56-
path: tempDir,
57-
udfPath: "",
58-
expectedResult: "1\n2\n3\n",
59-
},
6071
}
6172

6273
for _, tc := range testCases {
6374
t.Run(tc.name, func(t *testing.T) {
6475
// Call queryToBuffer
65-
result := queryToBuffer(tc.queryStr, tc.outputFormat, tc.path, tc.udfPath)
76+
result, err := queryToBuffer(tc.queryStr, tc.outputFormat, tc.path, tc.udfPath)
6677

6778
// Verify
68-
if string(result.Buf()) != tc.expectedResult {
69-
t.Errorf("%v queryToBuffer() with queryStr %v, outputFormat %v, path %v, udfPath %v, expect result: %v, got result: %v",
70-
tc.name, tc.queryStr, tc.outputFormat, tc.path, tc.udfPath, tc.expectedResult, string(result.Buf()))
79+
if tc.expectedErrMsg != "" {
80+
if err == nil {
81+
t.Errorf("%v queryToBuffer() with queryStr %v, outputFormat %v, path %v, udfPath %v, expect error message: %v, got no error",
82+
tc.name, tc.queryStr, tc.outputFormat, tc.path, tc.udfPath, tc.expectedErrMsg)
83+
} else {
84+
if err.Error() != tc.expectedErrMsg {
85+
t.Errorf("%v queryToBuffer() with queryStr %v, outputFormat %v, path %v, udfPath %v, expect error message: %v, got error message: %v",
86+
tc.name, tc.queryStr, tc.outputFormat, tc.path, tc.udfPath, tc.expectedErrMsg, err.Error())
87+
}
88+
}
89+
} else {
90+
if string(result.Buf()) != tc.expectedResult {
91+
t.Errorf("%v queryToBuffer() with queryStr %v, outputFormat %v, path %v, udfPath %v, expect result: %v, got result: %v",
92+
tc.name, tc.queryStr, tc.outputFormat, tc.path, tc.udfPath, tc.expectedResult, string(result.Buf()))
93+
}
7194
}
7295
})
7396
}

chdbstable/chdb.go

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,40 +7,62 @@ package chdbstable
77
*/
88
import "C"
99
import (
10+
"errors"
1011
"runtime"
1112
"unsafe"
1213
)
1314

14-
// LocalResult mirrors the C struct local_result in Go.
15+
// ChdbError is returned when the C function returns an error.
16+
type ChdbError struct {
17+
msg string
18+
}
19+
20+
func (e *ChdbError) Error() string {
21+
return e.msg
22+
}
23+
24+
// ErrNilResult is returned when the C function returns a nil pointer.
25+
var ErrNilResult = errors.New("chDB C function returned nil pointer")
26+
27+
// LocalResult mirrors the C struct local_result_v2 in Go.
1528
type LocalResult struct {
16-
cResult *C.struct_local_result
29+
cResult *C.struct_local_result_v2
1730
}
1831

1932
// newLocalResult creates a new LocalResult and sets a finalizer to free C memory.
20-
func newLocalResult(cResult *C.struct_local_result) *LocalResult {
33+
func newLocalResult(cResult *C.struct_local_result_v2) *LocalResult {
2134
result := &LocalResult{cResult: cResult}
2235
runtime.SetFinalizer(result, freeLocalResult)
2336
return result
2437
}
2538

2639
// freeLocalResult is called by the garbage collector.
2740
func freeLocalResult(result *LocalResult) {
28-
C.free_result(result.cResult)
41+
C.free_result_v2(result.cResult)
2942
}
3043

31-
// QueryStable calls the C function query_stable.
32-
func QueryStable(argc int, argv []string) *LocalResult {
44+
// QueryStable calls the C function query_stable_v2.
45+
func QueryStable(argc int, argv []string) (result *LocalResult, err error) {
3346
cArgv := make([]*C.char, len(argv))
3447
for i, s := range argv {
3548
cArgv[i] = C.CString(s)
3649
defer C.free(unsafe.Pointer(cArgv[i]))
3750
}
3851

39-
cResult := C.query_stable(C.int(argc), &cArgv[0])
40-
return newLocalResult(cResult)
52+
cResult := C.query_stable_v2(C.int(argc), &cArgv[0])
53+
if cResult == nil {
54+
// According to the C ABI of chDB v1.2.0, the C function query_stable_v2
55+
// returns nil if the query returns no data. This is not an error. We
56+
// will change this behavior in the future.
57+
return newLocalResult(cResult), nil
58+
}
59+
if cResult.error_message != nil {
60+
return nil, &ChdbError{msg: C.GoString(cResult.error_message)}
61+
}
62+
return newLocalResult(cResult), nil
4163
}
4264

43-
// Accessor methods to access fields of the local_result struct.
65+
// Accessor methods to access fields of the local_result_v2 struct.
4466
func (r *LocalResult) Buf() []byte {
4567
if r.cResult == nil {
4668
return nil

chdbstable/chdb.h

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ extern "C" {
1010
#endif
1111

1212
#define CHDB_EXPORT __attribute__((visibility("default")))
13-
struct CHDB_EXPORT local_result
13+
struct local_result
1414
{
1515
char * buf;
1616
size_t len;
@@ -20,9 +20,23 @@ struct CHDB_EXPORT local_result
2020
uint64_t bytes_read;
2121
};
2222

23+
struct local_result_v2
24+
{
25+
char * buf;
26+
size_t len;
27+
void * _vec; // std::vector<char> *, for freeing
28+
double elapsed;
29+
uint64_t rows_read;
30+
uint64_t bytes_read;
31+
char * error_message;
32+
};
33+
2334
CHDB_EXPORT struct local_result * query_stable(int argc, char ** argv);
2435
CHDB_EXPORT void free_result(struct local_result * result);
2536

37+
CHDB_EXPORT struct local_result_v2 * query_stable_v2(int argc, char ** argv);
38+
CHDB_EXPORT void free_result_v2(struct local_result_v2 * result);
39+
2640
#ifdef __cplusplus
2741
}
2842
#endif

0 commit comments

Comments
 (0)