// isAsOf analyzes a select statement to bypass the logic in newPlan(), // since that requires the transaction to be started already. If the returned // timestamp is not nil, it is the timestamp to which a transaction should // be set. // // max is a lower bound on what the transaction's timestamp will be. Used to // check that the user didn't specify a timestamp in the future. func isAsOf(planMaker *planner, stmt parser.Statement, max hlc.Timestamp) (*hlc.Timestamp, error) { s, ok := stmt.(*parser.Select) if !ok { return nil, nil } sc, ok := s.Select.(*parser.SelectClause) if !ok { return nil, nil } if sc.From == nil || sc.From.AsOf.Expr == nil { return nil, nil } te, err := sc.From.AsOf.Expr.TypeCheck(nil, parser.TypeString) if err != nil { return nil, err } d, err := te.Eval(&planMaker.evalCtx) if err != nil { return nil, err } var ts hlc.Timestamp switch d := d.(type) { case *parser.DString: // Allow nanosecond precision because the timestamp is only used by the // system and won't be returned to the user over pgwire. dt, err := parser.ParseDTimestamp(string(*d), time.Nanosecond) if err != nil { return nil, err } ts.WallTime = dt.Time.UnixNano() case *parser.DInt: ts.WallTime = int64(*d) case *parser.DDecimal: // Format the decimal into a string and split on `.` to extract the nanosecond // walltime and logical tick parts. s := d.String() parts := strings.SplitN(s, ".", 2) nanos, err := strconv.ParseInt(parts[0], 10, 64) if err != nil { return nil, errors.Wrap(err, "parse AS OF SYSTEM TIME argument") } var logical int64 if len(parts) > 1 { // logicalLength is the number of decimal digits expected in the // logical part to the right of the decimal. See the implementation of // cluster_logical_timestamp(). const logicalLength = 10 p := parts[1] if lp := len(p); lp > logicalLength { return nil, errors.Errorf("bad AS OF SYSTEM TIME argument: logical part has too many digits") } else if lp < logicalLength { p += strings.Repeat("0", logicalLength-lp) } logical, err = strconv.ParseInt(p, 10, 32) if err != nil { return nil, errors.Wrap(err, "parse AS OF SYSTEM TIME argument") } } ts.WallTime = nanos ts.Logical = int32(logical) default: return nil, fmt.Errorf("unexpected AS OF SYSTEM TIME argument: %s (%T)", d.ResolvedType(), d) } if max.Less(ts) { return nil, fmt.Errorf("cannot specify timestamp in the future") } return &ts, nil }
func TestBatchBuilderStress(t *testing.T) { defer leaktest.AfterTest(t)() stopper := stop.NewStopper() defer stopper.Stop() e := NewInMem(roachpb.Attributes{}, 1<<20) stopper.AddCloser(e) rng, _ := randutil.NewPseudoRand() for i := 0; i < 1000; i++ { count := 1 + rng.Intn(1000) func() { batch := e.NewBatch().(*rocksDBBatch) // Ensure that, even though we reach into the batch's internals with // dbPut etc, asking for the batch's Repr will get data from C++ and // not its unused builder. batch.flushes++ defer batch.Close() builder := &RocksDBBatchBuilder{} for j := 0; j < count; j++ { var ts hlc.Timestamp if rng.Float32() <= 0.9 { // Give 90% of keys timestamps. ts.WallTime = rng.Int63() if rng.Float32() <= 0.1 { // Give 10% of timestamps a non-zero logical component. ts.Logical = rng.Int31() } } key := MVCCKey{ Key: []byte(fmt.Sprintf("%d", rng.Intn(10000))), Timestamp: ts, } // Generate a random mixture of puts, deletes and merges. switch rng.Intn(3) { case 0: if err := dbPut(batch.batch, key, []byte("value")); err != nil { t.Fatal(err) } builder.Put(key, []byte("value")) case 1: if err := dbClear(batch.batch, key); err != nil { t.Fatal(err) } builder.Clear(key) case 2: if err := dbMerge(batch.batch, key, appender("bar")); err != nil { t.Fatal(err) } builder.Merge(key, appender("bar")) } } batchRepr := batch.Repr() builderRepr := builder.Finish() if !bytes.Equal(batchRepr, builderRepr) { t.Fatalf("expected [% x], but got [% x]", batchRepr, builderRepr) } }() } }