github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/sem/tree/as_of.go (about) 1 // Copyright 2018 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package tree 12 13 import ( 14 "context" 15 "strconv" 16 "strings" 17 "time" 18 19 "github.com/cockroachdb/apd" 20 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode" 21 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror" 22 "github.com/cockroachdb/cockroach/pkg/sql/types" 23 "github.com/cockroachdb/cockroach/pkg/util/duration" 24 "github.com/cockroachdb/cockroach/pkg/util/hlc" 25 "github.com/cockroachdb/errors" 26 ) 27 28 // FollowerReadTimestampFunctionName is the name of the function which can be 29 // used with AOST clauses to generate a timestamp likely to be safe for follower 30 // reads. 31 const FollowerReadTimestampFunctionName = "experimental_follower_read_timestamp" 32 33 var errInvalidExprForAsOf = errors.Errorf("AS OF SYSTEM TIME: only constant expressions or " + 34 FollowerReadTimestampFunctionName + " are allowed") 35 36 // EvalAsOfTimestamp evaluates the timestamp argument to an AS OF SYSTEM TIME query. 37 func EvalAsOfTimestamp( 38 ctx context.Context, asOf AsOfClause, semaCtx *SemaContext, evalCtx *EvalContext, 39 ) (tsss hlc.Timestamp, err error) { 40 // We need to save and restore the previous value of the field in 41 // semaCtx in case we are recursively called within a subquery 42 // context. 43 scalarProps := &semaCtx.Properties 44 defer scalarProps.Restore(*scalarProps) 45 scalarProps.Require("AS OF SYSTEM TIME", RejectSpecial|RejectSubqueries) 46 47 // In order to support the follower reads feature we permit this expression 48 // to be a simple invocation of the `FollowerReadTimestampFunction`. 49 // Over time we could expand the set of allowed functions or expressions. 50 // All non-function expressions must be const and must TypeCheck into a 51 // string. 52 var te TypedExpr 53 if fe, ok := asOf.Expr.(*FuncExpr); ok { 54 def, err := fe.Func.Resolve(semaCtx.SearchPath) 55 if err != nil { 56 return hlc.Timestamp{}, errInvalidExprForAsOf 57 } 58 if def.Name != FollowerReadTimestampFunctionName { 59 return hlc.Timestamp{}, errInvalidExprForAsOf 60 } 61 if te, err = fe.TypeCheck(ctx, semaCtx, types.TimestampTZ); err != nil { 62 return hlc.Timestamp{}, err 63 } 64 } else { 65 var err error 66 te, err = asOf.Expr.TypeCheck(ctx, semaCtx, types.String) 67 if err != nil { 68 return hlc.Timestamp{}, err 69 } 70 if !IsConst(evalCtx, te) { 71 return hlc.Timestamp{}, errInvalidExprForAsOf 72 } 73 } 74 75 d, err := te.Eval(evalCtx) 76 if err != nil { 77 return hlc.Timestamp{}, err 78 } 79 80 stmtTimestamp := evalCtx.GetStmtTimestamp() 81 ts, err := DatumToHLC(evalCtx, stmtTimestamp, d) 82 return ts, errors.Wrap(err, "AS OF SYSTEM TIME") 83 } 84 85 // DatumToHLC performs the conversion from a Datum to an HLC timestamp. 86 func DatumToHLC(evalCtx *EvalContext, stmtTimestamp time.Time, d Datum) (hlc.Timestamp, error) { 87 ts := hlc.Timestamp{} 88 var convErr error 89 switch d := d.(type) { 90 case *DString: 91 s := string(*d) 92 // Attempt to parse as timestamp. 93 if dt, err := ParseDTimestamp(evalCtx, s, time.Nanosecond); err == nil { 94 ts.WallTime = dt.Time.UnixNano() 95 break 96 } 97 // Attempt to parse as a decimal. 98 if dec, _, err := apd.NewFromString(s); err == nil { 99 ts, convErr = DecimalToHLC(dec) 100 break 101 } 102 // Attempt to parse as an interval. 103 if iv, err := ParseDInterval(s); err == nil { 104 if (iv.Duration == duration.Duration{}) { 105 convErr = errors.Errorf("interval value %v too small, absolute value must be >= %v", d, time.Microsecond) 106 } 107 ts.WallTime = duration.Add(stmtTimestamp, iv.Duration).UnixNano() 108 break 109 } 110 convErr = errors.Errorf("value is neither timestamp, decimal, nor interval") 111 case *DTimestamp: 112 ts.WallTime = d.UnixNano() 113 case *DTimestampTZ: 114 ts.WallTime = d.UnixNano() 115 case *DInt: 116 ts.WallTime = int64(*d) 117 case *DDecimal: 118 ts, convErr = DecimalToHLC(&d.Decimal) 119 case *DInterval: 120 ts.WallTime = duration.Add(stmtTimestamp, d.Duration).UnixNano() 121 default: 122 convErr = errors.Errorf("expected timestamp, decimal, or interval, got %s (%T)", d.ResolvedType(), d) 123 } 124 if convErr != nil { 125 return ts, convErr 126 } 127 zero := hlc.Timestamp{} 128 if ts == zero { 129 return ts, errors.Errorf("zero timestamp is invalid") 130 } else if ts.Less(zero) { 131 return ts, errors.Errorf("timestamp before 1970-01-01T00:00:00Z is invalid") 132 } 133 return ts, nil 134 } 135 136 // DecimalToHLC performs the conversion from an inputted DECIMAL datum for an 137 // AS OF SYSTEM TIME query to an HLC timestamp. 138 func DecimalToHLC(d *apd.Decimal) (hlc.Timestamp, error) { 139 // Format the decimal into a string and split on `.` to extract the nanosecond 140 // walltime and logical tick parts. 141 // TODO(mjibson): use d.Modf() instead of converting to a string. 142 s := d.Text('f') 143 parts := strings.SplitN(s, ".", 2) 144 nanos, err := strconv.ParseInt(parts[0], 10, 64) 145 if err != nil { 146 return hlc.Timestamp{}, pgerror.Wrapf(err, pgcode.Syntax, "parsing argument") 147 } 148 var logical int64 149 if len(parts) > 1 { 150 // logicalLength is the number of decimal digits expected in the 151 // logical part to the right of the decimal. See the implementation of 152 // cluster_logical_timestamp(). 153 const logicalLength = 10 154 p := parts[1] 155 if lp := len(p); lp > logicalLength { 156 return hlc.Timestamp{}, pgerror.Newf(pgcode.Syntax, "logical part has too many digits") 157 } else if lp < logicalLength { 158 p += strings.Repeat("0", logicalLength-lp) 159 } 160 logical, err = strconv.ParseInt(p, 10, 32) 161 if err != nil { 162 return hlc.Timestamp{}, pgerror.Wrapf(err, pgcode.Syntax, "parsing argument") 163 } 164 } 165 return hlc.Timestamp{ 166 WallTime: nanos, 167 Logical: int32(logical), 168 }, nil 169 }