// ParseQuantity turns str into a Quantity, or returns an error. func ParseQuantity(str string) (*Quantity, error) { parts := splitRE.FindStringSubmatch(strings.TrimSpace(str)) // regexp returns are entire match, followed by an entry for each () section. if len(parts) != 3 { return nil, ErrFormatWrong } amount := new(inf.Dec) if _, ok := amount.SetString(parts[1]); !ok { return nil, ErrNumeric } base, exponent, format, ok := quantitySuffixer.interpret(suffix(parts[2])) if !ok { return nil, ErrSuffix } // So that no one but us has to think about suffixes, remove it. if base == 10 { amount.SetScale(amount.Scale() + Scale(exponent).infScale()) } else if base == 2 { // numericSuffix = 2 ** exponent numericSuffix := big.NewInt(1).Lsh(bigOne, uint(exponent)) ub := amount.UnscaledBig() amount.SetUnscaledBig(ub.Mul(ub, numericSuffix)) } // Cap at min/max bounds. sign := amount.Sign() if sign == -1 { amount.Neg(amount) } // This rounds non-zero values up to the minimum representable value, under the theory that // if you want some resources, you should get some resources, even if you asked for way too small // of an amount. Arguably, this should be inf.RoundHalfUp (normal rounding), but that would have // the side effect of rounding values < .5n to zero. if v, ok := amount.Unscaled(); v != int64(0) || !ok { amount.Round(amount, Nano.infScale(), inf.RoundUp) } // The max is just a simple cap. if amount.Cmp(maxAllowed) > 0 { amount.Set(maxAllowed) } if format == BinarySI && amount.Cmp(decOne) < 0 && amount.Cmp(decZero) > 0 { // This avoids rounding and hopefully confusion, too. format = DecimalSI } if sign == -1 { amount.Neg(amount) } return &Quantity{amount, format}, nil }
// Test that a TransactionRetryError will retry the read until it succeeds. The // test is designed so that if the proto timestamps are bumped during retry // a failure will occur. func TestAsOfRetry(t *testing.T) { defer leaktest.AfterTest(t)() params, cmdFilters := createTestServerParams() // Disable one phase commits because they cannot be restarted. params.Knobs.Store.(*storage.StoreTestingKnobs).DisableOnePhaseCommits = true s, sqlDB, _ := serverutils.StartServer(t, params) defer s.Stopper().Stop() const val1 = 1 const val2 = 2 const name = "boulanger" if _, err := sqlDB.Exec(` CREATE DATABASE d; CREATE TABLE d.t (s STRING PRIMARY KEY, a INT); `); err != nil { t.Fatal(err) } var tsStart string if err := sqlDB.QueryRow(` INSERT INTO d.t (s, a) VALUES ($1, $2) RETURNING cluster_logical_timestamp(); `, name, val1).Scan(&tsStart); err != nil { t.Fatal(err) } var tsVal2 string if err := sqlDB.QueryRow("UPDATE d.t SET a = $1 RETURNING cluster_logical_timestamp()", val2).Scan(&tsVal2); err != nil { t.Fatal(err) } walltime := new(inf.Dec) if _, ok := walltime.SetString(tsVal2); !ok { t.Fatalf("couldn't set decimal: %s", tsVal2) } oneTick := inf.NewDec(1, 0) // Set tsVal1 to 1ns before tsVal2. tsVal1 := walltime.Sub(walltime, oneTick).String() // Set up error injection that causes retries. magicVals := createFilterVals(nil, nil) magicVals.restartCounts = map[string]int{ name: 5, } cleanupFilter := cmdFilters.AppendFilter( func(args storagebase.FilterArgs) *roachpb.Error { magicVals.Lock() defer magicVals.Unlock() switch req := args.Req.(type) { case *roachpb.ScanRequest: for key, count := range magicVals.restartCounts { if err := checkCorrectTxn(string(req.Key), magicVals, args.Hdr.Txn); err != nil { return roachpb.NewError(err) } if count > 0 && bytes.Contains(req.Key, []byte(key)) { magicVals.restartCounts[key]-- err := roachpb.NewTransactionRetryError() magicVals.failedValues[string(req.Key)] = failureRecord{err, args.Hdr.Txn} txn := args.Hdr.Txn.Clone() txn.Timestamp = txn.Timestamp.Add(0, 1) return roachpb.NewErrorWithTxn(err, &txn) } } } return nil }, false) var i int // Query with tsVal1 which should return the first value. Since tsVal1 is just // one nanosecond before tsVal2, any proto timestamp bumping will return val2 // and error. // Must specify the WHERE here to trigger the injection errors. if err := sqlDB.QueryRow(fmt.Sprintf("SELECT a FROM d.t AS OF SYSTEM TIME %s WHERE s = '%s'", tsVal1, name)).Scan(&i); err != nil { t.Fatal(err) } else if i != val1 { t.Fatalf("unexpected val: %v", i) } cleanupFilter() // Verify that the retry errors were injected. checkRestarts(t, magicVals) // Query with tsVal2 to ensure val2 is indeed present. if err := sqlDB.QueryRow(fmt.Sprintf("SELECT a FROM d.t AS OF SYSTEM TIME %s", tsVal2)).Scan(&i); err != nil { t.Fatal(err) } else if i != val2 { t.Fatalf("unexpected val: %v", i) } }
// ParseQuantity turns str into a Quantity, or returns an error. func ParseQuantity(str string) (Quantity, error) { if len(str) == 0 { return Quantity{}, ErrFormatWrong } if str == "0" { return Quantity{Format: DecimalSI, s: str}, nil } positive, value, num, denom, suf, err := parseQuantityString(str) if err != nil { return Quantity{}, err } base, exponent, format, ok := quantitySuffixer.interpret(suffix(suf)) if !ok { return Quantity{}, ErrSuffix } precision := int32(0) scale := int32(0) mantissa := int64(1) switch format { case DecimalExponent, DecimalSI: scale = exponent precision = maxInt64Factors - int32(len(num)+len(denom)) case BinarySI: scale = 0 switch { case exponent >= 0 && len(denom) == 0: // only handle positive binary numbers with the fast path mantissa = int64(int64(mantissa) << uint64(exponent)) // 1Mi (2^20) has ~6 digits of decimal precision, so exponent*3/10 -1 is roughly the precision precision = 15 - int32(len(num)) - int32(float32(exponent)*3/10) - 1 default: precision = -1 } } if precision >= 0 { // if we have a denominator, shift the entire value to the left by the number of places in the // denominator scale -= int32(len(denom)) if scale >= int32(Nano) { shifted := num + denom var value int64 value, err := strconv.ParseInt(shifted, 10, 64) if err != nil { return Quantity{}, ErrNumeric } if result, ok := int64Multiply(value, int64(mantissa)); ok { if !positive { result = -result } // if the number is in canonical form, reuse the string switch format { case BinarySI: if exponent%10 == 0 && (value&0x07 != 0) { return Quantity{i: int64Amount{value: result, scale: Scale(scale)}, Format: format, s: str}, nil } default: if scale%3 == 0 && !strings.HasSuffix(shifted, "000") && shifted[0] != '0' { return Quantity{i: int64Amount{value: result, scale: Scale(scale)}, Format: format, s: str}, nil } } return Quantity{i: int64Amount{value: result, scale: Scale(scale)}, Format: format}, nil } } } amount := new(inf.Dec) if _, ok := amount.SetString(value); !ok { return Quantity{}, ErrNumeric } // So that no one but us has to think about suffixes, remove it. if base == 10 { amount.SetScale(amount.Scale() + Scale(exponent).infScale()) } else if base == 2 { // numericSuffix = 2 ** exponent numericSuffix := big.NewInt(1).Lsh(bigOne, uint(exponent)) ub := amount.UnscaledBig() amount.SetUnscaledBig(ub.Mul(ub, numericSuffix)) } // Cap at min/max bounds. sign := amount.Sign() if sign == -1 { amount.Neg(amount) } // This rounds non-zero values up to the minimum representable value, under the theory that // if you want some resources, you should get some resources, even if you asked for way too small // of an amount. Arguably, this should be inf.RoundHalfUp (normal rounding), but that would have // the side effect of rounding values < .5n to zero. if v, ok := amount.Unscaled(); v != int64(0) || !ok { amount.Round(amount, Nano.infScale(), inf.RoundUp) } // The max is just a simple cap. // TODO: this prevents accumulating quantities greater than int64, for instance quota across a cluster if format == BinarySI && amount.Cmp(maxAllowed.Dec) > 0 { amount.Set(maxAllowed.Dec) } if format == BinarySI && amount.Cmp(decOne) < 0 && amount.Cmp(decZero) > 0 { // This avoids rounding and hopefully confusion, too. format = DecimalSI } if sign == -1 { amount.Neg(amount) } return Quantity{d: infDecAmount{amount}, Format: format}, nil }