From 011e6e251a24a7c96f7d9a09eff89c8593e54286 Mon Sep 17 00:00:00 2001 From: Charles-Edouard Ruault Date: Tue, 9 May 2017 15:05:25 +0200 Subject: [PATCH 01/17] =?UTF-8?q?Since=20one=20cannot=20replace=20the=20dr?= =?UTF-8?q?iver.DefaultParameterConverter=20to=20properly=20handle=20type?= =?UTF-8?q?=20conversions,=20handle=20at=20least=20time.Time=20columns=20p?= =?UTF-8?q?roperly=20by=20using=20the=20column=20type=20provided=20by=20Cr?= =?UTF-8?q?ate=20within=20the=20query=E2=80=99s=20results.=20driver.Defaul?= =?UTF-8?q?tParameterConverter=20override=20is=20scheduled=20for=20GO=201.?= =?UTF-8?q?9=20(see=20https://github.com/golang/go/issues/18415)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crate.go | 40 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/crate.go b/crate.go index 08dc5f6..4c64c2f 100644 --- a/crate.go +++ b/crate.go @@ -10,6 +10,11 @@ import ( "io" "net/http" "net/url" + "time" +) + +const ( + typeTime = "11" ) // Crate conn structure @@ -65,8 +70,18 @@ func (c *CrateDriver) query(stmt string, args []driver.Value) (*endpointResponse Stmt: stmt, } - if len(args) > 0 { + if l:=len(args); l > 0 { query.Args = args + //Process each column that needs conversion from time.Time to int64 for Crate + for i:= 0; i= r.rowcount { return io.EOF } - for i := range dest { - dest[i] = r.values[r.pos][i] + if (r.isTime[i] && (r.values[r.pos][i] != nil)) { //If column is flagged as time.Time then convert the int64 value to time.Time + if val, ok := r.values[r.pos][i].(json.Number); ok { + v , _ := val.Int64() + sec := v/int64(1000) + dest[i] = time.Unix(sec, (v-sec*int64(1000))*int64(1000000)) + } else { + return errors.New(fmt.Sprintf("Failed to convert column %s=%T to time\n", r.columns[i], r.values[r.pos][i])) + } + } else { + dest[i] = r.values[r.pos][i] + } } r.pos++ @@ -238,6 +268,8 @@ func (s *CrateStmt) NumInput() int { return -1 } + + // Register the driver func init() { sql.Register("crate", &CrateDriver{}) From 0def5fb002ab9d19508bc6d7b4dfe8935b455715 Mon Sep 17 00:00:00 2001 From: Charles-Edouard Ruault Date: Fri, 1 Sep 2017 18:54:11 +0200 Subject: [PATCH 02/17] Now that GO 1.9 is released properly handle data types in the driver only, no more tweaks in gorm --- crate.go | 93 ++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 66 insertions(+), 27 deletions(-) diff --git a/crate.go b/crate.go index 4c64c2f..fdb448d 100644 --- a/crate.go +++ b/crate.go @@ -11,10 +11,7 @@ import ( "net/http" "net/url" "time" -) - -const ( - typeTime = "11" + "reflect" ) // Crate conn structure @@ -22,6 +19,8 @@ type CrateDriver struct { Url string // Crate http endpoint url } +type GeoPoint [2]float64 + // Init a new "Connection" to a Crate Data Storage instance. // Note that the connection is not tested until the first query. func (c *CrateDriver) Open(crate_url string) (driver.Conn, error) { @@ -58,6 +57,39 @@ type endpointQuery struct { Args []driver.Value `json:"args,omitempty"` } +//CER : Convert map, Time & GeoPoint arguments to DB format. +func (c *CrateDriver) CheckNamedValue(v *driver.NamedValue) error { + if obj, ok := v.Value.(map[string]interface{}) ; ok { + var res= new(bytes.Buffer) + res.WriteString("{") + count := len(obj) + for key, val := range obj { + if reflect.ValueOf(val).Kind() == reflect.String { + res.WriteString(fmt.Sprintf("\"%s\": \"%v\"", key, val)) + } else { + res.WriteString(fmt.Sprintf("\"%s\": %v", key, val)) + } + count -- + if count > 0 { + res.WriteString(",") + } + } + res.WriteString("}") + v.Value = res.String() + return nil + } else if ts, ok := v.Value.(time.Time) ; ok { + if ts.IsZero() { + v.Value = 0 + } else { + v.Value = ts.In(time.UTC).UnixNano() / 1000000 + } + return nil + } else if _, ok := v.Value.(GeoPoint) ; ok { //No change required for GeoPoint + return nil + } + return driver.ErrSkip +} + // Query the database using prepared statements. // Read: https://crate.io/docs/stable/sql/rest.html for more information about the returned response. // Example: crate.Query("SELECT * FROM sys.cluster LIMIT ?", 10) @@ -72,16 +104,6 @@ func (c *CrateDriver) query(stmt string, args []driver.Value) (*endpointResponse if l:=len(args); l > 0 { query.Args = args - //Process each column that needs conversion from time.Time to int64 for Crate - for i:= 0; i Date: Mon, 4 Sep 2017 18:42:05 +0200 Subject: [PATCH 03/17] Improved GeoPoint by using a struct rather than an array of floats. Fixed code according to the GitHub boat comments Added Scan method on GeoPoint to allow gorm to handle GeoPoint embedded in an object --- crate.go | 84 +++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 59 insertions(+), 25 deletions(-) diff --git a/crate.go b/crate.go index fdb448d..41b3a3d 100644 --- a/crate.go +++ b/crate.go @@ -19,7 +19,26 @@ type CrateDriver struct { Url string // Crate http endpoint url } -type GeoPoint [2]float64 +//GeoPoint represents Crate GeoPoint column +type GeoPoint struct { + Lat float64 + Lon float64 +} + +//Scan : Implements Scanner interface to populate a GeoPoint when the result is an array of 2 floats +func (gp *GeoPoint) Scan(src interface{}) error { + if b, ok := src.([]interface{}) ; ok && len(b) == 2 { + var err error + if gp.Lon, err = b[0].(json.Number).Float64(); err == nil { + if gp.Lat, err = b[1].(json.Number).Float64(); err == nil { + return nil + } + } + return fmt.Errorf("failed to convert %v to GeoPoint : %v", src, err) + } + return fmt.Errorf("failed to convert %v to GeoPoint", src) +} + // Init a new "Connection" to a Crate Data Storage instance. // Note that the connection is not tested until the first query. @@ -57,25 +76,27 @@ type endpointQuery struct { Args []driver.Value `json:"args,omitempty"` } -//CER : Convert map, Time & GeoPoint arguments to DB format. +//CheckNamedValue Convert map, time & GeoPoint arguments to DB format. func (c *CrateDriver) CheckNamedValue(v *driver.NamedValue) error { if obj, ok := v.Value.(map[string]interface{}) ; ok { - var res= new(bytes.Buffer) - res.WriteString("{") - count := len(obj) - for key, val := range obj { - if reflect.ValueOf(val).Kind() == reflect.String { - res.WriteString(fmt.Sprintf("\"%s\": \"%v\"", key, val)) - } else { - res.WriteString(fmt.Sprintf("\"%s\": %v", key, val)) - } - count -- - if count > 0 { - res.WriteString(",") - } + //fmt.Printf("CheckNamedValue for map for %v -> %v\n", v.Name, len(obj)) + var res= new(bytes.Buffer) + res.WriteString("{") + count := len(obj) + for key, val := range obj { + if reflect.ValueOf(val).Kind() == reflect.String { + res.WriteString(fmt.Sprintf("\"%s\": \"%v\"", key, val)) + } else { + res.WriteString(fmt.Sprintf("\"%s\": %v", key, val)) } - res.WriteString("}") - v.Value = res.String() + count -- + if count > 0 { + res.WriteString(",") + } + } + res.WriteString("}") + //fmt.Printf("CheckNamedValue for %v converted to %s\n", v, res.String()) + v.Value = res.String() return nil } else if ts, ok := v.Value.(time.Time) ; ok { if ts.IsZero() { @@ -84,9 +105,16 @@ func (c *CrateDriver) CheckNamedValue(v *driver.NamedValue) error { v.Value = ts.In(time.UTC).UnixNano() / 1000000 } return nil - } else if _, ok := v.Value.(GeoPoint) ; ok { //No change required for GeoPoint + } else if gp, ok := v.Value.(GeoPoint) ; ok { + //fmt.Printf("CheckNamedValue for Geopoint (%f,%f) \n", gp.Lon, gp.Lat) + nGp := make([]float64, 2) + nGp[0] = gp.Lon + nGp[1] = gp.Lat + v.Value = &nGp return nil - } + } /*else { + fmt.Printf("CheckNamedValue for %v -> %v\n", v.Name, v.Value) + }*/ return driver.ErrSkip } @@ -232,18 +260,24 @@ func (r *Rows) Next(dest []driver.Value) error { sec := v / int64(1000) dest[i] = time.Unix(sec, (v-sec*int64(1000))*int64(1000000)) } else { - return fmt.Errorf("Failed to convert column %s=%T to time\n", r.columns[i], r.values[r.pos][i]) + return fmt.Errorf("failed to convert column %s=%T to time", r.columns[i], r.values[r.pos][i]) } } else if r.isSpecial[i] == typeGeoPoint { if psrc, ok := r.values[r.pos][i].([]interface{}) ; ok && (len(psrc) == 2) { var p GeoPoint - for i, c := range psrc { - if jn, ok := c.(json.Number) ; ok { - p[i], _ = jn.Float64() - } else { return fmt.Errorf("Failed to convert elem %v of %v to float", c, r.values[r.pos][i])} + var err error + if p.Lon, err = psrc[0].(json.Number).Float64(); err != nil { + return fmt.Errorf("failed to convert to latitude %v", psrc[0]) + } + if p.Lat, err = psrc[1].(json.Number).Float64(); err != nil { + return fmt.Errorf("failed to convert to longitude elem %v", psrc[1]) } dest[i] = &p - } else { return fmt.Errorf("Failed to convert to GeoPoint")} + } else if len(psrc) == 0 { + dest[i] = GeoPoint{} + } else { + return fmt.Errorf("failed to convert %v to GeoPoint", r.values[r.pos][i]) + } } } else { dest[i] = r.values[r.pos][i] From 7c51c1a969da22bf444e19d58ec7992ec2d97101 Mon Sep 17 00:00:00 2001 From: Charles-Edouard Ruault Date: Mon, 23 Oct 2017 16:00:38 +0200 Subject: [PATCH 04/17] Handle Crate's Array column type --- crate.go | 46 +++++++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/crate.go b/crate.go index 41b3a3d..0391275 100644 --- a/crate.go +++ b/crate.go @@ -11,7 +11,6 @@ import ( "net/http" "net/url" "time" - "reflect" ) // Crate conn structure @@ -25,6 +24,9 @@ type GeoPoint struct { Lon float64 } +//CrateArray represents an Array column type +type CrateArray []interface{} + //Scan : Implements Scanner interface to populate a GeoPoint when the result is an array of 2 floats func (gp *GeoPoint) Scan(src interface{}) error { if b, ok := src.([]interface{}) ; ok && len(b) == 2 { @@ -39,6 +41,18 @@ func (gp *GeoPoint) Scan(src interface{}) error { return fmt.Errorf("failed to convert %v to GeoPoint", src) } +//Scan : Implements Scanner interface to populate a CrateArray from the incoming data +func (arr *CrateArray) Scan(src interface{}) error { + if srcArr, ok := src.([]interface{}) ; ok { + *arr = make ([]interface{}, len(srcArr)) + for i, obj:=range(srcArr) { + (*arr)[i] = obj + } + return nil + } + return fmt.Errorf("failed to convert %v to CrateArray", src) +} + // Init a new "Connection" to a Crate Data Storage instance. // Note that the connection is not tested until the first query. @@ -76,27 +90,14 @@ type endpointQuery struct { Args []driver.Value `json:"args,omitempty"` } -//CheckNamedValue Convert map, time & GeoPoint arguments to DB format. +//CheckNamedValue Convert map, CrateArray, time & GeoPoint arguments to DB format. func (c *CrateDriver) CheckNamedValue(v *driver.NamedValue) error { if obj, ok := v.Value.(map[string]interface{}) ; ok { - //fmt.Printf("CheckNamedValue for map for %v -> %v\n", v.Name, len(obj)) - var res= new(bytes.Buffer) - res.WriteString("{") - count := len(obj) - for key, val := range obj { - if reflect.ValueOf(val).Kind() == reflect.String { - res.WriteString(fmt.Sprintf("\"%s\": \"%v\"", key, val)) - } else { - res.WriteString(fmt.Sprintf("\"%s\": %v", key, val)) - } - count -- - if count > 0 { - res.WriteString(",") - } + if len(obj) == 0 { + v.Value = "{}" + } else { + v.Value = obj } - res.WriteString("}") - //fmt.Printf("CheckNamedValue for %v converted to %s\n", v, res.String()) - v.Value = res.String() return nil } else if ts, ok := v.Value.(time.Time) ; ok { if ts.IsZero() { @@ -112,7 +113,11 @@ func (c *CrateDriver) CheckNamedValue(v *driver.NamedValue) error { nGp[1] = gp.Lat v.Value = &nGp return nil - } /*else { + } else if arr, ok := v.Value.(CrateArray) ; ok { + v.Value = arr + return nil + } + /*else { fmt.Printf("CheckNamedValue for %v -> %v\n", v.Name, v.Value) }*/ return driver.ErrSkip @@ -139,7 +144,6 @@ func (c *CrateDriver) query(stmt string, args []driver.Value) (*endpointResponse if err != nil { return nil, err } - data := bytes.NewReader(buf) resp, err := http.Post(endpoint, "application/json", data) From b801d28291ad96b72d5afef618de267d5c2fcf08 Mon Sep 17 00:00:00 2001 From: Charles-Edouard Ruault Date: Wed, 17 Jan 2018 15:43:24 +0100 Subject: [PATCH 05/17] Use our own basic json encoder rather than the GO lib one to properly handle floats with null decimals to prevent creating an int typed column in crate when a float is required --- crate.go | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 1 deletion(-) diff --git a/crate.go b/crate.go index 0391275..86efa91 100644 --- a/crate.go +++ b/crate.go @@ -11,6 +11,8 @@ import ( "net/http" "net/url" "time" + "reflect" + "strings" ) // Crate conn structure @@ -90,13 +92,101 @@ type endpointQuery struct { Args []driver.Value `json:"args,omitempty"` } +//encodeMap will encode the map stored in obj in json and store it as a string in the buffer buf +//This is used because one cannot rely on the json encoder because it will format any float with decimal part of 0 as an int +//If the first value to be stored in a new object's key is an int then all further values will be stored as int +//and one will loose the decimal part of each value... Our encoder will ensure that a float with a 0 decimal part +//is encoded as X.0 and not X +//Note it will not encode maps with keys other than strings and arrays of arrays/slice/map (no need for it in our context) +func encodeMap(buf *bytes.Buffer, obj map[string]interface{}) error{ + if len(obj) == 0 { + buf.WriteString("{}") + return nil + } + buf.WriteByte('{') + first := true + for k,v := range obj { + if first { + first = false + } else { + buf.WriteByte(',') + } + buf.WriteString(fmt.Sprintf("\"%s\":", k)) + fm := "%v" + tv := reflect.ValueOf(v) + switch tv.Kind() { + case reflect.Float64: + fallthrough + case reflect.Float32: + f := tv.Float() + i := float64(int64(f)) + if i == f { + fm = "%0.1f" + } + case reflect.Map: + t := reflect.TypeOf(v) + if t.Key().Kind() != reflect.String { + return fmt.Errorf("cannot encode map with keys of type %v", t) + } + if err := encodeMap(buf, v.(map[string]interface{})) ; err != nil { + return err + } + continue + case reflect.Slice: + fallthrough + case reflect.Array: + m := tv.Len() + if m == 0 { + buf.WriteString("[]") + return nil + } + tp := reflect.TypeOf(v).Elem().Kind() + conv := (tp == reflect.Float64 || tp == reflect.Float32) + switch tp { + case reflect.Map, reflect.Array, reflect.Slice: + return fmt.Errorf("cannot process array of %s", tv.Elem().Kind()) + case reflect.String: + fm = "\"%s\"" + } + buf.WriteByte('[') + for i:=0; i0 { + buf.WriteByte(',') + } + v := tv.Index(i) + if conv { + fv := v.Float() + i := float64(int32(fv)) + if i == fv { + buf.WriteString(fmt.Sprintf("%0.1f", v)) + continue + } + } + buf.WriteString(fmt.Sprintf("%v", v)) + } + buf.WriteByte(']') + continue + case reflect.String: + fm ="\"%s\"" + v = strings.Replace(v.(string), "\"", "\\\"", -1) + } + buf.WriteString(fmt.Sprintf(fm , v)) + } + buf.WriteByte('}') + return nil +} + //CheckNamedValue Convert map, CrateArray, time & GeoPoint arguments to DB format. func (c *CrateDriver) CheckNamedValue(v *driver.NamedValue) error { if obj, ok := v.Value.(map[string]interface{}) ; ok { if len(obj) == 0 { v.Value = "{}" } else { - v.Value = obj + res := bytes.Buffer{} + if err := encodeMap(&res, obj) ; err != nil { + return err + } + v.Value = res.String() } return nil } else if ts, ok := v.Value.(time.Time) ; ok { From cfa2930f4e405ba36e011498e6b1f3ff511ab6a6 Mon Sep 17 00:00:00 2001 From: Charles-Edouard Ruault Date: Wed, 17 Jan 2018 15:58:55 +0100 Subject: [PATCH 06/17] Convert v to Float before formatting with %f format --- crate.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crate.go b/crate.go index 86efa91..edbd64f 100644 --- a/crate.go +++ b/crate.go @@ -158,7 +158,7 @@ func encodeMap(buf *bytes.Buffer, obj map[string]interface{}) error{ fv := v.Float() i := float64(int32(fv)) if i == fv { - buf.WriteString(fmt.Sprintf("%0.1f", v)) + buf.WriteString(fmt.Sprintf("%0.1f", v.Float())) continue } } From a6dff48ba8343d090bf903d7cb156072dae553f4 Mon Sep 17 00:00:00 2001 From: Charles-Edouard Ruault Date: Fri, 19 Jan 2018 16:15:55 +0100 Subject: [PATCH 07/17] Implemented encoding of arrays of map/arrays --- crate.go | 112 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 65 insertions(+), 47 deletions(-) diff --git a/crate.go b/crate.go index edbd64f..f0def35 100644 --- a/crate.go +++ b/crate.go @@ -92,20 +92,69 @@ type endpointQuery struct { Args []driver.Value `json:"args,omitempty"` } +//encodeArray will encode the array represented by obj and store the result in buf +//It returns an error if obj contains a map with keys other than strings +func encodeArray(buf *bytes.Buffer, obj reflect.Value) error { + m := obj.Len() + if m == 0 { + buf.WriteString("[]") + return nil + } + buf.WriteByte('[') + var k reflect.Kind + for i:=0; i0 { + buf.WriteByte(',') + } else { + k = v.Kind() + } + switch k { + case reflect.Float32, reflect.Float64: + fv := v.Float() + i := float64(int32(fv)) + if i == fv { + buf.WriteString(fmt.Sprintf("%0.1f", fv)) + continue + } + case reflect.Map: + t := reflect.TypeOf(v) + if v.Type().Key().Kind() != reflect.String { + return fmt.Errorf("cannot encode map with keys of type %v", t) + } + if err := encodeMap(buf, v) ; err != nil { + return err + } + continue + case reflect.Slice, reflect.Array: + if err := encodeArray(buf, v); err != nil { + return err + } + continue + case reflect.String: + buf.WriteString(fmt.Sprintf("%s", strings.Replace(v.String(), "\"", "\\\"", -1))) + continue + } + buf.WriteString(fmt.Sprintf("%v", v)) + } + buf.WriteByte(']') + return nil +} + //encodeMap will encode the map stored in obj in json and store it as a string in the buffer buf //This is used because one cannot rely on the json encoder because it will format any float with decimal part of 0 as an int //If the first value to be stored in a new object's key is an int then all further values will be stored as int //and one will loose the decimal part of each value... Our encoder will ensure that a float with a 0 decimal part //is encoded as X.0 and not X -//Note it will not encode maps with keys other than strings and arrays of arrays/slice/map (no need for it in our context) -func encodeMap(buf *bytes.Buffer, obj map[string]interface{}) error{ - if len(obj) == 0 { +//Note it will not encode maps with keys other than strings +func encodeMap(buf *bytes.Buffer, obj reflect.Value) error{ + if obj.Len() == 0 { buf.WriteString("{}") return nil } buf.WriteByte('{') first := true - for k,v := range obj { + for _, k := range obj.MapKeys() { if first { first = false } else { @@ -113,62 +162,31 @@ func encodeMap(buf *bytes.Buffer, obj map[string]interface{}) error{ } buf.WriteString(fmt.Sprintf("\"%s\":", k)) fm := "%v" - tv := reflect.ValueOf(v) - switch tv.Kind() { - case reflect.Float64: - fallthrough - case reflect.Float32: - f := tv.Float() + v := obj.MapIndex(k).Elem() + switch v.Kind() { + case reflect.Float64, reflect.Float32: + f := v.Float() i := float64(int64(f)) if i == f { fm = "%0.1f" } case reflect.Map: t := reflect.TypeOf(v) - if t.Key().Kind() != reflect.String { + if v.Type().Key().Kind() != reflect.String { return fmt.Errorf("cannot encode map with keys of type %v", t) } - if err := encodeMap(buf, v.(map[string]interface{})) ; err != nil { + if err := encodeMap(buf, v) ; err != nil { return err } continue - case reflect.Slice: - fallthrough - case reflect.Array: - m := tv.Len() - if m == 0 { - buf.WriteString("[]") - return nil - } - tp := reflect.TypeOf(v).Elem().Kind() - conv := (tp == reflect.Float64 || tp == reflect.Float32) - switch tp { - case reflect.Map, reflect.Array, reflect.Slice: - return fmt.Errorf("cannot process array of %s", tv.Elem().Kind()) - case reflect.String: - fm = "\"%s\"" - } - buf.WriteByte('[') - for i:=0; i0 { - buf.WriteByte(',') - } - v := tv.Index(i) - if conv { - fv := v.Float() - i := float64(int32(fv)) - if i == fv { - buf.WriteString(fmt.Sprintf("%0.1f", v.Float())) - continue - } - } - buf.WriteString(fmt.Sprintf("%v", v)) + case reflect.Slice, reflect.Array: + if err := encodeArray(buf, v) ; err != nil { + return err } - buf.WriteByte(']') continue case reflect.String: - fm ="\"%s\"" - v = strings.Replace(v.(string), "\"", "\\\"", -1) + buf.WriteString(fmt.Sprintf("\"%s\"" , strings.Replace(v.String(), "\"", "\\\"", -1))) + continue } buf.WriteString(fmt.Sprintf(fm , v)) } @@ -183,7 +201,7 @@ func (c *CrateDriver) CheckNamedValue(v *driver.NamedValue) error { v.Value = "{}" } else { res := bytes.Buffer{} - if err := encodeMap(&res, obj) ; err != nil { + if err := encodeMap(&res, reflect.ValueOf(v.Value)) ; err != nil { return err } v.Value = res.String() From efa5085bf151b8e09a7e6bd7cf139a8993c5aa16 Mon Sep 17 00:00:00 2001 From: Charles-Edouard Ruault Date: Fri, 19 Jan 2018 18:37:50 +0100 Subject: [PATCH 08/17] Handle interface types in Array & Map --- crate.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/crate.go b/crate.go index f0def35..478cacf 100644 --- a/crate.go +++ b/crate.go @@ -102,12 +102,21 @@ func encodeArray(buf *bytes.Buffer, obj reflect.Value) error { } buf.WriteByte('[') var k reflect.Kind + ue := false for i:=0; i0 { buf.WriteByte(',') + if ue { + v = v.Elem() + } } else { k = v.Kind() + if k == reflect.Interface { + ue = true + v = v.Elem() + k = v.Type().Kind() + } } switch k { case reflect.Float32, reflect.Float64: @@ -163,7 +172,12 @@ func encodeMap(buf *bytes.Buffer, obj reflect.Value) error{ buf.WriteString(fmt.Sprintf("\"%s\":", k)) fm := "%v" v := obj.MapIndex(k).Elem() - switch v.Kind() { + vk := v.Kind() + if vk == reflect.Interface { + v = v.Elem() + vk = v.Type().Kind() + } + switch vk { case reflect.Float64, reflect.Float32: f := v.Float() i := float64(int64(f)) From 8d026242da58e2c46bd9930836ff7fb69e57a36b Mon Sep 17 00:00:00 2001 From: Charles-Edouard Ruault Date: Fri, 26 Jan 2018 14:12:19 +0100 Subject: [PATCH 09/17] Fix to satisty Error interface --- errors.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/errors.go b/errors.go index a934de5..681ca33 100644 --- a/errors.go +++ b/errors.go @@ -8,6 +8,6 @@ type CrateErr struct { } // Return error message, this is part of the error interface. -func (e *CrateErr) Error() string { +func (e CrateErr) Error() string { return e.Message } From cdfdf1f8bcfd68c780e367100d7f0bf25f0e6daf Mon Sep 17 00:00:00 2001 From: Charles-Edouard Ruault Date: Wed, 31 Jan 2018 13:30:53 +0100 Subject: [PATCH 10/17] Fixed JSON encoding causing issue on crate 2.3.2 because a String was sent as JSON. Now use Marshaller interface to properly handle our own types --- crate.go | 41 ++++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/crate.go b/crate.go index 478cacf..667eba0 100644 --- a/crate.go +++ b/crate.go @@ -13,6 +13,7 @@ import ( "time" "reflect" "strings" + //"log" ) // Crate conn structure @@ -29,6 +30,9 @@ type GeoPoint struct { //CrateArray represents an Array column type type CrateArray []interface{} +//crateMap used to store any map and force our own MarshalJSON method to be called +type crateMap map[string]interface{} + //Scan : Implements Scanner interface to populate a GeoPoint when the result is an array of 2 floats func (gp *GeoPoint) Scan(src interface{}) error { if b, ok := src.([]interface{}) ; ok && len(b) == 2 { @@ -208,18 +212,31 @@ func encodeMap(buf *bytes.Buffer, obj reflect.Value) error{ return nil } + +//MarshalJSON custom JSON marshal function to properly marshall maps containing floats with decimal part equals to 0 +func (v crateMap) MarshalJSON() ([]byte, error) { + res := bytes.Buffer{} + if err := encodeMap(&res, reflect.ValueOf(v)) ; err != nil { + return nil, err + } + //log.Printf("Result Map : %v", res.String()) + return res.Bytes(), nil +} + +//MarshalJSON custom JSON marshal function to properly handle arrays of floats with decimal part equals to 0 +func (v CrateArray) MarshalJSON() ([]byte, error) { + res := bytes.Buffer{} + if err := encodeArray(&res, reflect.ValueOf(v)) ; err != nil { + return nil, err + } + //log.Printf("Result Array : %v", res.String()) + return res.Bytes(), nil +} + //CheckNamedValue Convert map, CrateArray, time & GeoPoint arguments to DB format. func (c *CrateDriver) CheckNamedValue(v *driver.NamedValue) error { if obj, ok := v.Value.(map[string]interface{}) ; ok { - if len(obj) == 0 { - v.Value = "{}" - } else { - res := bytes.Buffer{} - if err := encodeMap(&res, reflect.ValueOf(v.Value)) ; err != nil { - return err - } - v.Value = res.String() - } + v.Value = crateMap(obj) return nil } else if ts, ok := v.Value.(time.Time) ; ok { if ts.IsZero() { @@ -235,8 +252,10 @@ func (c *CrateDriver) CheckNamedValue(v *driver.NamedValue) error { nGp[1] = gp.Lat v.Value = &nGp return nil - } else if arr, ok := v.Value.(CrateArray) ; ok { - v.Value = arr + } else if _, ok := v.Value.(CrateArray) ; ok { + return nil + } else if arr, ok := v.Value.([]interface{}) ; ok { + v.Value = CrateArray(arr) return nil } /*else { From 7a391ae17edadd5419e1e0e752c703782653eff6 Mon Sep 17 00:00:00 2001 From: Charles-Edouard Ruault Date: Thu, 25 Apr 2019 19:15:58 +0200 Subject: [PATCH 11/17] //Prevents rounding errors seen with floats like 0.01*41 which is 0.41000000000000003 ... //See https://floating-point-gui.de/ //Float values are now truncated to 10^-6 which is more than enough for our needs --- crate.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crate.go b/crate.go index 667eba0..bfac4e8 100644 --- a/crate.go +++ b/crate.go @@ -130,6 +130,10 @@ func encodeArray(buf *bytes.Buffer, obj reflect.Value) error { buf.WriteString(fmt.Sprintf("%0.1f", fv)) continue } + //Prevents rounding errors seen with floats like 0.01*41 which is 0.41000000000000003 ... + //See https://floating-point-gui.de/ + buf.WriteString(fmt.Sprintf("%0.6f", fv)) + continue case reflect.Map: t := reflect.TypeOf(v) if v.Type().Key().Kind() != reflect.String { From 0a627df0b2b14d702be1bc47706d276d27fe8bec Mon Sep 17 00:00:00 2001 From: Charles-Edouard Ruault Date: Fri, 10 May 2019 15:09:09 +0200 Subject: [PATCH 12/17] Complete fix for (Fix had only been applied to float in arrays, not in maps): Prevents rounding errors seen with floats like 0.01*41 which is 0.41000000000000003 ... See https://floating-point-gui.de/ Float values are now truncated to 10^-6 which is more than enough for our needs --- crate.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crate.go b/crate.go index bfac4e8..54842d2 100644 --- a/crate.go +++ b/crate.go @@ -190,8 +190,13 @@ func encodeMap(buf *bytes.Buffer, obj reflect.Value) error{ f := v.Float() i := float64(int64(f)) if i == f { - fm = "%0.1f" + buf.WriteString(fmt.Sprintf("%0.1f", f)) + continue } + //Prevents rounding errors seen with floats like 0.01*41 which is 0.41000000000000003 ... + //See https://floating-point-gui.de/ + buf.WriteString(fmt.Sprintf("%0.6f", f)) + continue case reflect.Map: t := reflect.TypeOf(v) if v.Type().Key().Kind() != reflect.String { From f550e9a82be836bb7239b7165569b21f1dc0ecdc Mon Sep 17 00:00:00 2001 From: Charles-Edouard Ruault Date: Mon, 27 May 2019 11:06:09 +0200 Subject: [PATCH 13/17] Prevent crash when processing nil maps. Speed up float processing by removing un-necessary conversion & comparison --- crate.go | 96 ++++++++++++++++++++++++++------------------------------ 1 file changed, 45 insertions(+), 51 deletions(-) diff --git a/crate.go b/crate.go index 54842d2..0dd3903 100644 --- a/crate.go +++ b/crate.go @@ -10,9 +10,9 @@ import ( "io" "net/http" "net/url" - "time" "reflect" "strings" + "time" //"log" ) @@ -35,7 +35,7 @@ type crateMap map[string]interface{} //Scan : Implements Scanner interface to populate a GeoPoint when the result is an array of 2 floats func (gp *GeoPoint) Scan(src interface{}) error { - if b, ok := src.([]interface{}) ; ok && len(b) == 2 { + if b, ok := src.([]interface{}); ok && len(b) == 2 { var err error if gp.Lon, err = b[0].(json.Number).Float64(); err == nil { if gp.Lat, err = b[1].(json.Number).Float64(); err == nil { @@ -49,9 +49,9 @@ func (gp *GeoPoint) Scan(src interface{}) error { //Scan : Implements Scanner interface to populate a CrateArray from the incoming data func (arr *CrateArray) Scan(src interface{}) error { - if srcArr, ok := src.([]interface{}) ; ok { - *arr = make ([]interface{}, len(srcArr)) - for i, obj:=range(srcArr) { + if srcArr, ok := src.([]interface{}); ok { + *arr = make([]interface{}, len(srcArr)) + for i, obj := range srcArr { (*arr)[i] = obj } return nil @@ -59,7 +59,6 @@ func (arr *CrateArray) Scan(src interface{}) error { return fmt.Errorf("failed to convert %v to CrateArray", src) } - // Init a new "Connection" to a Crate Data Storage instance. // Note that the connection is not tested until the first query. func (c *CrateDriver) Open(crate_url string) (driver.Conn, error) { @@ -107,9 +106,9 @@ func encodeArray(buf *bytes.Buffer, obj reflect.Value) error { buf.WriteByte('[') var k reflect.Kind ue := false - for i:=0; i0 { + if i > 0 { buf.WriteByte(',') if ue { v = v.Elem() @@ -117,6 +116,9 @@ func encodeArray(buf *bytes.Buffer, obj reflect.Value) error { } else { k = v.Kind() if k == reflect.Interface { + if v.IsNil() { + continue + } ue = true v = v.Elem() k = v.Type().Kind() @@ -124,22 +126,19 @@ func encodeArray(buf *bytes.Buffer, obj reflect.Value) error { } switch k { case reflect.Float32, reflect.Float64: - fv := v.Float() - i := float64(int32(fv)) - if i == fv { - buf.WriteString(fmt.Sprintf("%0.1f", fv)) - continue - } //Prevents rounding errors seen with floats like 0.01*41 which is 0.41000000000000003 ... //See https://floating-point-gui.de/ - buf.WriteString(fmt.Sprintf("%0.6f", fv)) + buf.WriteString(fmt.Sprintf("%0.6f", v.Float())) continue case reflect.Map: t := reflect.TypeOf(v) if v.Type().Key().Kind() != reflect.String { return fmt.Errorf("cannot encode map with keys of type %v", t) } - if err := encodeMap(buf, v) ; err != nil { + if v.IsNil() { + continue + } + if err := encodeMap(buf, v); err != nil { return err } continue @@ -164,7 +163,7 @@ func encodeArray(buf *bytes.Buffer, obj reflect.Value) error { //and one will loose the decimal part of each value... Our encoder will ensure that a float with a 0 decimal part //is encoded as X.0 and not X //Note it will not encode maps with keys other than strings -func encodeMap(buf *bytes.Buffer, obj reflect.Value) error{ +func encodeMap(buf *bytes.Buffer, obj reflect.Value) error { if obj.Len() == 0 { buf.WriteString("{}") return nil @@ -187,45 +186,41 @@ func encodeMap(buf *bytes.Buffer, obj reflect.Value) error{ } switch vk { case reflect.Float64, reflect.Float32: - f := v.Float() - i := float64(int64(f)) - if i == f { - buf.WriteString(fmt.Sprintf("%0.1f", f)) - continue - } //Prevents rounding errors seen with floats like 0.01*41 which is 0.41000000000000003 ... //See https://floating-point-gui.de/ - buf.WriteString(fmt.Sprintf("%0.6f", f)) + buf.WriteString(fmt.Sprintf("%0.6f", v.Float())) continue case reflect.Map: t := reflect.TypeOf(v) if v.Type().Key().Kind() != reflect.String { return fmt.Errorf("cannot encode map with keys of type %v", t) } - if err := encodeMap(buf, v) ; err != nil { + if v.IsNil() { + continue + } + if err := encodeMap(buf, v); err != nil { return err } continue case reflect.Slice, reflect.Array: - if err := encodeArray(buf, v) ; err != nil { + if err := encodeArray(buf, v); err != nil { return err } continue case reflect.String: - buf.WriteString(fmt.Sprintf("\"%s\"" , strings.Replace(v.String(), "\"", "\\\"", -1))) + buf.WriteString(fmt.Sprintf("\"%s\"", strings.Replace(v.String(), "\"", "\\\"", -1))) continue } - buf.WriteString(fmt.Sprintf(fm , v)) + buf.WriteString(fmt.Sprintf(fm, v)) } buf.WriteByte('}') return nil } - //MarshalJSON custom JSON marshal function to properly marshall maps containing floats with decimal part equals to 0 func (v crateMap) MarshalJSON() ([]byte, error) { res := bytes.Buffer{} - if err := encodeMap(&res, reflect.ValueOf(v)) ; err != nil { + if err := encodeMap(&res, reflect.ValueOf(v)); err != nil { return nil, err } //log.Printf("Result Map : %v", res.String()) @@ -233,9 +228,9 @@ func (v crateMap) MarshalJSON() ([]byte, error) { } //MarshalJSON custom JSON marshal function to properly handle arrays of floats with decimal part equals to 0 -func (v CrateArray) MarshalJSON() ([]byte, error) { +func (v CrateArray) MarshalJSON() ([]byte, error) { res := bytes.Buffer{} - if err := encodeArray(&res, reflect.ValueOf(v)) ; err != nil { + if err := encodeArray(&res, reflect.ValueOf(v)); err != nil { return nil, err } //log.Printf("Result Array : %v", res.String()) @@ -244,26 +239,26 @@ func (v CrateArray) MarshalJSON() ([]byte, error) { //CheckNamedValue Convert map, CrateArray, time & GeoPoint arguments to DB format. func (c *CrateDriver) CheckNamedValue(v *driver.NamedValue) error { - if obj, ok := v.Value.(map[string]interface{}) ; ok { + if obj, ok := v.Value.(map[string]interface{}); ok { v.Value = crateMap(obj) return nil - } else if ts, ok := v.Value.(time.Time) ; ok { + } else if ts, ok := v.Value.(time.Time); ok { if ts.IsZero() { v.Value = 0 } else { v.Value = ts.In(time.UTC).UnixNano() / 1000000 } return nil - } else if gp, ok := v.Value.(GeoPoint) ; ok { + } else if gp, ok := v.Value.(GeoPoint); ok { //fmt.Printf("CheckNamedValue for Geopoint (%f,%f) \n", gp.Lon, gp.Lat) nGp := make([]float64, 2) nGp[0] = gp.Lon nGp[1] = gp.Lat v.Value = &nGp return nil - } else if _, ok := v.Value.(CrateArray) ; ok { + } else if _, ok := v.Value.(CrateArray); ok { return nil - } else if arr, ok := v.Value.([]interface{}) ; ok { + } else if arr, ok := v.Value.([]interface{}); ok { v.Value = CrateArray(arr) return nil } @@ -285,7 +280,7 @@ func (c *CrateDriver) query(stmt string, args []driver.Value) (*endpointResponse Stmt: stmt, } - if l:=len(args); l > 0 { + if l := len(args); l > 0 { query.Args = args } @@ -339,13 +334,13 @@ func (c *CrateDriver) Query(stmt string, args []driver.Value) (driver.Rows, erro // Rows reader rows := &Rows{ - columns: res.Cols, - values: res.Rows, - rowcount: res.Rowcount, - isSpecial: make([]int64, len(res.Cols)), + columns: res.Cols, + values: res.Rows, + rowcount: res.Rowcount, + isSpecial: make([]int64, len(res.Cols)), } tcount := len(res.ColumnTypes) - for i:=0; i Date: Mon, 19 Apr 2021 19:15:31 +0200 Subject: [PATCH 14/17] Ticket SDRMDPTH-15 : Fixed string encoding in encodeArray. --- crate.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crate.go b/crate.go index 0dd3903..de4d195 100644 --- a/crate.go +++ b/crate.go @@ -13,7 +13,6 @@ import ( "reflect" "strings" "time" - //"log" ) // Crate conn structure @@ -148,7 +147,7 @@ func encodeArray(buf *bytes.Buffer, obj reflect.Value) error { } continue case reflect.String: - buf.WriteString(fmt.Sprintf("%s", strings.Replace(v.String(), "\"", "\\\"", -1))) + buf.WriteString(fmt.Sprintf("\"%s\"", strings.Replace(v.String(), "\"", "\\\"", -1))) continue } buf.WriteString(fmt.Sprintf("%v", v)) From 461e53d63d3186008e1782af7a5c2829a93df906 Mon Sep 17 00:00:00 2001 From: Charles-Edouard Ruault Date: Thu, 7 Oct 2021 10:39:10 +0200 Subject: [PATCH 15/17] Prepare for Crate 4.x : support authentication --- crate.go | 65 ++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 44 insertions(+), 21 deletions(-) diff --git a/crate.go b/crate.go index de4d195..b20dbed 100644 --- a/crate.go +++ b/crate.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "io" + "io/ioutil" "net/http" "net/url" "reflect" @@ -18,6 +19,9 @@ import ( // Crate conn structure type CrateDriver struct { Url string // Crate http endpoint url + username string + password string + httpClient *http.Client } //GeoPoint represents Crate GeoPoint column @@ -70,6 +74,12 @@ func (c *CrateDriver) Open(crate_url string) (driver.Conn, error) { sanUrl := fmt.Sprintf("%s://%s", u.Scheme, u.Host) c.Url = sanUrl + c.httpClient = &http.Client{} + + if u.User != nil { + c.username = u.User.Username() + c.password, _ = u.User.Password() + } return c, nil } @@ -288,39 +298,52 @@ func (c *CrateDriver) query(stmt string, args []driver.Value) (*endpointResponse if err != nil { return nil, err } - data := bytes.NewReader(buf) - - resp, err := http.Post(endpoint, "application/json", data) + data := bytes.NewReader(buf) + req, err := http.NewRequest("POST", endpoint, data) + if err != nil { + return nil, err + } + if c.username != "" { + req.SetBasicAuth(c.username, c.password) + } + req.Header.Add("Content-Type", "application/json") + resp, err := c.httpClient.Do(req) if err != nil { return nil, err } - defer resp.Body.Close() - // Parse response - res := &endpointResponse{} - d := json.NewDecoder(resp.Body) + if resp.StatusCode >= 200 && resp.StatusCode <= 299 || resp.StatusCode >= 400 && resp.StatusCode <= 499 || resp.StatusCode >= 500 && resp.StatusCode <= 599 { + // Parse response + res := &endpointResponse{} + d := json.NewDecoder(resp.Body) - // We need to set this, or long integers will be interpreted as floats - d.UseNumber() + // We need to set this, or long integers will be interpreted as floats + d.UseNumber() - err = d.Decode(res) + err = d.Decode(res) - if err != nil { - return nil, err - } + if err != nil { + return nil, err + } - // Check for db errors - if res.Error.Code != 0 { - err = &CrateErr{ - Code: res.Error.Code, - Message: res.Error.Message, + // Check for db errors + if res.Error.Code != 0 { + err = &CrateErr{ + Code: res.Error.Code, + Message: res.Error.Message, + } + return nil, err } - return nil, err + return res, nil + } else { + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + return nil, fmt.Errorf("invalid http status code %d, body: %s", resp.StatusCode, string(body)) } - - return res, nil } // Queries the database From 11d19f0db694bacad7e937fe430fcbc792b80baf Mon Sep 17 00:00:00 2001 From: Charles-Edouard Ruault Date: Mon, 11 Dec 2023 18:24:24 +0100 Subject: [PATCH 16/17] Fixed map of map element handling and added support for Int into map of map. --- crate.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/crate.go b/crate.go index b20dbed..ef336ed 100644 --- a/crate.go +++ b/crate.go @@ -187,7 +187,7 @@ func encodeMap(buf *bytes.Buffer, obj reflect.Value) error { } buf.WriteString(fmt.Sprintf("\"%s\":", k)) fm := "%v" - v := obj.MapIndex(k).Elem() + v := obj.MapIndex(k) vk := v.Kind() if vk == reflect.Interface { v = v.Elem() @@ -199,6 +199,14 @@ func encodeMap(buf *bytes.Buffer, obj reflect.Value) error { //See https://floating-point-gui.de/ buf.WriteString(fmt.Sprintf("%0.6f", v.Float())) continue + case reflect.Int64, reflect.Int32, reflect.Int, reflect.Int8: + //Prevents rounding errors seen with floats like 0.01*41 which is 0.41000000000000003 ... + //See https://floating-point-gui.de/ + buf.WriteString(fmt.Sprintf("%d", v.Int())) + continue + case reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uint: + buf.WriteString(fmt.Sprintf("%d", v.Uint())) + continue case reflect.Map: t := reflect.TypeOf(v) if v.Type().Key().Kind() != reflect.String { From c0f52ca6a82867b021581c127861e0d64bdb543c Mon Sep 17 00:00:00 2001 From: Charles-Edouard Ruault Date: Thu, 10 Oct 2024 15:13:33 +0200 Subject: [PATCH 17/17] return a CrateErr rather than &CrateErr to allow errors.As to be used; reformatted comments --- crate.go | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/crate.go b/crate.go index ef336ed..20cb662 100644 --- a/crate.go +++ b/crate.go @@ -18,25 +18,25 @@ import ( // Crate conn structure type CrateDriver struct { - Url string // Crate http endpoint url + Url string // Crate http endpoint url username string password string httpClient *http.Client } -//GeoPoint represents Crate GeoPoint column +// GeoPoint represents Crate GeoPoint column type GeoPoint struct { Lat float64 Lon float64 } -//CrateArray represents an Array column type +// CrateArray represents an Array column type type CrateArray []interface{} -//crateMap used to store any map and force our own MarshalJSON method to be called +// crateMap used to store any map and force our own MarshalJSON method to be called type crateMap map[string]interface{} -//Scan : Implements Scanner interface to populate a GeoPoint when the result is an array of 2 floats +// Scan : Implements Scanner interface to populate a GeoPoint when the result is an array of 2 floats func (gp *GeoPoint) Scan(src interface{}) error { if b, ok := src.([]interface{}); ok && len(b) == 2 { var err error @@ -50,7 +50,7 @@ func (gp *GeoPoint) Scan(src interface{}) error { return fmt.Errorf("failed to convert %v to GeoPoint", src) } -//Scan : Implements Scanner interface to populate a CrateArray from the incoming data +// Scan : Implements Scanner interface to populate a CrateArray from the incoming data func (arr *CrateArray) Scan(src interface{}) error { if srcArr, ok := src.([]interface{}); ok { *arr = make([]interface{}, len(srcArr)) @@ -104,8 +104,8 @@ type endpointQuery struct { Args []driver.Value `json:"args,omitempty"` } -//encodeArray will encode the array represented by obj and store the result in buf -//It returns an error if obj contains a map with keys other than strings +// encodeArray will encode the array represented by obj and store the result in buf +// It returns an error if obj contains a map with keys other than strings func encodeArray(buf *bytes.Buffer, obj reflect.Value) error { m := obj.Len() if m == 0 { @@ -166,12 +166,12 @@ func encodeArray(buf *bytes.Buffer, obj reflect.Value) error { return nil } -//encodeMap will encode the map stored in obj in json and store it as a string in the buffer buf -//This is used because one cannot rely on the json encoder because it will format any float with decimal part of 0 as an int -//If the first value to be stored in a new object's key is an int then all further values will be stored as int -//and one will loose the decimal part of each value... Our encoder will ensure that a float with a 0 decimal part -//is encoded as X.0 and not X -//Note it will not encode maps with keys other than strings +// encodeMap will encode the map stored in obj in json and store it as a string in the buffer buf +// This is used because one cannot rely on the json encoder because it will format any float with decimal part of 0 as an int +// If the first value to be stored in a new object's key is an int then all further values will be stored as int +// and one will loose the decimal part of each value... Our encoder will ensure that a float with a 0 decimal part +// is encoded as X.0 and not X +// Note it will not encode maps with keys other than strings func encodeMap(buf *bytes.Buffer, obj reflect.Value) error { if obj.Len() == 0 { buf.WriteString("{}") @@ -234,7 +234,7 @@ func encodeMap(buf *bytes.Buffer, obj reflect.Value) error { return nil } -//MarshalJSON custom JSON marshal function to properly marshall maps containing floats with decimal part equals to 0 +// MarshalJSON custom JSON marshal function to properly marshall maps containing floats with decimal part equals to 0 func (v crateMap) MarshalJSON() ([]byte, error) { res := bytes.Buffer{} if err := encodeMap(&res, reflect.ValueOf(v)); err != nil { @@ -244,7 +244,7 @@ func (v crateMap) MarshalJSON() ([]byte, error) { return res.Bytes(), nil } -//MarshalJSON custom JSON marshal function to properly handle arrays of floats with decimal part equals to 0 +// MarshalJSON custom JSON marshal function to properly handle arrays of floats with decimal part equals to 0 func (v CrateArray) MarshalJSON() ([]byte, error) { res := bytes.Buffer{} if err := encodeArray(&res, reflect.ValueOf(v)); err != nil { @@ -254,7 +254,7 @@ func (v CrateArray) MarshalJSON() ([]byte, error) { return res.Bytes(), nil } -//CheckNamedValue Convert map, CrateArray, time & GeoPoint arguments to DB format. +// CheckNamedValue Convert map, CrateArray, time & GeoPoint arguments to DB format. func (c *CrateDriver) CheckNamedValue(v *driver.NamedValue) error { if obj, ok := v.Value.(map[string]interface{}); ok { v.Value = crateMap(obj) @@ -322,7 +322,7 @@ func (c *CrateDriver) query(stmt string, args []driver.Value) (*endpointResponse } defer resp.Body.Close() - if resp.StatusCode >= 200 && resp.StatusCode <= 299 || resp.StatusCode >= 400 && resp.StatusCode <= 499 || resp.StatusCode >= 500 && resp.StatusCode <= 599 { + if resp.StatusCode >= 200 && resp.StatusCode <= 299 || resp.StatusCode >= 400 && resp.StatusCode <= 499 || resp.StatusCode >= 500 && resp.StatusCode <= 599 { // Parse response res := &endpointResponse{} d := json.NewDecoder(resp.Body) @@ -338,7 +338,7 @@ func (c *CrateDriver) query(stmt string, args []driver.Value) (*endpointResponse // Check for db errors if res.Error.Code != 0 { - err = &CrateErr{ + err = CrateErr{ Code: res.Error.Code, Message: res.Error.Message, }