Skip to content

Commit

Permalink
Merge pull request #167 from doug-martin/fix_time_issues
Browse files Browse the repository at this point in the history
v9.1.0
  • Loading branch information
doug-martin authored Sep 22, 2019
2 parents 837f5f6 + a7341ee commit 6c34fb4
Show file tree
Hide file tree
Showing 11 changed files with 167 additions and 25 deletions.
5 changes: 5 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# v9.1.0

* [FIXED] ExampleDoUpdate does't work in postgres [#156](https://github.com/doug-martin/goqu/issues/156)
* [FIXED] Issue with timezone being lost [#163](https://github.com/doug-martin/goqu/issues/163)

# v9.0.1

* [FIXED] Issue where `NULL`, `TRUE` and `FALSE` are interpolated when using an `IS` clause. [#165](https://github.com/doug-martin/goqu/issues/165)
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ We tried a few other sql builders but each was a thin wrapper around sql fragmen
* [Delete Dataset](./docs/deleting.md) - Docs and examples about creating and executing DELETE sql statements.
* [Prepared Statements](./docs/interpolation.md) - Docs about interpolation and prepared statements in `goqu`.
* [Database](./docs/database.md) - Docs and examples of using a Database to execute queries in `goqu`
* [Working with time.Time](./docs/time.md) - Docs on how to use alternate time locations.

## Quick Examples

Expand Down
10 changes: 9 additions & 1 deletion dialect/mysql/mysql_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import (
"time"

"github.com/doug-martin/goqu/v9"
"github.com/doug-martin/goqu/v9/dialect/mysql"
_ "github.com/doug-martin/goqu/v9/dialect/mysql"

_ "github.com/go-sql-driver/mysql"
"github.com/stretchr/testify/suite"
)
Expand Down Expand Up @@ -360,6 +360,14 @@ func (mt *mysqlTest) TestInsert() {
var newEntries []entry
mt.NoError(ds.Where(goqu.C("int").In([]uint32{11, 12, 13, 14})).ScanStructs(&newEntries))
mt.Len(newEntries, 4)
for i, e := range newEntries {
mt.Equal(entries[i].Int, e.Int)
mt.Equal(entries[i].Float, e.Float)
mt.Equal(entries[i].String, e.String)
mt.Equal(entries[i].Time.UTC().Format(mysql.DialectOptions().TimeFormat), e.Time.Format(mysql.DialectOptions().TimeFormat))
mt.Equal(entries[i].Bool, e.Bool)
mt.Equal(entries[i].Bytes, e.Bytes)
}

_, err = ds.Insert().Rows(
entry{Int: 15, Float: 1.500000, String: "1.500000", Time: now, Bool: false, Bytes: []byte("1.500000")},
Expand Down
9 changes: 9 additions & 0 deletions dialect/postgres/postgres_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -359,8 +359,17 @@ func (pt *postgresTest) TestInsert() {
pt.NoError(err)

var newEntries []entry

pt.NoError(ds.Where(goqu.C("int").In([]uint32{11, 12, 13, 14})).ScanStructs(&newEntries))
pt.Len(newEntries, 4)
for i, e := range newEntries {
pt.Equal(entries[i].Int, e.Int)
pt.Equal(entries[i].Float, e.Float)
pt.Equal(entries[i].String, e.String)
pt.Equal(entries[i].Time.Unix(), e.Time.Unix())
pt.Equal(entries[i].Bool, e.Bool)
pt.Equal(entries[i].Bytes, e.Bytes)
}

_, err = ds.Insert().Rows(
entry{Int: 15, Float: 1.500000, String: "1.500000", Time: now, Bool: false, Bytes: []byte("1.500000")},
Expand Down
62 changes: 62 additions & 0 deletions docs/time.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Working with time.Time

By default when interpolating `time.Time` (and `*time.Time`) `goqu` will convert it `UTC` before interpolating.

## Why?

For most use cases `UTC` should be preferred, if a timezone is specified it is usually ignored silently by `postgres` and `mysql` unless you configure your DB to run in a different timezone, leading to unexpected behavior.

## How to use a different default timezone?
`goqu` provides a **_global_** configuration settings to set the [location](https://golang.org/pkg/time/#Location) to convert all timestamps to.

To change the default timezone to covert time instances to you can use [`goqu.SetTimeLocation`](https://godoc.org/github.com/doug-martin/goqu#SetTimeLocation) to change the default timezone.

In the following example the default value `UTC` is used.

```go
created, err := time.Parse(time.RFC3339, "2019-10-01T15:01:00Z")
if err != nil {
panic(err)
}

ds := goqu.Insert("test").Rows(goqu.Record{
"address": "111 Address",
"name": "Bob Yukon",
"created": created,
})
```

Output:
```
INSERT INTO "test" ("address", "created", "name") VALUES ('111 Address', '2019-10-01T15:01:00Z', 'Bob Yukon')
```

In the following example `UTC` is overridden to `Asia/Shanghai`

```go
loc, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
panic(err)
}

goqu.SetTimeLocation(loc)

created, err := time.Parse(time.RFC3339, "2019-10-01T15:01:00Z")
if err != nil {
panic(err)
}

ds := goqu.Insert("test").Rows(goqu.Record{
"address": "111 Address",
"name": "Bob Yukon",
"created": created,
})
```

Output:
```
INSERT INTO "test" ("address", "created", "name") VALUES ('111 Address', '2019-10-01T23:01:00+08:00', 'Bob Yukon')
```



16 changes: 8 additions & 8 deletions expressions_example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -523,19 +523,19 @@ func ExampleDoUpdate() {

sql, args, _ := ds.
Rows(goqu.Record{"address": "111 Address"}).
OnConflict(goqu.DoUpdate("address", goqu.C("address").Set(goqu.I("EXCLUDED.address")))).
OnConflict(goqu.DoUpdate("address", goqu.C("address").Set(goqu.I("excluded.address")))).
ToSQL()
fmt.Println(sql, args)

sql, args, _ = ds.Prepared(true).
Rows(goqu.Record{"address": "111 Address"}).
OnConflict(goqu.DoUpdate("address", goqu.C("address").Set(goqu.I("EXCLUDED.address")))).
OnConflict(goqu.DoUpdate("address", goqu.C("address").Set(goqu.I("excluded.address")))).
ToSQL()
fmt.Println(sql, args)

// Output:
// INSERT INTO "items" ("address") VALUES ('111 Address') ON CONFLICT (address) DO UPDATE SET "address"="EXCLUDED"."address" []
// INSERT INTO "items" ("address") VALUES (?) ON CONFLICT (address) DO UPDATE SET "address"="EXCLUDED"."address" [111 Address]
// INSERT INTO "items" ("address") VALUES ('111 Address') ON CONFLICT (address) DO UPDATE SET "address"="excluded"."address" []
// INSERT INTO "items" ("address") VALUES (?) ON CONFLICT (address) DO UPDATE SET "address"="excluded"."address" [111 Address]
}

func ExampleDoUpdate_where() {
Expand All @@ -545,7 +545,7 @@ func ExampleDoUpdate_where() {
Rows(goqu.Record{"address": "111 Address"}).
OnConflict(goqu.DoUpdate(
"address",
goqu.C("address").Set(goqu.I("EXCLUDED.address"))).Where(goqu.I("items.updated").IsNull()),
goqu.C("address").Set(goqu.I("excluded.address"))).Where(goqu.I("items.updated").IsNull()),
).
ToSQL()
fmt.Println(sql, args)
Expand All @@ -554,14 +554,14 @@ func ExampleDoUpdate_where() {
Rows(goqu.Record{"address": "111 Address"}).
OnConflict(goqu.DoUpdate(
"address",
goqu.C("address").Set(goqu.I("EXCLUDED.address"))).Where(goqu.I("items.updated").IsNull()),
goqu.C("address").Set(goqu.I("excluded.address"))).Where(goqu.I("items.updated").IsNull()),
).
ToSQL()
fmt.Println(sql, args)

// Output:
// INSERT INTO "items" ("address") VALUES ('111 Address') ON CONFLICT (address) DO UPDATE SET "address"="EXCLUDED"."address" WHERE ("items"."updated" IS NULL) []
// INSERT INTO "items" ("address") VALUES (?) ON CONFLICT (address) DO UPDATE SET "address"="EXCLUDED"."address" WHERE ("items"."updated" IS NULL) [111 Address]
// INSERT INTO "items" ("address") VALUES ('111 Address') ON CONFLICT (address) DO UPDATE SET "address"="excluded"."address" WHERE ("items"."updated" IS NULL) []
// INSERT INTO "items" ("address") VALUES (?) ON CONFLICT (address) DO UPDATE SET "address"="excluded"."address" WHERE ("items"."updated" IS NULL) [111 Address]
}

func ExampleFIRST() {
Expand Down
9 changes: 9 additions & 0 deletions goqu.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ Please see https://github.com/doug-martin/goqu for an introduction to goqu.
package goqu

import (
"time"

"github.com/doug-martin/goqu/v9/internal/util"
"github.com/doug-martin/goqu/v9/sqlgen"
)

type DialectWrapper struct {
Expand Down Expand Up @@ -68,3 +71,9 @@ func New(dialect string, db SQLDatabase) *Database {
func SetColumnRenameFunction(renameFunc func(string) string) {
util.SetColumnRenameFunction(renameFunc)
}

// Set the location to use when interpolating time.Time instances. See https://golang.org/pkg/time/#LoadLocation
// NOTE: This has no effect when using prepared statements.
func SetTimeLocation(loc *time.Location) {
sqlgen.SetTimeLocation(loc)
}
33 changes: 33 additions & 0 deletions goqu_example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package goqu_test

import (
"fmt"
"time"

"github.com/DATA-DOG/go-sqlmock"
"github.com/doug-martin/goqu/v9"
Expand Down Expand Up @@ -222,3 +223,35 @@ func ExampleDialect_dbSqlite3() {
// {1 111 Test Addr Test1} true <nil>
// {1 111 Test Addr Test1} true <nil>
}

func ExampleSetTimeLocation() {

loc, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
panic(err)
}

created, err := time.Parse(time.RFC3339, "2019-10-01T15:01:00Z")
if err != nil {
panic(err)
}

// use original time with tz info
goqu.SetTimeLocation(loc)
ds := goqu.Insert("test").Rows(goqu.Record{
"address": "111 Address",
"name": "Bob Yukon",
"created": created,
})
sql, _, _ := ds.ToSQL()
fmt.Println(sql)

// convert time to UTC
goqu.SetTimeLocation(time.UTC)
sql, _, _ = ds.ToSQL()
fmt.Println(sql)

// Output:
// INSERT INTO "test" ("address", "created", "name") VALUES ('111 Address', '2019-10-01T23:01:00+08:00', 'Bob Yukon')
// INSERT INTO "test" ("address", "created", "name") VALUES ('111 Address', '2019-10-01T15:01:00Z', 'Bob Yukon')
}
2 changes: 1 addition & 1 deletion sqlgen/expression_sql_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ func (esg *expressionSQLGenerator) literalTime(b sb.SQLBuilder, t time.Time) {
esg.placeHolderSQL(b, t)
return
}
esg.Generate(b, t.Format(esg.dialectOptions.TimeFormat))
esg.Generate(b, t.In(timeLocation).Format(esg.dialectOptions.TimeFormat))
}

// Generates SQL for a Float Value
Expand Down
34 changes: 19 additions & 15 deletions sqlgen/expression_sql_generator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,29 +237,33 @@ func (esgs *expressionSQLGeneratorSuite) TestGenerate_BoolTypes() {
func (esgs *expressionSQLGeneratorSuite) TestGenerate_TimeTypes() {
var nt *time.Time

asiaShanghai, err := time.LoadLocation("Asia/Shanghai")
ts, err := time.Parse(time.RFC3339, "2019-10-01T15:01:00Z")
esgs.Require().NoError(err)
testDatas := []time.Time{
time.Now().UTC(),
time.Now().In(asiaShanghai),
}
originalLoc := timeLocation

for _, n := range testDatas {
now := n
esgs.assertCases(
NewExpressionSQLGenerator("test", DefaultDialectOptions()),
expressionTestCase{val: now, sql: "'" + now.Format(time.RFC3339Nano) + "'"},
expressionTestCase{val: now, sql: "?", isPrepared: true, args: []interface{}{now}},
loc, err := time.LoadLocation("Asia/Shanghai")
esgs.Require().NoError(err)

expressionTestCase{val: &now, sql: "'" + now.Format(time.RFC3339Nano) + "'"},
expressionTestCase{val: &now, sql: "?", isPrepared: true, args: []interface{}{now}},
)
}
SetTimeLocation(loc)
// non time
esgs.assertCases(
NewExpressionSQLGenerator("test", DefaultDialectOptions()),
expressionTestCase{val: ts, sql: "'2019-10-01T23:01:00+08:00'"},
expressionTestCase{val: ts, sql: "?", isPrepared: true, args: []interface{}{ts}},
)
SetTimeLocation(time.UTC)
// utc time
esgs.assertCases(
NewExpressionSQLGenerator("test", DefaultDialectOptions()),
expressionTestCase{val: ts, sql: "'2019-10-01T15:01:00Z'"},
expressionTestCase{val: ts, sql: "?", isPrepared: true, args: []interface{}{ts}},
)
esgs.assertCases(
NewExpressionSQLGenerator("test", DefaultDialectOptions()),
expressionTestCase{val: nt, sql: "NULL"},
expressionTestCase{val: nt, sql: "?", isPrepared: true, args: []interface{}{nil}},
)
SetTimeLocation(originalLoc)
}

func (esgs *expressionSQLGeneratorSuite) TestGenerate_NilTypes() {
Expand Down
11 changes: 11 additions & 0 deletions sqlgen/sqlgen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package sqlgen

import "time"

var timeLocation = time.UTC

// Set the location to use when interpolating time.Time instances. See https://golang.org/pkg/time/#LoadLocation
// NOTE: This has no effect when using prepared statements.
func SetTimeLocation(loc *time.Location) {
timeLocation = loc
}

0 comments on commit 6c34fb4

Please sign in to comment.