Skip to content

Commit

Permalink
Switch to the new parser
Browse files Browse the repository at this point in the history
  • Loading branch information
kvap committed Oct 3, 2018
1 parent cae2ac1 commit 8ee3bce
Show file tree
Hide file tree
Showing 6 changed files with 26 additions and 289 deletions.
130 changes: 0 additions & 130 deletions encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,9 @@ import (
"fmt"
"reflect"
"strconv"
"strings"
"time"
)

const (
dateFormat = "2006-01-02"
timeFormat = "2006-01-02 15:04:05"
timeZoneBorder = "\\'"
)

var (
textEncode encoder = new(textEncoder)
)
Expand All @@ -23,18 +16,9 @@ type encoder interface {
Encode(value driver.Value) ([]byte, error)
}

type decoder interface {
Decode(t string, value []byte) (driver.Value, error)
}

type textEncoder struct {
}

type textDecoder struct {
location *time.Location
useDBLocation bool
}

// Encode encodes driver value into string
// Note: there is 2 convention:
// type string will be quoted
Expand Down Expand Up @@ -123,117 +107,3 @@ func (e *textEncoder) encodeArray(value reflect.Value) ([]byte, error) {
}
return append(res, ']'), nil
}

func (d *textDecoder) Decode(t string, value []byte) (driver.Value, error) {
v := string(value)
switch t {
case "Date":
uv := unquote(v)
if uv == "0000-00-00" {
return time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC), nil
}
return time.ParseInLocation(dateFormat, uv, d.location)
case "DateTime":
uv := unquote(v)
if uv == "0000-00-00 00:00:00" {
return time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC), nil
}
return time.ParseInLocation(timeFormat, uv, d.location)
case "UInt8":
vv, err := strconv.ParseUint(v, 10, 8)
return uint8(vv), err
case "UInt16":
vv, err := strconv.ParseUint(v, 10, 16)
return uint16(vv), err
case "UInt32":
vv, err := strconv.ParseUint(v, 10, 32)
return uint32(vv), err
case "UInt64":
return strconv.ParseUint(v, 10, 64)
case "Int8":
vv, err := strconv.ParseInt(v, 10, 8)
return int8(vv), err
case "Int16":
vv, err := strconv.ParseInt(v, 10, 16)
return int16(vv), err
case "Int32":
vv, err := strconv.ParseInt(v, 10, 32)
return int32(vv), err
case "Int64":
return strconv.ParseInt(v, 10, 64)
case "Float32":
vv, err := strconv.ParseFloat(v, 64)
return float32(vv), err
case "Float64":
return strconv.ParseFloat(v, 64)
case "String":
return unescape(unquote(v)), nil
}

// got zoned datetime
if strings.HasPrefix(t, "DateTime") {
var (
loc *time.Location
err error
)

if d.useDBLocation {
left := strings.Index(t, timeZoneBorder)
if left == -1 {
return nil, fmt.Errorf("time zone not found")
}
right := strings.LastIndex(t, timeZoneBorder)
timeZoneName := t[left+len(timeZoneBorder) : right]

loc, err = time.LoadLocation(timeZoneName)
if err != nil {
return nil, err
}
} else {
loc = d.location
}

var t time.Time
if t, err = time.ParseInLocation(timeFormat, unquote(v), loc); err != nil {
return t, err
}
return t.In(d.location), nil
}

if strings.HasPrefix(t, "FixedString") {
return unescape(unquote(v)), nil
}
if strings.HasPrefix(t, "Array") {
if len(v) > 0 && v[0] == '[' && v[len(v)-1] == ']' {
var items []string
subType := t[6 : len(t)-1]
// check that array is not empty ([])
if len(v) > 2 {
// check if array of strings and not empty (['example'])
if subType == "String" || strings.HasPrefix(subType, "FixedString") {
items = strings.Split(v[2:len(v)-2], "','")
for i, v := range items {
items[i] = unescape(v)
}
} else {
items = strings.Split(v[1:len(v)-1], ",")
}
}

r := reflect.MakeSlice(reflect.SliceOf(columnType(subType)), len(items), len(items))
for i, item := range items {
vv, err := d.Decode(subType, []byte(item))
if err != nil {
return nil, err
}
r.Index(i).Set(reflect.ValueOf(vv))
}
return r.Interface(), nil
}
return nil, ErrMalformed
}
if strings.HasPrefix(t, "Enum") {
return unquote(v), nil
}
return value, nil
}
59 changes: 0 additions & 59 deletions encoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,62 +50,3 @@ func TestTextEncoder(t *testing.T) {
}
}
}

func TestTextDecoder(t *testing.T) {
dt := time.Date(2011, 3, 6, 6, 20, 0, 0, time.UTC)
d := time.Date(2012, 5, 31, 0, 0, 0, 0, time.UTC)
zerodt := time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC)
testCases := []struct {
tt string
value string
expected interface{}
}{
{"Int8", "1", int8(1)},
{"Int16", "1", int16(1)},
{"Int32", "1", int32(1)},
{"Int64", "1", int64(1)},
{"UInt8", "1", uint8(1)},
{"UInt16", "1", uint16(1)},
{"UInt32", "1", uint32(1)},
{"UInt64", "1", uint64(1)},
{"Float32", "1", float32(1)},
{"Float64", "1", float64(1)},
{"Date", "'2012-05-31'", d},
{"Date", "'0000-00-00'", zerodt},
{"DateTime", "'2011-03-06 06:20:00'", dt},
{"DateTime", "'0000-00-00 00:00:00'", zerodt},
{"DateTime(\\'Europe/Moscow\\')", "'2011-03-06 06:20:00'", dt},
{"String", "'hello'", "hello"},
{"String", `'\\\\\'hello'`, `\\'hello`},
{"FixedString(5)", "'hello'", "hello"},
{"FixedString(7)", `'\\\\\'hello'`, `\\'hello`},
{"Enum8('one'=1)", "'one'", "one"},
{"Enum16('one'=1)", "'one'", "one"},
{"Array(UInt32)", "[1,2]", []uint32{1, 2}},
{"Array(UInt32)", "[]", []uint32{}},
{"Array(String)", "['one, two','one\\'']", []string{"one, two", "one'"}},
{"Array(String)", "['']", []string{""}},
{"Array(String)", "[]", []string{}},
{"Array(FixedString(3)", "['1,2','2,3']", []string{"1,2", "2,3"}},
}

dec := &textDecoder{location: time.UTC, useDBLocation: false}
for i, tc := range testCases {
v, err := dec.Decode(tc.tt, []byte(tc.value))
if assert.NoError(t, err, "%d", i) {
assert.Equal(t, tc.expected, v)
}
}
}

func TestDecodeTimeWithLocation(t *testing.T) {
dt := time.Date(2011, 3, 6, 3, 20, 0, 0, time.UTC)
dataType := "DateTime(\\'Europe/Moscow\\')"
dtStr := "'2011-03-06 06:20:00'"
dec := &textDecoder{location: time.UTC, useDBLocation: true}

v, err := dec.Decode(dataType, []byte(dtStr))
if assert.NoError(t, err) {
assert.Equal(t, dt, v)
}
}
58 changes: 2 additions & 56 deletions helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,24 @@ package clickhouse
import (
"bytes"
"net/http"
"reflect"
"strings"
"time"
)

var (
escaper = strings.NewReplacer(`\`, `\\`, `'`, `\'`)
unescaper = strings.NewReplacer(`\\`, `\`, `\'`, `'`)
dateFormat = "2006-01-02"
timeFormat = "2006-01-02 15:04:05"
)

func escape(s string) string {
return escaper.Replace(s)
}

func unescape(s string) string {
return unescaper.Replace(s)
}

func quote(s string) string {
return "'" + s + "'"
}

func unquote(s string) string {
if len(s) > 0 && s[0] == '\'' && s[len(s)-1] == '\'' {
return s[1 : len(s)-1]
}
return s
}

func formatTime(value time.Time) string {
return quote(value.Format(timeFormat))
}
Expand Down Expand Up @@ -81,46 +70,3 @@ func splitTSV(data []byte, out []string) int {
}
return -1
}

func columnType(name string) reflect.Type {
switch name {
case "Date", "DateTime":
return reflect.ValueOf(time.Time{}).Type()
case "UInt8":
return reflect.ValueOf(uint8(0)).Type()
case "UInt16":
return reflect.ValueOf(uint16(0)).Type()
case "UInt32":
return reflect.ValueOf(uint32(0)).Type()
case "UInt64":
return reflect.ValueOf(uint64(0)).Type()
case "Int8":
return reflect.ValueOf(int8(0)).Type()
case "Int16":
return reflect.ValueOf(int16(0)).Type()
case "Int32":
return reflect.ValueOf(int32(0)).Type()
case "Int64":
return reflect.ValueOf(int64(0)).Type()
case "Float32":
return reflect.ValueOf(float32(0)).Type()
case "Float64":
return reflect.ValueOf(float64(0)).Type()
case "String":
return reflect.ValueOf("").Type()
}
if strings.HasPrefix(name, "FixedString") {
return reflect.ValueOf("").Type()
}
if strings.HasPrefix(name, "Array") {
subType := columnType(name[6 : len(name)-1])
if subType != nil {
return reflect.SliceOf(subType)
}
return nil
}
if strings.HasPrefix(name, "Enum") {
return reflect.ValueOf("").Type()
}
return nil
}
38 changes: 0 additions & 38 deletions helpers_test.go

This file was deleted.

Loading

0 comments on commit 8ee3bce

Please sign in to comment.