-
Notifications
You must be signed in to change notification settings - Fork 15
/
conn.go
191 lines (156 loc) · 5.1 KB
/
conn.go
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
182
183
184
185
186
187
188
189
190
191
// SPDX-FileCopyrightText: 2020 SAP SE
// SPDX-FileCopyrightText: 2021 SAP SE
// SPDX-FileCopyrightText: 2022 SAP SE
// SPDX-FileCopyrightText: 2023 SAP SE
//
// SPDX-License-Identifier: Apache-2.0
package ase
import (
"context"
"database/sql/driver"
"fmt"
"sync"
"github.com/SAP/go-dblib/asetypes"
"github.com/SAP/go-dblib/tds"
)
// Interface satisfaction checks
var (
_ driver.Conn = (*Conn)(nil)
_ driver.ConnPrepareContext = (*Conn)(nil)
_ driver.ExecerContext = (*Conn)(nil)
_ driver.QueryerContext = (*Conn)(nil)
_ driver.Pinger = (*Conn)(nil)
)
// Conn implements the driver.Conn interface.
type Conn struct {
Conn *tds.Conn
Channel *tds.Channel
Info *Info
// TODO I don't particularly like locking statements like this
stmts map[int]*Stmt
// TODO: iirc conns aren't used in multiple threads at the same time
stmtLock *sync.RWMutex
}
// NewConn returns a connection with the passed configuration.
func NewConn(ctx context.Context, dsn *Info) (*Conn, error) {
return NewConnWithHooks(ctx, dsn, nil, nil)
}
// NewConnWithHooks returns a connection with the passed configuration.
func NewConnWithHooks(ctx context.Context, info *Info, envChangeHooks []tds.EnvChangeHook, eedHooks []tds.EEDHook) (*Conn, error) {
conn := &Conn{
Info: info,
stmts: map[int]*Stmt{},
stmtLock: &sync.RWMutex{},
}
// Cannot pass the passed context along here as tds.NewConn creates
// a child context from the passed context.
// Otherwise the context isn't being used, so using
// context.Background is fine.
var err error
conn.Conn, err = tds.NewConn(context.Background(), &info.Info)
if err != nil {
return nil, fmt.Errorf("go-ase: error opening connection to TDS server: %w", err)
}
conn.Channel, err = conn.Conn.NewChannel()
if err != nil {
conn.Close()
return nil, fmt.Errorf("go-ase: error opening logical channel: %w", err)
}
if drv.envChangeHooks != nil {
if err := conn.Channel.RegisterEnvChangeHooks(drv.envChangeHooks...); err != nil {
return nil, fmt.Errorf("go-ase: error registering driver EnvChangeHooks: %w", err)
}
}
if envChangeHooks != nil {
if err := conn.Channel.RegisterEnvChangeHooks(envChangeHooks...); err != nil {
return nil, fmt.Errorf("go-ase: error registering argument EnvChangeHooks: %w", err)
}
}
if drv.eedHooks != nil {
if err := conn.Channel.RegisterEEDHooks(drv.eedHooks...); err != nil {
return nil, fmt.Errorf("go-ase: error registering driver EEDHooks: %w", err)
}
}
if eedHooks != nil {
if err := conn.Channel.RegisterEEDHooks(eedHooks...); err != nil {
return nil, fmt.Errorf("go-ase: error registering argument EEDHooks: %w", err)
}
}
loginConfig, err := tds.NewLoginConfig(&info.Info)
if err != nil {
conn.Close()
return nil, fmt.Errorf("go-ase: error creating login config: %w", err)
}
loginConfig.AppName = info.AppName
if err := conn.Channel.Login(ctx, loginConfig); err != nil {
conn.Close()
return nil, fmt.Errorf("go-ase: error logging in: %w", err)
}
// TODO can this be passed another way?
if info.Database != "" {
if _, err = conn.ExecContext(ctx, "use "+info.Database, nil); err != nil {
return nil, fmt.Errorf("go-ase: error switching to database %s: %w", info.Database, err)
}
}
return conn, nil
}
// Close implements the driver.Conn interface.
func (c *Conn) Close() error {
if err := c.Conn.Close(); err != nil {
return fmt.Errorf("go-ase: error closing TDS connection: %w", err)
}
return nil
}
// ExecContext implements the driver.ExecerContext.
func (c *Conn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
rows, result, err := c.GenericExec(ctx, query, args)
if rows != nil {
rows.Close()
}
return result, err
}
type NoQueryCursor bool
// QueryContext implements the driver.QueryerContext.
//
// QueryContext utilizes cursors unless c.Info.NoQueryCursor is set or
// the context has the value "NoQueryContext" set to true.
//
// If the context has NoQueryCursor set it overrides
// c.Info.NoQueryCursor.
func (c *Conn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
noQueryCursor := c.Info.NoQueryCursor
ctxNoQueryCursor, ok := ctx.Value(NoQueryCursor(true)).(bool)
if ok {
noQueryCursor = ctxNoQueryCursor
}
if noQueryCursor {
rows, _, err := c.GenericExec(ctx, query, args)
return rows, err
}
cursor, err := c.NewCursorWithValues(ctx, query, args)
if err != nil {
return nil, err
}
return cursor.Fetch(ctx)
}
// Ping implements the driver.Pinger interface.
func (c Conn) Ping(ctx context.Context) error {
// TODO implement ErrBadConn check
rows, _, err := c.language(ctx, "select 'ping'")
if err != nil {
return fmt.Errorf("go-ase: error pinging database: %w", err)
}
if err := rows.Close(); err != nil {
return fmt.Errorf("go-ase: error closing rows from ping: %w", err)
}
return nil
}
// CheckNamedValue implements the driver.NamedValueChecker interface.
func (conn *Conn) CheckNamedValue(nv *driver.NamedValue) error {
v, err := asetypes.DefaultValueConverter.ConvertValue(nv.Value)
if err != nil {
return err
}
nv.Value = v
return nil
}