-
Notifications
You must be signed in to change notification settings - Fork 4
/
unsafe.go
130 lines (117 loc) · 3.79 KB
/
unsafe.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
// sqlinternals - retrieve driver.Rows from sql.*Row / sql.*Rows
//
// Copyright 2013 Arne Hormann. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package sqlinternals
import (
"database/sql"
"database/sql/driver"
"reflect"
"unsafe"
)
var (
// field offsets for unsafe access (types are checked beforehand)
offsetRowRows uintptr // sql.Row.rows: sql.*Rows
offsetRowsRowsi uintptr // sql.Rows.rowsi: driver.Rows
)
// internal error type
type internalErr string
func (e internalErr) Error() string {
return string(e)
}
const (
errArgNil = internalErr("argument must not be nil")
errArgWrongType = internalErr("argument was not *sql.Row or *sql.Rows")
errRowRowsNil = internalErr("'err' xor 'rows' in sql.Row must be nil")
errRowsRowsiNil = internalErr("'rowsi driver.Rows' in sql.Rows is nil")
)
// a driver.Rows implementatiton so we are able
// to get a type assignable to driver.Rows with reflect
type dummyRows struct{}
func (d dummyRows) Columns() []string {
return nil
}
func (d dummyRows) Close() error {
return nil
}
func (d dummyRows) Next(dest []driver.Value) error {
return nil
}
// basic type assertion, panic on error
func panicIfUnassignable(field reflect.StructField, assignable reflect.Type, panicMsg string) {
fType := field.Type
if assignable == fType || assignable.AssignableTo(fType) {
return
}
panic(panicMsg + "; " + assignable.String() + " is not assignable to " + fType.String())
}
func init() {
// all types we need to check as templates
var (
tRow reflect.Type = reflect.TypeOf(sql.Row{})
tRows reflect.Type = reflect.TypeOf(sql.Rows{})
tRowsPtr reflect.Type = reflect.TypeOf(&sql.Rows{})
tDriverRows reflect.Type = reflect.TypeOf((driver.Rows)(dummyRows{}))
)
var i, expectFields, fields int
// sql.Row must have a field "rows sql.*Rows"
for i, expectFields, fields = 0, 1, tRow.NumField(); i < fields; i++ {
field := tRow.Field(i)
switch field.Name {
case "rows":
panicIfUnassignable(field, tRowsPtr,
"database/sql/Row.rows is not database/sql/*Rows")
offsetRowRows = field.Offset
expectFields--
}
}
if expectFields != 0 {
panic("unexpected structure of database/sql/Row")
}
// sql.Rows must have a field "rowsi driver.Rows"
for i, expectFields, fields = 0, 1, tRows.NumField(); i < fields; i++ {
if field := tRows.Field(i); field.Name == "rowsi" {
panicIfUnassignable(field, tDriverRows,
"database/sql/Rows.rowsi is not database/sql/driver/Rows")
offsetRowsRowsi = field.Offset
expectFields--
}
}
if expectFields != 0 {
panic("unexpected structure of database/sql/Rows")
}
}
// Inspect extracts the internal driver.Rows from sql.*Row or sql.*Rows.
// This can be used by a driver to work around issue 5606 in Go until a better way exists.
func Inspect(sqlStruct interface{}) (interface{}, error) {
// All of this has to use unsafe to access unexported fields, but it's robust:
// we checked the types and structure in init.
if sqlStruct == nil {
return nil, errArgNil
}
var rows *sql.Rows
switch v := sqlStruct.(type) {
case *sql.Row:
// extract rows from sql/*Row, if v.rows is nil, an error is returned.
rowsPtr := (uintptr)((unsafe.Pointer)(v)) + offsetRowRows
unsafeRows := *(**sql.Rows)((unsafe.Pointer)(rowsPtr))
if unsafeRows == nil {
return nil, errRowRowsNil
}
rows = unsafeRows
case *sql.Rows:
rows = v
default:
return errArgWrongType, nil
}
// return rowsi from sql.*Rows, if rows.rowsi is nil an error is returned.
rowsiPtr := offsetRowsRowsi + (uintptr)((unsafe.Pointer)(rows))
rowsi := *(*driver.Rows)((unsafe.Pointer)(rowsiPtr))
if rowsi == nil {
return nil, errRowsRowsiNil
}
return rowsi, nil
}