├── doc.go ├── .gitignore ├── example_test.go ├── convert.go ├── integration_test.sql ├── current_time.go ├── README.md ├── Makefile ├── logging.go ├── LICENSE ├── row_reader_test.go ├── integration_test.go ├── row_reader.go └── connection.go /doc.go: -------------------------------------------------------------------------------- 1 | // Author: imos (Kentaro Imajo) 2 | 3 | // ImoSQL is a SQL utility library for Go language. This library provides 4 | // functions to fill rows instantiating a SQL table schema, and this library 5 | // provides functions specifying types while built-in SQL functions use 6 | // interface{} and do not specify any types. 7 | package imosql 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package imosql_test 2 | 3 | import ( 4 | imosql "./" 5 | "fmt" 6 | "log" 7 | ) 8 | 9 | func ExampleOpen() { 10 | // This is an example using github.com/go-sql-driver/mysql. DataSourceName is 11 | // dependent of a SQL driver, so please consult your SQL driver's document. 12 | con, err := imosql.Open("mysql", "user:password@tcp(host:port)/database") 13 | if err != nil { 14 | log.Fatal(err) 15 | } 16 | fmt.Printf("1 + 1 = %d\n", con.IntegerOrDie("SELECT 1 + 1")) 17 | } 18 | -------------------------------------------------------------------------------- /convert.go: -------------------------------------------------------------------------------- 1 | package imosql 2 | 3 | import ( 4 | "strconv" 5 | "time" 6 | ) 7 | 8 | func convertStringToInteger(stringResult string) (result int64, err error) { 9 | result, err = strconv.ParseInt(stringResult, 10, 64) 10 | return 11 | } 12 | 13 | func convertStringToTime(stringResult string) (result time.Time, err error) { 14 | loc, err := time.LoadLocation("UTC") 15 | if err != nil { 16 | return 17 | } 18 | result, err = time.ParseInLocation("2006-01-02 15:04:05", stringResult, loc) 19 | return 20 | } 21 | -------------------------------------------------------------------------------- /integration_test.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS `test` ( 2 | `test_id` int(11) NOT NULL AUTO_INCREMENT, 3 | `test_string` varchar(32) NOT NULL, 4 | `test_int` int(11) NOT NULL, 5 | `test_time` datetime NOT NULL, 6 | PRIMARY KEY (`test_id`) 7 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=4; 8 | 9 | INSERT INTO `test` (`test_id`, `test_string`, `test_int`, `test_time`) VALUES 10 | (1, 'foo', 1, '2000-01-01 00:00:00'), 11 | (2, 'bar', 2, '2001-02-03 04:05:06'), 12 | (3, 'foobar', 3, '0001-01-01 00:00:00'); 13 | -------------------------------------------------------------------------------- /current_time.go: -------------------------------------------------------------------------------- 1 | package imosql 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | var lastAdjustTime int64 = 0 8 | var timeGapInNanoseconds *int64 = nil 9 | 10 | func (c *Connection) CurrentTime() time.Time { 11 | if timeGapInNanoseconds == nil || lastAdjustTime < time.Now().Unix()-600 { 12 | timeGapInNanoseconds = new(int64) 13 | *timeGapInNanoseconds = 14 | time.Now().UnixNano() - c.TimeOrDie("SELECT UTC_TIMESTAMP()").UnixNano() 15 | printLogf("the current time gap is %d ms.", *timeGapInNanoseconds/1000000) 16 | lastAdjustTime = time.Now().Unix() 17 | } 18 | return time.Unix(0, time.Now().UnixNano()-*timeGapInNanoseconds) 19 | } 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ImoSQL 2 | ====== 3 | 4 | ImoSQL is a SQL utility library for Go language. 5 | This library provides functions to fill rows instantiating a SQL table schema, 6 | and this library provides functions specifying types 7 | while built-in SQL functions use interface{} and do not specify any types. 8 | 9 | * **Latest stable Release:** [None](https://github.com/go-sql-driver/mysql/releases) 10 | * **Build status:** [drone.io](https://drone.io/github.com/imos/imosql) 11 | * ImoSQL is tested using the go-sql-driver/mysql package and a real MySQL 12 | server on drone.io in addition to regular unit tests. 13 | 14 | For more details, see [GoDoc](http://godoc.org/github.com/imos/imosql). 15 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | export GOPATH = $(shell pwd | sed -e 's/\/src\/.*$$//g') 2 | 3 | test: build 4 | go test -v 5 | .PHONY: test 6 | 7 | integration-test: build 8 | mysql -u root -e 'SELECT VERSION();' 9 | mysql -u root -e 'CREATE DATABASE test;' 10 | mysql -u root -D test < integration_test.sql 11 | go test -v --enable_integration_test 12 | .PHONY: integration-test 13 | 14 | build: get 15 | go build 16 | .PHONY: build 17 | 18 | get: version 19 | go get github.com/go-sql-driver/mysql 20 | go get 21 | .PHONY: get 22 | 23 | version: 24 | @go version 25 | .PHONY: version 26 | 27 | format: 28 | gofmt -w ./ 29 | .PHONY: format 30 | 31 | document: 32 | godoc -http=:6060 33 | 34 | info: 35 | @echo "GOPATH=$${GOPATH}" 36 | .PHONY: info 37 | -------------------------------------------------------------------------------- /logging.go: -------------------------------------------------------------------------------- 1 | package imosql 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "strings" 8 | ) 9 | 10 | var isLogging = flag.Bool("imosql_logging", false, "Show SQL queries.") 11 | 12 | // SetLogging enables ImoSQL logging if mode is true, otherwise disables ImoSQL 13 | // logging. 14 | func SetLogging(mode bool) { 15 | isLogging = new(bool) 16 | *isLogging = mode 17 | } 18 | 19 | // IsLogging returns true iff ImoSQL logging is enabled. ImoSQL logging can be 20 | // enabled by imosql_logging flag. 21 | func IsLogging() bool { 22 | return *isLogging 23 | } 24 | 25 | func printLog(a ...interface{}) { 26 | if IsLogging() { 27 | message := fmt.Sprint(a...) 28 | log.Println(strings.TrimSpace(message)) 29 | } 30 | return 31 | } 32 | 33 | func printLogf(format string, a ...interface{}) { 34 | if IsLogging() { 35 | message := fmt.Sprintf(format, a...) 36 | log.Println(strings.TrimSpace(message)) 37 | } 38 | return 39 | } 40 | 41 | func errorf(format string, a ...interface{}) (err error) { 42 | err = fmt.Errorf(format, a...) 43 | printLog(err) 44 | return 45 | } 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Kentaro IMAJO 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /row_reader_test.go: -------------------------------------------------------------------------------- 1 | package imosql_test 2 | 3 | import ( 4 | imosql "." 5 | "database/sql" 6 | "encoding/json" 7 | "reflect" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | type RowExample struct { 13 | Bool bool `sql:"row_bool"` 14 | BoolPtr *bool `sql:"row_bool_ptr"` 15 | Int64 int64 `sql:"row_int64"` 16 | Int64Ptr *int64 `sql:"row_int64_ptr"` 17 | String string `sql:"row_string"` 18 | StringPtr *string `sql:"row_string_ptr"` 19 | Datetime time.Time `sql:"row_datetime"` 20 | } 21 | 22 | func parseField(rowReader *imosql.RowReader, input string, fieldName string) ( 23 | output string, err error) { 24 | var rowReflection reflect.Value 25 | if input == "NULL" { 26 | rowReflection, err = rowReader.ParseFields([]sql.NullString{ 27 | sql.NullString{String: "", Valid: false}}) 28 | } else { 29 | rowReflection, err = rowReader.ParseFields([]sql.NullString{ 30 | sql.NullString{String: input, Valid: true}}) 31 | } 32 | if err != nil { 33 | return 34 | } 35 | byteOutput, err := 36 | json.Marshal(rowReflection.Elem().FieldByName(fieldName).Interface()) 37 | if err != nil { 38 | return 39 | } 40 | output = string(byteOutput) 41 | return 42 | } 43 | 44 | func testParseFields( 45 | t *testing.T, columnName string, fieldName string, 46 | testCases map[string]string) { 47 | rows := []RowExample{} 48 | rowReader, err := imosql.NewRowReader(&rows) 49 | if err != nil { 50 | t.Error("failed to create a RowReader:", err) 51 | } 52 | rowReader.SetColumns([]string{columnName}) 53 | for input, expectedOutput := range testCases { 54 | output, err := parseField(rowReader, input, fieldName) 55 | if err != nil { 56 | if expectedOutput != "ERROR" { 57 | t.Error("failed to parse:", err) 58 | } 59 | } else { 60 | if output != expectedOutput { 61 | t.Errorf( 62 | "output for %#v should be %s, but %s", input, expectedOutput, output) 63 | } 64 | } 65 | } 66 | } 67 | 68 | func TestParseFields_Bool(t *testing.T) { 69 | testParseFields( 70 | t, "row_bool", "Bool", 71 | map[string]string{ 72 | "0": `false`, 73 | "1": `true`, 74 | "-1": `true`, 75 | "2": `true`, 76 | "string": `true`, 77 | "": `false`, 78 | "NULL": `false`, 79 | }) 80 | } 81 | 82 | func TestParseFields_BoolPtr(t *testing.T) { 83 | testParseFields( 84 | t, "row_bool_ptr", "BoolPtr", 85 | map[string]string{ 86 | "0": `false`, 87 | "1": `true`, 88 | "-1": `true`, 89 | "2": `true`, 90 | "string": `true`, 91 | "": `false`, 92 | "NULL": `null`, 93 | }) 94 | } 95 | 96 | func TestParseFields_Int64(t *testing.T) { 97 | testParseFields( 98 | t, "row_int64", "Int64", 99 | map[string]string{ 100 | "12345": `12345`, 101 | "01234": `1234`, 102 | "-1234": `-1234`, 103 | "string": `ERROR`, 104 | "": `ERROR`, 105 | "NULL": `0`, 106 | }) 107 | } 108 | 109 | func TestParseFields_Int64Ptr(t *testing.T) { 110 | testParseFields( 111 | t, "row_int64_ptr", "Int64Ptr", 112 | map[string]string{ 113 | "12345": `12345`, 114 | "01234": `1234`, 115 | "-1234": `-1234`, 116 | "string": `ERROR`, 117 | "": `ERROR`, 118 | "NULL": `null`, 119 | }) 120 | } 121 | 122 | func TestParseFields_String(t *testing.T) { 123 | testParseFields( 124 | t, "row_string", "String", 125 | map[string]string{ 126 | "12345": `"12345"`, 127 | "01234": `"01234"`, 128 | "-1234": `"-1234"`, 129 | "string": `"string"`, 130 | "": `""`, 131 | "NULL": `""`, 132 | }) 133 | } 134 | 135 | func TestParseFields_StringPtr(t *testing.T) { 136 | testParseFields( 137 | t, "row_string_ptr", "StringPtr", 138 | map[string]string{ 139 | "12345": `"12345"`, 140 | "01234": `"01234"`, 141 | "-1234": `"-1234"`, 142 | "string": `"string"`, 143 | "": `""`, 144 | "NULL": `null`, 145 | }) 146 | } 147 | 148 | func TestParseFields_Datetime(t *testing.T) { 149 | testParseFields( 150 | t, "row_datetime", "Datetime", 151 | map[string]string{ 152 | "0000-00-00 00:00:00": `"0001-01-01T00:00:00Z"`, 153 | "0000-00-00 00:00:01": "ERROR", 154 | "0001-01-01 00:00:00": `"0001-01-01T00:00:00Z"`, 155 | "2001-02-03 04:05:06": `"2001-02-03T04:05:06Z"`, 156 | "9999-12-31 23:59:59": `"9999-12-31T23:59:59Z"`, 157 | "9999-99-99 99:99:99": "ERROR", 158 | "NULL": `"0001-01-01T00:00:00Z"`, 159 | }) 160 | } 161 | -------------------------------------------------------------------------------- /integration_test.go: -------------------------------------------------------------------------------- 1 | package imosql_test 2 | 3 | import ( 4 | imosql "." 5 | "encoding/json" 6 | "flag" 7 | _ "github.com/go-sql-driver/mysql" 8 | "reflect" 9 | "testing" 10 | "time" 11 | ) 12 | 13 | var enableIntegrationTest = flag.Bool( 14 | "enable_integration_test", false, 15 | "Enables integration test using an actual MySQL server.") 16 | 17 | var db *imosql.Connection = nil 18 | 19 | func openDatabase() { 20 | if !*enableIntegrationTest { 21 | return 22 | } 23 | if db == nil { 24 | var err error = nil 25 | db, err = imosql.Open("mysql", "root@/test") 26 | if err != nil { 27 | panic(err) 28 | } 29 | } 30 | } 31 | 32 | func TestConnect(t *testing.T) { 33 | openDatabase() 34 | } 35 | 36 | func TestInteger(t *testing.T) { 37 | openDatabase() 38 | if db == nil { 39 | return 40 | } 41 | actual := db.IntegerOrDie("SELECT 1 + 1") 42 | expected := int64(2) 43 | if expected != actual { 44 | t.Errorf("expected: %v, actual: %v", expected, actual) 45 | } 46 | } 47 | 48 | type TestRow struct { 49 | Id int `sql:"test_id"` 50 | String string `sql:"test_string"` 51 | Int int64 `sql:"test_int"` 52 | Time time.Time `sql:"test_time"` 53 | } 54 | 55 | func checkInterfaceEqual(t *testing.T, expected string, actual interface{}) { 56 | var expectedInterface interface{} 57 | if err := json.Unmarshal([]byte(expected), &expectedInterface); err != nil { 58 | t.Fatalf("failed to decode an expected value: %s", err) 59 | } 60 | actualJson, err := json.Marshal(actual) 61 | if err != nil { 62 | t.Fatalf("failed to encode an actual value: %s", err) 63 | } 64 | var actualInterface interface{} 65 | if err := json.Unmarshal(actualJson, &actualInterface); err != nil { 66 | t.Fatalf("failed to decode an actual value: %s", err) 67 | } 68 | if !reflect.DeepEqual(expectedInterface, actualInterface) { 69 | t.Errorf("expected: %#v, actual: %#v.", expectedInterface, actualInterface) 70 | } 71 | } 72 | 73 | func TestCheckInterfaceEqual(t *testing.T) { 74 | location, err := time.LoadLocation("UTC") 75 | if err != nil { 76 | t.Errorf("failed to LoadLocation: %s", err) 77 | } 78 | rows := []TestRow{ 79 | TestRow{ 80 | Id: 2, String: "bar", Int: 2, 81 | Time: time.Date(2001, 2, 3, 4, 5, 6, 0, location), 82 | }, 83 | } 84 | checkInterfaceEqual( 85 | t, 86 | `[{"Id": 2, "String": "bar", "Int": 2, "Time": "2001-02-03T04:05:06Z"}]`, 87 | rows) 88 | if t.Failed() { 89 | t.Fatalf("this test must pass.") 90 | } 91 | rows[0].Time = time.Date(2002, 2, 3, 4, 5, 6, 0, location) 92 | childTest := testing.T{} 93 | checkInterfaceEqual( 94 | &childTest, 95 | `[{"Id": 2, "String": "bar", "Int": 2, "Time": "2001-02-03T04:05:06Z"}]`, 96 | rows) 97 | if !childTest.Failed() { 98 | t.Fatalf("this test must not pass.") 99 | } 100 | } 101 | 102 | func TestRows(t *testing.T) { 103 | openDatabase() 104 | if db == nil { 105 | return 106 | } 107 | rows := []TestRow{} 108 | db.RowsOrDie(&rows, "SELECT * FROM test ORDER BY test_id") 109 | checkInterfaceEqual( 110 | t, 111 | `[{"Id": 1, "String": "foo", "Int": 1, "Time": "2000-01-01T00:00:00Z"}, 112 | {"Id": 2, "String": "bar", "Int": 2, "Time": "2001-02-03T04:05:06Z"}, 113 | {"Id": 3, "String": "foobar", "Int": 3, "Time": "0001-01-01T00:00:00Z"}]`, 114 | rows) 115 | db.RowsOrDie(&rows, "SELECT test_id, test_int FROM test ORDER BY test_id") 116 | checkInterfaceEqual( 117 | t, 118 | `[{"Id": 1, "String": "", "Int": 1, "Time": "0001-01-01T00:00:00Z"}, 119 | {"Id": 2, "String": "", "Int": 2, "Time": "0001-01-01T00:00:00Z"}, 120 | {"Id": 3, "String": "", "Int": 3, "Time": "0001-01-01T00:00:00Z"}]`, 121 | rows) 122 | db.RowsOrDie(&rows, "SELECT * FROM test ORDER BY test_id DESC") 123 | checkInterfaceEqual( 124 | t, 125 | `[{"Id": 3, "String": "foobar", "Int": 3, "Time": "0001-01-01T00:00:00Z"}, 126 | {"Id": 2, "String": "bar", "Int": 2, "Time": "2001-02-03T04:05:06Z"}, 127 | {"Id": 1, "String": "foo", "Int": 1, "Time": "2000-01-01T00:00:00Z"}]`, 128 | rows) 129 | db.RowsOrDie(&rows, "SELECT * FROM test WHERE test_id = ?", 2) 130 | checkInterfaceEqual( 131 | t, 132 | `[{"Id": 2, "String": "bar", "Int": 2, "Time": "2001-02-03T04:05:06Z"}]`, 133 | rows) 134 | db.RowsOrDie(&rows, "SELECT * FROM test WHERE test_id = 4") 135 | checkInterfaceEqual(t, "[]", rows) 136 | 137 | row := TestRow{} 138 | if !db.RowOrDie(&row, "SELECT * FROM test ORDER BY test_id") { 139 | t.Fatalf("no result.") 140 | } 141 | checkInterfaceEqual( 142 | t, 143 | `{"Id": 1, "String": "foo", "Int": 1, "Time": "2000-01-01T00:00:00Z"}`, 144 | row) 145 | if !db.RowOrDie(&row, "SELECT * FROM test ORDER BY test_id DESC") { 146 | t.Fatalf("no result.") 147 | } 148 | checkInterfaceEqual( 149 | t, 150 | `{"Id": 3, "String": "foobar", "Int": 3, "Time": "0001-01-01T00:00:00Z"}`, 151 | row) 152 | if !db.RowOrDie(&row, "SELECT * FROM test WHERE test_id = ?", 2) { 153 | t.Fatalf("no result.") 154 | } 155 | checkInterfaceEqual( 156 | t, 157 | `{"Id": 2, "String": "bar", "Int": 2, "Time": "2001-02-03T04:05:06Z"}`, 158 | row) 159 | if db.RowOrDie(&row, "SELECT * FROM test WHERE test_id = 4") { 160 | t.Errorf("there should be no results for test_id = 4.") 161 | } 162 | } 163 | 164 | func TestLogging(t *testing.T) { 165 | imosql.SetLogging(true) 166 | TestRows(t) 167 | } 168 | -------------------------------------------------------------------------------- /row_reader.go: -------------------------------------------------------------------------------- 1 | package imosql 2 | 3 | import ( 4 | "database/sql" 5 | "reflect" 6 | "strconv" 7 | "time" 8 | ) 9 | 10 | type RowReader struct { 11 | rowsPtr interface{} 12 | rowType reflect.Type 13 | columnNameToFieldIndex map[string]int 14 | columns []string 15 | columnIndexToFieldIndex []int 16 | } 17 | 18 | func NewRowReader(rowsPtr interface{}) (rowReader *RowReader, err error) { 19 | if reflect.ValueOf(rowsPtr).Kind() != reflect.Ptr { 20 | err = errorf( 21 | "rowsPtr must be a pointer but %s.", 22 | reflect.ValueOf(rowsPtr).Kind().String()) 23 | return 24 | } 25 | rows := reflect.ValueOf(rowsPtr).Elem() 26 | if rows.Kind() != reflect.Slice { 27 | err = errorf("rows must be a slice but %s.", rows.Kind().String()) 28 | return 29 | } 30 | rows.SetLen(0) 31 | if rows.Type().Elem().Kind() != reflect.Struct { 32 | err = errorf( 33 | "rows must be a slice of a struct but a slice of %s.", 34 | rows.Type().Elem().Kind().String()) 35 | return 36 | } 37 | rowType := rows.Type().Elem() 38 | columnNameToFieldIndex := map[string]int{} 39 | for fieldIndex := 0; fieldIndex < rowType.NumField(); fieldIndex++ { 40 | field := rowType.Field(fieldIndex) 41 | if field.Tag.Get("sql") == "" { 42 | err = errorf( 43 | "every field of a row struct must have a sql tag: %s", field.Name) 44 | return 45 | } 46 | columnNameToFieldIndex[field.Tag.Get("sql")] = fieldIndex 47 | } 48 | rowReader = &RowReader{ 49 | rowsPtr: rowsPtr, 50 | rowType: rowType, 51 | columnNameToFieldIndex: columnNameToFieldIndex, 52 | } 53 | return 54 | } 55 | 56 | func (rr *RowReader) SetColumns(columns []string) error { 57 | if len(columns) == 0 { 58 | return errorf("# of columns must be >0.") 59 | } 60 | rr.columnIndexToFieldIndex = []int{} 61 | for _, columnName := range columns { 62 | if fieldIndex, ok := rr.columnNameToFieldIndex[columnName]; ok { 63 | rr.columnIndexToFieldIndex = 64 | append(rr.columnIndexToFieldIndex, fieldIndex) 65 | } else { 66 | rr.columnIndexToFieldIndex = 67 | append(rr.columnIndexToFieldIndex, -1) 68 | } 69 | } 70 | rr.columns = make([]string, len(columns)) 71 | copy(rr.columns, columns) 72 | return nil 73 | } 74 | 75 | func parseField(output reflect.Value, input string) error { 76 | if output.Kind() == reflect.Ptr { 77 | if output.IsNil() { 78 | output.Set(reflect.New(output.Type().Elem())) 79 | } 80 | output = output.Elem() 81 | } 82 | switch output.Interface().(type) { 83 | case time.Time: 84 | if input == "0000-00-00 00:00:00" { 85 | input = "0001-01-01 00:00:00" 86 | } 87 | location, err := time.LoadLocation("UTC") 88 | if err != nil { 89 | return err 90 | } 91 | result, err := time.ParseInLocation("2006-01-02 15:04:05", input, location) 92 | if err != nil { 93 | return err 94 | } 95 | output.Set(reflect.ValueOf(result)) 96 | return nil 97 | } 98 | switch output.Kind() { 99 | case reflect.Bool: 100 | if input == "0" || input == "" { 101 | output.SetBool(false) 102 | } else { 103 | output.SetBool(true) 104 | } 105 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 106 | intValue, err := strconv.ParseInt(input, 10, 64) 107 | if err != nil { 108 | return err 109 | } 110 | output.SetInt(intValue) 111 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, 112 | reflect.Uint64, reflect.Uintptr: 113 | uintValue, err := strconv.ParseUint(input, 10, 64) 114 | if err != nil { 115 | return err 116 | } 117 | output.SetUint(uintValue) 118 | case reflect.String: 119 | output.SetString(input) 120 | default: 121 | return errorf("unsupported type: %s.", output.Type().String()) 122 | } 123 | return nil 124 | } 125 | 126 | func (rr *RowReader) ParseFields(fields []sql.NullString) (row reflect.Value, err error) { 127 | if len(fields) != len(rr.columnIndexToFieldIndex) { 128 | err = errorf("len(fields) != len(rr.columnIndexToFieldIndex)") 129 | return 130 | } 131 | row = reflect.New(rr.rowType) 132 | for columnIndex, fieldValue := range fields { 133 | if !fieldValue.Valid { 134 | continue 135 | } 136 | if rr.columnIndexToFieldIndex[columnIndex] < 0 { 137 | continue 138 | } 139 | if err = parseField( 140 | row.Elem().Field(rr.columnIndexToFieldIndex[columnIndex]), 141 | fieldValue.String); err != nil { 142 | return 143 | } 144 | } 145 | return 146 | } 147 | 148 | func (rr *RowReader) Read(rows *sql.Rows, limit int) error { 149 | if limit < -1 { 150 | return errorf("limit must be -1 or no less than 0: limit = %d.", limit) 151 | } 152 | numRows := 0 153 | if len(rr.columns) == 0 { 154 | return errorf("SetColumns must be called beforehand.") 155 | } 156 | fields := make([]sql.NullString, len(rr.columns)) 157 | interfaceFields := make([]interface{}, len(fields)) 158 | for fieldIndex, _ := range fields { 159 | interfaceFields[fieldIndex] = &fields[fieldIndex] 160 | } 161 | for rows.Next() { 162 | if numRows == limit { 163 | break 164 | } 165 | numRows++ 166 | if err := rows.Scan(interfaceFields...); err != nil { 167 | return err 168 | } 169 | row, err := rr.ParseFields(fields) 170 | if err != nil { 171 | return err 172 | } 173 | reflect.ValueOf(rr.rowsPtr).Elem().Set( 174 | reflect.Append(reflect.ValueOf(rr.rowsPtr).Elem(), row.Elem())) 175 | } 176 | return nil 177 | } 178 | -------------------------------------------------------------------------------- /connection.go: -------------------------------------------------------------------------------- 1 | package imosql 2 | 3 | import ( 4 | "database/sql" 5 | "flag" 6 | "reflect" 7 | "time" 8 | ) 9 | 10 | // Connection stores a SQL conneciton and provides main utility functions of 11 | // ImoSQL. 12 | type Connection struct { 13 | sql *sql.DB 14 | } 15 | 16 | var connection *Connection = nil 17 | var driverName = flag.String( 18 | "driver_name", "mysql", "Specifies a driver name.") 19 | var dataSourceName = flag.String( 20 | "data_source_name", "", 21 | "Specifies a driver-specific data source name. This flag overrides the "+ 22 | "default data source name.") 23 | 24 | // Open opens a database specified by its database driver name and a 25 | // driver-specific data source name, which are the same arguments as 26 | // the database/sql package uses. 27 | func Open(defaultDriverName string, defaultDataSourceName string) (connection *Connection, err error) { 28 | connection = new(Connection) 29 | if *dataSourceName != "" { 30 | connection.sql, err = sql.Open(*driverName, *dataSourceName) 31 | } else { 32 | connection.sql, err = sql.Open(defaultDriverName, defaultDataSourceName) 33 | } 34 | if err != nil { 35 | err = errorf("failed to connect to the databse: %s", err) 36 | return 37 | } 38 | if connection.sql == nil { 39 | err = errorf("there is no connection to the databse.") 40 | return 41 | } 42 | err = connection.Ping() 43 | if err != nil { 44 | err = errorf("failed to ping: %s", err) 45 | return 46 | } 47 | return 48 | } 49 | 50 | // Ping verifies a connection th the databse is still alive, estabilishing a 51 | // connecion if necessary. This function just calls DB.Ping in database/sql. 52 | func (c *Connection) Ping() error { 53 | return c.sql.Ping() 54 | } 55 | 56 | //////////////////////////////////////////////////////////////////////////////// 57 | // No-value query functions 58 | //////////////////////////////////////////////////////////////////////////////// 59 | 60 | // Execute runs a SQL command using DB.Exec. This is primitive and returns 61 | // sql.Result, which is returned by DB.Exec. If sql.Result is not necessary, 62 | // Connection.Change or Connection.Command should be used instead. When ImoSQL 63 | // logging is enabled, this function tries to output the last insert ID and the 64 | // number of affected rows by the query. 65 | func (c *Connection) Execute(query string, args ...interface{}) (result sql.Result, err error) { 66 | printLogf("running a SQL command: %s; %v.", query, args) 67 | result, err = c.sql.Exec(query, args...) 68 | if err != nil { 69 | err = errorf("failed to run a SQL command: %s", err) 70 | return 71 | } 72 | if IsLogging() { 73 | insertId, err := result.LastInsertId() 74 | if err == nil && insertId != 0 { 75 | printLogf("last insert ID is %d.", insertId) 76 | } 77 | rowsAffected, err := result.RowsAffected() 78 | if err == nil { 79 | printLogf("# of affected rows is %d.", rowsAffected) 80 | } 81 | err = nil 82 | } 83 | return 84 | } 85 | 86 | // ExecuteOrDie runs Connection.Execute. If Connection.Execute fails, 87 | // ExecuteOrDie panics. 88 | func (c *Connection) ExecuteOrDie(query string, args ...interface{}) sql.Result { 89 | result, err := c.Execute(query, args...) 90 | if err != nil { 91 | panic(err) 92 | } 93 | return result 94 | } 95 | 96 | // Command runs a SQL command. 97 | func (c *Connection) Command(query string, args ...interface{}) error { 98 | _, err := c.Execute(query, args...) 99 | return err 100 | } 101 | 102 | // CommandOrDie runs Connection.Command. If Connection.Command fails, this 103 | // function panics. 104 | func (c *Connection) CommandOrDie(query string, args ...interface{}) { 105 | err := c.Command(query, args...) 106 | if err != nil { 107 | panic(err) 108 | } 109 | } 110 | 111 | // Change runs a SQL command changing a SQL table. If the command changes 112 | // nothing, Change returns an error. Be careful that UPDATE, which is a SQL 113 | // command, may change nothing even if it matches some rows if it results in 114 | // changing nothing. 115 | func (c *Connection) Change(query string, args ...interface{}) error { 116 | result, err := c.Execute(query, args...) 117 | if err != nil { 118 | return err 119 | } 120 | rowsAffected, err := result.RowsAffected() 121 | if err != nil { 122 | return err 123 | } 124 | if rowsAffected == 0 { 125 | return errorf("no row was updated.") 126 | } 127 | return nil 128 | } 129 | 130 | // ChangeOrDie runs Connection.Change. If Connection.Change fails, this 131 | // function panics. 132 | func (c *Connection) ChangeOrDie(query string, args ...interface{}) { 133 | err := c.Change(query, args...) 134 | if err != nil { 135 | panic(err) 136 | } 137 | } 138 | 139 | //////////////////////////////////////////////////////////////////////////////// 140 | // Single-value query functions 141 | //////////////////////////////////////////////////////////////////////////////// 142 | 143 | func (c *Connection) parseSingleValue(result interface{}, query string, args ...interface{}) error { 144 | if reflect.TypeOf(result).Kind() != reflect.Ptr { 145 | return errorf( 146 | "result must be a pointer but %s.", 147 | reflect.TypeOf(result).Kind().String()) 148 | } 149 | printLogf("running a SQL query: %s; %v.", query, args) 150 | rows, err := c.sql.Query(query, args...) 151 | if err != nil { 152 | return errorf("failed to run a SQL query: %s", err) 153 | } 154 | defer rows.Close() 155 | if !rows.Next() { 156 | if reflect.TypeOf(result).Elem().Kind() == reflect.Ptr { 157 | reflect.ValueOf(result).Elem().Set( 158 | reflect.ValueOf(nil).Convert(reflect.TypeOf(result).Elem())) 159 | return nil 160 | } else { 161 | return errorf("no result.") 162 | } 163 | } 164 | var stringResult string 165 | err = rows.Scan(&stringResult) 166 | if err != nil { 167 | return errorf("failed to scan one field: %s", err) 168 | } 169 | err = parseField(reflect.ValueOf(result), stringResult) 170 | if err != nil { 171 | return errorf("failed to parse a field: %s", err) 172 | } 173 | return nil 174 | } 175 | 176 | func (c *Connection) String(query string, args ...interface{}) (result string, err error) { 177 | err = c.parseSingleValue(&result, query, args...) 178 | return 179 | } 180 | 181 | func (c *Connection) Integer(query string, args ...interface{}) (result int64, err error) { 182 | err = c.parseSingleValue(&result, query, args...) 183 | return 184 | } 185 | 186 | func (c *Connection) Time(query string, args ...interface{}) (result time.Time, err error) { 187 | err = c.parseSingleValue(&result, query, args...) 188 | return 189 | } 190 | 191 | func (c *Connection) StringOrDie(query string, args ...interface{}) string { 192 | result, err := c.String(query, args...) 193 | if err != nil { 194 | panic(err) 195 | } 196 | return result 197 | } 198 | 199 | func (c *Connection) IntegerOrDie(query string, args ...interface{}) int64 { 200 | result, err := c.Integer(query, args...) 201 | if err != nil { 202 | panic(err) 203 | } 204 | return result 205 | } 206 | 207 | func (c *Connection) TimeOrDie(query string, args ...interface{}) time.Time { 208 | result, err := c.Time(query, args...) 209 | if err != nil { 210 | panic(err) 211 | } 212 | return result 213 | } 214 | 215 | //////////////////////////////////////////////////////////////////////////////// 216 | // Multiple-value query functions 217 | //////////////////////////////////////////////////////////////////////////////// 218 | 219 | func (c *Connection) parseRows(rowsPtr interface{}, limit int, query string, args ...interface{}) error { 220 | rowReader, err := NewRowReader(rowsPtr) 221 | if err != nil { 222 | return errorf("failed to create a RowReader: %s", err) 223 | } 224 | printLogf("running a SQL query: %s; %v.", query, args) 225 | inputRows, err := c.sql.Query(query, args...) 226 | if err != nil { 227 | return errorf("failed to run a SQL query: %s", err) 228 | } 229 | defer inputRows.Close() 230 | columns, err := inputRows.Columns() 231 | if err != nil { 232 | return errorf("failed to get columns: %s", err) 233 | } 234 | if len(columns) == 0 { 235 | return errorf("no columns.") 236 | } 237 | rowReader.SetColumns(columns) 238 | if err := rowReader.Read(inputRows, limit); err != nil { 239 | return errorf("failed to read rows: %s", err) 240 | } 241 | return nil 242 | } 243 | 244 | func (c *Connection) Rows(rowsPtr interface{}, query string, args ...interface{}) error { 245 | return c.parseRows(rowsPtr, -1, query, args...) 246 | } 247 | 248 | // Row fills rowPtr with a result for a given SQL query. This function returns 249 | // true iff there is at least one results, otherwise returns false. 250 | func (c *Connection) Row(rowPtr interface{}, query string, args ...interface{}) (found bool, err error) { 251 | if reflect.ValueOf(rowPtr).Type().Kind() != reflect.Ptr { 252 | err = errorf( 253 | "rowPtr must be a pointer, but %s.", 254 | reflect.ValueOf(rowPtr).Type().Kind()) 255 | return 256 | } 257 | rowsPtr := reflect.New(reflect.SliceOf(reflect.ValueOf(rowPtr).Type().Elem())) 258 | err = c.parseRows(rowsPtr.Interface(), 1, query, args...) 259 | if err != nil { 260 | return 261 | } 262 | if rowsPtr.Elem().Len() == 1 { 263 | reflect.ValueOf(rowPtr).Elem().Set(rowsPtr.Elem().Index(0)) 264 | found = true 265 | } 266 | return 267 | } 268 | 269 | func (c *Connection) RowsOrDie(rowsPtr interface{}, query string, args ...interface{}) { 270 | err := c.Rows(rowsPtr, query, args...) 271 | if err != nil { 272 | panic(err) 273 | } 274 | } 275 | 276 | // RowOrDie runs Connection.Row. If Connection.Row fails, this function panics. 277 | // This function returns true iff there is at least one results, otherwise 278 | // returns false. 279 | func (c *Connection) RowOrDie(rowPtr interface{}, query string, args ...interface{}) bool { 280 | found, err := c.Row(rowPtr, query, args...) 281 | if err != nil { 282 | panic(err) 283 | } 284 | return found 285 | } 286 | --------------------------------------------------------------------------------