diff --git a/CHANGELOG.md b/CHANGELOG.md index 052e66b62e..d06a0a8144 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -145,6 +145,7 @@ ## Features - [Issue #92](https://github.com/influxdb/influxdb/issues/92). Change '==' to '=' and '!=' to '<>' +- [Issue #88](https://github.com/influxdb/influxdb/issues/88). Support datetime strings ## Bugfixes diff --git a/src/parser/parser_test.go b/src/parser/parser_test.go index 16a35a609c..608ee9a6f9 100644 --- a/src/parser/parser_test.go +++ b/src/parser/parser_test.go @@ -209,6 +209,44 @@ func (self *QueryParserSuite) TestParseSelectWithTimeCondition(c *C) { c.Assert(q.GetStartTime().Round(time.Minute), Equals, time.Now().Add(-24*time.Hour).Round(time.Minute)) } +func (self *QueryParserSuite) TestParseSelectWithPartialTimeString(c *C) { + for actual, expected := range map[string]string{ + "2013-08-15": "2013-08-15 00:00:00", + "2013-08-15 7": "2013-08-15 07:00:00", + "2013-08-15 7:04": "2013-08-15 07:04:00", + "2013-08-15 15": "2013-08-15 15:00:00", + "2013-08-15 15:14": "2013-08-15 15:14:00", + "2013-08-15 15:14:26": "2013-08-15 15:14:26", + } { + t, err := time.Parse("2006-01-02 15:04:05", expected) + c.Assert(err, IsNil) + q, err := ParseQuery(fmt.Sprintf("select value, time from t where time > '%s';", actual)) + c.Assert(err, IsNil) + + // note: the time condition will be removed + c.Assert(q.GetWhereCondition(), IsNil) + + startTime := q.GetStartTime() + c.Assert(startTime, Equals, t) + } +} + +func (self *QueryParserSuite) TestParseSelectWithTimeString(c *C) { + t, err := time.Parse("2006-01-02 15:04:05", "2013-08-12 23:32:01.232") + c.Assert(err, IsNil) + q, err := ParseQuery("select value, time from t where time > '2013-08-12 23:32:01.232';") + c.Assert(err, IsNil) + + // note: the time condition will be removed + c.Assert(q.GetWhereCondition(), IsNil) + + startTime := q.GetStartTime() + c.Assert(startTime, Equals, t) + + milliseconds := startTime.Sub(startTime.Round(time.Second)).Nanoseconds() / 1000000 + c.Assert(milliseconds, Equals, int64(232)) +} + func (self *QueryParserSuite) TestParseSelectWithAnd(c *C) { q, err := ParseQuery("select value from cpu.idle where value > exp() * 2 and value < exp() * 3;") c.Assert(err, IsNil) diff --git a/src/parser/query_api.go b/src/parser/query_api.go index cc16660821..bc4b1724d5 100644 --- a/src/parser/query_api.go +++ b/src/parser/query_api.go @@ -2,6 +2,7 @@ package parser import ( "fmt" + "regexp" "sort" "strconv" "strings" @@ -166,6 +167,41 @@ func (self *Query) GetEndTime() time.Time { return self.endTime } +// parse time that matches the following format: +// 2006-01-02 [15[:04[:05[.000]]]] +// notice, hour, minute and seconds are optional +var time_regex *regexp.Regexp + +func init() { + var err error + time_regex, err = regexp.Compile( + "^([0-9]{4}|[0-9]{2})-[0-9]{1,2}-[0-9]{1,2}( [0-9]{1,2}(:[0-9]{1,2}(:[0-9]{1,2}?(\\.[0-9]+)?)?)?)?$") + if err != nil { + panic(err) + } +} + +func parseTimeString(t string) (time.Time, error) { + submatches := time_regex.FindStringSubmatch(t) + if len(submatches) == 0 { + return ZERO_TIME, fmt.Errorf("%s isn't a valid time string", t) + } + + if submatches[5] != "" || submatches[4] != "" { + return time.Parse("2006-01-02 15:04:05", t) + } + + if submatches[3] != "" { + return time.Parse("2006-01-02 15:04", t) + } + + if submatches[2] != "" { + return time.Parse("2006-01-02 15", t) + } + + return time.Parse("2006-01-02", t) +} + // parse time expressions, e.g. now() - 1d func parseTime(expr *Expression) (int64, error) { if value, ok := expr.GetLeftValue(); ok { @@ -177,6 +213,11 @@ func parseTime(expr *Expression) (int64, error) { return 0, fmt.Errorf("Invalid use of function %s", value.Name) } + if value.Type == ValueString { + t, err := parseTimeString(value.Name) + return t.UnixNano(), err + } + name := value.Name parsedFloat, err := strconv.ParseFloat(name[:len(name)-1], 64) @@ -297,7 +338,7 @@ func getReferencedColumnsFromCondition(condition *WhereCondition, mapping map[st func isNumericValue(value *Value) bool { switch value.Type { - case ValueDuration, ValueFloat, ValueInt: + case ValueDuration, ValueFloat, ValueInt, ValueString: return true default: return false @@ -365,7 +406,7 @@ func getTime(condition *WhereCondition, isParsingStartTime bool) (*WhereConditio if err != nil { return nil, ZERO_TIME, err } - return nil, time.Unix(nanoseconds/int64(time.Second), nanoseconds%int64(time.Second)), nil + return nil, time.Unix(nanoseconds/int64(time.Second), nanoseconds%int64(time.Second)).UTC(), nil } leftCondition, _ := condition.GetLeftWhereCondition()