forked from duckdb/duckdb-go
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathduckdb.go
More file actions
181 lines (149 loc) · 4.16 KB
/
duckdb.go
File metadata and controls
181 lines (149 loc) · 4.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// Package duckdb implements a database/sql driver for the DuckDB database.
package duckdb
import (
"context"
"database/sql"
"database/sql/driver"
"fmt"
"net/url"
"strings"
"sync"
"github.com/duckdb/duckdb-go/v2/mapping"
)
var GetInstanceCache = sync.OnceValue(
func() mapping.InstanceCache {
return mapping.CreateInstanceCache()
})
func init() {
sql.Register("duckdb", Driver{})
}
type Driver struct{}
func (d Driver) Open(dsn string) (driver.Conn, error) {
c, err := d.OpenConnector(dsn)
if err != nil {
return nil, err
}
return c.Connect(context.Background())
}
func (Driver) OpenConnector(dsn string) (driver.Connector, error) {
return NewConnector(dsn, func(execerContext driver.ExecerContext) error {
return nil
})
}
type Connector struct {
// The internal DuckDB database.
db mapping.Database
// Callback to perform additional initialization steps.
connInitFn func(execer driver.ExecerContext) error
// ctxStore stores the context of the current query/exec/etc. of a connection.
ctxStore *contextStore
// True, if the connector has been closed, else false.
closed bool
}
// NewConnector opens a new Connector for a DuckDB database.
// The user must close the Connector, if it is not passed to the sql.OpenDB function.
// Otherwise, sql.DB closes the Connector when calling sql.DB.Close().
func NewConnector(dsn string, connInitFn func(execer driver.ExecerContext) error) (*Connector, error) {
inMemory := false
const inMemoryName = ":memory:"
// If necessary, trim the in-memory prefix, and determine if this is an in-memory database.
if dsn == inMemoryName || strings.HasPrefix(dsn, inMemoryName+"?") {
dsn = dsn[len(inMemoryName):]
inMemory = true
} else if dsn == "" || strings.HasPrefix(dsn, "?") {
inMemory = true
}
parsedDSN, err := url.Parse(dsn)
if err != nil {
return nil, getError(errParseDSN, err)
}
config, err := prepareConfig(parsedDSN)
if err != nil {
return nil, err
}
defer mapping.DestroyConfig(&config)
var db mapping.Database
var errMsg string
var state mapping.State
if inMemory {
// Open an in-memory database.
state = mapping.OpenExt("", &db, config, &errMsg)
} else {
// Open a file-backed database.
state = mapping.GetOrCreateFromCache(GetInstanceCache(), getDBPath(dsn), &db, config, &errMsg)
}
if state == mapping.StateError {
mapping.Close(&db)
return nil, getError(errConnect, getDuckDBError(errMsg))
}
return &Connector{
db: db,
connInitFn: connInitFn,
ctxStore: newContextStore(),
}, nil
}
func (*Connector) Driver() driver.Driver {
return Driver{}
}
func (c *Connector) Connect(ctx context.Context) (driver.Conn, error) {
var mc mapping.Connection
if mapping.Connect(c.db, &mc) == mapping.StateError {
return nil, getError(errConnect, nil)
}
conn := newConn(mc, c.ctxStore)
cleanupCtx := conn.setContext(ctx)
defer cleanupCtx()
if c.connInitFn != nil {
if err := c.connInitFn(conn); err != nil {
return nil, err
}
}
return conn, nil
}
func (c *Connector) Close() error {
if c.closed {
return nil
}
mapping.Close(&c.db)
c.closed = true
return nil
}
func getDBPath(dsn string) string {
idx := strings.Index(dsn, "?")
if idx < 0 {
idx = len(dsn)
}
return dsn[0:idx]
}
func prepareConfig(parsedDSN *url.URL) (mapping.Config, error) {
var config mapping.Config
if mapping.CreateConfig(&config) == mapping.StateError {
mapping.DestroyConfig(&config)
return config, getError(errCreateConfig, nil)
}
if err := setConfigOption(config, "duckdb_api", "go"); err != nil {
return config, err
}
// Early-out, if the DSN does not contain configuration options.
if len(parsedDSN.RawQuery) == 0 {
return config, nil
}
for k, v := range parsedDSN.Query() {
if len(v) == 0 {
continue
}
if err := setConfigOption(config, k, v[0]); err != nil {
return config, err
}
}
return config, nil
}
func setConfigOption(config mapping.Config, name, option string) error {
if mapping.SetConfig(config, name, option) == mapping.StateError {
mapping.DestroyConfig(&config)
return getError(errSetConfig, fmt.Errorf("%s=%s", name, option))
}
return nil
}