mirror of
https://github.com/glebarez/go-sqlite.git
synced 2025-10-04 15:32:46 +08:00
driver: support _time_format DSN query param
Building on the _pragma query param support added in 508747c9
, support
a new _time_format query param.
If _time_format is set to "sqlite", written times use the time package
format "2006-01-02 15:04:05.999999999-07:00". This is the format
mattn/go-sqlite3 uses and is format 4 at
https://sqlite.org/lang_datefunc.html#time_values.
By default, times are formatted with time.Time.String. This maintains
compatibility with existing users of the driver.
Fixes #47
This commit is contained in:
73
all_test.go
73
all_test.go
@@ -556,6 +556,10 @@ func TestConcurrentGoroutines(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestConcurrentProcesses(t *testing.T) {
|
func TestConcurrentProcesses(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("skipping test in short mode")
|
||||||
|
}
|
||||||
|
|
||||||
dir, err := ioutil.TempDir("", "sqlite-test-")
|
dir, err := ioutil.TempDir("", "sqlite-test-")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -1166,6 +1170,7 @@ func TestTimeScan(t *testing.T) {
|
|||||||
{s: "2021-01-02 16:39:17+00:00", w: ref.Truncate(time.Second)},
|
{s: "2021-01-02 16:39:17+00:00", w: ref.Truncate(time.Second)},
|
||||||
{s: "2021-01-02T16:39:17.123456+00:00", w: ref.Truncate(time.Microsecond)},
|
{s: "2021-01-02T16:39:17.123456+00:00", w: ref.Truncate(time.Microsecond)},
|
||||||
{s: "2021-01-02 16:39:17.123456+00:00", w: ref.Truncate(time.Microsecond)},
|
{s: "2021-01-02 16:39:17.123456+00:00", w: ref.Truncate(time.Microsecond)},
|
||||||
|
{s: "2021-01-02 16:39:17.123456Z", w: ref.Truncate(time.Microsecond)},
|
||||||
{s: "2021-01-02 12:39:17-04:00", w: ref.Truncate(time.Second)},
|
{s: "2021-01-02 12:39:17-04:00", w: ref.Truncate(time.Second)},
|
||||||
{s: "2021-01-02 16:39:17", w: ref.Truncate(time.Second)},
|
{s: "2021-01-02 16:39:17", w: ref.Truncate(time.Second)},
|
||||||
{s: "2021-01-02T16:39:17", w: ref.Truncate(time.Second)},
|
{s: "2021-01-02T16:39:17", w: ref.Truncate(time.Second)},
|
||||||
@@ -1190,7 +1195,6 @@ func TestTimeScan(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var got time.Time
|
var got time.Time
|
||||||
|
|
||||||
if err := db.QueryRow("select y from x").Scan(&got); err != nil {
|
if err := db.QueryRow("select y from x").Scan(&got); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -1214,6 +1218,69 @@ func TestTimeLocaltime(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTimeFormat(t *testing.T) {
|
||||||
|
ref := time.Date(2021, 1, 2, 16, 39, 17, 123456789, time.UTC)
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
f string
|
||||||
|
w string
|
||||||
|
}{
|
||||||
|
{f: "", w: "2021-01-02 16:39:17.123456789 +0000 UTC"},
|
||||||
|
{f: "sqlite", w: "2021-01-02 16:39:17.123456789+00:00"},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
t.Run("", func(t *testing.T) {
|
||||||
|
dsn := "file::memory:"
|
||||||
|
if c.f != "" {
|
||||||
|
q := make(url.Values)
|
||||||
|
q.Set("_time_format", c.f)
|
||||||
|
dsn += "?" + q.Encode()
|
||||||
|
}
|
||||||
|
db, err := sql.Open(driverName, dsn)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
if _, err := db.Exec("drop table if exists x; create table x (y text)"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := db.Exec(`insert into x values (?)`, ref); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var got string
|
||||||
|
if err := db.QueryRow(`select y from x`).Scan(&got); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got != c.w {
|
||||||
|
t.Fatal(got, c.w)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTimeFormatBad(t *testing.T) {
|
||||||
|
db, err := sql.Open(driverName, "file::memory:?_time_format=bogus")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
// Error doesn't appear until a connection is opened.
|
||||||
|
_, err = db.Exec("select 1")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("wanted error")
|
||||||
|
}
|
||||||
|
|
||||||
|
want := `unknown _time_format "bogus"`
|
||||||
|
if got := err.Error(); got != want {
|
||||||
|
t.Fatalf("got error %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// https://sqlite.org/lang_expr.html#varparam
|
// https://sqlite.org/lang_expr.html#varparam
|
||||||
// https://gitlab.com/cznic/sqlite/-/issues/42
|
// https://gitlab.com/cznic/sqlite/-/issues/42
|
||||||
func TestBinding(t *testing.T) {
|
func TestBinding(t *testing.T) {
|
||||||
@@ -1402,6 +1469,10 @@ func testBindingError(t *testing.T, query func(db *sql.DB, query string, args ..
|
|||||||
|
|
||||||
// https://gitlab.com/cznic/sqlite/-/issues/51
|
// https://gitlab.com/cznic/sqlite/-/issues/51
|
||||||
func TestIssue51(t *testing.T) {
|
func TestIssue51(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("skipping test in short mode")
|
||||||
|
}
|
||||||
|
|
||||||
tempDir, err := ioutil.TempDir("", "")
|
tempDir, err := ioutil.TempDir("", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
35
sqlite.go
35
sqlite.go
@@ -336,6 +336,22 @@ func (c *conn) parseTimeString(s0 string, x int) (interface{}, bool) {
|
|||||||
return s0, false
|
return s0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// writeTimeFormats are the names and formats supported
|
||||||
|
// by the `_time_format` DSN query param.
|
||||||
|
var writeTimeFormats = map[string]string{
|
||||||
|
"sqlite": parseTimeFormats[0],
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *conn) formatTime(t time.Time) string {
|
||||||
|
// Before configurable write time formats were supported,
|
||||||
|
// time.Time.String was used. Maintain that default to
|
||||||
|
// keep existing driver users formatting times the same.
|
||||||
|
if c.writeTimeFormat == "" {
|
||||||
|
return t.String()
|
||||||
|
}
|
||||||
|
return t.Format(c.writeTimeFormat)
|
||||||
|
}
|
||||||
|
|
||||||
// RowsColumnTypeDatabaseTypeName may be implemented by Rows. It should return
|
// RowsColumnTypeDatabaseTypeName may be implemented by Rows. It should return
|
||||||
// the database system type name without the length. Type names should be
|
// the database system type name without the length. Type names should be
|
||||||
// uppercase. Examples of returned types: "VARCHAR", "NVARCHAR", "VARCHAR2",
|
// uppercase. Examples of returned types: "VARCHAR", "NVARCHAR", "VARCHAR2",
|
||||||
@@ -726,6 +742,8 @@ type conn struct {
|
|||||||
// Context handling can cause conn.Close and conn.interrupt to be invoked
|
// Context handling can cause conn.Close and conn.interrupt to be invoked
|
||||||
// concurrently.
|
// concurrently.
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
|
|
||||||
|
writeTimeFormat string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newConn(dsn string) (*conn, error) {
|
func newConn(dsn string) (*conn, error) {
|
||||||
@@ -759,7 +777,7 @@ func newConn(dsn string) (*conn, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = applyPragmas(c, query); err != nil {
|
if err = applyQueryParams(c, query); err != nil {
|
||||||
c.Close()
|
c.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -767,11 +785,12 @@ func newConn(dsn string) (*conn, error) {
|
|||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyPragmas(c *conn, query string) error {
|
func applyQueryParams(c *conn, query string) error {
|
||||||
q, err := url.ParseQuery(query)
|
q, err := url.ParseQuery(query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, v := range q["_pragma"] {
|
for _, v := range q["_pragma"] {
|
||||||
cmd := "pragma " + v
|
cmd := "pragma " + v
|
||||||
_, err := c.exec(context.Background(), cmd, nil)
|
_, err := c.exec(context.Background(), cmd, nil)
|
||||||
@@ -779,6 +798,16 @@ func applyPragmas(c *conn, query string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if v := q.Get("_time_format"); v != "" {
|
||||||
|
f, ok := writeTimeFormats[v]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("unknown _time_format %q", v)
|
||||||
|
}
|
||||||
|
c.writeTimeFormat = f
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1005,7 +1034,7 @@ func (c *conn) bind(pstmt uintptr, n int, args []driver.NamedValue) (allocs []ui
|
|||||||
return allocs, err
|
return allocs, err
|
||||||
}
|
}
|
||||||
case time.Time:
|
case time.Time:
|
||||||
if p, err = c.bindText(pstmt, i, x.String()); err != nil {
|
if p, err = c.bindText(pstmt, i, c.formatTime(x)); err != nil {
|
||||||
return allocs, err
|
return allocs, err
|
||||||
}
|
}
|
||||||
case nil:
|
case nil:
|
||||||
|
@@ -9,13 +9,14 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"modernc.org/tcl"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"modernc.org/tcl"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -26,6 +27,10 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestTclTest(t *testing.T) {
|
func TestTclTest(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("skipping test in short mode")
|
||||||
|
}
|
||||||
|
|
||||||
blacklist := map[string]struct{}{}
|
blacklist := map[string]struct{}{}
|
||||||
switch runtime.GOARCH {
|
switch runtime.GOARCH {
|
||||||
case "386", "arm":
|
case "386", "arm":
|
||||||
|
Reference in New Issue
Block a user