-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement date, time and timestamp literals #10921
Changes from all commits
393c508
8822d1e
0e8f838
d574c57
6913fd6
a48a161
865d610
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -189,6 +189,7 @@ var stateToMysqlCode = map[vterrors.State]struct { | |
vterrors.WrongNumberOfColumnsInSelect: {num: ERWrongNumberOfColumnsInSelect, state: SSWrongNumberOfColumns}, | ||
vterrors.WrongTypeForVar: {num: ERWrongTypeForVar, state: SSClientError}, | ||
vterrors.WrongValueForVar: {num: ERWrongValueForVar, state: SSClientError}, | ||
vterrors.WrongValue: {num: ERWrongValue, state: SSUnknownSQLState}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The SQL state here is also in MySQL unknown (
|
||
vterrors.WrongFieldWithGroup: {num: ERWrongFieldWithGroup, state: SSClientError}, | ||
vterrors.ServerNotAvailable: {num: ERServerIsntAvailable, state: SSNetError}, | ||
vterrors.CantDoThisInTransaction: {num: ERCantDoThisDuringAnTransaction, state: SSCantDoThisDuringAnTransaction}, | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -106,7 +106,24 @@ func (nz *normalizer) WalkSelect(cursor *Cursor) bool { | |
return nz.err == nil // only continue if we haven't found any errors | ||
} | ||
|
||
func validateLiteral(node *Literal) (err error) { | ||
switch node.Type { | ||
case DateVal: | ||
_, err = ParseDate(node.Val) | ||
case TimeVal: | ||
_, err = ParseTime(node.Val) | ||
case TimestampVal: | ||
_, err = ParseDateTime(node.Val) | ||
} | ||
return err | ||
} | ||
|
||
func (nz *normalizer) convertLiteralDedup(node *Literal, cursor *Cursor) { | ||
err := validateLiteral(node) | ||
if err != nil { | ||
nz.err = err | ||
} | ||
|
||
// If value is too long, don't dedup. | ||
// Such values are most likely not for vindexes. | ||
// We save a lot of CPU because we avoid building | ||
|
@@ -146,6 +163,11 @@ func (nz *normalizer) convertLiteralDedup(node *Literal, cursor *Cursor) { | |
|
||
// convertLiteral converts an Literal without the dedup. | ||
func (nz *normalizer) convertLiteral(node *Literal, cursor *Cursor) { | ||
err := validateLiteral(node) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tried various approaches, but injecting this in the normalization step seemed the easiest but it needs to be checked then from here and also in |
||
if err != nil { | ||
nz.err = err | ||
} | ||
|
||
bval := SQLToBindvar(node) | ||
if bval == nil { | ||
return | ||
|
@@ -223,6 +245,15 @@ func SQLToBindvar(node SQLNode) *querypb.BindVariable { | |
return nil | ||
} | ||
v, err = sqltypes.NewValue(sqltypes.HexNum, []byte(fmt.Sprintf("0x%x", ui))) | ||
case DateVal: | ||
v, err = sqltypes.NewValue(sqltypes.Date, node.Bytes()) | ||
case TimeVal: | ||
v, err = sqltypes.NewValue(sqltypes.Time, node.Bytes()) | ||
case TimestampVal: | ||
// This is actually a DATETIME MySQL type. The timestamp literal | ||
// syntax is part of the SQL standard and MySQL DATETIME matches | ||
// the type best. | ||
v, err = sqltypes.NewValue(sqltypes.Datetime, node.Bytes()) | ||
default: | ||
return nil | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
/* | ||
Copyright 2019 The Vitess Authors. | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
|
||
http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package sqlparser | ||
|
||
import ( | ||
"fmt" | ||
"strconv" | ||
"strings" | ||
"time" | ||
|
||
vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc" | ||
"vitess.io/vitess/go/vt/vterrors" | ||
) | ||
|
||
var dateFormats = []string{"2006-01-02", "06-01-02", "20060102", "060102"} | ||
var datetimeFormats = []string{"2006-01-02 15:04:05.9", "06-01-02 15:04:05.9", "20060102150405.9", "060102150405.9"} | ||
var timeWithDayFormats = []string{"15:04:05.9", "15:04", "15"} | ||
var timeWithoutDayFormats = []string{"15:04:05.9", "15:04", "150405.9", "0405", "05"} | ||
|
||
func ParseDate(in string) (t time.Time, err error) { | ||
for _, f := range dateFormats { | ||
t, err = time.Parse(f, in) | ||
if err == nil { | ||
return t, nil | ||
} | ||
} | ||
return t, vterrors.NewErrorf(vtrpcpb.Code_INVALID_ARGUMENT, vterrors.WrongValue, "incorrect DATE value: '%s'", in) | ||
} | ||
|
||
func ParseTime(in string) (t time.Time, err error) { | ||
// ParseTime is right now only excepting on specific | ||
// time format and doesn't accept all formats MySQL accepts. | ||
// Can be improved in the future as needed. | ||
if in == "" { | ||
return t, vterrors.NewErrorf(vtrpcpb.Code_INVALID_ARGUMENT, vterrors.WrongValue, "incorrect TIME value: '%s'", in) | ||
} | ||
start := 0 | ||
neg := in[start] == '-' | ||
if neg { | ||
start++ | ||
} | ||
|
||
parts := strings.Split(in[start:], " ") | ||
if len(parts) > 2 { | ||
return t, vterrors.NewErrorf(vtrpcpb.Code_INVALID_ARGUMENT, vterrors.WrongValue, "incorrect TIME value: '%s'", in) | ||
} | ||
days := 0 | ||
hourMinuteSeconds := parts[0] | ||
if len(parts) == 2 { | ||
days, err = strconv.Atoi(parts[0]) | ||
if err != nil { | ||
fmt.Printf("atoi failed: %+v\n", err) | ||
return t, vterrors.NewErrorf(vtrpcpb.Code_INVALID_ARGUMENT, vterrors.WrongValue, "incorrect TIME value: '%s'", in) | ||
} | ||
if days < 0 { | ||
// Double negative which is not allowed | ||
return t, vterrors.NewErrorf(vtrpcpb.Code_INVALID_ARGUMENT, vterrors.WrongValue, "incorrect TIME value: '%s'", in) | ||
} | ||
if days > 34 { | ||
return t, vterrors.NewErrorf(vtrpcpb.Code_INVALID_ARGUMENT, vterrors.WrongValue, "incorrect TIME value: '%s'", in) | ||
} | ||
for _, f := range timeWithDayFormats { | ||
t, err = time.Parse(f, parts[1]) | ||
if err == nil { | ||
break | ||
} | ||
} | ||
} else { | ||
for _, f := range timeWithoutDayFormats { | ||
t, err = time.Parse(f, hourMinuteSeconds) | ||
if err == nil { | ||
break | ||
} | ||
} | ||
} | ||
|
||
if err != nil { | ||
return t, vterrors.NewErrorf(vtrpcpb.Code_INVALID_ARGUMENT, vterrors.WrongValue, "incorrect TIME value: '%s'", in) | ||
} | ||
|
||
// setting the date to today's date, because t is "0000-01-01 xx:xx:xx" | ||
now := time.Now() | ||
year, month, day := now.Date() | ||
if neg { | ||
// If we have a negative time, we start with the start of today | ||
// and substract the total duration of the parsed time. | ||
today := time.Date(year, month, day, 0, 0, 0, 0, t.Location()) | ||
duration := time.Duration(days)*24*time.Hour + | ||
time.Duration(t.Hour())*time.Hour + | ||
time.Duration(t.Minute())*time.Minute + | ||
time.Duration(t.Second())*time.Second + | ||
time.Duration(t.Nanosecond())*time.Nanosecond | ||
t = today.Add(-duration) | ||
} else { | ||
// In case of a positive time, we can take a quicker | ||
// shortcut and add the date of today. | ||
t = t.AddDate(year, int(month-1), day-1+days) | ||
} | ||
return t, nil | ||
} | ||
|
||
func ParseDateTime(in string) (t time.Time, err error) { | ||
for _, f := range datetimeFormats { | ||
t, err = time.Parse(f, in) | ||
if err == nil { | ||
return t, nil | ||
} | ||
} | ||
return t, vterrors.NewErrorf(vtrpcpb.Code_INVALID_ARGUMENT, vterrors.WrongValue, "incorrect DATETIME value: '%s'", in) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the same error type as MySQL: