488 lines
12 KiB
Go
488 lines
12 KiB
Go
package parser
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/influxdb/influxdb/common"
|
|
)
|
|
|
|
// this file provides the high level api of the query object
|
|
|
|
func uniq(slice []string) []string {
|
|
// TODO: optimize this, maybe ?
|
|
uniqueMap := map[string]bool{}
|
|
for _, name := range slice {
|
|
uniqueMap[name] = true
|
|
}
|
|
slice = []string{}
|
|
for name := range uniqueMap {
|
|
slice = append(slice, name)
|
|
}
|
|
sort.Strings(slice)
|
|
return slice
|
|
}
|
|
|
|
func (self *SelectDeleteCommonQuery) WillReturnSingleSeries() bool {
|
|
fromClause := self.GetFromClause()
|
|
if fromClause.Type != FromClauseArray {
|
|
return false
|
|
}
|
|
|
|
if len(fromClause.Names) > 1 {
|
|
return false
|
|
}
|
|
|
|
if _, ok := fromClause.Names[0].Name.GetCompiledRegex(); ok {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (self *SelectDeleteCommonQuery) GetTableAliases(name string) []string {
|
|
names := self.GetFromClause().Names
|
|
if len(names) == 1 && names[0].Name.Type == ValueRegex {
|
|
return []string{name}
|
|
}
|
|
|
|
aliases := []string{}
|
|
|
|
for _, fromName := range names {
|
|
if fromName.Name.Name != name {
|
|
continue
|
|
}
|
|
|
|
if fromName.Alias == "" {
|
|
aliases = append(aliases, name)
|
|
continue
|
|
}
|
|
|
|
aliases = append(aliases, fromName.Alias)
|
|
}
|
|
return aliases
|
|
}
|
|
|
|
func (self *SelectQuery) revertAlias(mapping map[string][]string) {
|
|
fromClause := self.GetFromClause()
|
|
if fromClause.Type != FromClauseInnerJoin {
|
|
return
|
|
}
|
|
|
|
columns := make(map[string]map[string]bool)
|
|
|
|
for _, table := range fromClause.Names {
|
|
name := table.Name.Name
|
|
alias := name
|
|
if table.Alias != "" {
|
|
alias = table.Alias
|
|
}
|
|
|
|
for _, column := range mapping[alias] {
|
|
tableColumns := columns[name]
|
|
if tableColumns == nil {
|
|
tableColumns = make(map[string]bool)
|
|
columns[name] = tableColumns
|
|
}
|
|
tableColumns[column] = true
|
|
}
|
|
|
|
delete(mapping, alias)
|
|
}
|
|
|
|
for table, tableColumns := range columns {
|
|
mapping[table] = []string{}
|
|
for column := range tableColumns {
|
|
mapping[table] = append(mapping[table], column)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Returns true if the query has some expression in the select clause
|
|
func (self *SelectQuery) ContainsArithmeticOperators() bool {
|
|
for _, column := range self.GetColumnNames() {
|
|
if column.Type == ValueExpression {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Returns true if the query has aggregate functions applied to the
|
|
// columns
|
|
func (self *SelectQuery) HasAggregates() bool {
|
|
for _, column := range self.GetColumnNames() {
|
|
if column.IsFunctionCall() {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Returns a mapping from the time series names (or regex) to the
|
|
// column names that are references
|
|
func (self *SelectQuery) GetReferencedColumns() map[*Value][]string {
|
|
return self.getColumns(true)
|
|
}
|
|
|
|
func (self *SelectQuery) GetResultColumns() map[*Value][]string {
|
|
return self.getColumns(false)
|
|
}
|
|
|
|
func (self *SelectQuery) getColumns(includeWhereClause bool) map[*Value][]string {
|
|
mapping := make(map[string][]string)
|
|
|
|
notPrefixedColumns := []string{}
|
|
for _, value := range self.GetColumnNames() {
|
|
if value.Name == "time" || value.Name == "sequence_number" {
|
|
continue
|
|
}
|
|
notPrefixedColumns = append(notPrefixedColumns, getReferencedColumnsFromValue(value, mapping)...)
|
|
}
|
|
|
|
if !self.IsSinglePointQuery() {
|
|
if condition := self.GetWhereCondition(); condition != nil && includeWhereClause {
|
|
notPrefixedColumns = append(notPrefixedColumns, getReferencedColumnsFromCondition(condition, mapping)...)
|
|
}
|
|
|
|
for _, groupBy := range self.groupByClause.Elems {
|
|
notPrefixedColumns = append(notPrefixedColumns, getReferencedColumnsFromValue(groupBy, mapping)...)
|
|
}
|
|
}
|
|
|
|
notPrefixedColumns = uniq(notPrefixedColumns)
|
|
|
|
self.revertAlias(mapping)
|
|
|
|
addedTables := make(map[string]bool)
|
|
|
|
returnedMapping := make(map[*Value][]string)
|
|
for _, tableName := range self.GetFromClause().Names {
|
|
value := tableName.Name
|
|
if _, ok := value.GetCompiledRegex(); ok {
|
|
// this is a regex table, cannot be referenced, only unreferenced
|
|
// columns will be attached to regex table names
|
|
returnedMapping[value] = notPrefixedColumns
|
|
continue
|
|
}
|
|
|
|
name := value.Name
|
|
if addedTables[name] {
|
|
continue
|
|
}
|
|
addedTables[name] = true
|
|
returnedMapping[value] = uniq(append(mapping[name], notPrefixedColumns...))
|
|
if len(returnedMapping[value]) > 1 && returnedMapping[value][0] == "*" {
|
|
returnedMapping[value] = returnedMapping[value][:1]
|
|
}
|
|
|
|
delete(mapping, name)
|
|
}
|
|
|
|
if len(mapping) == 0 {
|
|
return returnedMapping
|
|
}
|
|
|
|
// if `mapping` still have some mappings, then we have mistaken a
|
|
// column name with dots with a prefix.column, see issue #240
|
|
for prefix, columnNames := range mapping {
|
|
for _, columnName := range columnNames {
|
|
for table, columns := range returnedMapping {
|
|
if len(returnedMapping[table]) > 1 && returnedMapping[table][0] == "*" {
|
|
continue
|
|
}
|
|
returnedMapping[table] = append(columns, prefix+"."+columnName)
|
|
}
|
|
}
|
|
delete(mapping, prefix)
|
|
}
|
|
|
|
return returnedMapping
|
|
}
|
|
|
|
// Returns the start time of the query. Queries can only have
|
|
// one condition of the form time > start_time
|
|
func (self *BasicQuery) GetStartTime() time.Time {
|
|
return self.startTime
|
|
}
|
|
|
|
func (self *BasicQuery) IsStartTimeSpecified() bool {
|
|
return self.startTimeSpecified
|
|
}
|
|
|
|
// Returns the start time of the query. Queries can only have
|
|
// one condition of the form time > start_time
|
|
func (self *BasicQuery) 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 timeRegex *regexp.Regexp
|
|
|
|
func init() {
|
|
var err error
|
|
timeRegex, 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 := timeRegex.FindStringSubmatch(t)
|
|
if len(submatches) == 0 {
|
|
return nil, fmt.Errorf("%s isn't a valid time string", t)
|
|
}
|
|
|
|
if submatches[5] != "" || submatches[4] != "" {
|
|
t, err := time.Parse("2006-01-02 15:04:05", t)
|
|
return &t, err
|
|
}
|
|
|
|
if submatches[3] != "" {
|
|
t, err := time.Parse("2006-01-02 15:04", t)
|
|
return &t, err
|
|
}
|
|
|
|
if submatches[2] != "" {
|
|
t, err := time.Parse("2006-01-02 15", t)
|
|
return &t, err
|
|
}
|
|
|
|
_t, err := time.Parse("2006-01-02", t)
|
|
return &_t, err
|
|
}
|
|
|
|
func parseTimeWithoutSuffix(value string) (int64, error) {
|
|
var err error
|
|
var f float64
|
|
var i int64
|
|
if strings.Contains(value, ".") {
|
|
f, err = strconv.ParseFloat(value, 64)
|
|
i = int64(f)
|
|
} else {
|
|
i, err = strconv.ParseInt(value, 10, 64)
|
|
}
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return i, nil
|
|
}
|
|
|
|
// parse time expressions, e.g. now() - 1d
|
|
func parseTime(value *Value) (int64, error) {
|
|
if value.Type != ValueExpression {
|
|
if value.IsFunctionCall() && strings.ToLower(value.Name) == "now" {
|
|
return time.Now().UTC().UnixNano(), nil
|
|
}
|
|
|
|
if value.IsFunctionCall() {
|
|
return 0, fmt.Errorf("Invalid use of function %s", value.Name)
|
|
}
|
|
|
|
if value.Type == ValueString {
|
|
t, err := parseTimeString(value.Name)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return t.UnixNano(), err
|
|
}
|
|
|
|
duration, err := common.ParseTimeDuration(value.Name)
|
|
|
|
return duration, err
|
|
}
|
|
|
|
leftValue, err := parseTime(value.Elems[0])
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
rightValue, err := parseTime(value.Elems[1])
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
switch value.Name {
|
|
case "+":
|
|
return leftValue + rightValue, nil
|
|
case "-":
|
|
return leftValue - rightValue, nil
|
|
default:
|
|
return 0, fmt.Errorf("Cannot use '%s' in a time expression", value.Name)
|
|
}
|
|
}
|
|
|
|
func getReferencedColumnsFromValue(v *Value, mapping map[string][]string) (notAssigned []string) {
|
|
switch v.Type {
|
|
case ValueSimpleName, ValueTableName:
|
|
if idx := strings.LastIndex(v.Name, "."); idx != -1 {
|
|
tableName := v.Name[:idx]
|
|
columnName := v.Name[idx+1:]
|
|
mapping[tableName] = append(mapping[tableName], columnName)
|
|
return
|
|
}
|
|
notAssigned = append(notAssigned, v.Name)
|
|
case ValueWildcard:
|
|
notAssigned = append(notAssigned, "*")
|
|
case ValueExpression, ValueFunctionCall:
|
|
for _, value := range v.Elems {
|
|
newNotAssignedColumns := getReferencedColumnsFromValue(value, mapping)
|
|
if len(newNotAssignedColumns) > 0 && newNotAssignedColumns[0] == "*" {
|
|
newNotAssignedColumns = newNotAssignedColumns[1:]
|
|
}
|
|
|
|
notAssigned = append(notAssigned, newNotAssignedColumns...)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func getReferencedColumnsFromCondition(condition *WhereCondition, mapping map[string][]string) (notPrefixed []string) {
|
|
if left, ok := condition.GetLeftWhereCondition(); ok {
|
|
notPrefixed = append(notPrefixed, getReferencedColumnsFromCondition(left, mapping)...)
|
|
notPrefixed = append(notPrefixed, getReferencedColumnsFromCondition(condition.Right, mapping)...)
|
|
return
|
|
}
|
|
|
|
expr, _ := condition.GetBoolExpression()
|
|
notPrefixed = append(notPrefixed, getReferencedColumnsFromValue(expr, mapping)...)
|
|
return
|
|
}
|
|
|
|
func isNumericValue(value *Value) bool {
|
|
switch value.Type {
|
|
case ValueDuration, ValueFloat, ValueInt, ValueString:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// parse the start time or end time from the where conditions and return the new condition
|
|
// without the time clauses, or nil if there are no where conditions left
|
|
func getTime(condition *WhereCondition, isParsingStartTime bool) (*WhereCondition, *time.Time, error) {
|
|
if condition == nil {
|
|
return nil, nil, nil
|
|
}
|
|
|
|
if expr, ok := condition.GetBoolExpression(); ok {
|
|
switch expr.Type {
|
|
case ValueDuration, ValueFloat, ValueInt, ValueString, ValueWildcard:
|
|
return nil, nil, fmt.Errorf("Invalid where expression: %v", expr)
|
|
}
|
|
|
|
if expr.Type == ValueFunctionCall {
|
|
return condition, nil, nil
|
|
}
|
|
|
|
leftValue := expr.Elems[0]
|
|
isTimeOnLeft := leftValue.Type != ValueExpression && leftValue.Type != ValueFunctionCall
|
|
rightValue := expr.Elems[1]
|
|
isTimeOnRight := rightValue.Type != ValueExpression && rightValue.Type != ValueFunctionCall
|
|
|
|
// this can only be the case if the where condition
|
|
// is of the form `"time" > 123456789`, so let's see
|
|
// which side is a float value
|
|
if isTimeOnLeft && isTimeOnRight {
|
|
if isNumericValue(rightValue) {
|
|
isTimeOnRight = false
|
|
} else {
|
|
isTimeOnLeft = false
|
|
}
|
|
}
|
|
|
|
// if this expression isn't "time > xxx" or "xxx < time" then return
|
|
// TODO: we should do a check to make sure "time" doesn't show up in
|
|
// either expressions
|
|
if !isTimeOnLeft && !isTimeOnRight {
|
|
return condition, nil, nil
|
|
}
|
|
|
|
var timeExpression *Value
|
|
if !isTimeOnRight {
|
|
if leftValue.Name != "time" {
|
|
return condition, nil, nil
|
|
}
|
|
timeExpression = rightValue
|
|
} else if !isTimeOnLeft {
|
|
if rightValue.Name != "time" {
|
|
return condition, nil, nil
|
|
}
|
|
timeExpression = leftValue
|
|
} else {
|
|
return nil, nil, fmt.Errorf("Invalid time condition %v", condition)
|
|
}
|
|
|
|
switch expr.Name {
|
|
case ">":
|
|
if isParsingStartTime && !isTimeOnLeft || !isParsingStartTime && !isTimeOnRight {
|
|
return condition, nil, nil
|
|
}
|
|
case "<":
|
|
if !isParsingStartTime && !isTimeOnLeft || isParsingStartTime && !isTimeOnRight {
|
|
return condition, nil, nil
|
|
}
|
|
case "=":
|
|
nanoseconds, err := parseTime(timeExpression)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
t := time.Unix(0, nanoseconds).UTC()
|
|
return condition, &t, nil
|
|
default:
|
|
return nil, nil, fmt.Errorf("Cannot use time with '%s'", expr.Name)
|
|
}
|
|
|
|
nanoseconds, err := parseTime(timeExpression)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
t := time.Unix(0, nanoseconds).UTC()
|
|
return nil, &t, nil
|
|
}
|
|
|
|
leftCondition, _ := condition.GetLeftWhereCondition()
|
|
newLeftCondition, timeLeft, err := getTime(leftCondition, isParsingStartTime)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
newRightCondition, timeRight, err := getTime(condition.Right, isParsingStartTime)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
if condition.Operation == "OR" && (timeLeft != nil || timeRight != nil) {
|
|
// we can't have two start times or'd together
|
|
return nil, nil, fmt.Errorf("Invalid where clause, time must appear twice to specify start and end time")
|
|
}
|
|
|
|
newCondition := condition
|
|
if newLeftCondition == nil {
|
|
newCondition = newRightCondition
|
|
} else if newRightCondition == nil {
|
|
newCondition = newLeftCondition
|
|
} else {
|
|
newCondition.Left = newLeftCondition
|
|
newCondition.Right = newRightCondition
|
|
}
|
|
|
|
if timeLeft == nil {
|
|
return newCondition, timeRight, nil
|
|
}
|
|
if timeRight == nil {
|
|
return newCondition, timeLeft, nil
|
|
}
|
|
if isParsingStartTime && timeLeft.Unix() < timeRight.Unix() {
|
|
return newCondition, timeLeft, nil
|
|
}
|
|
if !isParsingStartTime && timeLeft.Unix() > timeRight.Unix() {
|
|
return newCondition, timeLeft, nil
|
|
}
|
|
return newCondition, timeRight, nil
|
|
}
|