diff --git a/.gitignore b/.gitignore index 78f591a..5416c21 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,5 @@ # Go workspace file go.work +/test.db +/tests/test.db diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9a7bb84 --- /dev/null +++ b/Makefile @@ -0,0 +1,11 @@ +get:: +# go get -modfile=go_test.mod + go get -modfile=go.mod +# cp go_test.sum go.sum + + +tests:: + rm -rf test.db + sqlite3 -batch test.db "" + #go test -v ./... + go test -modfile=go_test.mod ./... -v diff --git a/README.md b/README.md index 0369023..6e27650 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,12 @@ for rows.Next() { rec := Rows2Map(rows) data = append(data, rec) } - ``` +check also [go_sql_raw_test.go](go_sql_raw_test.go) + ## TODO -* ... \ No newline at end of file +* type converting from DB to go in function `update` + +## Credits +* module inspired by https://gist.github.com/SchumacherFM/69a167bec7dea644a20e \ No newline at end of file diff --git a/go.mod b/go.mod index 8930454..b6c4bd4 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,4 @@ module github.com/mysiar-org/go-sql-raw go 1.22.2 + diff --git a/go_sql_raw.go b/go_sql_raw.go new file mode 100644 index 0000000..07673f3 --- /dev/null +++ b/go_sql_raw.go @@ -0,0 +1,80 @@ +package go_sql_raw + +import ( + "database/sql" + "fmt" + "log" + "strconv" +) + +type RawSqlType map[string]interface{} + +func Error(err error) { + if err != nil { + log.Fatal(err) + } +} + +func Rows2Map(rows *sql.Rows) RawSqlType { + columns, err := rows.Columns() + columnTypes, err := rows.ColumnTypes() + Error(err) + rc := newMapRawSqlScan(columns, columnTypes) + err = rc.update(rows) + Error(err) + return rc.get() +} + +type mapRawSqlScan struct { + cp []interface{} + row RawSqlType + colCount int + colNames []string + colTypes []*sql.ColumnType +} + +func (s *mapRawSqlScan) update(rows *sql.Rows) error { + if err := rows.Scan(s.cp...); err != nil { + return err + } + + for i := 0; i < s.colCount; i++ { + if rb, ok := s.cp[i].(*sql.RawBytes); ok { + val := string(*rb) + var parsed any + switch s.colTypes[i].DatabaseTypeName() { + case "DECIMAL": + parsed, _ = strconv.ParseFloat(val, 64) + case "INT": + parsed, _ = strconv.ParseInt(val, 10, 64) + default: + parsed = val + } + s.row[s.colNames[i]] = parsed + *rb = nil + } else { + return fmt.Errorf("Cannot convert index %d column %s to type *sql.RawBytes", i, s.colNames[i]) + } + } + + return nil +} + +func (s *mapRawSqlScan) get() RawSqlType { + return s.row +} + +func newMapRawSqlScan(columnNames []string, columnTypes []*sql.ColumnType) *mapRawSqlScan { + lenCN := len(columnNames) + s := &mapRawSqlScan{ + cp: make([]interface{}, lenCN), + row: make(RawSqlType, lenCN), + colCount: lenCN, + colNames: columnNames, + colTypes: columnTypes, + } + for i := 0; i < lenCN; i++ { + s.cp[i] = new(sql.RawBytes) + } + return s +} diff --git a/go_test.mod b/go_test.mod new file mode 100644 index 0000000..5af3f14 --- /dev/null +++ b/go_test.mod @@ -0,0 +1,14 @@ +module github.com/mysiar-org/go-sql-raw + +go 1.22.2 + +require ( + github.com/mattn/go-sqlite3 v1.14.22 + github.com/stretchr/testify v1.9.0 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go_test.sum b/go_test.sum new file mode 100644 index 0000000..ff051a4 --- /dev/null +++ b/go_test.sum @@ -0,0 +1,12 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/tests/go_sql_raw_test.go b/tests/go_sql_raw_test.go new file mode 100644 index 0000000..81e58a7 --- /dev/null +++ b/tests/go_sql_raw_test.go @@ -0,0 +1,72 @@ +package tests + +import ( + "database/sql" + "fmt" + _ "github.com/mattn/go-sqlite3" + "github.com/mysiar-org/go-sql-raw" + "github.com/stretchr/testify/assert" + "strconv" + "testing" +) + +func Test(t *testing.T) { + const file string = "test.db" + db, err := sql.Open("sqlite3", file) + chkError(err) + _, err = db.Exec(dropTable()) + chkError(err) + _, err = db.Exec(createTable()) + chkError(err) + _, err = db.Exec(insertData()) + chkError(err) + + var rows *sql.Rows + rows, err = db.Query("SELECT * FROM album ORDER BY id") + + var data []go_sql_raw.RawSqlType + for rows.Next() { + rec := go_sql_raw.Rows2Map(rows) + data = append(data, rec) + } + + var expectedIds = []int64{1, 2, 3, 4, 5} + var expectedPrices = []float64{56.99, 63.99, 17.99, 34.98, 80.99} + var expectedArtists = []string{"John Coltrane", "John Coltrane", "Gerry Mulligan", "Sarah Vaughan", "Schizma"} + var expectedTitle = []string{"Blue Train", "Giant Steps", "Jeru", "Sarah Vaughan", "Upadek"} + + for idx, entry := range data { + id, _ := strconv.ParseInt(fmt.Sprintf("%v", entry["id"]), 10, 64) + assert.Equal(t, expectedIds[idx], id) + price, _ := strconv.ParseFloat(fmt.Sprintf("%v", entry["price"]), 64) + assert.Equal(t, expectedPrices[idx], price) + assert.Equal(t, expectedArtists[idx], entry["artist"]) + assert.Equal(t, expectedTitle[idx], entry["title"]) + } +} + +func dropTable() string { + return "DROP TABLE IF EXISTS album" +} + +func createTable() string { + return "CREATE TABLE album (id INT AUTO_INCREMENT NOT NULL, title VARCHAR(128) NOT NULL, artist VARCHAR(255) NOT NULL, price DECIMAL(5,2) NOT NULL, PRIMARY KEY (`id`))" +} + +func insertData() string { + return ` +INSERT INTO album (id, title, artist, price) +VALUES + (1, 'Blue Train', 'John Coltrane', 56.99), + (2, 'Giant Steps', 'John Coltrane', 63.99), + (3, 'Jeru', 'Gerry Mulligan', 17.99), + (4, 'Sarah Vaughan', 'Sarah Vaughan', 34.98), + (5, 'Upadek', 'Schizma', 80.99) +` +} + +func chkError(err error) { + if err != nil { + fmt.Println(err) + } +}