| // Copyright 2015 The Vanadium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package query_functions |
| |
| // TODO(jkline): Probably rename this file to time_functions.go |
| |
| import ( |
| "time" |
| |
| ds "v.io/v23/query/engine/datasource" |
| "v.io/v23/query/engine/internal/conversions" |
| "v.io/v23/query/engine/internal/query_parser" |
| "v.io/v23/query/syncql" |
| ) |
| |
| // If possible, check if arg is convertible to a location. Fields and not yet computed |
| // functions cannot be checked and will just return nil. |
| func checkIfPossibleThatArgIsConvertibleToLocation(db ds.Database, arg *query_parser.Operand) error { |
| var locStr *query_parser.Operand |
| var err error |
| switch arg.Type { |
| case query_parser.TypBigInt, query_parser.TypBigRat, query_parser.TypBool, query_parser.TypComplex, query_parser.TypFloat, query_parser.TypInt, query_parser.TypStr, query_parser.TypTime, query_parser.TypUint: |
| if locStr, err = conversions.ConvertValueToString(arg); err != nil { |
| if err != nil { |
| return syncql.NewErrLocationConversionError(db.GetContext(), arg.Off, err) |
| } else { |
| return nil |
| } |
| } |
| case query_parser.TypFunction: |
| if arg.Function.Computed { |
| if locStr, err = conversions.ConvertValueToString(arg.Function.RetValue); err != nil { |
| if err != nil { |
| return syncql.NewErrLocationConversionError(db.GetContext(), arg.Off, err) |
| } else { |
| return nil |
| } |
| } |
| } else { |
| // Arg is uncomputed function, can't make determination about arg. |
| return nil |
| } |
| default: |
| // Arg is not a literal or function, can't make determination about arg. |
| return nil |
| } |
| _, err = time.LoadLocation(locStr.Str) |
| if err != nil { |
| return syncql.NewErrLocationConversionError(db.GetContext(), arg.Off, err) |
| } else { |
| return nil |
| } |
| } |
| |
| // Time(layout, value string) |
| // e.g., Time("Mon Jan 2 15:04:05 -0700 MST 2006", "Tue Aug 25 10:01:00 -0700 PDT 2015") |
| // e.g., Time("Jan 2 15:04 MST 2006", "Aug 25 10:01 PDT 2015") |
| func timeFunc(db ds.Database, off int64, args []*query_parser.Operand) (*query_parser.Operand, error) { |
| layoutOp, err := conversions.ConvertValueToString(args[0]) |
| if err != nil { |
| return nil, err |
| } |
| valueOp, err := conversions.ConvertValueToString(args[1]) |
| if err != nil { |
| return nil, err |
| } |
| // Mon Jan 2 15:04:05 -0700 MST 2006 |
| tim, err := time.Parse(layoutOp.Str, valueOp.Str) |
| if err != nil { |
| return nil, err |
| } |
| return makeTimeOp(off, tim), nil |
| } |
| |
| // now() |
| func now(db ds.Database, off int64, args []*query_parser.Operand) (*query_parser.Operand, error) { |
| return makeTimeOp(off, time.Now()), nil |
| } |
| func timeInLocation(db ds.Database, off int64, args []*query_parser.Operand) (time.Time, error) { |
| var timeOp *query_parser.Operand |
| var locOp *query_parser.Operand |
| var err error |
| if timeOp, err = conversions.ConvertValueToTime(args[0]); err != nil { |
| return time.Time{}, err |
| } |
| if locOp, err = conversions.ConvertValueToString(args[1]); err != nil { |
| return time.Time{}, err |
| } |
| var loc *time.Location |
| if loc, err = time.LoadLocation(locOp.Str); err != nil { |
| return time.Time{}, err |
| } |
| return timeOp.Time.In(loc), nil |
| } |
| |
| // Year(v.InvoiceDate, "America/Los_Angeles") |
| func year(db ds.Database, off int64, args []*query_parser.Operand) (*query_parser.Operand, error) { |
| if tim, err := timeInLocation(db, off, args); err != nil { |
| return nil, err |
| } else { |
| return makeIntOp(off, int64(tim.Year())), nil |
| } |
| } |
| |
| // Month(v.InvoiceDate, "America/Los_Angeles") |
| func month(db ds.Database, off int64, args []*query_parser.Operand) (*query_parser.Operand, error) { |
| if tim, err := timeInLocation(db, off, args); err != nil { |
| return nil, err |
| } else { |
| return makeIntOp(off, int64(tim.Month())), nil |
| } |
| } |
| |
| // Day(v.InvoiceDate, "America/Los_Angeles") |
| func day(db ds.Database, off int64, args []*query_parser.Operand) (*query_parser.Operand, error) { |
| if tim, err := timeInLocation(db, off, args); err != nil { |
| return nil, err |
| } else { |
| return makeIntOp(off, int64(tim.Day())), nil |
| } |
| } |
| |
| // Hour(v.InvoiceDate, "America/Los_Angeles") |
| func hour(db ds.Database, off int64, args []*query_parser.Operand) (*query_parser.Operand, error) { |
| if tim, err := timeInLocation(db, off, args); err != nil { |
| return nil, err |
| } else { |
| return makeIntOp(off, int64(tim.Hour())), nil |
| } |
| } |
| |
| // Minute(v.InvoiceDate, "America/Los_Angeles") |
| func minute(db ds.Database, off int64, args []*query_parser.Operand) (*query_parser.Operand, error) { |
| if tim, err := timeInLocation(db, off, args); err != nil { |
| return nil, err |
| } else { |
| return makeIntOp(off, int64(tim.Minute())), nil |
| } |
| } |
| |
| // Second(v.InvoiceDate, "America/Los_Angeles") |
| func second(db ds.Database, off int64, args []*query_parser.Operand) (*query_parser.Operand, error) { |
| if tim, err := timeInLocation(db, off, args); err != nil { |
| return nil, err |
| } else { |
| return makeIntOp(off, int64(tim.Second())), nil |
| } |
| } |
| |
| // Nanosecond(v.InvoiceDate, "America/Los_Angeles") |
| func nanosecond(db ds.Database, off int64, args []*query_parser.Operand) (*query_parser.Operand, error) { |
| if tim, err := timeInLocation(db, off, args); err != nil { |
| return nil, err |
| } else { |
| return makeIntOp(off, int64(tim.Nanosecond())), nil |
| } |
| } |
| |
| // Weekday(v.InvoiceDate, "America/Los_Angeles") |
| func weekday(db ds.Database, off int64, args []*query_parser.Operand) (*query_parser.Operand, error) { |
| if tim, err := timeInLocation(db, off, args); err != nil { |
| return nil, err |
| } else { |
| return makeIntOp(off, int64(tim.Weekday())), nil |
| } |
| } |
| |
| // YearDay(v.InvoiceDate, "America/Los_Angeles") |
| func yearDay(db ds.Database, off int64, args []*query_parser.Operand) (*query_parser.Operand, error) { |
| if tim, err := timeInLocation(db, off, args); err != nil { |
| return nil, err |
| } else { |
| return makeIntOp(off, int64(tim.YearDay())), nil |
| } |
| } |
| |
| func makeTimeOp(off int64, tim time.Time) *query_parser.Operand { |
| var o query_parser.Operand |
| o.Off = off |
| o.Type = query_parser.TypTime |
| o.Time = tim |
| return &o |
| } |
| |
| func secondArgLocationCheck(db ds.Database, off int64, args []*query_parser.Operand) error { |
| // At this point, for the args that can be evaluated before execution, it is known that |
| // there are two args, a time followed by a string. |
| // Just need to check that the 2nd arg is convertible to a location. |
| if err := checkIfPossibleThatArgIsConvertibleToLocation(db, args[1]); err != nil { |
| return err |
| } |
| return nil |
| } |