-
Notifications
You must be signed in to change notification settings - Fork 3
/
driver.go
186 lines (169 loc) · 6.82 KB
/
driver.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
// Package **cli** provides access to a **DB2 database** using DB2 Call Level Interface (**CLI**) API.
// This requires **cgo** and DB2 _cli/odbc_ driver **libdb2.so**. It is not possible to use this driver to create a statically linked Go package because
// IBM doesn't provide the DB2 _cli/odbc_ driver as _libdb2.a_ static library.
// On **Windows**, DB2 _cli/odbc_ library is not compatiable with **gcc**, but **cgo** requires **gcc**. Hence, this driver is not
// supported on Windows.
//
// **cli** is based on *alexbrainman's* odbc package: https://github.com/alexbrainman/odbc.
//
// This package registers a driver for the standard Go **database/sql** package and used through the
// **database/sql** API.
//
// import _ "github.com/asifjalil/cli"
//
// ### Error Handling
// The package has no exported API except two functions-**SQLCode()** and **SQLState()**-for inspecting
// DB2 CLI error. The function signature is as follows:
// func (e *cliError) SQLCode() int
// func (e *cliError) SQLState() string
//
// Since package **cli** is imported for side-effects only, use the following code
// pattern to access SQLCode() and SQLState():
// func checkError(err error) {
// type sqlcode interface {
// SQLCode() int
// }
// if err != nil {
// if err, ok := err.(sqlcode); ok {
// log.Println(err.SQLCode())
// }
// log.Fatal(err)
// }
// }
// The local interface can include SQLState() also for inspecting SQLState from DB2 CLI.
//
// **SQLCODE** is a return code from a IBM DB2 SQL operation.
// This code can be zero (0), negative, or positive.
// 0 means successful execution.
// Negative means unsuccessful execution with an error.
// For example -911 means a lock timeout occurred with a rollback.
// Positive means successful execution with a warning.
// For example +100 means no rows found or end of table.
//
// Search "SQL messages" in DB2 Information Center to find out more about SQLCODE.
//
// **SQLSTATE** is a return code like SQLCODE.
// But instead of a number, it is a five character error code that is consistent across all IBM database products.
// SQLSTATE follows this format: ccsss, where cc indicates class and sss indicates subclass.
// Search "SQLSTATE Messages" in DB2 Information Center for more detail.
//
// ### Connection String
// This driver uses DB2 CLI function **SQLConnect** and **SQLDriverConnect** in driver.Open(...).
// To use **SQLConnect**, start the name or the DSN string with keyword sqlconnect. This keyword is case insensitive.
// The connection string needs to follow this syntax to be valid:
//
// "sqlconnect;[DATABASE=<database_name>;][UID=<user_id>;][PWD=<password>;]"
//
// [...] means optional. If a database_name is not provided, then SAMPLE is
// used as the database name. Also note that each keyword and value ends with
// a semicolon. The keyword "sqlconnect" doesn't take a value but ends with a semi-colon.
// Examples:
// db, err := sql.Open("cli", "sqlconnect;")
// db, err := sql.Open("cli", "sqlconnect; DATABASE=\"SAMPLE\";")
//
// Any other connection string must follow the connection string rule that is
// valid with SQLDriverConnect. For example, this is a valid dsn/connection string
// for SQLDriverConnect:
//
// "DSN=Sample; UID=asif; PWD=secrect; AUTOCOMMIT=0; CONNECTTYPE=1;"
// Examples:
// db, err := sql.Open("cli", "DSN=Sample; UID=asif; PWD=secrect; AUTOCOMMIT=0; CONNECTTYPE=1;")
// db, err := sql.Open("cli", "DATABASE=db; HOSTNAME=dbhost; PORT=40000; PROTOCOL=TCPIP; UID=me; PWD=secret;")
//
// Search **SQLDriverConnect** in DB2 LUW *Information Center* for more detail.
//
// ## Installation
// IBM DB2 for Linux, Unix and Windows (DB2 LUW) implements its own ODBC driver.
// This package uses the DB2 ODBC/CLI driver through cgo.
// As such this package requires DB2 C headers and libraries for compilation.
// If you don't have DB2 LUW installed on the system, then you can install
// the free, community DB2 version *DB2 Express-C*.
// You can also use *IBM Data Server Driver package*. It includes the required headers and libraries
// but not a DB2 database manager.
//
// To install, download this package by running the following:
// go get -d github.com/asifjalil/cli
// Go to the following directory:
// $GOPATH/src/github.com/asifjalil/cli
// In that directory run the following to install the package:
// ./install.sh
// This script and this driver only works on Mac OS and Linux.
//
// ## Usage
// See `example_test.go`.
package cli
// Adding new C function based on alexbrainman's odbc driver.
// https://github.com/alexbrainman/odbc/blob/master/api/api_unix.go
// This is so we pass -d=checkptr.
/*
#cgo LDFLAGS: -ldb2
#include <stdint.h>
#include <sqlcli1.h>
SQLRETURN sqlSetEnvUIntPtrAttr(SQLHENV environmentHandle, SQLINTEGER attribute, uintptr_t valuePtr, SQLINTEGER stringLength) {
return SQLSetEnvAttr(environmentHandle, attribute, (SQLPOINTER)valuePtr, stringLength);
}
SQLRETURN sqlSetConnectUIntPtrAttr(SQLHDBC connectionHandle, SQLINTEGER attribute, uintptr_t valuePtr, SQLINTEGER stringLength) {
return SQLSetConnectAttr(connectionHandle, attribute, (SQLPOINTER)valuePtr, stringLength);
}
*/
import "C"
import (
"database/sql"
"fmt"
)
var drv impl
type (
sql_DATE_STRUCT struct {
year C.SQLSMALLINT
month C.SQLUSMALLINT
day C.SQLUSMALLINT
}
sql_TIMESTAMP_STRUCT struct {
year C.SQLSMALLINT
month C.SQLUSMALLINT
day C.SQLUSMALLINT
hour C.SQLUSMALLINT
minute C.SQLUSMALLINT
second C.SQLUSMALLINT
fraction C.SQLUINTEGER
}
sql_TIME_STRUCT struct {
hour C.SQLUSMALLINT
minute C.SQLUSMALLINT
second C.SQLUSMALLINT
}
)
type impl struct {
henv C.SQLHANDLE // environment handle
}
func initDriver() error {
// Allocate environment handle
ret := C.SQLAllocHandle(C.SQL_HANDLE_ENV, C.SQL_NULL_HANDLE, &drv.henv)
if !success(ret) {
return fmt.Errorf("database/sql/driver: [asifjalil][CLI driver]Failed to allocate environment handle; rc: %d ", int(ret))
}
//use ODBC v3
ret = sqlSetEnvUIntPtrAttr(C.SQLHENV(drv.henv),
C.SQL_ATTR_ODBC_VERSION,
uintptr(C.SQL_OV_ODBC3), 0)
if !success(ret) {
defer C.SQLFreeHandle(C.SQL_HANDLE_ENV, drv.henv)
return formatError(C.SQL_HANDLE_ENV, drv.henv)
}
return nil
}
func init() {
err := initDriver()
if err != nil {
panic(err)
}
sql.Register("cli", &drv)
}
func sqlSetEnvUIntPtrAttr(environmentHandle C.SQLHENV, attribute C.SQLINTEGER, valuePtr uintptr, stringLength C.SQLINTEGER) (ret C.SQLRETURN) {
r := C.sqlSetEnvUIntPtrAttr(C.SQLHENV(environmentHandle), C.SQLINTEGER(attribute), C.uintptr_t(valuePtr), C.SQLINTEGER(stringLength))
return C.SQLRETURN(r)
}
func sqlSetConnectUIntPtrAttr(connectionHandle C.SQLHDBC, attribute C.SQLINTEGER, valuePtr uintptr, stringLength C.SQLINTEGER) (ret C.SQLRETURN) {
r := C.sqlSetConnectUIntPtrAttr(C.SQLHDBC(connectionHandle), C.SQLINTEGER(attribute), C.uintptr_t(valuePtr), C.SQLINTEGER(stringLength))
return C.SQLRETURN(r)
}