forked from minus5/gofreetds
-
Notifications
You must be signed in to change notification settings - Fork 0
/
fetch.go
185 lines (172 loc) · 5.33 KB
/
fetch.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
package freetds
import (
"errors"
// "fmt"
"unsafe"
)
/*
#include <sybfront.h>
#include <sybdb.h>
static int my_dbcount(DBPROCESS * dbproc) {
return DBCOUNT(dbproc);
}
*/
import "C"
func (conn *Conn) fetchResults() ([]*Result, error) {
results := make([]*Result, 0)
for {
erc := C.dbresults(conn.dbproc)
if erc == C.NO_MORE_RESULTS {
break
}
if erc == C.FAIL {
return nil, conn.raise(errors.New("dbresults failed"))
}
result := NewResult()
conn.currentResult = result
cols := int(C.dbnumcols(conn.dbproc))
columns := make([]column, cols)
for i := 0; i < cols; i++ {
no := C.int(i + 1)
name := C.GoString(C.dbcolname(conn.dbproc, no))
size := C.dbcollen(conn.dbproc, no)
typ := C.dbcoltype(conn.dbproc, no)
if typ == SYBUNIQUE {
size = 36
}
bindTyp, typ := dbbindtype(typ)
result.addColumn(name, int(size), int(typ))
if bindTyp == C.NTBSTRINGBIND && C.SYBCHAR != typ && C.SYBTEXT != typ {
size = C.DBINT(C.dbwillconvert(typ, C.SYBCHAR))
}
col := &columns[i]
// detecting varchar(max) or varbinary(max) types
col.canVary = (size == 2147483647 && typ == SYBCHAR) ||
(size == 1073741823 && typ == SYBBINARY)
col.name = name
col.typ = int(typ)
col.size = int(size)
col.bindTyp = int(bindTyp)
// If row data can vary, don't bind it now, read the data later using C.dbdata when scanning rows.
if !col.canVary {
col.buffer = make([]byte, size+1)
erc = C.dbbind(conn.dbproc, no, bindTyp, size+1, (*C.BYTE)(&col.buffer[0]))
//fmt.Printf("dbbind %d, %d, %v\n", bindTyp, size+1, col.buffer)
if erc == C.FAIL {
return nil, errors.New("dbbind failed: no such column or no such conversion possible, or target buffer too small")
}
}
// We still use dbnullbind for all variable and non variable columns. Should work fine.
erc = C.dbnullbind(conn.dbproc, no, &col.status)
if erc == C.FAIL {
return nil, errors.New("dbnullbind failed")
}
}
for i := 0; ; i++ {
rowCode := C.dbnextrow(conn.dbproc)
if rowCode == C.NO_MORE_ROWS {
break
}
if rowCode == C.REG_ROW {
for j := 0; j < cols; j++ {
col := columns[j]
//fmt.Printf("col: %#v\nvalue:%s\n", col, col.Value())
no := C.int(j + 1)
// if canVary is true, we don't rely on dbbind to do it's thing,
// but instead we will ask C.dbdata() for pointer to the data.
// We cannot call C.dbbind here, because for that it's too late (we already called C.dbnextrow()).
if col.canVary {
// actual size for this row
// dbdata returns null if data are null.
// Source: http://lists.ibiblio.org/pipermail/freetds/2015q2/029392.html
// From Sybase documentation:
// "A NULL BYTE pointer is returned if there is no such column or if the
// data has a null value. To make sure that the data is really a null
// value, you should always check for a return of 0 from *dbdatlen*."
//
// From Microsoft documentation:
// "A NULL BYTE pointer is returned if there is no such column or if the
// data has a null value. To make sure that the data is really a null
// value, check for a return of 0 from *dbdatlen*."
//
// So you can use: dbdata()==nil && dbdatlen()==0
// @see http://www.freetds.org/reference/a00341.html#gaee60c306a22383805a4b9caa647a1e16
size := C.dbdatlen(conn.dbproc, no)
data := C.dbdata(conn.dbproc, no)
if data == nil && size != 0 {
return nil, errors.New("dbdata failed: server returned non-nil data with size 0")
}
if data != nil {
// @see https://github.com/golang/go/wiki/cgo
col.buffer = C.GoBytes(unsafe.Pointer(data), C.int(size))
}
}
result.addValue(i, j, col.Value())
}
}
}
result.RowsAffected = int(C.my_dbcount(conn.dbproc))
if C.dbhasretstat(conn.dbproc) == C.TRUE {
result.ReturnValue = int(C.dbretstatus(conn.dbproc))
}
results = append(results, result)
conn.currentResult = nil
}
if len(conn.Error) > 0 {
return results, conn.raise(nil)
}
return results, nil
}
type column struct {
name string
typ int
size int
status C.DBINT
bindTyp int
buffer []byte
canVary bool
}
func (col *column) Value() interface{} {
if col.status == -1 {
return nil
}
return sqlBufToType(col.typ, col.buffer)
}
func dbbindtype(datatype C.int) (C.int, C.int) {
switch datatype {
//this will map decimal, and numeric datatypes to float
case C.SYBDECIMAL, C.SYBNUMERIC:
return C.FLT8BIND, C.SYBFLT8
//for all other types return datatype as second param
case C.SYBIMAGE, C.SYBVARBINARY, C.SYBBINARY:
return C.BINARYBIND, datatype
case C.SYBBIT:
return C.BITBIND, datatype
case C.SYBTEXT, C.SYBVARCHAR, C.SYBCHAR:
return C.NTBSTRINGBIND, datatype
case C.SYBDATETIME:
return C.DATETIMEBIND, datatype
case C.SYBDATETIME4:
return C.SMALLDATETIMEBIND, datatype
case C.SYBFLT8:
return C.FLT8BIND, datatype
case C.SYBREAL:
return C.REALBIND, datatype
case C.SYBINT1:
return C.TINYBIND, datatype
case C.SYBINT2:
return C.SMALLBIND, datatype
case C.SYBINT4:
return C.INTBIND, datatype
case C.SYBINT8:
return C.BIGINTBIND, datatype
case C.SYBMONEY:
return C.MONEYBIND, datatype
case C.SYBMONEY4:
return C.SMALLMONEYBIND, datatype
case SYBUNIQUE:
return C.STRINGBIND, C.SYBCHAR
}
//TODO - log unknown datatype
return C.NTBSTRINGBIND, datatype
}