forked from mirror/go-sqlite3
319 lines
9.6 KiB
Go
319 lines
9.6 KiB
Go
|
// Copyright (C) 2018 The Go-SQLite3 Authors.
|
||
|
//
|
||
|
// Use of this source code is governed by an MIT-style
|
||
|
// license that can be found in the LICENSE file.
|
||
|
|
||
|
// +build cgo
|
||
|
|
||
|
package sqlite3
|
||
|
|
||
|
import (
|
||
|
"database/sql"
|
||
|
"database/sql/driver"
|
||
|
"errors"
|
||
|
"net/url"
|
||
|
"os"
|
||
|
"strings"
|
||
|
"testing"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
func timezone(t time.Time) string { return t.Format("-07:00") }
|
||
|
|
||
|
func TestTimestamp(t *testing.T) {
|
||
|
tempFilename := TempFilename(t)
|
||
|
defer os.Remove(tempFilename)
|
||
|
db, err := sql.Open("sqlite3", tempFilename)
|
||
|
if err != nil {
|
||
|
t.Fatal("Failed to open database:", err)
|
||
|
}
|
||
|
defer db.Close()
|
||
|
|
||
|
_, err = db.Exec("DROP TABLE foo")
|
||
|
_, err = db.Exec("CREATE TABLE foo(id INTEGER, ts timeSTAMP, dt DATETIME)")
|
||
|
if err != nil {
|
||
|
t.Fatal("Failed to create table:", err)
|
||
|
}
|
||
|
|
||
|
timestamp1 := time.Date(2012, time.April, 6, 22, 50, 0, 0, time.UTC)
|
||
|
timestamp2 := time.Date(2006, time.January, 2, 15, 4, 5, 123456789, time.UTC)
|
||
|
timestamp3 := time.Date(2012, time.November, 4, 0, 0, 0, 0, time.UTC)
|
||
|
tzTest := time.FixedZone("TEST", -9*3600-13*60)
|
||
|
tests := []struct {
|
||
|
value interface{}
|
||
|
expected time.Time
|
||
|
}{
|
||
|
{"nonsense", time.Time{}},
|
||
|
{"0000-00-00 00:00:00", time.Time{}},
|
||
|
{time.Time{}.Unix(), time.Time{}},
|
||
|
{timestamp1, timestamp1},
|
||
|
{timestamp2.Unix(), timestamp2.Truncate(time.Second)},
|
||
|
{timestamp2.UnixNano() / int64(time.Millisecond), timestamp2.Truncate(time.Millisecond)},
|
||
|
{timestamp1.In(tzTest), timestamp1.In(tzTest)},
|
||
|
{timestamp1.Format("2006-01-02 15:04:05.000"), timestamp1},
|
||
|
{timestamp1.Format("2006-01-02T15:04:05.000"), timestamp1},
|
||
|
{timestamp1.Format("2006-01-02 15:04:05"), timestamp1},
|
||
|
{timestamp1.Format("2006-01-02T15:04:05"), timestamp1},
|
||
|
{timestamp2, timestamp2},
|
||
|
{"2006-01-02 15:04:05.123456789", timestamp2},
|
||
|
{"2006-01-02T15:04:05.123456789", timestamp2},
|
||
|
{"2006-01-02T05:51:05.123456789-09:13", timestamp2.In(tzTest)},
|
||
|
{"2012-11-04", timestamp3},
|
||
|
{"2012-11-04 00:00", timestamp3},
|
||
|
{"2012-11-04 00:00:00", timestamp3},
|
||
|
{"2012-11-04 00:00:00.000", timestamp3},
|
||
|
{"2012-11-04T00:00", timestamp3},
|
||
|
{"2012-11-04T00:00:00", timestamp3},
|
||
|
{"2012-11-04T00:00:00.000", timestamp3},
|
||
|
{"2006-01-02T15:04:05.123456789Z", timestamp2},
|
||
|
{"2012-11-04Z", timestamp3},
|
||
|
{"2012-11-04 00:00Z", timestamp3},
|
||
|
{"2012-11-04 00:00:00Z", timestamp3},
|
||
|
{"2012-11-04 00:00:00.000Z", timestamp3},
|
||
|
{"2012-11-04T00:00Z", timestamp3},
|
||
|
{"2012-11-04T00:00:00Z", timestamp3},
|
||
|
{"2012-11-04T00:00:00.000Z", timestamp3},
|
||
|
}
|
||
|
for i := range tests {
|
||
|
_, err = db.Exec("INSERT INTO foo(id, ts, dt) VALUES(?, ?, ?)", i, tests[i].value, tests[i].value)
|
||
|
if err != nil {
|
||
|
t.Fatal("Failed to insert timestamp:", err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
rows, err := db.Query("SELECT id, ts, dt FROM foo ORDER BY id ASC")
|
||
|
if err != nil {
|
||
|
t.Fatal("Unable to query foo table:", err)
|
||
|
}
|
||
|
defer rows.Close()
|
||
|
|
||
|
seen := 0
|
||
|
for rows.Next() {
|
||
|
var id int
|
||
|
var ts, dt time.Time
|
||
|
|
||
|
if err := rows.Scan(&id, &ts, &dt); err != nil {
|
||
|
t.Error("Unable to scan results:", err)
|
||
|
continue
|
||
|
}
|
||
|
if id < 0 || id >= len(tests) {
|
||
|
t.Error("Bad row id: ", id)
|
||
|
continue
|
||
|
}
|
||
|
seen++
|
||
|
if !tests[id].expected.Equal(ts) {
|
||
|
t.Errorf("Timestamp value for id %v (%v) should be %v, not %v", id, tests[id].value, tests[id].expected, dt)
|
||
|
}
|
||
|
if !tests[id].expected.Equal(dt) {
|
||
|
t.Errorf("Datetime value for id %v (%v) should be %v, not %v", id, tests[id].value, tests[id].expected, dt)
|
||
|
}
|
||
|
if timezone(tests[id].expected) != timezone(ts) {
|
||
|
t.Errorf("Timezone for id %v (%v) should be %v, not %v", id, tests[id].value,
|
||
|
timezone(tests[id].expected), timezone(ts))
|
||
|
}
|
||
|
if timezone(tests[id].expected) != timezone(dt) {
|
||
|
t.Errorf("Timezone for id %v (%v) should be %v, not %v", id, tests[id].value,
|
||
|
timezone(tests[id].expected), timezone(dt))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if seen != len(tests) {
|
||
|
t.Errorf("Expected to see %d rows", len(tests))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestTimezoneConversion(t *testing.T) {
|
||
|
zones := []string{"UTC", "US/Central", "US/Pacific", "Local"}
|
||
|
for _, tz := range zones {
|
||
|
tempFilename := TempFilename(t)
|
||
|
defer os.Remove(tempFilename)
|
||
|
db, err := sql.Open("sqlite3", tempFilename+"?tz="+url.QueryEscape(tz))
|
||
|
if err != nil {
|
||
|
t.Fatal("Failed to open database:", err)
|
||
|
}
|
||
|
defer db.Close()
|
||
|
|
||
|
_, err = db.Exec("DROP TABLE foo")
|
||
|
_, err = db.Exec("CREATE TABLE foo(id INTEGER, ts TIMESTAMP, dt DATETIME)")
|
||
|
if err != nil {
|
||
|
t.Fatal("Failed to create table:", err)
|
||
|
}
|
||
|
|
||
|
loc, err := time.LoadLocation(tz)
|
||
|
if err != nil {
|
||
|
t.Fatal("Failed to load location:", err)
|
||
|
}
|
||
|
|
||
|
timestamp1 := time.Date(2012, time.April, 6, 22, 50, 0, 0, time.UTC)
|
||
|
timestamp2 := time.Date(2006, time.January, 2, 15, 4, 5, 123456789, time.UTC)
|
||
|
timestamp3 := time.Date(2012, time.November, 4, 0, 0, 0, 0, time.UTC)
|
||
|
tests := []struct {
|
||
|
value interface{}
|
||
|
expected time.Time
|
||
|
}{
|
||
|
{"nonsense", time.Time{}.In(loc)},
|
||
|
{"0000-00-00 00:00:00", time.Time{}.In(loc)},
|
||
|
{timestamp1, timestamp1.In(loc)},
|
||
|
{timestamp1.Unix(), timestamp1.In(loc)},
|
||
|
{timestamp1.In(time.FixedZone("TEST", -7*3600)), timestamp1.In(loc)},
|
||
|
{timestamp1.Format("2006-01-02 15:04:05.000"), timestamp1.In(loc)},
|
||
|
{timestamp1.Format("2006-01-02T15:04:05.000"), timestamp1.In(loc)},
|
||
|
{timestamp1.Format("2006-01-02 15:04:05"), timestamp1.In(loc)},
|
||
|
{timestamp1.Format("2006-01-02T15:04:05"), timestamp1.In(loc)},
|
||
|
{timestamp2, timestamp2.In(loc)},
|
||
|
{"2006-01-02 15:04:05.123456789", timestamp2.In(loc)},
|
||
|
{"2006-01-02T15:04:05.123456789", timestamp2.In(loc)},
|
||
|
{"2012-11-04", timestamp3.In(loc)},
|
||
|
{"2012-11-04 00:00", timestamp3.In(loc)},
|
||
|
{"2012-11-04 00:00:00", timestamp3.In(loc)},
|
||
|
{"2012-11-04 00:00:00.000", timestamp3.In(loc)},
|
||
|
{"2012-11-04T00:00", timestamp3.In(loc)},
|
||
|
{"2012-11-04T00:00:00", timestamp3.In(loc)},
|
||
|
{"2012-11-04T00:00:00.000", timestamp3.In(loc)},
|
||
|
}
|
||
|
for i := range tests {
|
||
|
_, err = db.Exec("INSERT INTO foo(id, ts, dt) VALUES(?, ?, ?)", i, tests[i].value, tests[i].value)
|
||
|
if err != nil {
|
||
|
t.Fatal("Failed to insert timestamp:", err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
rows, err := db.Query("SELECT id, ts, dt FROM foo ORDER BY id ASC")
|
||
|
if err != nil {
|
||
|
t.Fatal("Unable to query foo table:", err)
|
||
|
}
|
||
|
defer rows.Close()
|
||
|
|
||
|
seen := 0
|
||
|
for rows.Next() {
|
||
|
var id int
|
||
|
var ts, dt time.Time
|
||
|
|
||
|
if err := rows.Scan(&id, &ts, &dt); err != nil {
|
||
|
t.Error("Unable to scan results:", err)
|
||
|
continue
|
||
|
}
|
||
|
if id < 0 || id >= len(tests) {
|
||
|
t.Error("Bad row id: ", id)
|
||
|
continue
|
||
|
}
|
||
|
seen++
|
||
|
if !tests[id].expected.Equal(ts) {
|
||
|
t.Errorf("Timestamp value for id %v (%v) should be %v, not %v", id, tests[id].value, tests[id].expected, ts)
|
||
|
}
|
||
|
if !tests[id].expected.Equal(dt) {
|
||
|
t.Errorf("Datetime value for id %v (%v) should be %v, not %v", id, tests[id].value, tests[id].expected, dt)
|
||
|
}
|
||
|
if tests[id].expected.Location().String() != ts.Location().String() {
|
||
|
t.Errorf("Location for id %v (%v) should be %v, not %v", id, tests[id].value, tests[id].expected.Location().String(), ts.Location().String())
|
||
|
}
|
||
|
if tests[id].expected.Location().String() != dt.Location().String() {
|
||
|
t.Errorf("Location for id %v (%v) should be %v, not %v", id, tests[id].value, tests[id].expected.Location().String(), dt.Location().String())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if seen != len(tests) {
|
||
|
t.Errorf("Expected to see %d rows", len(tests))
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestDateTimeLocal(t *testing.T) {
|
||
|
zone := "Asia/Tokyo"
|
||
|
tempFilename := TempFilename(t)
|
||
|
defer os.Remove(tempFilename)
|
||
|
db, err := sql.Open("sqlite3", tempFilename+"?tz="+zone)
|
||
|
if err != nil {
|
||
|
t.Fatal("Failed to open database:", err)
|
||
|
}
|
||
|
db.Exec("CREATE TABLE foo (dt datetime);")
|
||
|
db.Exec("INSERT INTO foo VALUES('2015-03-05 15:16:17');")
|
||
|
|
||
|
row := db.QueryRow("select * from foo")
|
||
|
var d time.Time
|
||
|
err = row.Scan(&d)
|
||
|
if err != nil {
|
||
|
t.Fatal("Failed to scan datetime:", err)
|
||
|
}
|
||
|
if d.Hour() == 15 || !strings.Contains(d.String(), "JST") {
|
||
|
t.Fatal("Result should have timezone", d)
|
||
|
}
|
||
|
db.Close()
|
||
|
|
||
|
db, err = sql.Open("sqlite3", tempFilename)
|
||
|
if err != nil {
|
||
|
t.Fatal("Failed to open database:", err)
|
||
|
}
|
||
|
|
||
|
row = db.QueryRow("select * from foo")
|
||
|
err = row.Scan(&d)
|
||
|
if err != nil {
|
||
|
t.Fatal("Failed to scan datetime:", err)
|
||
|
}
|
||
|
if d.UTC().Hour() != 15 || !strings.Contains(d.String(), "UTC") {
|
||
|
t.Fatalf("Result should not have timezone %v %v", zone, d.String())
|
||
|
}
|
||
|
|
||
|
_, err = db.Exec("DELETE FROM foo")
|
||
|
if err != nil {
|
||
|
t.Fatal("Failed to delete table:", err)
|
||
|
}
|
||
|
dt, err := time.Parse("2006/1/2 15/4/5 -0700 MST", "2015/3/5 15/16/17 +0900 JST")
|
||
|
if err != nil {
|
||
|
t.Fatal("Failed to parse datetime:", err)
|
||
|
}
|
||
|
db.Exec("INSERT INTO foo VALUES(?);", dt)
|
||
|
|
||
|
db.Close()
|
||
|
db, err = sql.Open("sqlite3", tempFilename+"?tz="+zone)
|
||
|
if err != nil {
|
||
|
t.Fatal("Failed to open database:", err)
|
||
|
}
|
||
|
|
||
|
row = db.QueryRow("select * from foo")
|
||
|
err = row.Scan(&d)
|
||
|
if err != nil {
|
||
|
t.Fatal("Failed to scan datetime:", err)
|
||
|
}
|
||
|
if d.Hour() != 15 || !strings.Contains(d.String(), "JST") {
|
||
|
t.Fatalf("Result should have timezone: %v => %v", zone, d.String())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const CurrentTimeStamp = "2006-01-02 15:04:05"
|
||
|
|
||
|
type TimeStamp struct{ *time.Time }
|
||
|
|
||
|
func (t TimeStamp) Scan(value interface{}) error {
|
||
|
var err error
|
||
|
switch v := value.(type) {
|
||
|
case string:
|
||
|
*t.Time, err = time.Parse(CurrentTimeStamp, v)
|
||
|
case []byte:
|
||
|
*t.Time, err = time.Parse(CurrentTimeStamp, string(v))
|
||
|
default:
|
||
|
err = errors.New("invalid type for current_timestamp")
|
||
|
}
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
func (t TimeStamp) Value() (driver.Value, error) {
|
||
|
return t.Time.Format(CurrentTimeStamp), nil
|
||
|
}
|
||
|
|
||
|
func TestDateTimeNow(t *testing.T) {
|
||
|
tempFilename := TempFilename(t)
|
||
|
defer os.Remove(tempFilename)
|
||
|
db, err := sql.Open("sqlite3", tempFilename)
|
||
|
if err != nil {
|
||
|
t.Fatal("Failed to open database:", err)
|
||
|
}
|
||
|
defer db.Close()
|
||
|
|
||
|
var d time.Time
|
||
|
err = db.QueryRow("SELECT datetime('now')").Scan(TimeStamp{&d})
|
||
|
if err != nil {
|
||
|
t.Fatal("Failed to scan datetime:", err)
|
||
|
}
|
||
|
}
|