8000 Squashed commit of "Add Column Metadata #152" by ephemeralowl · Pull Request #187 · DATA-DOG/go-sqlmock · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Squashed commit of "Add Column Metadata #152" #187

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/examples/blog/blog
/examples/orders/orders
/examples/basic/basic
.idea
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ It only asserts that argument is of `time.Time` type.

## Change Log

- **2019-04-06** - added functionality to mock a sql MetaData request
- **2019-02-13** - added `go.mod` removed the references and suggestions using `gopkg.in`.
- **2018-12-11** - added expectation of Rows to be closed, while mocking expected query.
- **2018-12-11** - introduced an option to provide **QueryMatcher** in order to customize SQL query matching.
Expand Down
75 changes: 75 additions & 0 deletions rows.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"encoding/csv"
"fmt"
"io"
"reflect"
"strings"
)

Expand Down Expand Up @@ -60,6 +61,41 @@ func (rs *rowSets) Next(dest []driver.Value) error {
return r.nextErr[r.pos-1]
}

// search the last definition of metadata
func (rs *rowSets) getDefinition(index int) *Column {
for i := rs.pos; i >= 0; i-- {
if rs.sets[i].def != nil && len(rs.sets[i].def) > 0 {
return rs.sets[i].def[index]
}
}
return NewColumn("", "", "", false, 0, 0, 0)
}

// ColumnTypeLength is defined from driver.RowColumnTypeLength
func (rs *rowSets) ColumnTypeLength(index int) (length int64, ok bool) {
return rs.getDefinition(index).length, false
}

// ColumnTypeNullable is defined from driver.RowColumnTypeNullable
func (rs *rowSets) ColumnTypeNullable(index int) (nullable, ok bool) {
return rs.getDefinition(index).nullable, false
}

// ColumnTypePrecisionScale is defined from driver.RowColumnTypePrecisionScale
func (rs *rowSets) ColumnTypePrecisionScale(index int) (precision, scale int64, ok bool) {
return rs.getDefinition(index).precision, rs.getDefinition(index).scale, false
}

// ColumnTypeScanType is defined from driver.RowsColumnTypeScanType
func (rs *rowSets) ColumnTypeScanType(index int) reflect.Type {
return rs.getDefinition(index).scanType
}

// ColumnTypeDatabaseTypeName is defined RowsColumnTypeDatabaseTypeName
func (rs *rowSets) ColumnTypeDatabaseTypeName(index int) string {
return rs.getDefinition(index).dbTyp
}

// transforms to debuggable printable string
func (rs *rowSets) String() string {
if rs.empty() {
Expand Down Expand Up @@ -115,24 +151,63 @@ func (rs *rowSets) invalidateRaw() {
rs.raw = nil
}

// Column is a mocked column Metadata for rows.ColumnTypes()
type Column struct {
name, dbTyp string
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we make all these fields exported, removing NewColumn and NewColumnSimple functions. and move everything related to Column in column.go file

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jopicornell these are the only changes I would like to be made here

nullable bool
length, precision, scale int64
scanType reflect.Type
}

// Rows is a mocked collection of rows to
// return for Query result
type Rows struct {
converter driver.ValueConverter
cols []string
def []*Column
rows [][]driver.Value
pos int
nextErr map[int]error
closeErr error
}

// New Column allows to create a Column Metadata definition
func NewColumn(name, dbTyp string, exampleValue interface{}, nullable bool, length, precision, scale int64) *Column {
return &Column{name, dbTyp, nullable, length, precision, scale, reflect.TypeOf(exampleValue)}
}

func NewColumnSimple(name string) *Column {
return &Column{name: name}
}

// NewRows allows Rows to be created from a
// sql driver.Value slice or from the CSV string and
// to be used as sql driver.Rows.
// Use Sqlmock.NewRows instead if using a custom converter
func NewRows(columns []string) *Rows {
definition := make([]*Column, len(columns))
for i, column := range columns {
definition[i] = NewColumnSimple(column)
}

return &Rows{
cols: columns,
def: definition,
nextErr: make(map[int]error),
converter: driver.DefaultParameterConverter,
}
}

// NewRowsWithColumnDefinition see PR-152
func NewRowsWithColumnDefinition(columns ...*Column) *Rows {
cols := make([]string, len(columns))
for i, column := range columns {
cols[i] = column.name
}

return &Rows{
cols: cols,
def: columns,
nextErr: make(map[int]error),
converter: driver.DefaultParameterConverter,
}
Expand Down
161 changes: 161 additions & 0 deletions rows_go18_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"database/sql"
"encoding/json"
"fmt"
"reflect"
"testing"
)

Expand Down Expand Up @@ -203,3 +204,163 @@ func TestQueryRowBytesNotInvalidatedByClose_jsonRawMessageIntoCustomBytes(t *tes
}
queryRowBytesNotInvalidatedByClose(t, rows, scan, []byte(`{"thing": "one", "thing2": "two"}`))
}

func TestNewColumnWithDefinition(t *testing.T) {

t.Run("with one ResultSet", func(t *testing.T) {
db, mock, _ := New()
column1 := mock.NewColumn("test", "VARCHAR", "", true, 100, 0, 0)
column2 := mock.NewColumn("number", "DECIMAL", float64(0.0), false, 0, 10, 4)
rows := mock.NewRowsWithColumnDefinition(column1, column2)
rows.AddRow("foo.bar", float64(10.123))

mQuery := mock.ExpectQuery("SELECT test, number from dummy")
isQuery := mQuery.WillReturnRows(rows)
isQueryClosed := mQuery.RowsWillBeClosed()
isDbClosed := mock.ExpectClose()

query, _ := db.Query("SELECT test, number from dummy")

if false == isQuery.fulfilled() {
t.Fatal("Query is not executed")
}

if query.Next() {
var test string
var number float64

if queryError := query.Scan(&test, &number); queryError != nil {
t.Fatal(queryError)
} else if test != "foo.bar" {
t.Fatal("field test is not 'foo.bar'")
} else if number != float64(10.123) {
t.Fatal("field number is not '10.123'")
}

if columnTypes, colTypErr := query.ColumnTypes(); colTypErr != nil {
t.Fatal(colTypErr)
} else if len(columnTypes) != 2 {
t.Fatal("number of columnTypes")
} else if name := columnTypes[0].Name(); name != "test" {
t.Fatalf("field 'test' has a wrong name '%s'", name)
} else if dbTyp := columnTypes[0].DatabaseTypeName(); dbTyp != "VARCHAR" {
t.Fatalf("field 'test' has a wrong db type '%s'", dbTyp)
} else if columnTypes[0].ScanType().Kind() != reflect.String {
t.Fatal("field 'test' has a wrong scanType")
} else if precision, scale, _ := columnTypes[0].DecimalSize(); precision != 0 || scale != 0 {
t.Fatal("field 'test' has a wrong precision, scale")
} else if length, _ := columnTypes[0].Length(); length != 100 {
t.Fatalf("field 'test' has a wrong length '%d'", length)
} else if name := columnTypes[1].Name(); name != "number" {
t.Fatalf("field 'number' has a wrong name '%s'", name)
} else if dbTyp := columnTypes[1].DatabaseTypeName(); dbTyp != "DECIMAL" {
t.Fatalf("field 'number' has a wrong db type '%s'", dbTyp)
} else if columnTypes[1].ScanType().Kind() != reflect.Float64 {
t.Fatal("field 'number' has a wrong scanType")
} else if precision, scale, _ := columnTypes[1].DecimalSize(); precision != int64(10) || scale != int64(4) {
t.Fatal("field 'number' has a wrong precision, scale")
} else if length, _ := columnTypes[1].Length(); length != 0 {
t.Fatal("field 'number' has a wrong length")
}
} else {
t.Fatal("no result set")
}

query.Close()
if false == isQueryClosed.fulfilled() {
t.Fatal("Query is not executed")
}

db.Close()
if false == isDbClosed.fulfilled() {
t.Fatal("Query is not closed")
}
})

t.Run("with more then one ResultSet", func(t *testing.T) {
db, mock, _ := New()
column1 := mock.NewColumn("test", "VARCHAR", "", true, 100, 0, 0)
column2 := mock.NewColumn("number", "DECIMAL", float64(0.0), false, 0, 10, 4)
rows := mock.NewRowsWithColumnDefinition(column1, column2)
rows.AddRow("foo.bar", float64(10.123))
rows.AddRow("bar.foo", float64(123.10))
rows.AddRow("lollipop", float64(10.321))

mQuery := mock.ExpectQuery("SELECT test, number from dummy")
isQuery := mQuery.WillReturnRows(rows)
isQueryClosed := mQuery.RowsWillBeClosed()
isDbClosed := mock.ExpectClose()

query, _ := db.Query("SELECT test, number from dummy")

if false == isQuery.fulfilled() {
t.Fatal("Query is not executed")
}

rowsSi := 0

if query.Next() {
var test string
var number float64

if queryError := query.Scan(&test, &number); queryError != nil {
t.Fatal(queryError)

} else if rowsSi == 0 && test != "foo.bar" {
t.Fatal("field test is not 'foo.bar'")
} else if rowsSi == 0 && number != float64(10.123) {
t.Fatal("field number is not '10.123'")

} else if rowsSi == 1 && test != "bar.foo" {
t.Fatal("field test is not 'bar.bar'")
} else if rowsSi == 1 && number != float64(123.10) {
t.Fatal("field number is not '123.10'")

} else if rowsSi == 2 && test != "lollipop" {
t.Fatal("field test is not 'lollipop'")
} else if rowsSi == 2 && number != float64(10.321) {
t.Fatal("field number is not '10.321'")
}

rowsSi++

if columnTypes, colTypErr := query.ColumnTypes(); colTypErr != nil {
t.Fatal(colTypErr)
} else if len(columnTypes) != 2 {
t.Fatal("number of columnTypes")
} else if name := columnTypes[0].Name(); name != "test" {
t.Fatalf("field 'test' has a wrong name '%s'", name)
} else if dbTyp := columnTypes[0].DatabaseTypeName(); dbTyp != "VARCHAR" {
t.Fatalf("field 'test' has a wrong db type '%s'", dbTyp)
} else if columnTypes[0].ScanType().Kind() != reflect.String {
t.Fatal("field 'test' has a wrong scanType")
} else if precision, scale, _ := columnTypes[0].DecimalSize(); precision != 0 || scale != 0 {
t.Fatal("field 'test' has a wrong precision, scale")
} else if length, _ := columnTypes[0].Length(); length != 100 {
t.Fatalf("field 'test' has a wrong length '%d'", length)
} else if name := columnTypes[1].Name(); name != "number" {
t.Fatalf("field 'number' has a wrong name '%s'", name)
} else if dbTyp := columnTypes[1].DatabaseTypeName(); dbTyp != "DECIMAL" {
t.Fatalf("field 'number' has a wrong db type '%s'", dbTyp)
} else if columnTypes[1].ScanType().Kind() != reflect.Float64 {
t.Fatal("field 'number' has a wrong scanType")
} else if precision, scale, _ := columnTypes[1].DecimalSize(); precision != int64(10) || scale != int64(4) {
t.Fatal("field 'number' has a wrong precision, scale")
} else if length, _ := columnTypes[1].Length(); length != 0 {
t.Fatal("field 'number' has a wrong length")
}
} else {
t.Fatal("no result set")
}

query.Close()
if false == isQueryClosed.fulfilled() {
t.Fatal("Query is not executed")
}

db.Close()
if false == isDbClosed.fulfilled() {
t.Fatal("Query is not closed")
}
})
}
21 changes: 20 additions & 1 deletion sqlmock.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (
// for any kind of database action in order to mock
// and test real database behavior.
type Sqlmock interface {

// ExpectClose queues an expectation for this database
// action to be triggered. the *ExpectedClose allows
// to mock database response
Expand Down Expand Up @@ -74,6 +73,13 @@ type Sqlmock interface {
// sql driver.Value slice or from the CSV string and
// to be used as sql driver.Rows.
NewRows(columns []string) *Rows

// NewRowsWithColumnDefinition allows Rows to be created from a
// sql driver.Value slice with a definition of sql metadata
NewRowsWithColumnDefinition(columns ...*Column) *Rows

// New Column allows to create a Column Metadata definition
NewColumn(name, dbTyp string, exampleValue interface{}, nullable bool, length, precision, scale int64) *Column
}

type sqlmock struct {
Expand Down Expand Up @@ -587,3 +593,16 @@ func (c *sqlmock) NewRows(columns []string) *Rows {
r.converter = c.converter
return r
}

// NewRowsWithColumnDefinition allows Rows to be created from a
// sql driver.Value slice with a definition of sql metadata
func (c *sqlmock) NewRowsWithColumnDefinition(columns ...*Column) *Rows {
r := NewRowsWithColumnDefinition(columns...)
r.converter = c.converter
return r
}

// New Column allows to create a Column Metadata definition
func (c *sqlmock) NewColumn(name, dbTyp string, exampleValue interface{}, nullable bool, length, precision, scale int64) *Column {
return NewColumn(name, dbTyp, exampleValue, nullable, length, precision, scale)
}
0