Skip to content

Commit 55cd56d

Browse files
authored
Merge pull request #347 from DataDog/dd/vtable
Add Go API for virtual tables
2 parents 3439fd2 + 9efa963 commit 55cd56d

File tree

7 files changed

+945
-1
lines changed

7 files changed

+945
-1
lines changed

_example/vtable/main.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package main
2+
3+
import (
4+
"database/sql"
5+
"fmt"
6+
"github.com/mattn/go-sqlite3"
7+
"log"
8+
)
9+
10+
func main() {
11+
sql.Register("sqlite3_with_extensions", &sqlite3.SQLiteDriver{
12+
ConnectHook: func(conn *sqlite3.SQLiteConn) error {
13+
return conn.CreateModule("github", githubModule{})
14+
},
15+
})
16+
db, err := sql.Open("sqlite3_with_extensions", ":memory:")
17+
if err != nil {
18+
log.Fatal(err)
19+
}
20+
defer db.Close()
21+
22+
_, err = db.Exec("create virtual table repo using github(id, full_name, description, html_url)")
23+
if err != nil {
24+
log.Fatal(err)
25+
}
26+
27+
rows, err := db.Query("select id, full_name, description, html_url from repo")
28+
if err != nil {
29+
log.Fatal(err)
30+
}
31+
defer rows.Close()
32+
for rows.Next() {
33+
var id, full_name, description, html_url string
34+
rows.Scan(&id, &full_name, &description, &html_url)
35+
fmt.Printf("%s: %s\n\t%s\n\t%s\n\n", id, full_name, description, html_url)
36+
}
37+
}

_example/vtable/vtable.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"github.com/DataDog/go-sqlite3"
7+
"io/ioutil"
8+
"net/http"
9+
)
10+
11+
type GithubRepo struct {
12+
ID int `json:"id"`
13+
FullName string `json:"full_name"`
14+
Description string `json:"description"`
15+
HtmlURL string `json:"html_url"`
16+
}
17+
18+
type githubModule struct {
19+
}
20+
21+
func (m githubModule) Create(c *sqlite3.SQLiteConn, args []string) (sqlite3.VTab, error) {
22+
err := c.DeclareVTab(fmt.Sprintf(`
23+
CREATE TABLE %s (
24+
id INT,
25+
full_name TEXT,
26+
description TEXT,
27+
html_url TEXT
28+
)`, args[0]))
29+
if err != nil {
30+
return nil, err
31+
}
32+
return &ghRepoTable{}, nil
33+
}
34+
35+
func (m githubModule) Connect(c *sqlite3.SQLiteConn, args []string) (sqlite3.VTab, error) {
36+
return m.Create(c, args)
37+
}
38+
39+
func (m githubModule) DestroyModule() {}
40+
41+
type ghRepoTable struct {
42+
repos []GithubRepo
43+
}
44+
45+
func (v *ghRepoTable) Open() (sqlite3.VTabCursor, error) {
46+
resp, err := http.Get("https://api.github.com/repositories")
47+
if err != nil {
48+
return nil, err
49+
}
50+
defer resp.Body.Close()
51+
52+
body, err := ioutil.ReadAll(resp.Body)
53+
if err != nil {
54+
return nil, err
55+
}
56+
57+
repos := make([]GithubRepo, 0)
58+
if err := json.Unmarshal(body, &repos); err != nil {
59+
return nil, err
60+
}
61+
return &ghRepoCursor{0, repos}, nil
62+
}
63+
64+
func (v *ghRepoTable) BestIndex(cst []sqlite3.InfoConstraint, ob []sqlite3.InfoOrderBy) (*sqlite3.IndexResult, error) {
65+
return &sqlite3.IndexResult{}, nil
66+
}
67+
68+
func (v *ghRepoTable) Disconnect() error { return nil }
69+
func (v *ghRepoTable) Destroy() error { return nil }
70+
71+
type ghRepoCursor struct {
72+
index int
73+
repos []GithubRepo
74+
}
75+
76+
func (vc *ghRepoCursor) Column(c *sqlite3.SQLiteContext, col int) error {
77+
switch col {
78+
case 0:
79+
c.ResultInt(vc.repos[vc.index].ID)
80+
case 1:
81+
c.ResultText(vc.repos[vc.index].FullName)
82+
case 2:
83+
c.ResultText(vc.repos[vc.index].Description)
84+
case 3:
85+
c.ResultText(vc.repos[vc.index].HtmlURL)
86+
}
87+
return nil
88+
}
89+
90+
func (vc *ghRepoCursor) Filter(idxNum int, idxStr string, vals []interface{}) error {
91+
vc.index = 0
92+
return nil
93+
}
94+
95+
func (vc *ghRepoCursor) Next() error {
96+
vc.index++
97+
return nil
98+
}
99+
100+
func (vc *ghRepoCursor) EOF() bool {
101+
return vc.index >= len(vc.repos)
102+
}
103+
104+
func (vc *ghRepoCursor) Rowid() (int64, error) {
105+
return int64(vc.index), nil
106+
}
107+
108+
func (vc *ghRepoCursor) Close() error {
109+
return nil
110+
}

sqlite3.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -627,11 +627,11 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
627627

628628
// Close the connection.
629629
func (c *SQLiteConn) Close() error {
630-
deleteHandles(c)
631630
rv := C.sqlite3_close_v2(c.db)
632631
if rv != C.SQLITE_OK {
633632
return c.lastError()
634633
}
634+
deleteHandles(c)
635635
c.db = nil
636636
runtime.SetFinalizer(c, nil)
637637
return nil

sqlite_context.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Copyright (C) 2014 Yasuhiro Matsumoto <[email protected]>.
2+
//
3+
// Use of this source code is governed by an MIT-style
4+
// license that can be found in the LICENSE file.
5+
6+
package sqlite3
7+
8+
/*
9+
10+
#ifndef USE_LIBSQLITE3
11+
#include <sqlite3-binding.h>
12+
#else
13+
#include <sqlite3.h>
14+
#endif
15+
#include <stdlib.h>
16+
// These wrappers are necessary because SQLITE_TRANSIENT
17+
// is a pointer constant, and cgo doesn't translate them correctly.
18+
19+
static inline void my_result_text(sqlite3_context *ctx, char *p, int np) {
20+
sqlite3_result_text(ctx, p, np, SQLITE_TRANSIENT);
21+
}
22+
23+
static inline void my_result_blob(sqlite3_context *ctx, void *p, int np) {
24+
sqlite3_result_blob(ctx, p, np, SQLITE_TRANSIENT);
25+
}
26+
*/
27+
import "C"
28+
29+
import (
30+
"math"
31+
"reflect"
32+
"unsafe"
33+
)
34+
35+
const i64 = unsafe.Sizeof(int(0)) > 4
36+
37+
type ZeroBlobLength int32
38+
type SQLiteContext C.sqlite3_context
39+
40+
// ResultBool sets the result of an SQL function.
41+
func (c *SQLiteContext) ResultBool(b bool) {
42+
if b {
43+
c.ResultInt(1)
44+
} else {
45+
c.ResultInt(0)
46+
}
47+
}
48+
49+
// ResultBlob sets the result of an SQL function.
50+
// See: sqlite3_result_blob, http://sqlite.org/c3ref/result_blob.html
51+
func (c *SQLiteContext) ResultBlob(b []byte) {
52+
if i64 && len(b) > math.MaxInt32 {
53+
C.sqlite3_result_error_toobig((*C.sqlite3_context)(c))
54+
return
55+
}
56+
var p *byte
57+
if len(b) > 0 {
58+
p = &b[0]
59+
}
60+
C.my_result_blob((*C.sqlite3_context)(c), unsafe.Pointer(p), C.int(len(b)))
61+
}
62+
63+
// ResultDouble sets the result of an SQL function.
64+
// See: sqlite3_result_double, http://sqlite.org/c3ref/result_blob.html
65+
func (c *SQLiteContext) ResultDouble(d float64) {
66+
C.sqlite3_result_double((*C.sqlite3_context)(c), C.double(d))
67+
}
68+
69+
// ResultInt sets the result of an SQL function.
70+
// See: sqlite3_result_int, http://sqlite.org/c3ref/result_blob.html
71+
func (c *SQLiteContext) ResultInt(i int) {
72+
if i64 && (i > math.MaxInt32 || i < math.MinInt32) {
73+
C.sqlite3_result_int64((*C.sqlite3_context)(c), C.sqlite3_int64(i))
74+
} else {
75+
C.sqlite3_result_int((*C.sqlite3_context)(c), C.int(i))
76+
}
77+
}
78+
79+
// ResultInt64 sets the result of an SQL function.
80+
// See: sqlite3_result_int64, http://sqlite.org/c3ref/result_blob.html
81+
func (c *SQLiteContext) ResultInt64(i int64) {
82+
C.sqlite3_result_int64((*C.sqlite3_context)(c), C.sqlite3_int64(i))
83+
}
84+
85+
// ResultNull sets the result of an SQL function.
86+
// See: sqlite3_result_null, http://sqlite.org/c3ref/result_blob.html
87+
func (c *SQLiteContext) ResultNull() {
88+
C.sqlite3_result_null((*C.sqlite3_context)(c))
89+
}
90+
91+
// ResultText sets the result of an SQL function.
92+
// See: sqlite3_result_text, http://sqlite.org/c3ref/result_blob.html
93+
func (c *SQLiteContext) ResultText(s string) {
94+
h := (*reflect.StringHeader)(unsafe.Pointer(&s))
95+
cs, l := (*C.char)(unsafe.Pointer(h.Data)), C.int(h.Len)
96+
C.my_result_text((*C.sqlite3_context)(c), cs, l)
97+
}
98+
99+
// ResultZeroblob sets the result of an SQL function.
100+
// See: sqlite3_result_zeroblob, http://sqlite.org/c3ref/result_blob.html
101+
func (c *SQLiteContext) ResultZeroblob(n ZeroBlobLength) {
102+
C.sqlite3_result_zeroblob((*C.sqlite3_context)(c), C.int(n))
103+
}

0 commit comments

Comments
 (0)