Skip to content
This repository was archived by the owner on Jan 28, 2021. It is now read-only.

Commit d161e2d

Browse files
authored
Add CHAR and DATETIME types support (#823)
Add CHAR and DATETIME types support
2 parents 6ee998b + 4c175cf commit d161e2d

File tree

5 files changed

+194
-28
lines changed

5 files changed

+194
-28
lines changed

_integration/go/mysql_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ func TestGrafana(t *testing.T) {
8282
{"name", "TEXT"},
8383
{"email", "TEXT"},
8484
{"phone_numbers", "JSON"},
85-
{"created_at", "DATETIME"},
85+
{"created_at", "TIMESTAMP"},
8686
},
8787
},
8888
{

engine_test.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -2277,7 +2277,7 @@ func TestDDL(t *testing.T) {
22772277
testQuery(t, e,
22782278
"CREATE TABLE t1(a INTEGER, b TEXT, c DATE, "+
22792279
"d TIMESTAMP, e VARCHAR(20), f BLOB NOT NULL, "+
2280-
"b1 BOOL, b2 BOOLEAN NOT NULL)",
2280+
"b1 BOOL, b2 BOOLEAN NOT NULL, g DATETIME, h CHAR(40))",
22812281
[]sql.Row(nil),
22822282
)
22832283

@@ -2296,6 +2296,8 @@ func TestDDL(t *testing.T) {
22962296
{Name: "f", Type: sql.Blob, Source: "t1"},
22972297
{Name: "b1", Type: sql.Uint8, Nullable: true, Source: "t1"},
22982298
{Name: "b2", Type: sql.Uint8, Source: "t1"},
2299+
{Name: "g", Type: sql.Datetime, Nullable: true, Source: "t1"},
2300+
{Name: "h", Type: sql.Text, Nullable: true, Source: "t1"},
22992301
}
23002302

23012303
require.Equal(s, testTable.Schema())

sql/parse/parse_test.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
)
1414

1515
var fixtures = map[string]sql.Node{
16-
`CREATE TABLE t1(a INTEGER, b TEXT, c DATE, d TIMESTAMP, e VARCHAR(20), f BLOB NOT NULL)`: plan.NewCreateTable(
16+
`CREATE TABLE t1(a INTEGER, b TEXT, c DATE, d TIMESTAMP, e VARCHAR(20), f BLOB NOT NULL, g DATETIME, h CHAR(40))`: plan.NewCreateTable(
1717
sql.UnresolvedDatabase(""),
1818
"t1",
1919
sql.Schema{{
@@ -40,6 +40,14 @@ var fixtures = map[string]sql.Node{
4040
Name: "f",
4141
Type: sql.Blob,
4242
Nullable: false,
43+
}, {
44+
Name: "g",
45+
Type: sql.Datetime,
46+
Nullable: true,
47+
}, {
48+
Name: "h",
49+
Type: sql.Text,
50+
Nullable: true,
4351
}},
4452
),
4553
`DESCRIBE TABLE foo;`: plan.NewDescribe(

sql/type.go

+133-4
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ var (
2727
// ErrConvertingToTime is thrown when a value cannot be converted to a Time
2828
ErrConvertingToTime = errors.NewKind("value %q can't be converted to time.Time")
2929

30-
// ErrVarCharTruncation is thrown when a value is textually longer than the destination capacity
30+
// ErrCharTruncation is thrown when a Char value is textually longer than the destination capacity
31+
ErrCharTruncation = errors.NewKind("string value of %q is longer than destination capacity %d")
32+
33+
// ErrVarCharTruncation is thrown when a VarChar value is textually longer than the destination capacity
3134
ErrVarCharTruncation = errors.NewKind("string value of %q is longer than destination capacity %d")
3235

3336
// ErrValueNotNil is thrown when a value that was expected to be nil, is not
@@ -198,6 +201,8 @@ var (
198201
Timestamp timestampT
199202
// Date is a date with day, month and year.
200203
Date dateT
204+
// Datetime is a date and a time
205+
Datetime datetimeT
201206
// Text is a string type.
202207
Text textT
203208
// Boolean is a boolean type.
@@ -218,6 +223,11 @@ func Array(underlying Type) Type {
218223
return arrayT{underlying}
219224
}
220225

226+
// Char returns a new Char type of the given length.
227+
func Char(length int) Type {
228+
return charT{length: length}
229+
}
230+
221231
// VarChar returns a new VarChar type of the given length.
222232
func VarChar(length int) Type {
223233
return varCharT{length: length}
@@ -254,10 +264,16 @@ func MysqlTypeToType(sql query.Type) (Type, error) {
254264
return Date, nil
255265
case sqltypes.Text:
256266
return Text, nil
267+
case sqltypes.Char:
268+
// Since we can't get the size of the sqltypes.Char to instantiate a
269+
// specific Char(length) type we return a Text here
270+
return Text, nil
257271
case sqltypes.VarChar:
258272
// Since we can't get the size of the sqltypes.VarChar to instantiate a
259273
// specific VarChar(length) type we return a Text here
260274
return Text, nil
275+
case sqltypes.Datetime:
276+
return Datetime, nil
261277
case sqltypes.Bit:
262278
return Boolean, nil
263279
case sqltypes.TypeJSON:
@@ -597,6 +613,109 @@ func (t dateT) Compare(a, b interface{}) (int, error) {
597613
return 0, nil
598614
}
599615

616+
type datetimeT struct{}
617+
618+
// DatetimeLayout is the layout of the MySQL date format in the representation
619+
// Go understands.
620+
const DatetimeLayout = "2006-01-02 15:04:05"
621+
622+
func (t datetimeT) String() string { return "DATETIME" }
623+
624+
func (t datetimeT) Type() query.Type {
625+
return sqltypes.Datetime
626+
}
627+
628+
func (t datetimeT) SQL(v interface{}) (sqltypes.Value, error) {
629+
if v == nil {
630+
return sqltypes.NULL, nil
631+
}
632+
633+
v, err := t.Convert(v)
634+
if err != nil {
635+
return sqltypes.Value{}, err
636+
}
637+
638+
return sqltypes.MakeTrusted(
639+
sqltypes.Datetime,
640+
[]byte(v.(time.Time).Format(DatetimeLayout)),
641+
), nil
642+
}
643+
644+
func (t datetimeT) Convert(v interface{}) (interface{}, error) {
645+
switch value := v.(type) {
646+
case time.Time:
647+
return value.UTC(), nil
648+
case string:
649+
t, err := time.Parse(DatetimeLayout, value)
650+
if err != nil {
651+
return nil, ErrConvertingToTime.Wrap(err, v)
652+
}
653+
return t.UTC(), nil
654+
default:
655+
ts, err := Int64.Convert(v)
656+
if err != nil {
657+
return nil, ErrInvalidType.New(reflect.TypeOf(v))
658+
}
659+
660+
return time.Unix(ts.(int64), 0).UTC(), nil
661+
}
662+
}
663+
664+
func (t datetimeT) Compare(a, b interface{}) (int, error) {
665+
av := a.(time.Time)
666+
bv := b.(time.Time)
667+
if av.Before(bv) {
668+
return -1, nil
669+
} else if av.After(bv) {
670+
return 1, nil
671+
}
672+
return 0, nil
673+
}
674+
675+
type charT struct {
676+
length int
677+
}
678+
679+
func (t charT) Capacity() int { return t.length }
680+
681+
func (t charT) String() string { return fmt.Sprintf("CHAR(%d)", t.length) }
682+
683+
func (t charT) Type() query.Type {
684+
return sqltypes.Char
685+
}
686+
687+
func (t charT) SQL(v interface{}) (sqltypes.Value, error) {
688+
if v == nil {
689+
return sqltypes.MakeTrusted(sqltypes.Char, nil), nil
690+
}
691+
692+
v, err := t.Convert(v)
693+
if err != nil {
694+
return sqltypes.Value{}, err
695+
}
696+
697+
return sqltypes.MakeTrusted(sqltypes.Char, []byte(v.(string))), nil
698+
}
699+
700+
// Converts any value that can be casted to a string
701+
func (t charT) Convert(v interface{}) (interface{}, error) {
702+
val, err := cast.ToStringE(v)
703+
if err != nil {
704+
return nil, ErrConvertToSQL.New(t)
705+
}
706+
707+
if len(val) > t.length {
708+
return nil, ErrCharTruncation.New(val, t.length)
709+
}
710+
return val, nil
711+
}
712+
713+
// Compares two strings lexicographically
714+
func (t charT) Compare(a interface{}, b interface{}) (int, error) {
715+
return strings.Compare(a.(string), b.(string)), nil
716+
}
717+
718+
600719
type varCharT struct {
601720
length int
602721
}
@@ -1013,9 +1132,9 @@ func IsInteger(t Type) bool {
10131132
return IsSigned(t) || IsUnsigned(t)
10141133
}
10151134

1016-
// IsTime checks if t is a timestamp or date.
1135+
// IsTime checks if t is a timestamp, date or datetime
10171136
func IsTime(t Type) bool {
1018-
return t == Timestamp || t == Date
1137+
return t == Timestamp || t == Date || t == Datetime
10191138
}
10201139

10211140
// IsDecimal checks if t is decimal type.
@@ -1025,7 +1144,13 @@ func IsDecimal(t Type) bool {
10251144

10261145
// IsText checks if t is a text type.
10271146
func IsText(t Type) bool {
1028-
return t == Text || t == Blob || t == JSON || IsVarChar(t)
1147+
return t == Text || t == Blob || t == JSON || IsVarChar(t) || IsChar(t)
1148+
}
1149+
1150+
// IsChar checks if t is a Char type.
1151+
func IsChar(t Type) bool {
1152+
_, ok := t.(charT)
1153+
return ok
10291154
}
10301155

10311156
// IsVarChar checks if t is a varchar type.
@@ -1082,9 +1207,13 @@ func MySQLTypeName(t Type) string {
10821207
case sqltypes.Float64:
10831208
return "DOUBLE"
10841209
case sqltypes.Timestamp:
1210+
return "TIMESTAMP"
1211+
case sqltypes.Datetime:
10851212
return "DATETIME"
10861213
case sqltypes.Date:
10871214
return "DATE"
1215+
case sqltypes.Char:
1216+
return "CHAR"
10881217
case sqltypes.VarChar:
10891218
return "VARCHAR"
10901219
case sqltypes.Text:

sql/type_test.go

+48-21
Original file line numberDiff line numberDiff line change
@@ -236,40 +236,57 @@ func TestExtraTimestamps(t *testing.T) {
236236
}
237237
}
238238

239-
func TestDate(t *testing.T) {
239+
// Generic tests for Date and Datetime.
240+
// typ should be Date or Datetime
241+
func commonTestsDatesTypes(typ Type, layout string, t *testing.T) {
240242
require := require.New(t)
241243
now := time.Now().UTC()
242-
v, err := Date.Convert(now)
244+
v, err := typ.Convert(now)
243245
require.NoError(err)
244-
require.Equal(now.Format(DateLayout), v.(time.Time).Format(DateLayout))
246+
require.Equal(now.Format(layout), v.(time.Time).Format(layout))
245247

246-
v, err = Date.Convert(now.Format(DateLayout))
248+
v, err = typ.Convert(now.Format(layout))
247249
require.NoError(err)
248250
require.Equal(
249-
now.Format(DateLayout),
250-
v.(time.Time).Format(DateLayout),
251+
now.Format(layout),
252+
v.(time.Time).Format(layout),
251253
)
252254

253-
v, err = Date.Convert(now.Unix())
255+
v, err = typ.Convert(now.Unix())
254256
require.NoError(err)
255257
require.Equal(
256-
now.Format(DateLayout),
257-
v.(time.Time).Format(DateLayout),
258+
now.Format(layout),
259+
v.(time.Time).Format(layout),
258260
)
259261

260-
sql, err := Date.SQL(now)
262+
sql, err := typ.SQL(now)
261263
require.NoError(err)
262-
require.Equal([]byte(now.Format(DateLayout)), sql.Raw())
264+
require.Equal([]byte(now.Format(layout)), sql.Raw())
265+
266+
after := now.Add(26 * time.Hour)
267+
lt(t, typ, now, after)
268+
eq(t, typ, now, now)
269+
gt(t, typ, after, now)
270+
}
271+
272+
func TestDate(t *testing.T) {
273+
commonTestsDatesTypes(Date, DateLayout, t)
263274

275+
now := time.Now().UTC()
264276
after := now.Add(time.Second)
265277
eq(t, Date, now, after)
266278
eq(t, Date, now, now)
267279
eq(t, Date, after, now)
280+
}
268281

269-
after = now.Add(26 * time.Hour)
270-
lt(t, Date, now, after)
271-
eq(t, Date, now, now)
272-
gt(t, Date, after, now)
282+
func TestDatetime(t *testing.T) {
283+
commonTestsDatesTypes(Datetime, DatetimeLayout, t)
284+
285+
now := time.Now().UTC()
286+
after := now.Add(time.Millisecond)
287+
lt(t, Datetime, now, after)
288+
eq(t, Datetime, now, now)
289+
gt(t, Datetime, after, now)
273290
}
274291

275292
func TestBlob(t *testing.T) {
@@ -325,20 +342,22 @@ func TestTuple(t *testing.T) {
325342
gt(t, typ, []interface{}{1, 2, 4}, []interface{}{1, 2, 3})
326343
}
327344

328-
func TestVarChar(t *testing.T) {
329-
typ := VarChar(3)
330-
require.True(t, IsVarChar(typ))
345+
// Generic test for Char and VarChar types.
346+
// genType should be sql.Char or sql.VarChar
347+
func testCharTypes(genType func(int) Type, checkType func(Type) bool, t *testing.T) {
348+
typ := genType(3)
349+
require.True(t, checkType(typ))
331350
require.True(t, IsText(typ))
332351
convert(t, typ, "foo", "foo")
333352
fooByte := []byte{'f', 'o', 'o'}
334353
convert(t, typ, fooByte, "foo")
335354

336-
typ = VarChar(1)
355+
typ = genType(1)
337356
convertErr(t, typ, "foo")
338357
convertErr(t, typ, fooByte)
339358
convertErr(t, typ, 123)
340359

341-
typ = VarChar(10)
360+
typ = genType(10)
342361
convert(t, typ, 123, "123")
343362
convertErr(t, typ, 1234567890123)
344363

@@ -353,10 +372,18 @@ func TestVarChar(t *testing.T) {
353372
require.NoError(t, err)
354373

355374
convert(t, typ, text, "abc")
356-
typ1 := VarChar(1)
375+
typ1 := genType(1)
357376
convertErr(t, typ1, text)
358377
}
359378

379+
func TestChar(t *testing.T) {
380+
testCharTypes(Char, IsChar, t)
381+
}
382+
383+
func TestVarChar(t *testing.T) {
384+
testCharTypes(VarChar, IsVarChar, t)
385+
}
386+
360387
func TestArray(t *testing.T) {
361388
require := require.New(t)
362389

0 commit comments

Comments
 (0)