Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions hrpc/hrpc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1638,6 +1638,51 @@ func TestDeserializeCellBlocksScan(t *testing.T) {
}
}

// TestMutateTimestampBounds tests the valid timestamp bounds for Mutate requests
func TestMutateTimestampBounds(t *testing.T) {
ctx := context.Background()
tn := []byte("table_name")
key := []byte("key_name")
tcases := []struct {
name string
expErr bool
ts uint64
}{
{
name: "Valid zero timestamp",
expErr: false,
ts: 0,
},
{
name: "Valid max timestamp",
expErr: false,
ts: MaxTimestamp,
},
{
name: "Invalid MaxUint64 timestamp",
expErr: true,
ts: math.MaxUint64,
},
{
name: "Invalid timestamp MaxInt64 < ts < MaxUint64",
expErr: true,
ts: math.MaxInt64 + 57,
},
}

for _, tc := range tcases {
t.Run(tc.name, func(t *testing.T) {
_, err := baseMutate(ctx, tn, key, nil, TimestampUint64(tc.ts))
if tc.expErr && err == nil {
t.Fatalf("Expected error creating Mutate with ts %d, but didn't get one", tc.ts)
}
if !tc.expErr && err != nil {
t.Fatalf("Expected no error creating Mutate with ts %d, but got: %v", tc.ts, err)
}
})
}
}

func confirmScanAttributes(ctx context.Context, s *Scan, table, start, stop []byte,
fam map[string][]string, fltr filter.Filter, numberOfRows uint32,
renewInterval time.Duration, renewalScan bool) bool {
Expand Down
12 changes: 8 additions & 4 deletions hrpc/mutate.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"context"
"encoding/binary"
"errors"
"math"
"fmt"
"time"

"github.com/tsuna/gohbase/pb"
Expand Down Expand Up @@ -100,6 +100,9 @@ func TimestampUint64(ts uint64) func(Call) error {
if !ok {
return errors.New("'TimestampUint64' option can only be used with mutation queries")
}
if ts > MaxTimestamp {
return fmt.Errorf("timestamp is greater than max: (%d > %d)", ts, MaxTimestamp)
}
m.timestamp = ts
return nil
}
Expand Down Expand Up @@ -442,8 +445,8 @@ func (m *Mutate) valuesToCellblocks() ([]byte, int32, uint32) {
cbs := make([]byte, 0, cbsLen)

var ts uint64
if m.timestamp == MaxTimestamp {
ts = math.MaxInt64 // Java's Long.MAX_VALUE use for HBase's LATEST_TIMESTAMP
if m.timestamp > MaxTimestamp {
ts = MaxTimestamp
} else {
ts = m.timestamp
}
Expand Down Expand Up @@ -494,7 +497,8 @@ var durabilities = []*pb.MutationProto_Durability{

func (m *Mutate) toProto(isCellblocks bool, cbs [][]byte) (*pb.MutateRequest, [][]byte, uint32) {
var ts *uint64
if m.timestamp != MaxTimestamp {

if m.timestamp < MaxTimestamp {
ts = &m.timestamp
}

Expand Down
13 changes: 10 additions & 3 deletions hrpc/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ package hrpc

import (
"errors"
"fmt"
"math"
"time"

"github.com/tsuna/gohbase/filter"
"github.com/tsuna/gohbase/pb"
)

// baseQuery bundles common fields that can be provided for quering requests: Scans and Gets
// baseQuery bundles common fields that can be provided for querying requests: Scans and Gets
type baseQuery struct {
families map[string][]string
filter *pb.Filter
Expand Down Expand Up @@ -150,14 +151,20 @@ func TimeRange(from, to time.Time) func(Call) error {
// TimeRangeUint64 is used as a parameter for request creation.
// Adds TimeRange constraint to a request.
// from and to should be in milliseconds
// // It will get values in range [from, to[ ('to' is exclusive).
// It will get values in range [from, to) ('to' is exclusive).
func TimeRangeUint64(from, to uint64) func(Call) error {
return func(hc Call) error {
if c, ok := hc.(hasQueryOptions); ok {
if from >= to {
// or equal is becuase 'to' is exclusive
// or equal is because 'to' is exclusive
return errors.New("'from' timestamp is greater or equal to 'to' timestamp")
}
// HBase timestamps are Long, cannot provide timestamps that exceed Long.MAX_VALUE or
// else HBase will throw an IllegalArgumentException
if from > MaxTimestamp || to > MaxTimestamp {
return fmt.Errorf("timestamp greater than MaxTimestamp: from: %d, to: %d, max: %d",
from, to, MaxTimestamp)
}
c.setTimeRangeUint64(from, to)
return nil
}
Expand Down
89 changes: 89 additions & 0 deletions hrpc/query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,3 +245,92 @@ func TestPriority(t *testing.T) {
t.Errorf("expected error when creating Put with Priority, but got none")
}
}

func TestTimeRangeUint64(t *testing.T) {
tcases := []struct {
name string
from uint64
to uint64
// the error for this invalid timerange comes from creating Scan or Get, want the timestamp
// validation to happen here instead of getting Exceptions from HBase
expErr bool
}{
{
name: "valid scan range",
from: 1,
to: 123456789,
},
{
name: "valid scan range, at MaxTimestamp",
from: MaxTimestamp - 1,
to: MaxTimestamp,
},
{
name: "valid scan range, 0 to MaxTimestamp",
from: 0,
to: MaxTimestamp,
},
{
name: "end before start",
from: 125,
to: 100,
expErr: true,
},
{
name: "end == start",
from: MaxTimestamp,
to: MaxTimestamp,
expErr: true,
},
{
name: "end beyond Long.MAX_VALUE",
from: 0,
to: math.MaxInt64 + 11,
expErr: true,
},
{
name: "invalid scan range, at MaxTimestamp",
from: MaxTimestamp,
to: MaxTimestamp + 1,
expErr: true,
},
{
name: "start and end beyond Long.MAX_VALUE",
from: math.MaxInt64 + 11,
to: math.MaxInt64 + 12,
expErr: true,
},
{
name: "MaxUint64",
from: 0,
to: math.MaxUint64,
expErr: true,
},
}

for _, tc := range tcases {
t.Run(tc.name, func(t *testing.T) {
validateErr := func(t *testing.T, err error) {
if tc.expErr {
if err == nil {
t.Fatal("Did not get error creating request as expected")
}
t.Logf("Got error as expected creating request: %v", err)
} else {
if err != nil {
t.Fatalf("Unexpected error creating request: %v", err)
}
}
}
ctx := context.Background()
table := []byte("tablename")

// Both Scans and Gets use the TimeRange
_, err := NewScan(ctx, table, TimeRangeUint64(tc.from, tc.to))
validateErr(t, err)

_, err = NewGet(ctx, table, []byte("key"), TimeRangeUint64(tc.from, tc.to))
validateErr(t, err)
})
}
}
5 changes: 3 additions & 2 deletions hrpc/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ const (
DefaultMaxVersions uint32 = 1
// MinTimestamp default value for minimum timestamp for scan queries
MinTimestamp uint64 = 0
// MaxTimestamp default value for maximum timestamp for scan queries
MaxTimestamp = math.MaxUint64
// MaxTimestamp default value for maximum timestamp for scan queries.
// This is Java's Long.MAX_VALUE
MaxTimestamp = math.MaxInt64
// DefaultMaxResultSize Maximum number of bytes fetched when calling a scanner's
// next method. The default value is 2MB, which is good for 1ge networks.
// With faster and/or high latency networks this value should be increased.
Expand Down