diff --git a/NOTICE b/NOTICE index 664188d..1e939d2 100644 --- a/NOTICE +++ b/NOTICE @@ -8,3 +8,7 @@ https://github.com/dropbox/godropbox/tree/master/database/sqlbuilder (BSD-3) This product contains a modified portion of 'snaker' which can be obtained at: https://github.com/serenize/snaker (MIT) + + +This product contains `FormatTimestamp` function from 'pq' which can be obtained at: +https://github.com/lib/pq (MIT) diff --git a/examples/quick-start/quick-start.go b/examples/quick-start/quick-start.go index b10906b..0fe031a 100644 --- a/examples/quick-start/quick-start.go +++ b/examples/quick-start/quick-start.go @@ -91,9 +91,7 @@ func jsonSave(path string, v interface{}) { err := ioutil.WriteFile(path, jsonText, 0644) - if err != nil { - panic(err) - } + panicOnError(err) } func printStatementInfo(stmt SelectStatement) { diff --git a/execution/execution.go b/execution/execution.go index 8a8fc24..3281b03 100644 --- a/execution/execution.go +++ b/execution/execution.go @@ -813,9 +813,7 @@ func (s *scanContext) rowElem(index int) interface{} { value, err := valuer.Value() - if err != nil { - panic(err) - } + utils.PanicOnError(err) return value } diff --git a/generator/internal/metadata/schema_meta_data.go b/generator/internal/metadata/schema_meta_data.go index b7d3b7c..0c54d7d 100644 --- a/generator/internal/metadata/schema_meta_data.go +++ b/generator/internal/metadata/schema_meta_data.go @@ -12,6 +12,7 @@ type SchemaMetaData struct { EnumsMetaData []MetaData } +// IsEmpty returns true if schema info does not contain any table, views or enums metadata func (s SchemaMetaData) IsEmpty() bool { return len(s.TablesMetaData) == 0 && len(s.ViewsMetaData) == 0 && len(s.EnumsMetaData) == 0 } diff --git a/internal/3rdparty/pq/format_timestamp.go b/internal/3rdparty/pq/format_timestamp.go new file mode 100644 index 0000000..9dcf541 --- /dev/null +++ b/internal/3rdparty/pq/format_timestamp.go @@ -0,0 +1,42 @@ +package pq + +// Copyright (c) 2011-2013, 'pq' Contributors Portions Copyright (C) 2011 Blake Mizerany + +import ( + "strconv" + "time" +) + +// FormatTimestamp formats t into Postgres' text format for timestamps. From: github.com/lib/pq +func FormatTimestamp(t time.Time) []byte { + // Need to send dates before 0001 A.D. with " BC" suffix, instead of the + // minus sign preferred by Go. + // Beware, "0000" in ISO is "1 BC", "-0001" is "2 BC" and so on + bc := false + if t.Year() <= 0 { + // flip year sign, and add 1, e.g: "0" will be "1", and "-10" will be "11" + t = t.AddDate((-t.Year())*2+1, 0, 0) + bc = true + } + b := []byte(t.Format("2006-01-02 15:04:05.999999999Z07:00")) + + _, offset := t.Zone() + offset = offset % 60 + if offset != 0 { + // RFC3339Nano already printed the minus sign + if offset < 0 { + offset = -offset + } + + b = append(b, ':') + if offset < 10 { + b = append(b, '0') + } + b = strconv.AppendInt(b, int64(offset), 10) + } + + if bc { + b = append(b, " BC"...) + } + return b +} diff --git a/internal/3rdparty/pq/format_timestamp_test.go b/internal/3rdparty/pq/format_timestamp_test.go new file mode 100644 index 0000000..9cceba2 --- /dev/null +++ b/internal/3rdparty/pq/format_timestamp_test.go @@ -0,0 +1,39 @@ +package pq + +// Copyright (c) 2011-2013, 'pq' Contributors Portions Copyright (C) 2011 Blake Mizerany + +import ( + "testing" + "time" +) + +var formatTimeTests = []struct { + time time.Time + expected string +}{ + {time.Time{}, "0001-01-01 00:00:00Z"}, + {time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "2001-02-03 04:05:06.123456789Z"}, + {time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 2*60*60)), "2001-02-03 04:05:06.123456789+02:00"}, + {time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", -6*60*60)), "2001-02-03 04:05:06.123456789-06:00"}, + {time.Date(2001, time.February, 3, 4, 5, 6, 0, time.FixedZone("", -(7*60*60+30*60+9))), "2001-02-03 04:05:06-07:30:09"}, + + {time.Date(1, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "0001-02-03 04:05:06.123456789Z"}, + {time.Date(1, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 2*60*60)), "0001-02-03 04:05:06.123456789+02:00"}, + {time.Date(1, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", -6*60*60)), "0001-02-03 04:05:06.123456789-06:00"}, + + {time.Date(0, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "0001-02-03 04:05:06.123456789Z BC"}, + {time.Date(0, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 2*60*60)), "0001-02-03 04:05:06.123456789+02:00 BC"}, + {time.Date(0, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", -6*60*60)), "0001-02-03 04:05:06.123456789-06:00 BC"}, + + {time.Date(1, time.February, 3, 4, 5, 6, 0, time.FixedZone("", -(7*60*60+30*60+9))), "0001-02-03 04:05:06-07:30:09"}, + {time.Date(0, time.February, 3, 4, 5, 6, 0, time.FixedZone("", -(7*60*60+30*60+9))), "0001-02-03 04:05:06-07:30:09 BC"}, +} + +func TestFormatTs(t *testing.T) { + for i, tt := range formatTimeTests { + val := string(FormatTimestamp(tt.time)) + if val != tt.expected { + t.Errorf("%d: incorrect time format %q, want %q", i, val, tt.expected) + } + } +} diff --git a/internal/jet/sql_builder.go b/internal/jet/sql_builder.go index d16aec4..5126e90 100644 --- a/internal/jet/sql_builder.go +++ b/internal/jet/sql_builder.go @@ -2,6 +2,7 @@ package jet import ( "bytes" + "github.com/go-jet/jet/internal/3rdparty/pq" "github.com/go-jet/jet/internal/utils" "github.com/google/uuid" "strconv" @@ -173,7 +174,7 @@ func argToString(value interface{}) string { case uuid.UUID: return stringQuote(bindVal.String()) case time.Time: - return stringQuote(string(utils.FormatTimestamp(bindVal))) + return stringQuote(string(pq.FormatTimestamp(bindVal))) default: return "[Unsupported type]" } diff --git a/internal/jet/table.go b/internal/jet/table.go index bf8285f..4379000 100644 --- a/internal/jet/table.go +++ b/internal/jet/table.go @@ -19,15 +19,17 @@ type Table interface { } // NewTable creates new table with schema Name, table Name and list of columns -func NewTable(schemaName, name string, columns ...ColumnExpression) SerializerTable { +func NewTable(schemaName, name string, column ColumnExpression, columns ...ColumnExpression) SerializerTable { + + columnList := append([]ColumnExpression{column}, columns...) t := tableImpl{ schemaName: schemaName, name: name, - columnList: columns, + columnList: columnList, } - for _, c := range columns { + for _, c := range columnList { c.setTableName(name) } diff --git a/internal/jet/table_test.go b/internal/jet/table_test.go new file mode 100644 index 0000000..30182bc --- /dev/null +++ b/internal/jet/table_test.go @@ -0,0 +1,33 @@ +package jet + +import ( + "gotest.tools/assert" + "testing" +) + +func TestNewTable(t *testing.T) { + newTable := NewTable("schema", "table", IntegerColumn("intCol")) + + assert.Equal(t, newTable.SchemaName(), "schema") + assert.Equal(t, newTable.TableName(), "table") + + assert.Equal(t, len(newTable.columns()), 1) + assert.Equal(t, newTable.columns()[0].Name(), "intCol") +} + +func TestNewJoinTable(t *testing.T) { + newTable1 := NewTable("schema", "table", IntegerColumn("intCol1")) + newTable2 := NewTable("schema", "table2", IntegerColumn("intCol2")) + + joinTable := NewJoinTable(newTable1, newTable2, InnerJoin, IntegerColumn("intCol1").EQ(IntegerColumn("intCol2"))) + + assertClauseSerialize(t, joinTable, `schema.table +INNER JOIN schema.table2 ON ("intCol1" = "intCol2")`) + + assert.Equal(t, joinTable.SchemaName(), "schema") + assert.Equal(t, joinTable.TableName(), "") + + assert.Equal(t, len(joinTable.columns()), 2) + assert.Equal(t, joinTable.columns()[0].Name(), "intCol1") + assert.Equal(t, joinTable.columns()[1].Name(), "intCol2") +} diff --git a/internal/testutils/test_utils.go b/internal/testutils/test_utils.go index c2779fe..714442c 100644 --- a/internal/testutils/test_utils.go +++ b/internal/testutils/test_utils.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/go-jet/jet/execution" "github.com/go-jet/jet/internal/jet" + "github.com/go-jet/jet/internal/utils" "gotest.tools/assert" "io/ioutil" "os" @@ -60,9 +61,7 @@ func SaveJSONFile(v interface{}, testRelativePath string) { filePath := getFullPath(testRelativePath) err := ioutil.WriteFile(filePath, jsonText, 0644) - if err != nil { - panic(err) - } + utils.PanicOnError(err) } // AssertJSONFile check if data json representation is the same as json at testRelativePath @@ -160,6 +159,7 @@ func AssertQueryPanicErr(t *testing.T, stmt jet.Statement, db execution.DB, dest stmt.Query(db, dest) } +// AssertFileContent check if file content at filePath contains expectedContent text. func AssertFileContent(t *testing.T, filePath string, contentBegin string, expectedContent string) { enumFileData, err := ioutil.ReadFile(filePath) @@ -172,6 +172,7 @@ func AssertFileContent(t *testing.T, filePath string, contentBegin string, expec assert.DeepEqual(t, string(enumFileData[beginIndex:]), expectedContent) } +// AssertFileNamesEqual check if all filesInfos are contained in fileNames func AssertFileNamesEqual(t *testing.T, fileInfos []os.FileInfo, fileNames ...string) { assert.Equal(t, len(fileInfos), len(fileNames)) diff --git a/internal/testutils/time_utils.go b/internal/testutils/time_utils.go index 8348a6c..5c62802 100644 --- a/internal/testutils/time_utils.go +++ b/internal/testutils/time_utils.go @@ -1,6 +1,7 @@ package testutils import ( + "github.com/go-jet/jet/internal/utils" "strings" "time" ) @@ -9,9 +10,7 @@ import ( func Date(t string) *time.Time { newTime, err := time.Parse("2006-01-02", t) - if err != nil { - panic(err) - } + utils.PanicOnError(err) return &newTime } @@ -27,9 +26,7 @@ func TimestampWithoutTimeZone(t string, precision int) *time.Time { newTime, err := time.Parse("2006-01-02 15:04:05"+precisionStr+" +0000", t+" +0000") - if err != nil { - panic(err) - } + utils.PanicOnError(err) return &newTime } @@ -38,9 +35,7 @@ func TimestampWithoutTimeZone(t string, precision int) *time.Time { func TimeWithoutTimeZone(t string) *time.Time { newTime, err := time.Parse("15:04:05", t) - if err != nil { - panic(err) - } + utils.PanicOnError(err) return &newTime } @@ -49,9 +44,7 @@ func TimeWithoutTimeZone(t string) *time.Time { func TimeWithTimeZone(t string) *time.Time { newTimez, err := time.Parse("15:04:05 -0700", t) - if err != nil { - panic(err) - } + utils.PanicOnError(err) return &newTimez } @@ -67,9 +60,7 @@ func TimestampWithTimeZone(t string, precision int) *time.Time { newTime, err := time.Parse("2006-01-02 15:04:05"+precisionStr+" -0700 MST", t) - if err != nil { - panic(err) - } + utils.PanicOnError(err) return &newTime } diff --git a/internal/utils/utils.go b/internal/utils/utils.go index 9e623df..ecc6471 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -7,9 +7,7 @@ import ( "os" "path/filepath" "reflect" - "strconv" "strings" - "time" ) // ToGoIdentifier converts database to Go identifier. @@ -109,40 +107,6 @@ func replaceInvalidChars(str string) string { return str } -// FormatTimestamp formats t into Postgres' text format for timestamps. From: github.com/lib/pq -func FormatTimestamp(t time.Time) []byte { - // Need to send dates before 0001 A.D. with " BC" suffix, instead of the - // minus sign preferred by Go. - // Beware, "0000" in ISO is "1 BC", "-0001" is "2 BC" and so on - bc := false - if t.Year() <= 0 { - // flip year sign, and add 1, e.g: "0" will be "1", and "-10" will be "11" - t = t.AddDate((-t.Year())*2+1, 0, 0) - bc = true - } - b := []byte(t.Format("2006-01-02 15:04:05.999999999Z07:00")) - - _, offset := t.Zone() - offset = offset % 60 - if offset != 0 { - // RFC3339Nano already printed the minus sign - if offset < 0 { - offset = -offset - } - - b = append(b, ':') - if offset < 10 { - b = append(b, '0') - } - b = strconv.AppendInt(b, int64(offset), 10) - } - - if bc { - b = append(b, " BC"...) - } - return b -} - // IsNil check if v is nil func IsNil(v interface{}) bool { return v == nil || (reflect.ValueOf(v).Kind() == reflect.Ptr && reflect.ValueOf(v).IsNil()) @@ -175,3 +139,10 @@ func MustBeInitializedPtr(val interface{}, errorStr string) { panic(errorStr) } } + +// PanicOnError panics if err is not nil +func PanicOnError(err error) { + if err != nil { + panic(err) + } +} diff --git a/mysql/table.go b/mysql/table.go index a4cf042..6d414a2 100644 --- a/mysql/table.go +++ b/mysql/table.go @@ -77,9 +77,9 @@ func (r *readableTableInterfaceImpl) CROSS_JOIN(table ReadableTable) joinSelectU } // NewTable creates new table with schema Name, table Name and list of columns -func NewTable(schemaName, name string, columns ...jet.ColumnExpression) Table { +func NewTable(schemaName, name string, column jet.ColumnExpression, columns ...jet.ColumnExpression) Table { t := &tableImpl{ - SerializerTable: jet.NewTable(schemaName, name, columns...), + SerializerTable: jet.NewTable(schemaName, name, column, columns...), } t.readableTableInterfaceImpl.parent = t diff --git a/postgres/table.go b/postgres/table.go index bc2f5c2..dc0b266 100644 --- a/postgres/table.go +++ b/postgres/table.go @@ -109,10 +109,10 @@ type tableImpl struct { } // NewTable creates new table with schema Name, table Name and list of columns -func NewTable(schemaName, name string, columns ...jet.ColumnExpression) Table { +func NewTable(schemaName, name string, column jet.ColumnExpression, columns ...jet.ColumnExpression) Table { t := &tableImpl{ - SerializerTable: jet.NewTable(schemaName, name, columns...), + SerializerTable: jet.NewTable(schemaName, name, column, columns...), } t.readableTableInterfaceImpl.parent = t diff --git a/tests/init/init.go b/tests/init/init.go index a054e31..22698b4 100644 --- a/tests/init/init.go +++ b/tests/init/init.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/go-jet/jet/generator/mysql" "github.com/go-jet/jet/generator/postgres" + "github.com/go-jet/jet/internal/utils" "github.com/go-jet/jet/tests/dbconfig" _ "github.com/go-sql-driver/mysql" _ "github.com/lib/pq" @@ -60,7 +61,7 @@ func initMySQLDB() { cmd.Stdout = os.Stdout err := cmd.Run() - panicOnError(err) + utils.PanicOnError(err) err = mysql.Generate("./.gentestdata/mysql", mysql.DBConnection{ Host: dbconfig.MySqLHost, @@ -70,7 +71,7 @@ func initMySQLDB() { DBName: dbName, }) - panicOnError(err) + utils.PanicOnError(err) } } @@ -104,22 +105,16 @@ func initPostgresDB() { SchemaName: schemaName, SslMode: "disable", }) - panicOnError(err) + utils.PanicOnError(err) } } func execFile(db *sql.DB, sqlFilePath string) { testSampleSql, err := ioutil.ReadFile(sqlFilePath) - panicOnError(err) + utils.PanicOnError(err) _, err = db.Exec(string(testSampleSql)) - panicOnError(err) -} - -func panicOnError(err error) { - if err != nil { - panic(err) - } + utils.PanicOnError(err) } func printOnError(err error) {