Skip to content

Commit 66fe75f

Browse files
committed
Added time.Time support
1 parent 3d78753 commit 66fe75f

File tree

6 files changed

+229
-6
lines changed

6 files changed

+229
-6
lines changed

sys/sqlite3/bind.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import "C"
2121

2222
import (
2323
"math"
24+
"time"
2425
"unsafe"
2526
)
2627

@@ -44,9 +45,9 @@ func (s *Statement) BindNamedInterface(name string, value interface{}) error {
4445
}
4546
}
4647

47-
// Bind int, uint, float, bool, string, []byte, or nil to a statement,
48+
// Bind int, uint, float, bool, string, []byte, time.Time or nil to a statement,
4849
// return any errors
49-
// TODO: Also accept time.Time, maybe custom types with Marshal and Unmarshal
50+
// TODO: Also accept custom types with Marshal and Unmarshal
5051
func (s *Statement) BindInterface(index int, value interface{}) error {
5152
if value == nil {
5253
return s.BindNull(index)
@@ -86,6 +87,12 @@ func (s *Statement) BindInterface(index int, value interface{}) error {
8687
return s.BindText(index, v)
8788
case []byte:
8889
return s.BindBlob(index, v)
90+
case time.Time:
91+
if v.IsZero() {
92+
return s.BindNull(index)
93+
} else {
94+
return s.BindText(index, v.Format(time.RFC3339))
95+
}
8996
default:
9097
return SQLITE_MISMATCH
9198
}

sys/sqlite3/bind_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package sqlite3_test
2+
3+
import (
4+
"math"
5+
"testing"
6+
"time"
7+
8+
"github.com/mutablelogic/go-sqlite/sys/sqlite3"
9+
)
10+
11+
func Test_Bind_001(t *testing.T) {
12+
db, err := sqlite3.OpenPath(":memory:", sqlite3.SQLITE_OPEN_CREATE, "")
13+
if err != nil {
14+
t.Fatal(err)
15+
}
16+
defer db.Close()
17+
st, _, err := db.Prepare("SELECT ?")
18+
if err != nil {
19+
t.Fatal(err)
20+
}
21+
defer st.Finalize()
22+
23+
now := time.Now()
24+
25+
var tests = []struct {
26+
in, out interface{}
27+
}{
28+
{int(1), int64(1)},
29+
{int8(2), int64(2)},
30+
{int16(3), int64(3)},
31+
{int32(4), int64(4)},
32+
{int64(5), int64(5)},
33+
{uint(6), int64(6)},
34+
{uint8(7), int64(7)},
35+
{uint16(8), int64(8)},
36+
{uint32(9), int64(9)},
37+
{uint64(10), int64(10)},
38+
{"test", "test"},
39+
{false, int64(0)},
40+
{true, int64(1)},
41+
{float64(math.Pi), float64(math.Pi)},
42+
{float32(math.Pi), float64(float32(math.Pi))},
43+
{now, now.Format(time.RFC3339)},
44+
{time.Time{}, nil},
45+
{nil, nil},
46+
}
47+
48+
for _, test := range tests {
49+
st.Reset()
50+
st.Bind(test.in)
51+
for st.Step() == sqlite3.SQLITE_ROW {
52+
if st.DataCount() != 1 {
53+
t.Error("Data count should be one")
54+
}
55+
out := st.ColumnInterface(0)
56+
if out != test.out {
57+
t.Errorf("Expected %v (%T) but got %v (%T) for bind type %T", test.out, test.out, out, out, test.in)
58+
}
59+
}
60+
}
61+
}

sys/sqlite3/column.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package sqlite3
88
import "C"
99

1010
import (
11+
"fmt"
1112
"reflect"
1213
"unsafe"
1314
)
@@ -110,3 +111,21 @@ func (s *Statement) ColumnBlob(index int) []byte {
110111
// Return slice
111112
return *(*[]byte)(unsafe.Pointer(&data))
112113
}
114+
115+
// Return column as interface
116+
func (s *Statement) ColumnInterface(index int) interface{} {
117+
t := s.ColumnType(index)
118+
switch t {
119+
case SQLITE_INTEGER:
120+
return s.ColumnInt64(index)
121+
case SQLITE_FLOAT:
122+
return s.ColumnDouble(index)
123+
case SQLITE_TEXT:
124+
return s.ColumnText(index)
125+
case SQLITE_NULL:
126+
return nil
127+
case SQLITE_BLOB:
128+
return s.ColumnBlob(index)
129+
}
130+
panic(fmt.Sprint("Bad type returned for column:", t))
131+
}

sys/sqlite3/results.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"io"
66
"reflect"
7+
"time"
78

89
"github.com/hashicorp/go-multierror"
910
)
@@ -25,6 +26,7 @@ type Results struct {
2526
var (
2627
typeText = reflect.TypeOf("")
2728
typeBlob = reflect.TypeOf([]byte{})
29+
typeTime = reflect.TypeOf(time.Time{})
2830
)
2931

3032
///////////////////////////////////////////////////////////////////////////////
@@ -61,7 +63,7 @@ func results(st *Statement, err error) *Results {
6163
return r
6264
}
6365

64-
// Return next row of values, or nil if there are no more rows.
66+
// Return next row of values, or (nil, io.EOF) if there are no more rows.
6567
// If arguments t are provided, then the values will be
6668
// cast to the types in t if that is possible, or else an error
6769
// will occur
@@ -206,9 +208,9 @@ func (r *Results) value(index int) (interface{}, error) {
206208
func (r *Results) castvalue(index int, t reflect.Type) (interface{}, error) {
207209
st := r.st.ColumnType(index)
208210

209-
// Check for null
211+
// Do NULL cases
210212
if st == SQLITE_NULL {
211-
return nil, nil
213+
return reflect.Zero(t).Interface(), nil
212214
}
213215

214216
// Do simple cases first
@@ -239,6 +241,14 @@ func (r *Results) castvalue(index int, t reflect.Type) (interface{}, error) {
239241
}
240242
// Do types
241243
switch t {
244+
case typeTime:
245+
if st == SQLITE_TEXT {
246+
return time.Parse(time.RFC3339, r.st.ColumnText(index))
247+
} else if st == SQLITE_FLOAT {
248+
return nil, fmt.Errorf("Cannot convert julian day number to time (at this time)")
249+
} else if st == SQLITE_INTEGER {
250+
return time.Unix(r.st.ColumnInt64(index), 0), nil
251+
}
242252
case typeBlob:
243253
if st == SQLITE_BLOB {
244254
return r.st.ColumnBlob(index), nil

sys/sqlite3/results_test.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package sqlite3_test
2+
3+
import (
4+
"io"
5+
"math"
6+
"reflect"
7+
"testing"
8+
"time"
9+
10+
"github.com/mutablelogic/go-sqlite/sys/sqlite3"
11+
)
12+
13+
func Test_Results_001(t *testing.T) {
14+
db, err := sqlite3.OpenPathEx(":memory:", sqlite3.SQLITE_OPEN_CREATE, "")
15+
if err != nil {
16+
t.Fatal(err)
17+
}
18+
defer db.Close()
19+
st, err := db.Prepare("SELECT ?")
20+
if err != nil {
21+
t.Fatal(err)
22+
}
23+
defer st.Close()
24+
25+
now := time.Now()
26+
27+
var tests = []struct {
28+
in, out interface{}
29+
}{
30+
{int(1), int64(1)},
31+
{int8(2), int64(2)},
32+
{int16(3), int64(3)},
33+
{int32(4), int64(4)},
34+
{int64(5), int64(5)},
35+
{uint(6), int64(6)},
36+
{uint8(7), int64(7)},
37+
{uint16(8), int64(8)},
38+
{uint32(9), int64(9)},
39+
{uint64(10), int64(10)},
40+
{"test", "test"},
41+
{false, int64(0)},
42+
{true, int64(1)},
43+
{float64(math.Pi), float64(math.Pi)},
44+
{float32(math.Pi), float64(float32(math.Pi))},
45+
{now, now.Format(time.RFC3339)},
46+
{time.Time{}, nil},
47+
{nil, nil},
48+
}
49+
50+
for _, test := range tests {
51+
r, err := st.Exec(0, test.in)
52+
if err != nil {
53+
t.Fatal(err)
54+
}
55+
for {
56+
values, err := r.Next()
57+
if err == io.EOF {
58+
break
59+
}
60+
if len(values) != 1 {
61+
t.Error("Data count should be one")
62+
}
63+
out := values[0]
64+
if out != test.out {
65+
t.Errorf("Expected %v (%T) but got %v (%T) for bind type %T", test.out, test.out, out, out, test.in)
66+
}
67+
}
68+
}
69+
}
70+
71+
func Test_Results_002(t *testing.T) {
72+
db, err := sqlite3.OpenPathEx(":memory:", sqlite3.SQLITE_OPEN_CREATE, "")
73+
if err != nil {
74+
t.Fatal(err)
75+
}
76+
defer db.Close()
77+
st, err := db.Prepare("SELECT ?")
78+
if err != nil {
79+
t.Fatal(err)
80+
}
81+
defer st.Close()
82+
83+
now := time.Now()
84+
85+
var tests = []struct {
86+
in, out interface{}
87+
}{
88+
{int(1), int(1)},
89+
{int8(2), int8(2)},
90+
{int16(3), int16(3)},
91+
{int32(4), int32(4)},
92+
{int64(5), int64(5)},
93+
{uint(6), uint(6)},
94+
{uint8(7), uint8(7)},
95+
{uint16(8), uint16(8)},
96+
{uint32(9), uint32(9)},
97+
{uint64(10), uint64(10)},
98+
{"test", "test"},
99+
{false, false},
100+
{true, true},
101+
{float64(math.Pi), float64(math.Pi)},
102+
{float32(math.Pi), float32(math.Pi)},
103+
{now, now.Truncate(time.Second)},
104+
{time.Time{}, time.Time{}},
105+
}
106+
107+
for _, test := range tests {
108+
r, err := st.Exec(0, test.in)
109+
if err != nil {
110+
t.Fatal(err)
111+
}
112+
for {
113+
values, err := r.Next(reflect.TypeOf(test.out))
114+
if err == io.EOF {
115+
break
116+
}
117+
if len(values) != 1 {
118+
t.Error("Data count should be one")
119+
}
120+
out := values[0]
121+
if out != test.out {
122+
t.Errorf("Expected %v (%T) but got %v (%T) for bind type %T", test.out, test.out, out, out, test.in)
123+
}
124+
}
125+
}
126+
}

sys/sqlite3/statement.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ static int _sqlite3_blocking_prepare_v2(
8686
if ((rc & 0xFF) != SQLITE_LOCKED) {
8787
return rc;
8888
}
89-
rc = _wait_for_unlock_notify(sqlite3_db_handle(stmt));
89+
rc = _wait_for_unlock_notify(db);
9090
if (rc != SQLITE_OK) {
9191
return rc;
9292
}

0 commit comments

Comments
 (0)