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  }