зеркало из https://github.com/github/vitess-gh.git
Merge branch 'master' into suguwork
This commit is contained in:
Коммит
29c7d9fdf0
3
Makefile
3
Makefile
|
@ -117,6 +117,9 @@ docker_lite:
|
|||
docker_guestbook:
|
||||
cd examples/kubernetes/guestbook && ./build.sh
|
||||
|
||||
docker_etcd:
|
||||
cd docker/etcd-lite && ./build.sh
|
||||
|
||||
# This rule loads the working copy of the code into a bootstrap image,
|
||||
# and then runs the tests inside Docker.
|
||||
# Example: $ make docker_test flavor=mariadb
|
||||
|
|
|
@ -3,12 +3,19 @@
|
|||
# This is the script to build the vitess/etcd-lite Docker image by extracting
|
||||
# the pre-built binaries from a vitess/etcde image.
|
||||
|
||||
version="v2.0.13"
|
||||
|
||||
set -e
|
||||
|
||||
# Build a fresh base vitess/etcd image
|
||||
(cd ../etcd ; sudo docker build -t vitess/etcd:$version .)
|
||||
|
||||
# Extract files from vitess/etcd image
|
||||
mkdir base
|
||||
sudo docker run -ti --rm -v $PWD/base:/base -u root vitess/etcd:v2.0.13 bash -c 'cp -R /go/bin/etcd /go/bin/etcdctl /base/'
|
||||
sudo docker run -ti --rm -v $PWD/base:/base -u root vitess/etcd:$version bash -c 'cp -R /go/bin/etcd /go/bin/etcdctl /base/'
|
||||
|
||||
# Build vitess/etcd-lite image
|
||||
sudo docker build -t vitess/etcd:v2.0.13-lite .
|
||||
sudo docker build -t vitess/etcd:$version-lite .
|
||||
|
||||
# Clean up temporary files
|
||||
sudo rm -rf base
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
// Copyright 2013, Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
// Imports and register the bsonp3 vtgateservice server
|
||||
|
||||
import (
|
||||
_ "github.com/youtube/vitess/go/vt/vtgate/bsonp3vtgateservice"
|
||||
)
|
|
@ -1,11 +0,0 @@
|
|||
// Copyright 2013, Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
// Imports and register the bsonp3 vtgateservice server
|
||||
|
||||
import (
|
||||
_ "github.com/youtube/vitess/go/vt/vtgate/bsonp3vtgateservice"
|
||||
)
|
|
@ -1,43 +0,0 @@
|
|||
// Copyright 2015 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package bsonp3clienttest
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/youtube/vitess/go/cmd/vtgateclienttest/goclienttest"
|
||||
"github.com/youtube/vitess/go/cmd/vtgateclienttest/services"
|
||||
"github.com/youtube/vitess/go/rpcplus"
|
||||
"github.com/youtube/vitess/go/rpcwrap/bsonrpc"
|
||||
"github.com/youtube/vitess/go/vt/vtgate/bsonp3vtgateservice"
|
||||
)
|
||||
|
||||
func TestBSONRPCP3GoClient(t *testing.T) {
|
||||
service := services.CreateServices()
|
||||
|
||||
// listen on a random port
|
||||
listener, err := net.Listen("tcp", ":0")
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot listen: %v", err)
|
||||
}
|
||||
defer listener.Close()
|
||||
|
||||
// Create a Go Rpc server and listen on the port
|
||||
server := rpcplus.NewServer()
|
||||
server.Register(bsonp3vtgateservice.New(service))
|
||||
|
||||
// create the HTTP server, serve the server from it
|
||||
handler := http.NewServeMux()
|
||||
bsonrpc.ServeCustomRPC(handler, server)
|
||||
httpServer := http.Server{
|
||||
Handler: handler,
|
||||
}
|
||||
go httpServer.Serve(listener)
|
||||
|
||||
// and run the test suite
|
||||
goclienttest.TestGoClient(t, "bsonp3", listener.Addr().String())
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
// Copyright 2015 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package bsonp3clienttest
|
||||
|
||||
import (
|
||||
// import the bsonp3 client, it will register itself
|
||||
_ "github.com/youtube/vitess/go/vt/vtgate/bsonp3vtgateconn"
|
||||
)
|
|
@ -1,11 +0,0 @@
|
|||
// Copyright 2013, Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
// Imports and register the bsonp3 vtgateservice server
|
||||
|
||||
import (
|
||||
_ "github.com/youtube/vitess/go/vt/vtgate/bsonp3vtgateservice"
|
||||
)
|
|
@ -127,12 +127,18 @@ func logError() {
|
|||
// EnableUpdateStreamService enables the RPC service for UpdateStream
|
||||
func EnableUpdateStreamService(dbname string, mysqld mysqlctl.MysqlDaemon) {
|
||||
defer logError()
|
||||
if UpdateStreamRpcService == nil {
|
||||
return
|
||||
}
|
||||
UpdateStreamRpcService.enable(dbname, mysqld)
|
||||
}
|
||||
|
||||
// DisableUpdateStreamService disables the RPC service for UpdateStream
|
||||
func DisableUpdateStreamService() {
|
||||
defer logError()
|
||||
if UpdateStreamRpcService == nil {
|
||||
return
|
||||
}
|
||||
UpdateStreamRpcService.disable()
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ import (
|
|||
|
||||
// TabletManagerProtocol is the implementation to use for tablet
|
||||
// manager protocol. It is exported for tests only.
|
||||
var TabletManagerProtocol = flag.String("tablet_manager_protocol", "bson", "the protocol to use to talk to vttablet")
|
||||
var TabletManagerProtocol = flag.String("tablet_manager_protocol", "grpc", "the protocol to use to talk to vttablet")
|
||||
|
||||
// ErrFunc is used by streaming RPCs that don't return a specific result
|
||||
type ErrFunc func() error
|
||||
|
|
|
@ -1,401 +0,0 @@
|
|||
// Copyright 2015, Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package bsonp3vtgateconn provides go rpc connectivity for VTGate,
|
||||
// with BSON-encoded proto3 structs.
|
||||
package bsonp3vtgateconn
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
mproto "github.com/youtube/vitess/go/mysql/proto"
|
||||
"github.com/youtube/vitess/go/rpcplus"
|
||||
"github.com/youtube/vitess/go/rpcwrap/bsonrpc"
|
||||
"github.com/youtube/vitess/go/vt/callerid"
|
||||
tproto "github.com/youtube/vitess/go/vt/tabletserver/proto"
|
||||
"github.com/youtube/vitess/go/vt/vterrors"
|
||||
"github.com/youtube/vitess/go/vt/vtgate/proto"
|
||||
"github.com/youtube/vitess/go/vt/vtgate/vtgateconn"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
qpb "github.com/youtube/vitess/go/vt/proto/query"
|
||||
topopb "github.com/youtube/vitess/go/vt/proto/topodata"
|
||||
pb "github.com/youtube/vitess/go/vt/proto/vtgate"
|
||||
)
|
||||
|
||||
func init() {
|
||||
vtgateconn.RegisterDialer("bsonp3", dial)
|
||||
}
|
||||
|
||||
type vtgateConn struct {
|
||||
rpcConn *rpcplus.Client
|
||||
}
|
||||
|
||||
func dial(ctx context.Context, address string, timeout time.Duration) (vtgateconn.Impl, error) {
|
||||
network := "tcp"
|
||||
if strings.Contains(address, "/") {
|
||||
network = "unix"
|
||||
}
|
||||
rpcConn, err := bsonrpc.DialHTTP(network, address, timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &vtgateConn{rpcConn: rpcConn}, nil
|
||||
}
|
||||
|
||||
func (conn *vtgateConn) Execute(ctx context.Context, query string, bindVars map[string]interface{}, tabletType topopb.TabletType, notInTransaction bool, session interface{}) (*mproto.QueryResult, interface{}, error) {
|
||||
var s *pb.Session
|
||||
if session != nil {
|
||||
s = session.(*pb.Session)
|
||||
}
|
||||
request := &pb.ExecuteRequest{
|
||||
CallerId: callerid.EffectiveCallerIDFromContext(ctx),
|
||||
Session: s,
|
||||
Query: tproto.BoundQueryToProto3(query, bindVars),
|
||||
TabletType: tabletType,
|
||||
NotInTransaction: notInTransaction,
|
||||
}
|
||||
response := &pb.ExecuteResponse{}
|
||||
if err := conn.rpcConn.Call(ctx, "VTGateP3.Execute", request, response); err != nil {
|
||||
return nil, session, vterrors.FromJSONError(err)
|
||||
}
|
||||
if response.Error != nil {
|
||||
return nil, response.Session, vterrors.FromVtRPCError(response.Error)
|
||||
}
|
||||
return mproto.Proto3ToQueryResult(response.Result), response.Session, nil
|
||||
}
|
||||
|
||||
func (conn *vtgateConn) ExecuteShards(ctx context.Context, query string, keyspace string, shards []string, bindVars map[string]interface{}, tabletType topopb.TabletType, notInTransaction bool, session interface{}) (*mproto.QueryResult, interface{}, error) {
|
||||
var s *pb.Session
|
||||
if session != nil {
|
||||
s = session.(*pb.Session)
|
||||
}
|
||||
request := &pb.ExecuteShardsRequest{
|
||||
CallerId: callerid.EffectiveCallerIDFromContext(ctx),
|
||||
Session: s,
|
||||
Query: tproto.BoundQueryToProto3(query, bindVars),
|
||||
Keyspace: keyspace,
|
||||
Shards: shards,
|
||||
TabletType: tabletType,
|
||||
NotInTransaction: notInTransaction,
|
||||
}
|
||||
response := &pb.ExecuteShardsResponse{}
|
||||
if err := conn.rpcConn.Call(ctx, "VTGateP3.ExecuteShards", request, response); err != nil {
|
||||
return nil, session, vterrors.FromJSONError(err)
|
||||
}
|
||||
if response.Error != nil {
|
||||
return nil, response.Session, vterrors.FromVtRPCError(response.Error)
|
||||
}
|
||||
return mproto.Proto3ToQueryResult(response.Result), response.Session, nil
|
||||
}
|
||||
|
||||
func (conn *vtgateConn) ExecuteKeyspaceIds(ctx context.Context, query string, keyspace string, keyspaceIds [][]byte, bindVars map[string]interface{}, tabletType topopb.TabletType, notInTransaction bool, session interface{}) (*mproto.QueryResult, interface{}, error) {
|
||||
var s *pb.Session
|
||||
if session != nil {
|
||||
s = session.(*pb.Session)
|
||||
}
|
||||
request := &pb.ExecuteKeyspaceIdsRequest{
|
||||
CallerId: callerid.EffectiveCallerIDFromContext(ctx),
|
||||
Session: s,
|
||||
Query: tproto.BoundQueryToProto3(query, bindVars),
|
||||
Keyspace: keyspace,
|
||||
KeyspaceIds: keyspaceIds,
|
||||
TabletType: tabletType,
|
||||
NotInTransaction: notInTransaction,
|
||||
}
|
||||
response := &pb.ExecuteKeyspaceIdsResponse{}
|
||||
if err := conn.rpcConn.Call(ctx, "VTGateP3.ExecuteKeyspaceIds", request, response); err != nil {
|
||||
return nil, session, vterrors.FromJSONError(err)
|
||||
}
|
||||
if response.Error != nil {
|
||||
return nil, response.Session, vterrors.FromVtRPCError(response.Error)
|
||||
}
|
||||
return mproto.Proto3ToQueryResult(response.Result), response.Session, nil
|
||||
}
|
||||
|
||||
func (conn *vtgateConn) ExecuteKeyRanges(ctx context.Context, query string, keyspace string, keyRanges []*topopb.KeyRange, bindVars map[string]interface{}, tabletType topopb.TabletType, notInTransaction bool, session interface{}) (*mproto.QueryResult, interface{}, error) {
|
||||
var s *pb.Session
|
||||
if session != nil {
|
||||
s = session.(*pb.Session)
|
||||
}
|
||||
request := &pb.ExecuteKeyRangesRequest{
|
||||
CallerId: callerid.EffectiveCallerIDFromContext(ctx),
|
||||
Session: s,
|
||||
Query: tproto.BoundQueryToProto3(query, bindVars),
|
||||
Keyspace: keyspace,
|
||||
KeyRanges: keyRanges,
|
||||
TabletType: tabletType,
|
||||
NotInTransaction: notInTransaction,
|
||||
}
|
||||
response := &pb.ExecuteKeyRangesResponse{}
|
||||
if err := conn.rpcConn.Call(ctx, "VTGateP3.ExecuteKeyRanges", request, response); err != nil {
|
||||
return nil, session, vterrors.FromJSONError(err)
|
||||
}
|
||||
if response.Error != nil {
|
||||
return nil, response.Session, vterrors.FromVtRPCError(response.Error)
|
||||
}
|
||||
return mproto.Proto3ToQueryResult(response.Result), response.Session, nil
|
||||
}
|
||||
|
||||
func (conn *vtgateConn) ExecuteEntityIds(ctx context.Context, query string, keyspace string, entityColumnName string, entityKeyspaceIDs []*pb.ExecuteEntityIdsRequest_EntityId, bindVars map[string]interface{}, tabletType topopb.TabletType, notInTransaction bool, session interface{}) (*mproto.QueryResult, interface{}, error) {
|
||||
var s *pb.Session
|
||||
if session != nil {
|
||||
s = session.(*pb.Session)
|
||||
}
|
||||
request := &pb.ExecuteEntityIdsRequest{
|
||||
CallerId: callerid.EffectiveCallerIDFromContext(ctx),
|
||||
Session: s,
|
||||
Query: tproto.BoundQueryToProto3(query, bindVars),
|
||||
Keyspace: keyspace,
|
||||
EntityColumnName: entityColumnName,
|
||||
EntityKeyspaceIds: entityKeyspaceIDs,
|
||||
TabletType: tabletType,
|
||||
NotInTransaction: notInTransaction,
|
||||
}
|
||||
response := &pb.ExecuteEntityIdsResponse{}
|
||||
if err := conn.rpcConn.Call(ctx, "VTGateP3.ExecuteEntityIds", request, response); err != nil {
|
||||
return nil, session, vterrors.FromJSONError(err)
|
||||
}
|
||||
if response.Error != nil {
|
||||
return nil, response.Session, vterrors.FromVtRPCError(response.Error)
|
||||
}
|
||||
return mproto.Proto3ToQueryResult(response.Result), response.Session, nil
|
||||
}
|
||||
|
||||
func (conn *vtgateConn) ExecuteBatchShards(ctx context.Context, queries []proto.BoundShardQuery, tabletType topopb.TabletType, asTransaction bool, session interface{}) ([]mproto.QueryResult, interface{}, error) {
|
||||
var s *pb.Session
|
||||
if session != nil {
|
||||
s = session.(*pb.Session)
|
||||
}
|
||||
request := &pb.ExecuteBatchShardsRequest{
|
||||
CallerId: callerid.EffectiveCallerIDFromContext(ctx),
|
||||
Session: s,
|
||||
Queries: proto.BoundShardQueriesToProto(queries),
|
||||
TabletType: tabletType,
|
||||
AsTransaction: asTransaction,
|
||||
}
|
||||
response := &pb.ExecuteBatchShardsResponse{}
|
||||
if err := conn.rpcConn.Call(ctx, "VTGateP3.ExecuteBatchShards", request, response); err != nil {
|
||||
return nil, session, vterrors.FromJSONError(err)
|
||||
}
|
||||
if response.Error != nil {
|
||||
return nil, response.Session, vterrors.FromVtRPCError(response.Error)
|
||||
}
|
||||
return mproto.Proto3ToQueryResults(response.Results), response.Session, nil
|
||||
}
|
||||
|
||||
func (conn *vtgateConn) ExecuteBatchKeyspaceIds(ctx context.Context, queries []proto.BoundKeyspaceIdQuery, tabletType topopb.TabletType, asTransaction bool, session interface{}) ([]mproto.QueryResult, interface{}, error) {
|
||||
var s *pb.Session
|
||||
if session != nil {
|
||||
s = session.(*pb.Session)
|
||||
}
|
||||
request := &pb.ExecuteBatchKeyspaceIdsRequest{
|
||||
CallerId: callerid.EffectiveCallerIDFromContext(ctx),
|
||||
Session: s,
|
||||
Queries: proto.BoundKeyspaceIdQueriesToProto(queries),
|
||||
TabletType: tabletType,
|
||||
AsTransaction: asTransaction,
|
||||
}
|
||||
response := &pb.ExecuteBatchKeyspaceIdsResponse{}
|
||||
if err := conn.rpcConn.Call(ctx, "VTGateP3.ExecuteBatchKeyspaceIds", request, response); err != nil {
|
||||
return nil, session, vterrors.FromJSONError(err)
|
||||
}
|
||||
if response.Error != nil {
|
||||
return nil, response.Session, vterrors.FromVtRPCError(response.Error)
|
||||
}
|
||||
return mproto.Proto3ToQueryResults(response.Results), response.Session, nil
|
||||
}
|
||||
|
||||
func (conn *vtgateConn) StreamExecute(ctx context.Context, query string, bindVars map[string]interface{}, tabletType topopb.TabletType) (<-chan *mproto.QueryResult, vtgateconn.ErrFunc, error) {
|
||||
return conn.StreamExecute2(ctx, query, bindVars, tabletType)
|
||||
}
|
||||
|
||||
func (conn *vtgateConn) StreamExecute2(ctx context.Context, query string, bindVars map[string]interface{}, tabletType topopb.TabletType) (<-chan *mproto.QueryResult, vtgateconn.ErrFunc, error) {
|
||||
req := &pb.StreamExecuteRequest{
|
||||
CallerId: callerid.EffectiveCallerIDFromContext(ctx),
|
||||
Query: tproto.BoundQueryToProto3(query, bindVars),
|
||||
TabletType: tabletType,
|
||||
}
|
||||
sr := make(chan *pb.StreamExecuteResponse, 10)
|
||||
c := conn.rpcConn.StreamGo("VTGateP3.StreamExecute", req, sr)
|
||||
srr := make(chan *qpb.QueryResult)
|
||||
go func() {
|
||||
for v := range sr {
|
||||
srr <- v.Result
|
||||
}
|
||||
close(srr)
|
||||
}()
|
||||
return sendStreamResults(c, srr)
|
||||
}
|
||||
|
||||
func (conn *vtgateConn) StreamExecuteShards(ctx context.Context, query string, keyspace string, shards []string, bindVars map[string]interface{}, tabletType topopb.TabletType) (<-chan *mproto.QueryResult, vtgateconn.ErrFunc, error) {
|
||||
return conn.StreamExecuteShards2(ctx, query, keyspace, shards, bindVars, tabletType)
|
||||
}
|
||||
|
||||
func (conn *vtgateConn) StreamExecuteShards2(ctx context.Context, query string, keyspace string, shards []string, bindVars map[string]interface{}, tabletType topopb.TabletType) (<-chan *mproto.QueryResult, vtgateconn.ErrFunc, error) {
|
||||
req := &pb.StreamExecuteShardsRequest{
|
||||
CallerId: callerid.EffectiveCallerIDFromContext(ctx),
|
||||
Query: tproto.BoundQueryToProto3(query, bindVars),
|
||||
Keyspace: keyspace,
|
||||
Shards: shards,
|
||||
TabletType: tabletType,
|
||||
}
|
||||
sr := make(chan *pb.StreamExecuteShardsResponse, 10)
|
||||
c := conn.rpcConn.StreamGo("VTGateP3.StreamExecuteShards", req, sr)
|
||||
srr := make(chan *qpb.QueryResult)
|
||||
go func() {
|
||||
for v := range sr {
|
||||
srr <- v.Result
|
||||
}
|
||||
close(srr)
|
||||
}()
|
||||
return sendStreamResults(c, srr)
|
||||
}
|
||||
|
||||
func (conn *vtgateConn) StreamExecuteKeyRanges(ctx context.Context, query string, keyspace string, keyRanges []*topopb.KeyRange, bindVars map[string]interface{}, tabletType topopb.TabletType) (<-chan *mproto.QueryResult, vtgateconn.ErrFunc, error) {
|
||||
return conn.StreamExecuteKeyRanges2(ctx, query, keyspace, keyRanges, bindVars, tabletType)
|
||||
}
|
||||
|
||||
func (conn *vtgateConn) StreamExecuteKeyRanges2(ctx context.Context, query string, keyspace string, keyRanges []*topopb.KeyRange, bindVars map[string]interface{}, tabletType topopb.TabletType) (<-chan *mproto.QueryResult, vtgateconn.ErrFunc, error) {
|
||||
req := &pb.StreamExecuteKeyRangesRequest{
|
||||
CallerId: callerid.EffectiveCallerIDFromContext(ctx),
|
||||
Query: tproto.BoundQueryToProto3(query, bindVars),
|
||||
Keyspace: keyspace,
|
||||
KeyRanges: keyRanges,
|
||||
TabletType: tabletType,
|
||||
}
|
||||
sr := make(chan *pb.StreamExecuteKeyRangesResponse, 10)
|
||||
c := conn.rpcConn.StreamGo("VTGateP3.StreamExecuteKeyRanges", req, sr)
|
||||
srr := make(chan *qpb.QueryResult)
|
||||
go func() {
|
||||
for v := range sr {
|
||||
srr <- v.Result
|
||||
}
|
||||
close(srr)
|
||||
}()
|
||||
return sendStreamResults(c, srr)
|
||||
}
|
||||
|
||||
func (conn *vtgateConn) StreamExecuteKeyspaceIds(ctx context.Context, query string, keyspace string, keyspaceIds [][]byte, bindVars map[string]interface{}, tabletType topopb.TabletType) (<-chan *mproto.QueryResult, vtgateconn.ErrFunc, error) {
|
||||
return conn.StreamExecuteKeyspaceIds2(ctx, query, keyspace, keyspaceIds, bindVars, tabletType)
|
||||
}
|
||||
|
||||
func (conn *vtgateConn) StreamExecuteKeyspaceIds2(ctx context.Context, query string, keyspace string, keyspaceIds [][]byte, bindVars map[string]interface{}, tabletType topopb.TabletType) (<-chan *mproto.QueryResult, vtgateconn.ErrFunc, error) {
|
||||
req := &pb.StreamExecuteKeyspaceIdsRequest{
|
||||
CallerId: callerid.EffectiveCallerIDFromContext(ctx),
|
||||
Query: tproto.BoundQueryToProto3(query, bindVars),
|
||||
Keyspace: keyspace,
|
||||
KeyspaceIds: keyspaceIds,
|
||||
TabletType: tabletType,
|
||||
}
|
||||
sr := make(chan *pb.StreamExecuteKeyspaceIdsResponse, 10)
|
||||
c := conn.rpcConn.StreamGo("VTGateP3.StreamExecuteKeyspaceIds", req, sr)
|
||||
srr := make(chan *qpb.QueryResult)
|
||||
go func() {
|
||||
for v := range sr {
|
||||
srr <- v.Result
|
||||
}
|
||||
close(srr)
|
||||
}()
|
||||
return sendStreamResults(c, srr)
|
||||
}
|
||||
|
||||
func sendStreamResults(c *rpcplus.Call, sr chan *qpb.QueryResult) (<-chan *mproto.QueryResult, vtgateconn.ErrFunc, error) {
|
||||
srout := make(chan *mproto.QueryResult, 1)
|
||||
go func() {
|
||||
defer close(srout)
|
||||
for r := range sr {
|
||||
srout <- mproto.Proto3ToQueryResult(r)
|
||||
}
|
||||
}()
|
||||
errFunc := func() error {
|
||||
return vterrors.FromJSONError(c.Error)
|
||||
}
|
||||
return srout, errFunc, nil
|
||||
}
|
||||
|
||||
func (conn *vtgateConn) Begin(ctx context.Context) (interface{}, error) {
|
||||
return conn.Begin2(ctx)
|
||||
}
|
||||
|
||||
func (conn *vtgateConn) Commit(ctx context.Context, session interface{}) error {
|
||||
return conn.Commit2(ctx, session)
|
||||
}
|
||||
|
||||
func (conn *vtgateConn) Rollback(ctx context.Context, session interface{}) error {
|
||||
return conn.Rollback2(ctx, session)
|
||||
}
|
||||
|
||||
func (conn *vtgateConn) Begin2(ctx context.Context) (interface{}, error) {
|
||||
request := &pb.BeginRequest{
|
||||
CallerId: callerid.EffectiveCallerIDFromContext(ctx),
|
||||
}
|
||||
response := &pb.BeginResponse{}
|
||||
if err := conn.rpcConn.Call(ctx, "VTGateP3.Begin", request, response); err != nil {
|
||||
return nil, vterrors.FromJSONError(err)
|
||||
}
|
||||
// Return a non-nil pointer
|
||||
session := &pb.Session{}
|
||||
if response.Session != nil {
|
||||
session = response.Session
|
||||
}
|
||||
return session, nil
|
||||
}
|
||||
|
||||
func (conn *vtgateConn) Commit2(ctx context.Context, session interface{}) error {
|
||||
s := session.(*pb.Session)
|
||||
request := &pb.CommitRequest{
|
||||
CallerId: callerid.EffectiveCallerIDFromContext(ctx),
|
||||
Session: s,
|
||||
}
|
||||
response := &pb.CommitResponse{}
|
||||
if err := conn.rpcConn.Call(ctx, "VTGateP3.Commit", request, response); err != nil {
|
||||
return vterrors.FromJSONError(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (conn *vtgateConn) Rollback2(ctx context.Context, session interface{}) error {
|
||||
s := session.(*pb.Session)
|
||||
request := &pb.RollbackRequest{
|
||||
CallerId: callerid.EffectiveCallerIDFromContext(ctx),
|
||||
Session: s,
|
||||
}
|
||||
response := &pb.RollbackResponse{}
|
||||
if err := conn.rpcConn.Call(ctx, "VTGateP3.Rollback", request, response); err != nil {
|
||||
return vterrors.FromJSONError(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (conn *vtgateConn) SplitQuery(ctx context.Context, keyspace string, query string, bindVars map[string]interface{}, splitColumn string, splitCount int) ([]*pb.SplitQueryResponse_Part, error) {
|
||||
request := &pb.SplitQueryRequest{
|
||||
CallerId: callerid.EffectiveCallerIDFromContext(ctx),
|
||||
Keyspace: keyspace,
|
||||
Query: tproto.BoundQueryToProto3(query, bindVars),
|
||||
SplitColumn: splitColumn,
|
||||
SplitCount: int64(splitCount),
|
||||
}
|
||||
response := &pb.SplitQueryResponse{}
|
||||
if err := conn.rpcConn.Call(ctx, "VTGateP3.SplitQuery", request, response); err != nil {
|
||||
return nil, vterrors.FromJSONError(err)
|
||||
}
|
||||
return response.Splits, nil
|
||||
}
|
||||
|
||||
func (conn *vtgateConn) GetSrvKeyspace(ctx context.Context, keyspace string) (*topopb.SrvKeyspace, error) {
|
||||
request := &pb.GetSrvKeyspaceRequest{
|
||||
Keyspace: keyspace,
|
||||
}
|
||||
response := &pb.GetSrvKeyspaceResponse{}
|
||||
if err := conn.rpcConn.Call(ctx, "VTGateP3.GetSrvKeyspace", request, response); err != nil {
|
||||
return nil, vterrors.FromJSONError(err)
|
||||
}
|
||||
return response.SrvKeyspace, nil
|
||||
}
|
||||
|
||||
func (conn *vtgateConn) Close() {
|
||||
conn.rpcConn.Close()
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
// Copyright 2015, Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package bsonp3vtgateconn
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/youtube/vitess/go/rpcplus"
|
||||
"github.com/youtube/vitess/go/rpcwrap/bsonrpc"
|
||||
"github.com/youtube/vitess/go/vt/vtgate/bsonp3vtgateservice"
|
||||
"github.com/youtube/vitess/go/vt/vtgate/vtgateconntest"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// TestBsonP3VTGateConn makes sure the BsonP3RPC service works
|
||||
func TestBsonP3VTGateConn(t *testing.T) {
|
||||
// fake service
|
||||
service := vtgateconntest.CreateFakeServer(t)
|
||||
|
||||
// listen on a random port
|
||||
listener, err := net.Listen("tcp", ":0")
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot listen: %v", err)
|
||||
}
|
||||
|
||||
// Create a Go Rpc server and listen on the port
|
||||
server := rpcplus.NewServer()
|
||||
server.Register(bsonp3vtgateservice.New(service))
|
||||
|
||||
// create the HTTP server, serve the server from it
|
||||
handler := http.NewServeMux()
|
||||
bsonrpc.ServeCustomRPC(handler, server)
|
||||
httpServer := http.Server{
|
||||
Handler: handler,
|
||||
}
|
||||
go httpServer.Serve(listener)
|
||||
|
||||
// Create a Go RPC client connecting to the server
|
||||
ctx := context.Background()
|
||||
client, err := dial(ctx, listener.Addr().String(), 30*time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("dial failed: %v", err)
|
||||
}
|
||||
vtgateconntest.RegisterTestDialProtocol(client)
|
||||
|
||||
// run the test suite
|
||||
vtgateconntest.TestSuite(t, client, service)
|
||||
vtgateconntest.TestErrorSuite(t, service)
|
||||
|
||||
// and clean up
|
||||
client.Close()
|
||||
}
|
|
@ -1,370 +0,0 @@
|
|||
// Copyright 2012, Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package bsonp3vtgateservice provides to go rpc glue for vtgate, with
|
||||
// BSON-encoded proto3 structs.
|
||||
package bsonp3vtgateservice
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"time"
|
||||
|
||||
"github.com/youtube/vitess/go/vt/callerid"
|
||||
"github.com/youtube/vitess/go/vt/servenv"
|
||||
"github.com/youtube/vitess/go/vt/vterrors"
|
||||
"github.com/youtube/vitess/go/vt/vtgate"
|
||||
"github.com/youtube/vitess/go/vt/vtgate/proto"
|
||||
"github.com/youtube/vitess/go/vt/vtgate/vtgateservice"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
mproto "github.com/youtube/vitess/go/mysql/proto"
|
||||
pb "github.com/youtube/vitess/go/vt/proto/vtgate"
|
||||
tproto "github.com/youtube/vitess/go/vt/tabletserver/proto"
|
||||
)
|
||||
|
||||
var (
|
||||
rpcTimeout = flag.Duration("bsonp3_timeout", 20*time.Second, "rpc timeout")
|
||||
)
|
||||
|
||||
// VTGateP3 is the public structure that is exported via BSON RPC
|
||||
type VTGateP3 struct {
|
||||
server vtgateservice.VTGateService
|
||||
}
|
||||
|
||||
// Execute is the RPC version of vtgateservice.VTGateService method
|
||||
func (vtg *VTGateP3) Execute(ctx context.Context, request *pb.ExecuteRequest, response *pb.ExecuteResponse) (err error) {
|
||||
defer vtg.server.HandlePanic(&err)
|
||||
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(*rpcTimeout))
|
||||
defer cancel()
|
||||
ctx = callerid.NewContext(ctx,
|
||||
request.CallerId,
|
||||
callerid.NewImmediateCallerID("bsonp3 client"))
|
||||
reply := new(proto.QueryResult)
|
||||
executeErr := vtg.server.Execute(ctx, string(request.Query.Sql), tproto.Proto3ToBindVariables(request.Query.BindVariables), request.TabletType, proto.ProtoToSession(request.Session), request.NotInTransaction, reply)
|
||||
if executeErr == nil {
|
||||
response.Error = vtgate.RPCErrorToVtRPCError(reply.Err)
|
||||
response.Result = mproto.QueryResultToProto3(reply.Result)
|
||||
response.Session = proto.SessionToProto(reply.Session)
|
||||
}
|
||||
return vterrors.ToJSONError(executeErr)
|
||||
}
|
||||
|
||||
// ExecuteShards is the RPC version of vtgateservice.VTGateService method
|
||||
func (vtg *VTGateP3) ExecuteShards(ctx context.Context, request *pb.ExecuteShardsRequest, response *pb.ExecuteShardsResponse) (err error) {
|
||||
defer vtg.server.HandlePanic(&err)
|
||||
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(*rpcTimeout))
|
||||
defer cancel()
|
||||
ctx = callerid.NewContext(ctx,
|
||||
request.CallerId,
|
||||
callerid.NewImmediateCallerID("bsonp3 client"))
|
||||
reply := new(proto.QueryResult)
|
||||
executeErr := vtg.server.ExecuteShards(ctx,
|
||||
string(request.Query.Sql),
|
||||
tproto.Proto3ToBindVariables(request.Query.BindVariables),
|
||||
request.Keyspace,
|
||||
request.Shards,
|
||||
request.TabletType,
|
||||
proto.ProtoToSession(request.Session),
|
||||
request.NotInTransaction,
|
||||
reply)
|
||||
if executeErr == nil {
|
||||
response.Error = vtgate.RPCErrorToVtRPCError(reply.Err)
|
||||
response.Result = mproto.QueryResultToProto3(reply.Result)
|
||||
response.Session = proto.SessionToProto(reply.Session)
|
||||
}
|
||||
return vterrors.ToJSONError(executeErr)
|
||||
}
|
||||
|
||||
// ExecuteKeyspaceIds is the RPC version of vtgateservice.VTGateService method
|
||||
func (vtg *VTGateP3) ExecuteKeyspaceIds(ctx context.Context, request *pb.ExecuteKeyspaceIdsRequest, response *pb.ExecuteKeyspaceIdsResponse) (err error) {
|
||||
defer vtg.server.HandlePanic(&err)
|
||||
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(*rpcTimeout))
|
||||
defer cancel()
|
||||
ctx = callerid.NewContext(ctx,
|
||||
request.CallerId,
|
||||
callerid.NewImmediateCallerID("bsonp3 client"))
|
||||
reply := new(proto.QueryResult)
|
||||
executeErr := vtg.server.ExecuteKeyspaceIds(ctx,
|
||||
string(request.Query.Sql),
|
||||
tproto.Proto3ToBindVariables(request.Query.BindVariables),
|
||||
request.Keyspace,
|
||||
request.KeyspaceIds,
|
||||
request.TabletType,
|
||||
proto.ProtoToSession(request.Session),
|
||||
request.NotInTransaction,
|
||||
reply)
|
||||
if executeErr == nil {
|
||||
response.Error = vtgate.RPCErrorToVtRPCError(reply.Err)
|
||||
response.Result = mproto.QueryResultToProto3(reply.Result)
|
||||
response.Session = proto.SessionToProto(reply.Session)
|
||||
}
|
||||
return vterrors.ToJSONError(executeErr)
|
||||
}
|
||||
|
||||
// ExecuteKeyRanges is the RPC version of vtgateservice.VTGateService method
|
||||
func (vtg *VTGateP3) ExecuteKeyRanges(ctx context.Context, request *pb.ExecuteKeyRangesRequest, response *pb.ExecuteKeyRangesResponse) (err error) {
|
||||
defer vtg.server.HandlePanic(&err)
|
||||
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(*rpcTimeout))
|
||||
defer cancel()
|
||||
ctx = callerid.NewContext(ctx,
|
||||
request.CallerId,
|
||||
callerid.NewImmediateCallerID("bsonp3 client"))
|
||||
reply := new(proto.QueryResult)
|
||||
executeErr := vtg.server.ExecuteKeyRanges(ctx,
|
||||
string(request.Query.Sql),
|
||||
tproto.Proto3ToBindVariables(request.Query.BindVariables),
|
||||
request.Keyspace,
|
||||
request.KeyRanges,
|
||||
request.TabletType,
|
||||
proto.ProtoToSession(request.Session),
|
||||
request.NotInTransaction,
|
||||
reply)
|
||||
if executeErr == nil {
|
||||
response.Error = vtgate.RPCErrorToVtRPCError(reply.Err)
|
||||
response.Result = mproto.QueryResultToProto3(reply.Result)
|
||||
response.Session = proto.SessionToProto(reply.Session)
|
||||
}
|
||||
return vterrors.ToJSONError(executeErr)
|
||||
}
|
||||
|
||||
// ExecuteEntityIds is the RPC version of vtgateservice.VTGateService method
|
||||
func (vtg *VTGateP3) ExecuteEntityIds(ctx context.Context, request *pb.ExecuteEntityIdsRequest, response *pb.ExecuteEntityIdsResponse) (err error) {
|
||||
defer vtg.server.HandlePanic(&err)
|
||||
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(*rpcTimeout))
|
||||
defer cancel()
|
||||
ctx = callerid.NewContext(ctx,
|
||||
request.CallerId,
|
||||
callerid.NewImmediateCallerID("bsonp3 client"))
|
||||
reply := new(proto.QueryResult)
|
||||
executeErr := vtg.server.ExecuteEntityIds(ctx,
|
||||
string(request.Query.Sql),
|
||||
tproto.Proto3ToBindVariables(request.Query.BindVariables),
|
||||
request.Keyspace,
|
||||
request.EntityColumnName,
|
||||
request.EntityKeyspaceIds,
|
||||
request.TabletType,
|
||||
proto.ProtoToSession(request.Session),
|
||||
request.NotInTransaction,
|
||||
reply)
|
||||
if executeErr == nil {
|
||||
response.Error = vtgate.RPCErrorToVtRPCError(reply.Err)
|
||||
response.Result = mproto.QueryResultToProto3(reply.Result)
|
||||
response.Session = proto.SessionToProto(reply.Session)
|
||||
}
|
||||
return vterrors.ToJSONError(executeErr)
|
||||
}
|
||||
|
||||
// ExecuteBatchShards is the RPC version of vtgateservice.VTGateService method
|
||||
func (vtg *VTGateP3) ExecuteBatchShards(ctx context.Context, request *pb.ExecuteBatchShardsRequest, response *pb.ExecuteBatchShardsResponse) (err error) {
|
||||
defer vtg.server.HandlePanic(&err)
|
||||
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(*rpcTimeout))
|
||||
defer cancel()
|
||||
ctx = callerid.NewContext(ctx,
|
||||
request.CallerId,
|
||||
callerid.NewImmediateCallerID("bsonp3 client"))
|
||||
reply := new(proto.QueryResultList)
|
||||
executeErr := vtg.server.ExecuteBatchShards(ctx,
|
||||
proto.ProtoToBoundShardQueries(request.Queries),
|
||||
request.TabletType,
|
||||
request.AsTransaction,
|
||||
proto.ProtoToSession(request.Session),
|
||||
reply)
|
||||
if executeErr == nil {
|
||||
response.Error = vtgate.RPCErrorToVtRPCError(reply.Err)
|
||||
response.Results = tproto.QueryResultListToProto3(reply.List)
|
||||
response.Session = proto.SessionToProto(reply.Session)
|
||||
}
|
||||
return vterrors.ToJSONError(executeErr)
|
||||
}
|
||||
|
||||
// ExecuteBatchKeyspaceIds is the RPC version of
|
||||
// vtgateservice.VTGateService method
|
||||
func (vtg *VTGateP3) ExecuteBatchKeyspaceIds(ctx context.Context, request *pb.ExecuteBatchKeyspaceIdsRequest, response *pb.ExecuteBatchKeyspaceIdsResponse) (err error) {
|
||||
defer vtg.server.HandlePanic(&err)
|
||||
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(*rpcTimeout))
|
||||
defer cancel()
|
||||
ctx = callerid.NewContext(ctx,
|
||||
request.CallerId,
|
||||
callerid.NewImmediateCallerID("bsonp3 client"))
|
||||
reply := new(proto.QueryResultList)
|
||||
executeErr := vtg.server.ExecuteBatchKeyspaceIds(ctx,
|
||||
proto.ProtoToBoundKeyspaceIdQueries(request.Queries),
|
||||
request.TabletType,
|
||||
request.AsTransaction,
|
||||
proto.ProtoToSession(request.Session),
|
||||
reply)
|
||||
if executeErr == nil {
|
||||
response.Error = vtgate.RPCErrorToVtRPCError(reply.Err)
|
||||
response.Results = tproto.QueryResultListToProto3(reply.List)
|
||||
response.Session = proto.SessionToProto(reply.Session)
|
||||
}
|
||||
return vterrors.ToJSONError(executeErr)
|
||||
}
|
||||
|
||||
// StreamExecute is the RPC version of vtgateservice.VTGateService method
|
||||
func (vtg *VTGateP3) StreamExecute(ctx context.Context, request *pb.StreamExecuteRequest, sendReply func(interface{}) error) (err error) {
|
||||
defer vtg.server.HandlePanic(&err)
|
||||
ctx = callerid.NewContext(ctx,
|
||||
request.CallerId,
|
||||
callerid.NewImmediateCallerID("bsonp3 client"))
|
||||
vtgErr := vtg.server.StreamExecute(ctx,
|
||||
string(request.Query.Sql),
|
||||
tproto.Proto3ToBindVariables(request.Query.BindVariables),
|
||||
request.TabletType,
|
||||
func(reply *proto.QueryResult) error {
|
||||
return sendReply(&pb.StreamExecuteResponse{
|
||||
Result: mproto.QueryResultToProto3(reply.Result),
|
||||
})
|
||||
})
|
||||
return vterrors.ToJSONError(vtgErr)
|
||||
}
|
||||
|
||||
// StreamExecuteShards is the RPC version of vtgateservice.VTGateService method
|
||||
func (vtg *VTGateP3) StreamExecuteShards(ctx context.Context, request *pb.StreamExecuteShardsRequest, sendReply func(interface{}) error) (err error) {
|
||||
defer vtg.server.HandlePanic(&err)
|
||||
ctx = callerid.NewContext(ctx,
|
||||
request.CallerId,
|
||||
callerid.NewImmediateCallerID("bsonp3 client"))
|
||||
vtgErr := vtg.server.StreamExecuteShards(ctx,
|
||||
string(request.Query.Sql),
|
||||
tproto.Proto3ToBindVariables(request.Query.BindVariables),
|
||||
request.Keyspace,
|
||||
request.Shards,
|
||||
request.TabletType,
|
||||
func(value *proto.QueryResult) error {
|
||||
return sendReply(&pb.StreamExecuteShardsResponse{
|
||||
Result: mproto.QueryResultToProto3(value.Result),
|
||||
})
|
||||
})
|
||||
return vterrors.ToJSONError(vtgErr)
|
||||
}
|
||||
|
||||
// StreamExecuteKeyspaceIds is the RPC version of
|
||||
// vtgateservice.VTGateService method
|
||||
func (vtg *VTGateP3) StreamExecuteKeyspaceIds(ctx context.Context, request *pb.StreamExecuteKeyspaceIdsRequest, sendReply func(interface{}) error) (err error) {
|
||||
defer vtg.server.HandlePanic(&err)
|
||||
ctx = callerid.NewContext(ctx,
|
||||
request.CallerId,
|
||||
callerid.NewImmediateCallerID("bsonp3 client"))
|
||||
vtgErr := vtg.server.StreamExecuteKeyspaceIds(ctx,
|
||||
string(request.Query.Sql),
|
||||
tproto.Proto3ToBindVariables(request.Query.BindVariables),
|
||||
request.Keyspace,
|
||||
request.KeyspaceIds,
|
||||
request.TabletType,
|
||||
func(value *proto.QueryResult) error {
|
||||
return sendReply(&pb.StreamExecuteKeyspaceIdsResponse{
|
||||
Result: mproto.QueryResultToProto3(value.Result),
|
||||
})
|
||||
})
|
||||
return vterrors.ToJSONError(vtgErr)
|
||||
}
|
||||
|
||||
// StreamExecuteKeyRanges is the RPC version of
|
||||
// vtgateservice.VTGateService method
|
||||
func (vtg *VTGateP3) StreamExecuteKeyRanges(ctx context.Context, request *pb.StreamExecuteKeyRangesRequest, sendReply func(interface{}) error) (err error) {
|
||||
defer vtg.server.HandlePanic(&err)
|
||||
ctx = callerid.NewContext(ctx,
|
||||
request.CallerId,
|
||||
callerid.NewImmediateCallerID("bsonp3 client"))
|
||||
vtgErr := vtg.server.StreamExecuteKeyRanges(ctx,
|
||||
string(request.Query.Sql),
|
||||
tproto.Proto3ToBindVariables(request.Query.BindVariables),
|
||||
request.Keyspace,
|
||||
request.KeyRanges,
|
||||
request.TabletType,
|
||||
func(value *proto.QueryResult) error {
|
||||
return sendReply(&pb.StreamExecuteKeyRangesResponse{
|
||||
Result: mproto.QueryResultToProto3(value.Result),
|
||||
})
|
||||
})
|
||||
return vterrors.ToJSONError(vtgErr)
|
||||
}
|
||||
|
||||
// Begin is the RPC version of vtgateservice.VTGateService method
|
||||
func (vtg *VTGateP3) Begin(ctx context.Context, request *pb.BeginRequest, response *pb.BeginResponse) (err error) {
|
||||
defer vtg.server.HandlePanic(&err)
|
||||
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(*rpcTimeout))
|
||||
defer cancel()
|
||||
ctx = callerid.NewContext(ctx,
|
||||
request.CallerId,
|
||||
callerid.NewImmediateCallerID("bsonp3 client"))
|
||||
outSession := new(proto.Session)
|
||||
vtgErr := vtg.server.Begin(ctx, outSession)
|
||||
if vtgErr == nil {
|
||||
response.Session = proto.SessionToProto(outSession)
|
||||
return nil
|
||||
}
|
||||
return vterrors.ToJSONError(vtgErr)
|
||||
}
|
||||
|
||||
// Commit is the RPC version of vtgateservice.VTGateService method
|
||||
func (vtg *VTGateP3) Commit(ctx context.Context, request *pb.CommitRequest, response *pb.CommitResponse) (err error) {
|
||||
defer vtg.server.HandlePanic(&err)
|
||||
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(*rpcTimeout))
|
||||
defer cancel()
|
||||
ctx = callerid.NewContext(ctx,
|
||||
request.CallerId,
|
||||
callerid.NewImmediateCallerID("bsonp3 client"))
|
||||
vtgErr := vtg.server.Commit(ctx, proto.ProtoToSession(request.Session))
|
||||
return vterrors.ToJSONError(vtgErr)
|
||||
}
|
||||
|
||||
// Rollback is the RPC version of vtgateservice.VTGateService method
|
||||
func (vtg *VTGateP3) Rollback(ctx context.Context, request *pb.RollbackRequest, response *pb.RollbackResponse) (err error) {
|
||||
defer vtg.server.HandlePanic(&err)
|
||||
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(*rpcTimeout))
|
||||
defer cancel()
|
||||
ctx = callerid.NewContext(ctx,
|
||||
request.CallerId,
|
||||
callerid.NewImmediateCallerID("bsonp3 client"))
|
||||
vtgErr := vtg.server.Rollback(ctx, proto.ProtoToSession(request.Session))
|
||||
return vterrors.ToJSONError(vtgErr)
|
||||
}
|
||||
|
||||
// SplitQuery is the RPC version of vtgateservice.VTGateService method
|
||||
func (vtg *VTGateP3) SplitQuery(ctx context.Context, request *pb.SplitQueryRequest, response *pb.SplitQueryResponse) (err error) {
|
||||
defer vtg.server.HandlePanic(&err)
|
||||
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(*rpcTimeout))
|
||||
defer cancel()
|
||||
ctx = callerid.NewContext(ctx,
|
||||
request.CallerId,
|
||||
callerid.NewImmediateCallerID("bsonp3 client"))
|
||||
splits, vtgErr := vtg.server.SplitQuery(ctx,
|
||||
request.Keyspace,
|
||||
string(request.Query.Sql),
|
||||
tproto.Proto3ToBindVariables(request.Query.BindVariables),
|
||||
request.SplitColumn,
|
||||
int(request.SplitCount))
|
||||
if vtgErr != nil {
|
||||
return vterrors.ToJSONError(vtgErr)
|
||||
}
|
||||
response.Splits = splits
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSrvKeyspace is the RPC version of vtgateservice.VTGateService method
|
||||
func (vtg *VTGateP3) GetSrvKeyspace(ctx context.Context, request *pb.GetSrvKeyspaceRequest, response *pb.GetSrvKeyspaceResponse) (err error) {
|
||||
defer vtg.server.HandlePanic(&err)
|
||||
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(*rpcTimeout))
|
||||
defer cancel()
|
||||
sk, vtgErr := vtg.server.GetSrvKeyspace(ctx, request.Keyspace)
|
||||
if vtgErr != nil {
|
||||
return vterrors.ToJSONError(vtgErr)
|
||||
}
|
||||
response.SrvKeyspace = sk
|
||||
return nil
|
||||
}
|
||||
|
||||
// New returns a new VTGateP3 service
|
||||
func New(vtGate vtgateservice.VTGateService) *VTGateP3 {
|
||||
return &VTGateP3{vtGate}
|
||||
}
|
||||
|
||||
func init() {
|
||||
vtgate.RegisterVTGates = append(vtgate.RegisterVTGates, func(vtGate vtgateservice.VTGateService) {
|
||||
// Using same name as regular gorpc, so they're enabled/disabled together.
|
||||
servenv.Register("vtgateservice", New(vtGate))
|
||||
})
|
||||
}
|
|
@ -12,7 +12,6 @@ import (
|
|||
|
||||
"github.com/youtube/vitess/go/rpcplus"
|
||||
"github.com/youtube/vitess/go/rpcwrap/bsonrpc"
|
||||
"github.com/youtube/vitess/go/vt/vtgate"
|
||||
"github.com/youtube/vitess/go/vt/vtgate/gorpcvtgateservice"
|
||||
"github.com/youtube/vitess/go/vt/vtgate/vtgateconntest"
|
||||
"golang.org/x/net/context"
|
||||
|
@ -32,8 +31,6 @@ func TestGoRPCVTGateConn(t *testing.T) {
|
|||
// Create a Go Rpc server and listen on the port
|
||||
server := rpcplus.NewServer()
|
||||
server.Register(gorpcvtgateservice.New(service))
|
||||
// TODO(aaijazi): remove this flag once all VtGate Gorpc clients properly support the new behavior
|
||||
*vtgate.RPCErrorOnlyInReply = true
|
||||
|
||||
// create the HTTP server, serve the server from it
|
||||
handler := http.NewServeMux()
|
||||
|
|
|
@ -48,10 +48,7 @@ func (vtg *VTGate) Execute(ctx context.Context, request *proto.Query, reply *pro
|
|||
request.NotInTransaction,
|
||||
reply)
|
||||
vtgate.AddVtGateError(vtgErr, &reply.Err)
|
||||
if *vtgate.RPCErrorOnlyInReply {
|
||||
return nil
|
||||
}
|
||||
return vtgErr
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExecuteShard is the RPC version of vtgateservice.VTGateService method
|
||||
|
@ -72,10 +69,7 @@ func (vtg *VTGate) ExecuteShard(ctx context.Context, request *proto.QueryShard,
|
|||
request.NotInTransaction,
|
||||
reply)
|
||||
vtgate.AddVtGateError(vtgErr, &reply.Err)
|
||||
if *vtgate.RPCErrorOnlyInReply {
|
||||
return nil
|
||||
}
|
||||
return vtgErr
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExecuteKeyspaceIds is the RPC version of vtgateservice.VTGateService method
|
||||
|
@ -96,10 +90,7 @@ func (vtg *VTGate) ExecuteKeyspaceIds(ctx context.Context, request *proto.Keyspa
|
|||
request.NotInTransaction,
|
||||
reply)
|
||||
vtgate.AddVtGateError(vtgErr, &reply.Err)
|
||||
if *vtgate.RPCErrorOnlyInReply {
|
||||
return nil
|
||||
}
|
||||
return vtgErr
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExecuteKeyRanges is the RPC version of vtgateservice.VTGateService method
|
||||
|
@ -120,10 +111,7 @@ func (vtg *VTGate) ExecuteKeyRanges(ctx context.Context, request *proto.KeyRange
|
|||
request.NotInTransaction,
|
||||
reply)
|
||||
vtgate.AddVtGateError(vtgErr, &reply.Err)
|
||||
if *vtgate.RPCErrorOnlyInReply {
|
||||
return nil
|
||||
}
|
||||
return vtgErr
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExecuteEntityIds is the RPC version of vtgateservice.VTGateService method
|
||||
|
@ -145,10 +133,7 @@ func (vtg *VTGate) ExecuteEntityIds(ctx context.Context, request *proto.EntityId
|
|||
request.NotInTransaction,
|
||||
reply)
|
||||
vtgate.AddVtGateError(vtgErr, &reply.Err)
|
||||
if *vtgate.RPCErrorOnlyInReply {
|
||||
return nil
|
||||
}
|
||||
return vtgErr
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExecuteBatchShard is the RPC version of vtgateservice.VTGateService method
|
||||
|
@ -166,10 +151,7 @@ func (vtg *VTGate) ExecuteBatchShard(ctx context.Context, request *proto.BatchQu
|
|||
request.Session,
|
||||
reply)
|
||||
vtgate.AddVtGateError(vtgErr, &reply.Err)
|
||||
if *vtgate.RPCErrorOnlyInReply {
|
||||
return nil
|
||||
}
|
||||
return vtgErr
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExecuteBatchKeyspaceIds is the RPC version of
|
||||
|
@ -188,10 +170,7 @@ func (vtg *VTGate) ExecuteBatchKeyspaceIds(ctx context.Context, request *proto.K
|
|||
request.Session,
|
||||
reply)
|
||||
vtgate.AddVtGateError(vtgErr, &reply.Err)
|
||||
if *vtgate.RPCErrorOnlyInReply {
|
||||
return nil
|
||||
}
|
||||
return vtgErr
|
||||
return nil
|
||||
}
|
||||
|
||||
// StreamExecute is the RPC version of vtgateservice.VTGateService method
|
||||
|
@ -225,18 +204,10 @@ func (vtg *VTGate) StreamExecute2(ctx context.Context, request *proto.Query, sen
|
|||
if vtgErr == nil {
|
||||
return nil
|
||||
}
|
||||
if *vtgate.RPCErrorOnlyInReply {
|
||||
// If there was an app error, send a QueryResult back with it.
|
||||
qr := new(proto.QueryResult)
|
||||
vtgate.AddVtGateError(vtgErr, &qr.Err)
|
||||
// Sending back errors this way is not backwards compatible. If a (new) server sends an additional
|
||||
// QueryResult with an error, and the (old) client doesn't know how to read it, it will cause
|
||||
// problems where the client will get out of sync with the number of QueryResults sent.
|
||||
// That's why this the error is only sent this way when the --rpc_errors_only_in_reply flag is set
|
||||
// (signalling that all clients are able to handle new-style errors).
|
||||
return sendReply(qr)
|
||||
}
|
||||
return vtgErr
|
||||
// If there was an app error, send a QueryResult back with it.
|
||||
qr := new(proto.QueryResult)
|
||||
vtgate.AddVtGateError(vtgErr, &qr.Err)
|
||||
return sendReply(qr)
|
||||
}
|
||||
|
||||
// StreamExecuteShard is the RPC version of vtgateservice.VTGateService method
|
||||
|
@ -274,18 +245,10 @@ func (vtg *VTGate) StreamExecuteShard2(ctx context.Context, request *proto.Query
|
|||
if vtgErr == nil {
|
||||
return nil
|
||||
}
|
||||
if *vtgate.RPCErrorOnlyInReply {
|
||||
// If there was an app error, send a QueryResult back with it.
|
||||
qr := new(proto.QueryResult)
|
||||
vtgate.AddVtGateError(vtgErr, &qr.Err)
|
||||
// Sending back errors this way is not backwards compatible. If a (new) server sends an additional
|
||||
// QueryResult with an error, and the (old) client doesn't know how to read it, it will cause
|
||||
// problems where the client will get out of sync with the number of QueryResults sent.
|
||||
// That's why this the error is only sent this way when the --rpc_errors_only_in_reply flag is set
|
||||
// (signalling that all clients are able to handle new-style errors).
|
||||
return sendReply(qr)
|
||||
}
|
||||
return vtgErr
|
||||
// If there was an app error, send a QueryResult back with it.
|
||||
qr := new(proto.QueryResult)
|
||||
vtgate.AddVtGateError(vtgErr, &qr.Err)
|
||||
return sendReply(qr)
|
||||
}
|
||||
|
||||
// StreamExecuteKeyspaceIds is the RPC version of
|
||||
|
@ -325,18 +288,10 @@ func (vtg *VTGate) StreamExecuteKeyspaceIds2(ctx context.Context, request *proto
|
|||
if vtgErr == nil {
|
||||
return nil
|
||||
}
|
||||
if *vtgate.RPCErrorOnlyInReply {
|
||||
// If there was an app error, send a QueryResult back with it.
|
||||
qr := new(proto.QueryResult)
|
||||
vtgate.AddVtGateError(vtgErr, &qr.Err)
|
||||
// Sending back errors this way is not backwards compatible. If a (new) server sends an additional
|
||||
// QueryResult with an error, and the (old) client doesn't know how to read it, it will cause
|
||||
// problems where the client will get out of sync with the number of QueryResults sent.
|
||||
// That's why this the error is only sent this way when the --rpc_errors_only_in_reply flag is set
|
||||
// (signalling that all clients are able to handle new-style errors).
|
||||
return sendReply(qr)
|
||||
}
|
||||
return vtgErr
|
||||
// If there was an app error, send a QueryResult back with it.
|
||||
qr := new(proto.QueryResult)
|
||||
vtgate.AddVtGateError(vtgErr, &qr.Err)
|
||||
return sendReply(qr)
|
||||
}
|
||||
|
||||
// StreamExecuteKeyRanges is the RPC version of
|
||||
|
@ -376,18 +331,10 @@ func (vtg *VTGate) StreamExecuteKeyRanges2(ctx context.Context, request *proto.K
|
|||
if vtgErr == nil {
|
||||
return nil
|
||||
}
|
||||
if *vtgate.RPCErrorOnlyInReply {
|
||||
// If there was an app error, send a QueryResult back with it.
|
||||
qr := new(proto.QueryResult)
|
||||
vtgate.AddVtGateError(vtgErr, &qr.Err)
|
||||
// Sending back errors this way is not backwards compatible. If a (new) server sends an additional
|
||||
// QueryResult with an error, and the (old) client doesn't know how to read it, it will cause
|
||||
// problems where the client will get out of sync with the number of QueryResults sent.
|
||||
// That's why this the error is only sent this way when the --rpc_errors_only_in_reply flag is set
|
||||
// (signalling that all clients are able to handle new-style errors).
|
||||
return sendReply(qr)
|
||||
}
|
||||
return vtgErr
|
||||
// If there was an app error, send a QueryResult back with it.
|
||||
qr := new(proto.QueryResult)
|
||||
vtgate.AddVtGateError(vtgErr, &qr.Err)
|
||||
return sendReply(qr)
|
||||
}
|
||||
|
||||
// Begin is the RPC version of vtgateservice.VTGateService method
|
||||
|
@ -426,10 +373,7 @@ func (vtg *VTGate) Begin2(ctx context.Context, request *proto.BeginRequest, repl
|
|||
reply.Session = &proto.Session{}
|
||||
vtgErr := vtg.server.Begin(ctx, reply.Session)
|
||||
vtgate.AddVtGateError(vtgErr, &reply.Err)
|
||||
if *vtgate.RPCErrorOnlyInReply {
|
||||
return nil
|
||||
}
|
||||
return vtgErr
|
||||
return nil
|
||||
}
|
||||
|
||||
// Commit2 is the RPC version of vtgateservice.VTGateService method
|
||||
|
@ -442,10 +386,7 @@ func (vtg *VTGate) Commit2(ctx context.Context, request *proto.CommitRequest, re
|
|||
callerid.NewImmediateCallerID("gorpc client"))
|
||||
vtgErr := vtg.server.Commit(ctx, request.Session)
|
||||
vtgate.AddVtGateError(vtgErr, &reply.Err)
|
||||
if *vtgate.RPCErrorOnlyInReply {
|
||||
return nil
|
||||
}
|
||||
return vtgErr
|
||||
return nil
|
||||
}
|
||||
|
||||
// Rollback2 is the RPC version of vtgateservice.VTGateService method
|
||||
|
@ -458,10 +399,7 @@ func (vtg *VTGate) Rollback2(ctx context.Context, request *proto.RollbackRequest
|
|||
callerid.NewImmediateCallerID("gorpc client"))
|
||||
vtgErr := vtg.server.Rollback(ctx, request.Session)
|
||||
vtgate.AddVtGateError(vtgErr, &reply.Err)
|
||||
if *vtgate.RPCErrorOnlyInReply {
|
||||
return nil
|
||||
}
|
||||
return vtgErr
|
||||
return nil
|
||||
}
|
||||
|
||||
// SplitQuery is the RPC version of vtgateservice.VTGateService method
|
||||
|
@ -480,10 +418,7 @@ func (vtg *VTGate) SplitQuery(ctx context.Context, request *proto.SplitQueryRequ
|
|||
request.SplitCount)
|
||||
reply.Splits = splits
|
||||
vtgate.AddVtGateError(vtgErr, &reply.Err)
|
||||
if *vtgate.RPCErrorOnlyInReply {
|
||||
return nil
|
||||
}
|
||||
return vtgErr
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSrvKeyspace is the RPC version of vtgateservice.VTGateService method
|
||||
|
|
|
@ -8,7 +8,6 @@ package vtgate
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
|
@ -97,11 +96,6 @@ type RegisterVTGate func(vtgateservice.VTGateService)
|
|||
// RegisterVTGates stores register funcs for VTGate server.
|
||||
var RegisterVTGates []RegisterVTGate
|
||||
|
||||
var (
|
||||
// RPCErrorOnlyInReply informs vtgateservice(s) about how to return errors.
|
||||
RPCErrorOnlyInReply = flag.Bool("rpc-error-only-in-reply", false, "if true, supported RPC calls from vtgateservice(s) will only return errors as part of the RPC server response")
|
||||
)
|
||||
|
||||
// Init initializes VTGate server.
|
||||
func Init(hc discovery.HealthCheck, topoServer topo.Server, serv SrvTopoServer, schema *planbuilder.Schema, cell string, retryDelay time.Duration, retryCount int, connTimeoutTotal, connTimeoutPerConn, connLife time.Duration, maxInFlight int, testGateway string) {
|
||||
if rpcVTGate != nil {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
package com.youtube.vitess.client;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
/**
|
||||
* RpcClientFactory creates a concrete RpcClient.
|
||||
*/
|
||||
public interface RpcClientFactory {
|
||||
RpcClient create(Context ctx, SocketAddress address);
|
||||
RpcClient create(Context ctx, InetSocketAddress address);
|
||||
}
|
||||
|
|
|
@ -7,14 +7,14 @@ import com.youtube.vitess.client.RpcClientFactory;
|
|||
import io.grpc.netty.NegotiationType;
|
||||
import io.grpc.netty.NettyChannelBuilder;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
/**
|
||||
* GrpcClientFactory creates RpcClients with the gRPC implemenation.
|
||||
*/
|
||||
public class GrpcClientFactory implements RpcClientFactory {
|
||||
@Override
|
||||
public RpcClient create(Context ctx, SocketAddress address) {
|
||||
public RpcClient create(Context ctx, InetSocketAddress address) {
|
||||
return new GrpcClient(
|
||||
NettyChannelBuilder.forAddress(address).negotiationType(NegotiationType.PLAINTEXT).build());
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
<module>gorpc</module>
|
||||
<module>grpc-client</module>
|
||||
<module>hadoop</module>
|
||||
<module>vtgate-client</module>
|
||||
</modules>
|
||||
|
||||
<organization>
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
VtGate Java Driver
|
||||
==================
|
||||
|
||||
Add the following repository and dependency to your pom.xml to use this client library.
|
||||
|
||||
```
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>youtube-snapshots</id>
|
||||
<url>https://github.com/youtube/mvn-repo/raw/master/snapshots</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
```
|
||||
|
||||
```
|
||||
<dependency>
|
||||
<groupId>com.youtube.vitess</groupId>
|
||||
<artifactId>vtgate-client</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</dependency>
|
||||
```
|
|
@ -1,150 +0,0 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.youtube.vitess</groupId>
|
||||
<artifactId>vitess-parent</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>vtgate-client</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.youtube.vitess</groupId>
|
||||
<artifactId>gorpc-client</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-codec</groupId>
|
||||
<artifactId>commons-codec</artifactId>
|
||||
<version>1.9</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.hadoop</groupId>
|
||||
<artifactId>hadoop-client</artifactId>
|
||||
<version>2.5.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>joda-time</groupId>
|
||||
<artifactId>joda-time</artifactId>
|
||||
<version>2.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.hadoop</groupId>
|
||||
<artifactId>hadoop-mapreduce-client-jobclient</artifactId>
|
||||
<version>2.5.1</version>
|
||||
<!-- Include the test jar to reuse Hadoop testing utils -->
|
||||
<type>test-jar</type>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<releases>
|
||||
<updatePolicy>never</updatePolicy>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
<id>central</id>
|
||||
<name>Central Repository</name>
|
||||
<url>https://repo.maven.apache.org/maven2</url>
|
||||
</pluginRepository>
|
||||
<pluginRepository>
|
||||
<id>protoc-plugin</id>
|
||||
<url>https://dl.bintray.com/sergei-ivanov/maven/</url>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
|
||||
<build>
|
||||
<extensions>
|
||||
<extension>
|
||||
<groupId>kr.motd.maven</groupId>
|
||||
<artifactId>os-maven-plugin</artifactId>
|
||||
<version>1.2.3.Final</version>
|
||||
</extension>
|
||||
</extensions>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.3</version>
|
||||
<configuration>
|
||||
<source>1.7</source>
|
||||
<target>1.7</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>2.17</version>
|
||||
<configuration>
|
||||
<argLine>${surefireArgLine}</argLine>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
<version>2.13</version>
|
||||
<configuration>
|
||||
<argLine>${failsafeArgLine}</argLine>
|
||||
<systemPropertyVariables>
|
||||
<vtgate.test.env>com.youtube.vitess.vtgate.TestEnv</vtgate.test.env>
|
||||
<vtgate.rpcclient.factory>com.youtube.vitess.vtgate.rpcclient.gorpc.BsonRpcClientFactory</vtgate.rpcclient.factory>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>integration-test</goal>
|
||||
<goal>verify</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>2.4</version>
|
||||
<configuration>
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>com.google.protobuf.tools</groupId>
|
||||
<artifactId>maven-protoc-plugin</artifactId>
|
||||
<version>0.4.2</version>
|
||||
<configuration>
|
||||
<protocArtifact>com.google.protobuf:protoc:${protobuf.java.version}:exe:${os.detected.classifier}</protocArtifact>
|
||||
<pluginId>grpc-java</pluginId>
|
||||
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
|
||||
<protoSourceRoot>../../proto</protoSourceRoot>
|
||||
<includes>
|
||||
<include>query.proto</include>
|
||||
<include>topodata.proto</include>
|
||||
<include>vtgate.proto</include>
|
||||
<include>vtrpc.proto</include>
|
||||
</includes>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>compile</goal>
|
||||
<goal>compile-custom</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
|
@ -1,90 +0,0 @@
|
|||
package com.youtube.vitess.vtgate;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class BatchQuery {
|
||||
private List<String> sqls;
|
||||
private List<List<BindVariable>> bindVarsList;
|
||||
private String keyspace;
|
||||
private String tabletType;
|
||||
private List<byte[]> keyspaceIds;
|
||||
private Object session;
|
||||
|
||||
private BatchQuery(String keyspace, String tabletType) {
|
||||
this.keyspace = keyspace;
|
||||
this.tabletType = tabletType;
|
||||
this.sqls = new LinkedList<>();
|
||||
this.bindVarsList = new LinkedList<>();
|
||||
}
|
||||
|
||||
public List<String> getSqls() {
|
||||
return sqls;
|
||||
}
|
||||
|
||||
public List<List<BindVariable>> getBindVarsList() {
|
||||
return bindVarsList;
|
||||
}
|
||||
|
||||
public String getTabletType() {
|
||||
return tabletType;
|
||||
}
|
||||
|
||||
public String getKeyspace() {
|
||||
return keyspace;
|
||||
}
|
||||
|
||||
public List<byte[]> getKeyspaceIds() {
|
||||
return keyspaceIds;
|
||||
}
|
||||
|
||||
public Object getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
public void setSession(Object session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
public static class BatchQueryBuilder {
|
||||
private BatchQuery query;
|
||||
|
||||
public BatchQueryBuilder(String keyspace, String tabletType) {
|
||||
query = new BatchQuery(keyspace, tabletType);
|
||||
}
|
||||
|
||||
public BatchQuery build() {
|
||||
if (query.sqls.size() == 0) {
|
||||
throw new IllegalStateException("query must have at least one sql");
|
||||
}
|
||||
if (query.keyspaceIds == null) {
|
||||
throw new IllegalStateException("query must have keyspaceIds set");
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
public BatchQueryBuilder addSqlAndBindVars(String sql, List<BindVariable> bindVars) {
|
||||
query.sqls.add(sql);
|
||||
query.bindVarsList.add(bindVars);
|
||||
return this;
|
||||
}
|
||||
|
||||
public BatchQueryBuilder withKeyspaceIds(List<KeyspaceId> keyspaceIds) {
|
||||
List<byte[]> kidsBytes = new ArrayList<>();
|
||||
for (KeyspaceId kid : keyspaceIds) {
|
||||
kidsBytes.add(kid.getBytes());
|
||||
}
|
||||
query.keyspaceIds = kidsBytes;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BatchQueryBuilder withAddedKeyspaceId(KeyspaceId keyspaceId) {
|
||||
if (query.getKeyspaceIds() == null) {
|
||||
query.keyspaceIds = new ArrayList<byte[]>();
|
||||
}
|
||||
query.getKeyspaceIds().add(keyspaceId.getBytes());
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
package com.youtube.vitess.vtgate;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class BatchQueryResponse {
|
||||
private List<QueryResult> results;
|
||||
private Object session;
|
||||
private String error;
|
||||
|
||||
public BatchQueryResponse(List<QueryResult> results, Object session, String error) {
|
||||
this.results = results;
|
||||
this.session = session;
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public List<QueryResult> getResults() {
|
||||
return results;
|
||||
}
|
||||
|
||||
public Object getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
public String getError() {
|
||||
return error;
|
||||
}
|
||||
}
|
|
@ -1,121 +0,0 @@
|
|||
package com.youtube.vitess.vtgate;
|
||||
|
||||
import com.google.common.primitives.UnsignedLong;
|
||||
|
||||
import org.apache.commons.lang.CharEncoding;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.format.DateTimeFormat;
|
||||
import org.joda.time.format.ISODateTimeFormat;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class BindVariable {
|
||||
private String name;
|
||||
private Type type;
|
||||
private long longVal;
|
||||
private UnsignedLong uLongVal;
|
||||
private double doubleVal;
|
||||
private byte[] byteArrayVal;
|
||||
|
||||
private BindVariable(String name, Type type) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public static BindVariable forNull(String name) {
|
||||
return new BindVariable(name, Type.NULL);
|
||||
}
|
||||
|
||||
public static BindVariable forInt(String name, Integer value) {
|
||||
return forLong(name, value.longValue());
|
||||
}
|
||||
|
||||
public static BindVariable forLong(String name, Long value) {
|
||||
BindVariable bv = new BindVariable(name, Type.LONG);
|
||||
bv.longVal = value;
|
||||
return bv;
|
||||
}
|
||||
|
||||
public static BindVariable forFloat(String name, Float value) {
|
||||
return forDouble(name, value.doubleValue());
|
||||
}
|
||||
|
||||
public static BindVariable forDouble(String name, Double value) {
|
||||
BindVariable bv = new BindVariable(name, Type.DOUBLE);
|
||||
bv.doubleVal = value;
|
||||
return bv;
|
||||
}
|
||||
|
||||
public static BindVariable forULong(String name, UnsignedLong value) {
|
||||
BindVariable bv = new BindVariable(name, Type.UNSIGNED_LONG);
|
||||
bv.uLongVal = value;
|
||||
return bv;
|
||||
}
|
||||
|
||||
public static BindVariable forDateTime(String name, DateTime value) {
|
||||
String dateTimeStr =
|
||||
value.toString(ISODateTimeFormat.dateHourMinuteSecondMillis()).replace('T', ' ');
|
||||
return forString(name, dateTimeStr);
|
||||
}
|
||||
|
||||
public static BindVariable forDate(String name, DateTime value) {
|
||||
String date = value.toString(ISODateTimeFormat.date());
|
||||
return forString(name, date);
|
||||
}
|
||||
|
||||
public static BindVariable forTime(String name, DateTime value) {
|
||||
String time = value.toString(DateTimeFormat.forPattern("HH:mm:ss"));
|
||||
return forString(name, time);
|
||||
}
|
||||
|
||||
public static BindVariable forString(String name, String value) {
|
||||
try {
|
||||
return forBytes(name, value.getBytes(CharEncoding.ISO_8859_1));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static BindVariable forBytes(String name, byte[] value) {
|
||||
BindVariable bv = new BindVariable(name, Type.BYTE_ARRAY);
|
||||
bv.byteArrayVal = value;
|
||||
return bv;
|
||||
}
|
||||
|
||||
public static BindVariable forShort(String name, Short value) {
|
||||
return forLong(name, value.longValue());
|
||||
}
|
||||
|
||||
public static BindVariable forBigDecimal(String name, BigDecimal value) {
|
||||
return forDouble(name, value.doubleValue());
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public long getLongVal() {
|
||||
return longVal;
|
||||
}
|
||||
|
||||
public UnsignedLong getULongVal() {
|
||||
return uLongVal;
|
||||
}
|
||||
|
||||
public double getDoubleVal() {
|
||||
return doubleVal;
|
||||
}
|
||||
|
||||
public byte[] getByteArrayVal() {
|
||||
return byteArrayVal;
|
||||
}
|
||||
|
||||
public static enum Type {
|
||||
NULL, LONG, UNSIGNED_LONG, DOUBLE, BYTE_ARRAY;
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
package com.youtube.vitess.vtgate;
|
||||
|
||||
public class Exceptions {
|
||||
|
||||
/**
|
||||
* Exception raised at the tablet or MySQL layer due to issues such as invalid syntax, etc.
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public static class DatabaseException extends Exception {
|
||||
public DatabaseException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception raised by MySQL due to violation of unique key constraint
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public static class IntegrityException extends DatabaseException {
|
||||
public IntegrityException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception caused due to irrecoverable connection failures or other low level exceptions
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public static class ConnectionException extends Exception {
|
||||
public ConnectionException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception raised due to fetching a non-existent field or with the wrong type
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public static class InvalidFieldException extends RuntimeException {
|
||||
public InvalidFieldException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,158 +0,0 @@
|
|||
package com.youtube.vitess.vtgate;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.primitives.UnsignedLong;
|
||||
import com.youtube.vitess.vtgate.Row.Cell;
|
||||
import org.apache.commons.lang.CharEncoding;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.format.DateTimeFormat;
|
||||
import org.joda.time.format.ISODateTimeFormat;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class Field {
|
||||
/**
|
||||
* MySQL field flags bitset values e.g. to distinguish between signed and unsigned integer.
|
||||
* Comments are taken from the original source code.
|
||||
* These numbers should exactly match values defined in dist/mysql-5.1.52/include/mysql_com.h
|
||||
*/
|
||||
public enum Flag {
|
||||
// VT_ZEROVALUE_FLAG is not part of the MySQL specification and only used in unit tests.
|
||||
VT_ZEROVALUE_FLAG(0),
|
||||
VT_NOT_NULL_FLAG (1), /* Field can't be NULL */
|
||||
VT_PRI_KEY_FLAG(2), /* Field is part of a primary key */
|
||||
VT_UNIQUE_KEY_FLAG(4), /* Field is part of a unique key */
|
||||
VT_MULTIPLE_KEY_FLAG(8), /* Field is part of a key */
|
||||
VT_BLOB_FLAG(16), /* Field is a blob */
|
||||
VT_UNSIGNED_FLAG(32), /* Field is unsigned */
|
||||
VT_ZEROFILL_FLAG(64), /* Field is zerofill */
|
||||
VT_BINARY_FLAG(128), /* Field is binary */
|
||||
/* The following are only sent to new clients */
|
||||
VT_ENUM_FLAG(256), /* field is an enum */
|
||||
VT_AUTO_INCREMENT_FLAG(512), /* field is a autoincrement field */
|
||||
VT_TIMESTAMP_FLAG(1024), /* Field is a timestamp */
|
||||
VT_SET_FLAG(2048), /* field is a set */
|
||||
VT_NO_DEFAULT_VALUE_FLAG(4096), /* Field doesn't have default value */
|
||||
VT_ON_UPDATE_NOW_FLAG(8192), /* Field is set to NOW on UPDATE */
|
||||
VT_NUM_FLAG(32768); /* Field is num (for clients) */
|
||||
|
||||
public long mysqlFlag;
|
||||
|
||||
Flag(long mysqlFlag) {
|
||||
this.mysqlFlag = mysqlFlag;
|
||||
}
|
||||
}
|
||||
|
||||
private String name;
|
||||
private FieldType type;
|
||||
private long mysqlFlags;
|
||||
|
||||
public static Field newFieldFromMysql(String name, int mysqlTypeId, long mysqlFlags) {
|
||||
for (FieldType ft : FieldType.values()) {
|
||||
if (ft.mysqlType == mysqlTypeId) {
|
||||
return new Field(name, ft, mysqlFlags);
|
||||
}
|
||||
}
|
||||
|
||||
throw new RuntimeException("Unknown MySQL type: " + mysqlTypeId);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static Field newFieldForTest(FieldType fieldType, Flag flag) {
|
||||
return new Field("dummyField", fieldType, flag.mysqlFlag);
|
||||
}
|
||||
|
||||
private Field(String name, FieldType type, long mysqlFlags) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.mysqlFlags = mysqlFlags;
|
||||
}
|
||||
|
||||
public Cell convertValueToCell(byte[] bytes) {
|
||||
if ((mysqlFlags & Flag.VT_UNSIGNED_FLAG.mysqlFlag) != 0) {
|
||||
return new Cell(name, convert(bytes), type.unsignedJavaType);
|
||||
} else {
|
||||
return new Cell(name, convert(bytes), type.javaType);
|
||||
}
|
||||
}
|
||||
|
||||
Object convert(byte[] bytes) {
|
||||
if (bytes == null || bytes.length == 0) {
|
||||
return null;
|
||||
}
|
||||
String s = null;
|
||||
try {
|
||||
s = new String(bytes, CharEncoding.ISO_8859_1);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case VT_DECIMAL:
|
||||
return new BigDecimal(s);
|
||||
case VT_TINY:
|
||||
return Integer.valueOf(s);
|
||||
case VT_SHORT:
|
||||
return Integer.valueOf(s);
|
||||
case VT_LONG:
|
||||
return Long.valueOf(s);
|
||||
case VT_FLOAT:
|
||||
return Float.valueOf(s);
|
||||
case VT_DOUBLE:
|
||||
return Double.valueOf(s);
|
||||
case VT_NULL:
|
||||
return null;
|
||||
case VT_TIMESTAMP:
|
||||
s = s.replace(' ', 'T');
|
||||
return DateTime.parse(s);
|
||||
case VT_LONGLONG:
|
||||
// This can be an unsigned or a signed long
|
||||
if ((mysqlFlags & Flag.VT_UNSIGNED_FLAG.mysqlFlag) != 0) {
|
||||
return UnsignedLong.valueOf(s);
|
||||
} else {
|
||||
return Long.valueOf(s);
|
||||
}
|
||||
case VT_INT24:
|
||||
return Integer.valueOf(s);
|
||||
case VT_DATE:
|
||||
return DateTime.parse(s, ISODateTimeFormat.date());
|
||||
case VT_TIME:
|
||||
DateTime d = DateTime.parse(s, DateTimeFormat.forPattern("HH:mm:ss"));
|
||||
return d;
|
||||
case VT_DATETIME:
|
||||
s = s.replace(' ', 'T');
|
||||
return DateTime.parse(s);
|
||||
case VT_YEAR:
|
||||
return Short.valueOf(s);
|
||||
case VT_NEWDATE:
|
||||
return DateTime.parse(s, ISODateTimeFormat.date());
|
||||
case VT_VARCHAR:
|
||||
return bytes;
|
||||
case VT_BIT:
|
||||
return bytes;
|
||||
case VT_NEWDECIMAL:
|
||||
return new BigDecimal(s);
|
||||
case VT_ENUM:
|
||||
return s;
|
||||
case VT_SET:
|
||||
return s;
|
||||
case VT_TINY_BLOB:
|
||||
return bytes;
|
||||
case VT_MEDIUM_BLOB:
|
||||
return bytes;
|
||||
case VT_LONG_BLOB:
|
||||
return bytes;
|
||||
case VT_BLOB:
|
||||
return bytes;
|
||||
case VT_VAR_STRING:
|
||||
return bytes;
|
||||
case VT_STRING:
|
||||
return bytes;
|
||||
case VT_GEOMETRY:
|
||||
return bytes;
|
||||
default:
|
||||
throw new RuntimeException("invalid field type " + this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
package com.youtube.vitess.vtgate;
|
||||
|
||||
import com.google.common.primitives.UnsignedLong;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* Represents all field types supported by Vitess and their corresponding types in Java. mysqlType
|
||||
* numbers should exactly match values defined in dist/mysql-5.1.52/include/mysql/mysql_com.h
|
||||
*
|
||||
*/
|
||||
public enum FieldType {
|
||||
VT_DECIMAL(0, BigDecimal.class),
|
||||
VT_TINY(1, Integer.class),
|
||||
VT_SHORT(2, Integer.class),
|
||||
VT_LONG(3, Long.class),
|
||||
VT_FLOAT(4, Float.class),
|
||||
VT_DOUBLE(5, Double.class),
|
||||
VT_NULL(6, null),
|
||||
VT_TIMESTAMP(7, DateTime.class),
|
||||
VT_LONGLONG(8, Long.class, UnsignedLong.class),
|
||||
VT_INT24(9, Integer.class),
|
||||
VT_DATE(10, DateTime.class),
|
||||
VT_TIME(11, DateTime.class),
|
||||
VT_DATETIME(12, DateTime.class),
|
||||
VT_YEAR(13, Short.class),
|
||||
VT_NEWDATE(14, DateTime.class),
|
||||
VT_VARCHAR(15, byte[].class),
|
||||
VT_BIT(16, byte[].class),
|
||||
VT_NEWDECIMAL(246, BigDecimal.class),
|
||||
VT_ENUM(247, String.class),
|
||||
VT_SET(248, String.class),
|
||||
VT_TINY_BLOB(249, byte[].class),
|
||||
VT_MEDIUM_BLOB(250, byte[].class),
|
||||
VT_LONG_BLOB(251, byte[].class),
|
||||
VT_BLOB(252, byte[].class),
|
||||
VT_VAR_STRING(253, byte[].class),
|
||||
VT_STRING(254, byte[].class),
|
||||
VT_GEOMETRY(255, byte[].class);
|
||||
|
||||
public final int mysqlType;
|
||||
public final Class javaType;
|
||||
public final Class unsignedJavaType;
|
||||
|
||||
FieldType(int mysqlType, Class javaType) {
|
||||
this.mysqlType = mysqlType;
|
||||
this.javaType = javaType;
|
||||
this.unsignedJavaType = javaType;
|
||||
}
|
||||
|
||||
FieldType(int mysqlType, Class javaType, Class unsignedJavaType) {
|
||||
this.mysqlType = mysqlType;
|
||||
this.javaType = javaType;
|
||||
this.unsignedJavaType = unsignedJavaType;
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
package com.youtube.vitess.vtgate;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class KeyRange {
|
||||
private static final String START = "Start";
|
||||
private static final String END = "End";
|
||||
private static final byte[] NULL_BYTES = "".getBytes();
|
||||
|
||||
public static final KeyRange ALL = new KeyRange(null, null);
|
||||
|
||||
KeyspaceId start;
|
||||
KeyspaceId end;
|
||||
|
||||
public KeyRange(KeyspaceId start, KeyspaceId end) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
public Map<String, byte[]> toMap() {
|
||||
byte[] startBytes = start == null ? NULL_BYTES : start.getBytes();
|
||||
byte[] endBytes = end == null ? NULL_BYTES : end.getBytes();
|
||||
Map<String, byte[]> map = new HashMap<>();
|
||||
map.put(START, startBytes);
|
||||
map.put(END, endBytes);
|
||||
return map;
|
||||
}
|
||||
}
|
|
@ -1,91 +0,0 @@
|
|||
package com.youtube.vitess.vtgate;
|
||||
|
||||
import com.google.common.primitives.Longs;
|
||||
import com.google.common.primitives.UnsignedLong;
|
||||
|
||||
import org.apache.commons.codec.DecoderException;
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
|
||||
/**
|
||||
* KeyspaceId can be either String or UnsignedLong. Use factory method valueOf to create instances
|
||||
*/
|
||||
public class KeyspaceId implements Comparable<KeyspaceId> {
|
||||
private Object id;
|
||||
|
||||
public static final String COL_NAME = "keyspace_id";
|
||||
|
||||
public KeyspaceId() {
|
||||
|
||||
}
|
||||
|
||||
public void setId(Object id) {
|
||||
if (!(id instanceof String) && !(id instanceof UnsignedLong)) {
|
||||
throw new IllegalArgumentException(
|
||||
"invalid id type, must be either String or UnsignedLong " + id.getClass());
|
||||
}
|
||||
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Object getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
if (id instanceof String) {
|
||||
try {
|
||||
return Hex.decodeHex(((String) id).toCharArray());
|
||||
} catch (DecoderException e) {
|
||||
throw new IllegalArgumentException("illegal string id", e);
|
||||
}
|
||||
} else {
|
||||
return Longs.toByteArray(((UnsignedLong) id).longValue());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a KeyspaceId from id which must be a String or UnsignedLong.
|
||||
*/
|
||||
public static KeyspaceId valueOf(Object id) {
|
||||
KeyspaceId kid = new KeyspaceId();
|
||||
kid.setId(id);
|
||||
return kid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o instanceof KeyspaceId) {
|
||||
return this.compareTo((KeyspaceId) o) == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
if (id instanceof UnsignedLong) {
|
||||
return ((UnsignedLong) id).hashCode();
|
||||
}
|
||||
return ((String) id).hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(KeyspaceId o) {
|
||||
if (o == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
|
||||
if (id instanceof UnsignedLong && o.id instanceof UnsignedLong) {
|
||||
UnsignedLong thisId = (UnsignedLong) id;
|
||||
UnsignedLong otherId = (UnsignedLong) o.id;
|
||||
return thisId.compareTo(otherId);
|
||||
}
|
||||
|
||||
if (id instanceof String && o.id instanceof String) {
|
||||
String thisId = (String) id;
|
||||
String otherId = (String) o.id;
|
||||
return thisId.compareTo(otherId);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("unexpected id types");
|
||||
}
|
||||
}
|
|
@ -1,132 +0,0 @@
|
|||
package com.youtube.vitess.vtgate;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Represents a VtGate query request. Use QueryBuilder to construct instances.
|
||||
*
|
||||
*/
|
||||
public class Query {
|
||||
private String sql;
|
||||
private String keyspace;
|
||||
private List<BindVariable> bindVars;
|
||||
private String tabletType;
|
||||
private List<byte[]> keyspaceIds;
|
||||
private List<Map<String, byte[]>> keyRanges;
|
||||
private boolean streaming;
|
||||
private Object session;
|
||||
|
||||
private Query(String sql, String keyspace, String tabletType) {
|
||||
this.sql = sql;
|
||||
this.keyspace = keyspace;
|
||||
this.tabletType = tabletType;
|
||||
}
|
||||
|
||||
public String getSql() {
|
||||
return sql;
|
||||
}
|
||||
|
||||
public String getKeyspace() {
|
||||
return keyspace;
|
||||
}
|
||||
|
||||
public List<BindVariable> getBindVars() {
|
||||
return bindVars;
|
||||
}
|
||||
|
||||
public String getTabletType() {
|
||||
return tabletType;
|
||||
}
|
||||
|
||||
public List<byte[]> getKeyspaceIds() {
|
||||
return keyspaceIds;
|
||||
}
|
||||
|
||||
public List<Map<String, byte[]>> getKeyRanges() {
|
||||
return keyRanges;
|
||||
}
|
||||
|
||||
public boolean isStreaming() {
|
||||
return streaming;
|
||||
}
|
||||
|
||||
public Object getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
public void setSession(Object session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
public static class QueryBuilder {
|
||||
private Query query;
|
||||
|
||||
public QueryBuilder(String sql, String keyspace, String tabletType) {
|
||||
query = new Query(sql, keyspace, tabletType);
|
||||
}
|
||||
|
||||
public Query build() {
|
||||
if (query.keyRanges == null && query.keyspaceIds == null) {
|
||||
throw new IllegalStateException("query must have either keyspaceIds or keyRanges");
|
||||
}
|
||||
if (query.keyRanges != null && query.keyspaceIds != null) {
|
||||
throw new IllegalStateException("query cannot have both keyspaceIds and keyRanges");
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
public QueryBuilder setBindVars(List<BindVariable> bindVars) {
|
||||
query.bindVars = bindVars;
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryBuilder setKeyspaceIds(List<KeyspaceId> keyspaceIds) {
|
||||
List<byte[]> kidsBytes = new ArrayList<>();
|
||||
for (KeyspaceId kid : keyspaceIds) {
|
||||
kidsBytes.add(kid.getBytes());
|
||||
}
|
||||
query.keyspaceIds = kidsBytes;
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryBuilder setKeyRanges(List<KeyRange> keyRanges) {
|
||||
List<Map<String, byte[]>> keyRangeMaps = new ArrayList<>();
|
||||
for (KeyRange kr : keyRanges) {
|
||||
keyRangeMaps.add(kr.toMap());
|
||||
}
|
||||
query.keyRanges = keyRangeMaps;
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryBuilder setStreaming(boolean streaming) {
|
||||
query.streaming = streaming;
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryBuilder addBindVar(BindVariable bindVariable) {
|
||||
if (query.getBindVars() == null) {
|
||||
query.bindVars = new ArrayList<BindVariable>();
|
||||
}
|
||||
query.getBindVars().add(bindVariable);
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryBuilder addKeyspaceId(KeyspaceId keyspaceId) {
|
||||
if (query.getKeyspaceIds() == null) {
|
||||
query.keyspaceIds = new ArrayList<byte[]>();
|
||||
}
|
||||
query.getKeyspaceIds().add(keyspaceId.getBytes());
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryBuilder addKeyRange(KeyRange keyRange) {
|
||||
if (query.getKeyRanges() == null) {
|
||||
query.keyRanges = new ArrayList<Map<String, byte[]>>();
|
||||
}
|
||||
query.getKeyRanges().add(keyRange.toMap());
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
package com.youtube.vitess.vtgate;
|
||||
|
||||
public class QueryResponse {
|
||||
private QueryResult result;
|
||||
private Object session;
|
||||
private String error;
|
||||
|
||||
public QueryResponse(QueryResult result, Object session, String error) {
|
||||
this.result = result;
|
||||
this.session = session;
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public QueryResult getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public Object getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
public String getError() {
|
||||
return error;
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
package com.youtube.vitess.vtgate;
|
||||
|
||||
import com.youtube.vitess.vtgate.cursor.Cursor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents a VtGate query result set. For selects, rows are better accessed through the iterator
|
||||
* {@link Cursor}.
|
||||
*/
|
||||
public class QueryResult {
|
||||
private List<Row> rows;
|
||||
private List<Field> fields;
|
||||
private long rowsAffected;
|
||||
private long lastRowId;
|
||||
|
||||
public QueryResult(List<Row> rows, List<Field> fields, long rowsAffected, long lastRowId) {
|
||||
this.rows = rows;
|
||||
this.fields = fields;
|
||||
this.rowsAffected = rowsAffected;
|
||||
this.lastRowId = lastRowId;
|
||||
}
|
||||
|
||||
public List<Field> getFields() {
|
||||
return fields;
|
||||
}
|
||||
|
||||
public List<Row> getRows() {
|
||||
return rows;
|
||||
}
|
||||
|
||||
public long getRowsAffected() {
|
||||
return rowsAffected;
|
||||
}
|
||||
|
||||
public long getLastRowId() {
|
||||
return lastRowId;
|
||||
}
|
||||
}
|
|
@ -1,182 +0,0 @@
|
|||
package com.youtube.vitess.vtgate;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.primitives.UnsignedLong;
|
||||
|
||||
import com.youtube.vitess.vtgate.Exceptions.InvalidFieldException;
|
||||
import com.youtube.vitess.vtgate.Row.Cell;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class Row implements Iterator<Cell>, Iterable<Cell> {
|
||||
private ImmutableMap<String, Cell> contents;
|
||||
private Iterator<String> iterator;
|
||||
|
||||
public Row(List<Cell> cells) {
|
||||
ImmutableMap.Builder<String, Cell> builder = new ImmutableMap.Builder<>();
|
||||
for (Cell cell : cells) {
|
||||
builder.put(cell.getName(), cell);
|
||||
}
|
||||
contents = builder.build();
|
||||
iterator = contents.keySet().iterator();
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return contents.keySet().size();
|
||||
}
|
||||
|
||||
public Object getObject(int index) throws InvalidFieldException {
|
||||
if (index >= size()) {
|
||||
throw new InvalidFieldException("invalid field index " + index);
|
||||
}
|
||||
return getObject(contents.keySet().asList().get(index));
|
||||
}
|
||||
|
||||
public Object getObject(String fieldName) throws InvalidFieldException {
|
||||
if (!contents.containsKey(fieldName)) {
|
||||
throw new InvalidFieldException("invalid field name " + fieldName);
|
||||
}
|
||||
return contents.get(fieldName).getValue();
|
||||
}
|
||||
|
||||
public Integer getInt(String fieldName) throws InvalidFieldException {
|
||||
return (Integer) getAndCheckType(fieldName, Integer.class);
|
||||
}
|
||||
|
||||
public Integer getInt(int index) throws InvalidFieldException {
|
||||
return (Integer) getAndCheckType(index, Integer.class);
|
||||
}
|
||||
|
||||
public UnsignedLong getULong(String fieldName) throws InvalidFieldException {
|
||||
return (UnsignedLong) getAndCheckType(fieldName, UnsignedLong.class);
|
||||
}
|
||||
|
||||
public UnsignedLong getULong(int index) throws InvalidFieldException {
|
||||
return (UnsignedLong) getAndCheckType(index, UnsignedLong.class);
|
||||
}
|
||||
|
||||
public String getString(String fieldName) throws InvalidFieldException {
|
||||
return (String) getAndCheckType(fieldName, String.class);
|
||||
}
|
||||
|
||||
public String getString(int index) throws InvalidFieldException {
|
||||
return (String) getAndCheckType(index, String.class);
|
||||
}
|
||||
|
||||
public Long getLong(String fieldName) throws InvalidFieldException {
|
||||
return (Long) getAndCheckType(fieldName, Long.class);
|
||||
}
|
||||
|
||||
public Long getLong(int index) throws InvalidFieldException {
|
||||
return (Long) getAndCheckType(index, Long.class);
|
||||
}
|
||||
|
||||
public Double getDouble(String fieldName) throws InvalidFieldException {
|
||||
return (Double) getAndCheckType(fieldName, Double.class);
|
||||
}
|
||||
|
||||
public Double getDouble(int index) throws InvalidFieldException {
|
||||
return (Double) getAndCheckType(index, Double.class);
|
||||
}
|
||||
|
||||
public Float getFloat(String fieldName) throws InvalidFieldException {
|
||||
return (Float) getAndCheckType(fieldName, Float.class);
|
||||
}
|
||||
|
||||
public Float getFloat(int index) throws InvalidFieldException {
|
||||
return (Float) getAndCheckType(index, Float.class);
|
||||
}
|
||||
|
||||
public DateTime getDateTime(String fieldName) throws InvalidFieldException {
|
||||
return (DateTime) getAndCheckType(fieldName, DateTime.class);
|
||||
}
|
||||
|
||||
public DateTime getDateTime(int index) throws InvalidFieldException {
|
||||
return (DateTime) getAndCheckType(index, DateTime.class);
|
||||
}
|
||||
|
||||
public byte[] getBytes(String fieldName) throws InvalidFieldException {
|
||||
return (byte[]) getAndCheckType(fieldName, byte[].class);
|
||||
}
|
||||
|
||||
public byte[] getBytes(int index) throws InvalidFieldException {
|
||||
return (byte[]) getAndCheckType(index, byte[].class);
|
||||
}
|
||||
|
||||
public BigDecimal getBigDecimal(String fieldName) throws InvalidFieldException {
|
||||
return (BigDecimal) getAndCheckType(fieldName, BigDecimal.class);
|
||||
}
|
||||
|
||||
public BigDecimal getBigDecimal(int index) throws InvalidFieldException {
|
||||
return (BigDecimal) getAndCheckType(index, BigDecimal.class);
|
||||
}
|
||||
|
||||
public Short getShort(String fieldName) throws InvalidFieldException {
|
||||
return (Short) getAndCheckType(fieldName, Short.class);
|
||||
}
|
||||
|
||||
public Short getShort(int index) throws InvalidFieldException {
|
||||
return (Short) getAndCheckType(index, Short.class);
|
||||
}
|
||||
|
||||
private Object getAndCheckType(String fieldName, Class clazz) throws InvalidFieldException {
|
||||
Object o = getObject(fieldName);
|
||||
if (o != null && !clazz.isInstance(o)) {
|
||||
throw new InvalidFieldException(
|
||||
"type mismatch expected:" + clazz.getName() + " actual: " + o.getClass().getName());
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
private Object getAndCheckType(int index, Class clazz) throws InvalidFieldException {
|
||||
return getAndCheckType(contents.keySet().asList().get(index), clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return iterator.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cell next() {
|
||||
return contents.get(iterator.next());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException("can't remove from row");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Cell> iterator() {
|
||||
return this;
|
||||
}
|
||||
|
||||
public static class Cell {
|
||||
private String name;
|
||||
private Object value;
|
||||
private Class type;
|
||||
|
||||
public Cell(String name, Object value, Class type) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public Class getType() {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
package com.youtube.vitess.vtgate;
|
||||
|
||||
public class SplitQueryRequest {
|
||||
private String sql;
|
||||
private String keyspace;
|
||||
private String splitColumn;
|
||||
private int splitCount;
|
||||
|
||||
public SplitQueryRequest(String sql, String keyspace, int splitCount, String splitColumn) {
|
||||
this.sql = sql;
|
||||
this.keyspace = keyspace;
|
||||
this.splitCount = splitCount;
|
||||
this.splitColumn = splitColumn;
|
||||
}
|
||||
|
||||
public String getSql() {
|
||||
return this.sql;
|
||||
}
|
||||
|
||||
public String getKeyspace() {
|
||||
return this.keyspace;
|
||||
}
|
||||
|
||||
public int getSplitCount() {
|
||||
return this.splitCount;
|
||||
}
|
||||
|
||||
public String getSplitColumn() {
|
||||
return this.splitColumn;
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
package com.youtube.vitess.vtgate;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class SplitQueryResponse {
|
||||
private Map<Query, Long> queries;
|
||||
private String error;
|
||||
|
||||
public SplitQueryResponse(Map<Query, Long> queries, String error) {
|
||||
this.queries = queries;
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public Map<Query, Long> getQueries() {
|
||||
return queries;
|
||||
}
|
||||
|
||||
public String getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,131 +0,0 @@
|
|||
package com.youtube.vitess.vtgate;
|
||||
|
||||
import com.google.common.primitives.UnsignedLong;
|
||||
import com.google.gson.Gson;
|
||||
import com.youtube.vitess.vtgate.KeyspaceId;
|
||||
import com.youtube.vitess.vtgate.rpcclient.RpcClientFactory;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Helper class to hold the configurations for VtGate setup used in integration tests
|
||||
*/
|
||||
public class TestEnv {
|
||||
public static final String PROPERTY_KEY_RPCCLIENT_FACTORY_CLASS = "vtgate.rpcclient.factory";
|
||||
private Map<String, List<String>> shardKidMap;
|
||||
private Map<String, Integer> tablets = new HashMap<>();
|
||||
private String keyspace;
|
||||
private Process pythonScriptProcess;
|
||||
private int port;
|
||||
private List<KeyspaceId> kids;
|
||||
|
||||
public void setKeyspace(String keyspace) {
|
||||
this.keyspace = keyspace;
|
||||
}
|
||||
|
||||
public String getKeyspace() {
|
||||
return this.keyspace;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return this.port;
|
||||
}
|
||||
|
||||
public void setPort(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public Process getPythonScriptProcess() {
|
||||
return this.pythonScriptProcess;
|
||||
}
|
||||
|
||||
public void setPythonScriptProcess(Process process) {
|
||||
this.pythonScriptProcess = process;
|
||||
}
|
||||
|
||||
public void setShardKidMap(Map<String, List<String>> shardKidMap) {
|
||||
this.shardKidMap = shardKidMap;
|
||||
}
|
||||
|
||||
public Map<String, List<String>> getShardKidMap() {
|
||||
return this.shardKidMap;
|
||||
}
|
||||
|
||||
public void addTablet(String type, int count) {
|
||||
tablets.put(type, count);
|
||||
}
|
||||
|
||||
public String getShardNames() {
|
||||
return StringUtils.join(shardKidMap.keySet(), ",");
|
||||
}
|
||||
|
||||
public String getTabletConfig() {
|
||||
return new Gson().toJson(tablets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all keyspaceIds in the Keyspace
|
||||
*/
|
||||
public List<KeyspaceId> getAllKeyspaceIds() {
|
||||
if (kids != null) {
|
||||
return kids;
|
||||
}
|
||||
|
||||
kids = new ArrayList<>();
|
||||
for (List<String> ids : shardKidMap.values()) {
|
||||
for (String id : ids) {
|
||||
kids.add(KeyspaceId.valueOf(UnsignedLong.valueOf(id)));
|
||||
}
|
||||
}
|
||||
return kids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all keyspaceIds in a specific shard
|
||||
*/
|
||||
public List<KeyspaceId> getKeyspaceIds(String shardName) {
|
||||
List<String> kidsStr = shardKidMap.get(shardName);
|
||||
if (kidsStr != null) {
|
||||
List<KeyspaceId> kids = new ArrayList<>();
|
||||
for (String kid : kidsStr) {
|
||||
kids.add(KeyspaceId.valueOf(UnsignedLong.valueOf(kid)));
|
||||
}
|
||||
return kids;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get setup command to launch a cluster.
|
||||
*/
|
||||
public List<String> getSetupCommand() {
|
||||
String vtTop = System.getenv("VTTOP");
|
||||
if (vtTop == null) {
|
||||
throw new RuntimeException("cannot find env variable: VTTOP");
|
||||
}
|
||||
List<String> command = new ArrayList<String>();
|
||||
command.add("python");
|
||||
command.add(vtTop + "/test/java_vtgate_test_helper.py");
|
||||
command.add("--shards");
|
||||
command.add(getShardNames());
|
||||
command.add("--tablet-config");
|
||||
command.add(getTabletConfig());
|
||||
command.add("--keyspace");
|
||||
command.add(keyspace);
|
||||
return command;
|
||||
}
|
||||
|
||||
public RpcClientFactory getRpcClientFactory() {
|
||||
String rpcClientFactoryClass = System.getProperty(PROPERTY_KEY_RPCCLIENT_FACTORY_CLASS);
|
||||
try {
|
||||
Class<?> clazz = Class.forName(rpcClientFactoryClass);
|
||||
return (RpcClientFactory)clazz.newInstance();
|
||||
} catch (ClassNotFoundException|IllegalAccessException|InstantiationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,126 +0,0 @@
|
|||
package com.youtube.vitess.vtgate;
|
||||
|
||||
import com.youtube.vitess.vtgate.Exceptions.ConnectionException;
|
||||
import com.youtube.vitess.vtgate.Exceptions.DatabaseException;
|
||||
import com.youtube.vitess.vtgate.Exceptions.IntegrityException;
|
||||
import com.youtube.vitess.vtgate.cursor.Cursor;
|
||||
import com.youtube.vitess.vtgate.cursor.CursorImpl;
|
||||
import com.youtube.vitess.vtgate.cursor.StreamCursor;
|
||||
import com.youtube.vitess.vtgate.rpcclient.RpcClient;
|
||||
import com.youtube.vitess.vtgate.rpcclient.RpcClientFactory;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* A single threaded VtGate client
|
||||
*
|
||||
* <p>Usage:
|
||||
*
|
||||
* <pre>
|
||||
*VtGate vtGate = VtGate.connect(addresses);
|
||||
*Query query = new QueryBuilder()...add params...build();
|
||||
*Cursor cursor = vtGate.execute(query);
|
||||
*for(Row row : cursor) {
|
||||
* processRow(row);
|
||||
*}
|
||||
*
|
||||
*For DMLs
|
||||
*vtgate.begin();
|
||||
*Query query = new QueryBuilder()...add params...build();
|
||||
*vtgate.execute(query);
|
||||
*vtgate.commit();
|
||||
*
|
||||
*vtgate.close();
|
||||
*</pre>
|
||||
*
|
||||
*/
|
||||
public class VtGate {
|
||||
private static String INTEGRITY_ERROR_MSG = "(errno 1062)";
|
||||
private RpcClient client;
|
||||
private Object session;
|
||||
|
||||
/**
|
||||
* Opens connection to a VtGate server. Connection remains open until close() is called.
|
||||
*
|
||||
* @param addresses comma separated list of host:port pairs
|
||||
* @param timeoutMs connection timeout in milliseconds, 0 for no timeout
|
||||
* @throws ConnectionException
|
||||
*/
|
||||
public static VtGate connect(String addresses, int timeoutMs, RpcClientFactory rpcFactory) throws ConnectionException {
|
||||
List<String> addressList = Arrays.asList(addresses.split(","));
|
||||
int index = new Random().nextInt(addressList.size());
|
||||
RpcClient client = rpcFactory.create(addressList.get(index), timeoutMs);
|
||||
return new VtGate(client);
|
||||
}
|
||||
|
||||
private VtGate(RpcClient client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public void begin() throws ConnectionException {
|
||||
session = client.begin();
|
||||
}
|
||||
|
||||
public Cursor execute(Query query) throws DatabaseException, ConnectionException {
|
||||
if (session != null) {
|
||||
query.setSession(session);
|
||||
}
|
||||
QueryResponse response = client.execute(query);
|
||||
if (response.getSession() != null) {
|
||||
session = response.getSession();
|
||||
}
|
||||
String error = response.getError();
|
||||
if (error != null) {
|
||||
if (error.contains(INTEGRITY_ERROR_MSG)) {
|
||||
throw new IntegrityException(error);
|
||||
}
|
||||
throw new DatabaseException(response.getError());
|
||||
}
|
||||
if (query.isStreaming()) {
|
||||
return new StreamCursor(response.getResult(), client);
|
||||
}
|
||||
return new CursorImpl(response.getResult());
|
||||
}
|
||||
|
||||
/**
|
||||
* Split a query into primary key range query parts. Rows corresponding to the sub queries will
|
||||
* add up to original queries' rows. Sub queries are by default built to run against 'rdonly'
|
||||
* instances. Batch jobs or MapReduce jobs that needs to scan all rows can use these queries to
|
||||
* parallelize full table scans.
|
||||
*/
|
||||
public Map<Query, Long> splitQuery(String keyspace, String sql, int splitCount, String pkColumn)
|
||||
throws ConnectionException, DatabaseException {
|
||||
SplitQueryRequest req = new SplitQueryRequest(sql, keyspace, splitCount, pkColumn);
|
||||
SplitQueryResponse response = client.splitQuery(req);
|
||||
if (response.getError() != null) {
|
||||
throw new DatabaseException(response.getError());
|
||||
}
|
||||
return response.getQueries();
|
||||
}
|
||||
|
||||
public void commit() throws ConnectionException {
|
||||
try {
|
||||
client.commit(session);
|
||||
} finally {
|
||||
session = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void rollback() throws ConnectionException {
|
||||
try {
|
||||
client.rollback(session);
|
||||
} finally {
|
||||
session = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void close() throws ConnectionException {
|
||||
if (session != null) {
|
||||
rollback();
|
||||
}
|
||||
client.close();
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
package com.youtube.vitess.vtgate.cursor;
|
||||
|
||||
import com.youtube.vitess.vtgate.Row;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
public interface Cursor extends Iterator<Row>, Iterable<Row> {
|
||||
public long getRowsAffected();
|
||||
|
||||
public long getLastRowId();
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
package com.youtube.vitess.vtgate.cursor;
|
||||
|
||||
import com.youtube.vitess.vtgate.QueryResult;
|
||||
import com.youtube.vitess.vtgate.Row;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
public class CursorImpl implements Cursor {
|
||||
private QueryResult result;
|
||||
private Iterator<Row> iter;
|
||||
|
||||
public CursorImpl(QueryResult result) {
|
||||
this.result = result;
|
||||
this.iter = result.getRows().iterator();
|
||||
}
|
||||
|
||||
public Row next() {
|
||||
return this.iter.next();
|
||||
}
|
||||
|
||||
public boolean hasNext() {
|
||||
return this.iter.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException("cannot remove from results");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Row> iterator() {
|
||||
return this;
|
||||
}
|
||||
|
||||
public long getRowsAffected() {
|
||||
return result.getRowsAffected();
|
||||
}
|
||||
|
||||
public long getLastRowId() {
|
||||
return result.getLastRowId();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,97 +0,0 @@
|
|||
package com.youtube.vitess.vtgate.cursor;
|
||||
|
||||
import com.youtube.vitess.vtgate.Exceptions.ConnectionException;
|
||||
import com.youtube.vitess.vtgate.QueryResult;
|
||||
import com.youtube.vitess.vtgate.Row;
|
||||
import com.youtube.vitess.vtgate.rpcclient.RpcClient;
|
||||
|
||||
import org.apache.log4j.LogManager;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* StreamCursor is for iterating through stream query results. When the current buffer is completed,
|
||||
* StreamCursor does an RPC fetch for the next set until the stream ends or an error occurs.
|
||||
*
|
||||
*/
|
||||
public class StreamCursor implements Cursor {
|
||||
static final Logger logger = LogManager.getLogger(StreamCursor.class.getName());
|
||||
|
||||
private QueryResult queryResult;
|
||||
private Iterator<Row> iterator;
|
||||
private RpcClient client;
|
||||
private boolean streamEnded;
|
||||
|
||||
public StreamCursor(QueryResult currentResult, RpcClient client) {
|
||||
this.queryResult = currentResult;
|
||||
this.iterator = currentResult.getRows().iterator();
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
fetchMoreIfNeeded();
|
||||
return iterator.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Row next() {
|
||||
fetchMoreIfNeeded();
|
||||
return this.iterator.next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException("cannot remove from results");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Row> iterator() {
|
||||
return this;
|
||||
}
|
||||
|
||||
public long getRowsAffected() {
|
||||
throw new UnsupportedOperationException("not supported for streaming cursors");
|
||||
}
|
||||
|
||||
public long getLastRowId() {
|
||||
throw new UnsupportedOperationException("not supported for streaming cursors");
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the iterator by fetching more rows if current buffer is done
|
||||
*/
|
||||
private void fetchMoreIfNeeded() {
|
||||
// Either current buffer is not empty or we have reached end of stream,
|
||||
// nothing to do here.
|
||||
if (this.iterator.hasNext() || streamEnded) {
|
||||
return;
|
||||
}
|
||||
|
||||
QueryResult qr;
|
||||
try {
|
||||
qr = client.streamNext(queryResult.getFields());
|
||||
} catch (ConnectionException e) {
|
||||
logger.error("connection exception while streaming", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
// null reply indicates EndOfStream, mark stream as ended and return
|
||||
if (qr == null) {
|
||||
streamEnded = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// For scatter streaming queries, VtGate sends fields data from each
|
||||
// shard. Since fields has already been fetched, just ignore these and
|
||||
// fetch the next batch.
|
||||
if (qr.getRows().size() == 0) {
|
||||
fetchMoreIfNeeded();
|
||||
} else {
|
||||
// We got more rows, update the current buffer and iterator
|
||||
queryResult = qr;
|
||||
iterator = queryResult.getRows().iterator();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,96 +0,0 @@
|
|||
# Hadoop Integration
|
||||
|
||||
This package contains the necessary implementations for providing Hadoop support on Vitess. This allows mapreducing over tables stored in Vitess from Hadoop.
|
||||
|
||||
Let's look at an example. Consider a table with the following schema that is stored in Vitess across several shards.
|
||||
|
||||
```
|
||||
create table sample_table (
|
||||
id bigint auto_increment,
|
||||
name varchar(64),
|
||||
keyspace_id bigint(20) unsigned NOT NULL,
|
||||
primary key (id)) Engine=InnoDB;
|
||||
```
|
||||
|
||||
Let's say we want to write a MapReduce job that imports this table from Vitess to HDFS where each row is turned into a CSV record in HDFS.
|
||||
|
||||
We can use [VitessInputFormat](https://github.com/youtube/vitess/blob/090830a29c10ae813a2edae18ad780067fb46c05/java/vtgate-client/src/main/java/com/youtube/vitess/vtgate/hadoop/VitessInputFormat.java), an implementation of Hadoop's [InputFormat](http://hadoop.apache.org/docs/stable/api/org/apache/hadoop/mapred/InputFormat.html), for that. With VitessInputFormat, rows from the source table are streamed to the mapper task. Each input record has a [NullWritable](https://hadoop.apache.org/docs/r2.2.0/api/org/apache/hadoop/io/NullWritable.html) key (no key, really), and [RowWritable](https://github.com/youtube/vitess/blob/090830a29c10ae813a2edae18ad780067fb46c05/java/vtgate-client/src/main/java/com/youtube/vitess/vtgate/hadoop/writables/RowWritable.java) as value, which is a writable implementation for the entire row's contents.
|
||||
|
||||
Here is an example implementation of our mapper, which transforms each row into a CSV Text.
|
||||
|
||||
```java
|
||||
public class TableCsvMapper extends
|
||||
Mapper<NullWritable, RowWritable, NullWritable, Text> {
|
||||
@Override
|
||||
public void map(NullWritable key, RowWritable value, Context context)
|
||||
throws IOException, InterruptedException {
|
||||
Row row = value.getRow();
|
||||
StringBuilder asCsv = new StringBuilder();
|
||||
asCsv.append(row.getInt("id"));
|
||||
asCsv.append(",");
|
||||
asCsv.append(row.getString("name"));
|
||||
asCsv.append(",");
|
||||
asCsv.append(row.getULong("keyspace_id"));
|
||||
context.write(NullWritable.get(), new Text(asCsv.toString()));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The controller code for this MR job is shown below. Note that we are not specifying any sharding/replication related information here. VtGate figures out the right number of shards and replicas to fetch the rows from. The MR author only needs to worry about which rows to fetch (query), how to process them (mapper) and the extent of parallelism (splitCount).
|
||||
|
||||
```java
|
||||
public static void main(String[] args) {
|
||||
Configuration conf = new Configuration();
|
||||
Job job = Job.getInstance(conf, "map vitess table");
|
||||
job.setJarByClass(VitessInputFormat.class);
|
||||
job.setMapperClass(TableCsvMapper.class);
|
||||
String vtgateAddresses = "localhost:15011,localhost:15012,localhost:15013";
|
||||
String keyspace = "SAMPLE_KEYSPACE";
|
||||
String query = "select id, name from sample_table";
|
||||
int splitCount = 100;
|
||||
VitessInputFormat.setInput(job, vtgateAddresses, keyspace, query, splitCount);
|
||||
job.setMapOutputKeyClass(NullWritable.class);
|
||||
job.setMapOutputValueClass(Text.class);
|
||||
...// set reducer and outputpath and launch the job
|
||||
}
|
||||
```
|
||||
|
||||
Refer [this integration test](https://github.com/youtube/vitess/blob/090830a29c10ae813a2edae18ad780067fb46c05/java/vtgate-client/src/test/java/com/youtube/vitess/vtgate/integration/hadoop/MapReduceIT.java) for a working example a MapReduce job on Vitess.
|
||||
|
||||
## How it Works
|
||||
|
||||
VitessInputFormat relies on VtGate's [SplitQuery](https://github.com/youtube/vitess/blob/c680b39852662739a4f5f539ad3ff48ae83d26d1/go/vt/vtgate/vtgateservice/interface.go#L38) service to obtain the input splits. This service accepts a SplitQueryRequest which consists of an input query and the desired number of splits (splitCount). SplitQuery returns SplitQueryResult, which has a list of SplitQueryParts. SplitQueryPart consists of a KeyRangeQuery and a size estimate of how many rows this sub-query might return. SplitQueryParts return rows that are mutually exclusive and collectively exhaustive - all rows belonging to the original input query will be returned by one and exactly one SplitQueryPart.
|
||||
|
||||
VitessInputFormat turns each SplitQueryPart into a mapper task. The number of splits generated may not be exactly equal to the desired split count specified in the input. Specifically, if the desired split count is not a multiple of the number of shards, then VtGate will round it up to the next bigger multiple of number of shards.
|
||||
|
||||
In addition to splitting the query, the SplitQuery service also acts as a gatekeeper that rejects queries unsuitable for MapReduce. Queries with potentially expensive operations such as Joins, Group By, inner queries, Distinct, Order By, etc are not allowed. Specifically, only input queries of the following syntax are permitted.
|
||||
|
||||
```
|
||||
select [list of columns] from table where [list of simple column filters];
|
||||
```
|
||||
|
||||
There are additional constraints on the table schema to ensure that the sub queries do not result in full table scans. The table must have a primary key (simple or composite) and the leading primary key must be of one of the following types.
|
||||
```
|
||||
VT_TINY, VT_SHORT, VT_LONG, VT_LONGLONG, VT_INT24, VT_FLOAT, VT_DOUBLE
|
||||
```
|
||||
|
||||
#### Split Generation
|
||||
|
||||
Here's how SplitQuery works. VtGate forwards the input query to randomly chosen ‘rdonly’ vttablets in each shard with a split count, M = original split count / N, where N is the number of shards. Each vttablet parses the query and rejects it if it does not meet the constraints. If it is a valid query, the tablet fetches the min and max value for the leading primary key column from MySQL. Split the [min, max] range of into M intervals. Construct subqueries by appending where clauses corresponding to PK range intervals to the original query and return it to VtGate. VtGate aggregates the splits received from tablets and constructs KeyRangeQueries by appending KeyRange corresponding to that shard. The following diagram depicts this flow for a sample request of split size 6 on a cluster with two shards.
|
||||
|
||||
![Image](https://raw.githubusercontent.com/youtube/vitess/090830a29c10ae813a2edae18ad780067fb46c05/java/vtgate-client/src/main/java/com/youtube/vitess/vtgate/hadoop/SplitQuery.png)
|
||||
|
||||
## Other Considerations
|
||||
|
||||
1. Specifying splitCount - Each split is a streaming query executed by a single mapper task. splitCount determines the number of mapper tasks that will be created and thus the extent of parallelism. Having too few, but long-running, splits would limit the throughput of the MR job as a whole. Long-running splits also makes retries of individual tasks failures more expensive as compared to leaner splits. On the other side, having too many splits can lead to extra overhead in task setup and connection overhead with VtGate. So, identifying the ideal split count is a balance between the two.
|
||||
|
||||
2. Joining multiple tables - Currently Vitess does not currently mapping over joined tables. However, this can be easily achieved by writing a multi-mapper MapReduce job and performing a reduce-side join in the MR job.
|
||||
|
||||
3. Views - Database Views are not great for full-table scans. If you need to map over a View, consider mapping over the underlying tables instead.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 33 KiB |
|
@ -1,78 +0,0 @@
|
|||
package com.youtube.vitess.vtgate.hadoop;
|
||||
|
||||
import com.youtube.vitess.vtgate.rpcclient.RpcClientFactory;
|
||||
import org.apache.hadoop.conf.Configuration;
|
||||
|
||||
/**
|
||||
* Collection of configuration properties used for {@link VitessInputFormat}
|
||||
*/
|
||||
public class VitessConf {
|
||||
public static final String HOSTS = "vitess.vtgate.hosts";
|
||||
public static final String CONN_TIMEOUT_MS = "vitess.vtgate.conn_timeout_ms";
|
||||
public static final String INPUT_KEYSPACE = "vitess.vtgate.hadoop.keyspace";
|
||||
public static final String INPUT_QUERY = "vitess.vtgate.hadoop.input_query";
|
||||
public static final String SPLITS = "vitess.vtgate.hadoop.splits";
|
||||
public static final String SPLIT_COLUMN = "vitess.vtgate.hadoop.splitcolumn";
|
||||
public static final String RPC_FACTORY_CLASS = "vtgate.rpcclient.factory";
|
||||
public static final String HOSTS_DELIM = ",";
|
||||
|
||||
private Configuration conf;
|
||||
|
||||
public VitessConf(Configuration conf) {
|
||||
this.conf = conf;
|
||||
}
|
||||
|
||||
public String getHosts() {
|
||||
return conf.get(HOSTS);
|
||||
}
|
||||
|
||||
public void setHosts(String hosts) {
|
||||
conf.set(HOSTS, hosts);
|
||||
}
|
||||
|
||||
public int getTimeoutMs() {
|
||||
return conf.getInt(CONN_TIMEOUT_MS, 0);
|
||||
}
|
||||
|
||||
public void setTimeoutMs(int timeoutMs) {
|
||||
conf.setInt(CONN_TIMEOUT_MS, timeoutMs);
|
||||
}
|
||||
|
||||
public String getKeyspace() {
|
||||
return conf.get(INPUT_KEYSPACE);
|
||||
}
|
||||
|
||||
public void setKeyspace(String keyspace) {
|
||||
conf.set(INPUT_KEYSPACE, keyspace);
|
||||
}
|
||||
|
||||
public String getInputQuery() {
|
||||
return conf.get(INPUT_QUERY);
|
||||
}
|
||||
|
||||
public void setInputQuery(String query) {
|
||||
conf.set(INPUT_QUERY, query);
|
||||
}
|
||||
|
||||
public int getSplits() {
|
||||
return conf.getInt(SPLITS, 1);
|
||||
}
|
||||
|
||||
public void setSplits(int splits) {
|
||||
conf.setInt(SPLITS, splits);
|
||||
}
|
||||
|
||||
public String getSplitColumn() {
|
||||
return conf.get(SPLIT_COLUMN);
|
||||
}
|
||||
|
||||
public void setSplitColumn(String splitColumn) {
|
||||
conf.set(SPLIT_COLUMN, splitColumn);
|
||||
}
|
||||
|
||||
public String getRpcFactoryClass() { return conf.get(RPC_FACTORY_CLASS); }
|
||||
|
||||
public void setRpcFactoryClass(Class<? extends RpcClientFactory> clz) {
|
||||
conf.set(RPC_FACTORY_CLASS, clz.getName());
|
||||
}
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
package com.youtube.vitess.vtgate.hadoop;
|
||||
|
||||
import com.youtube.vitess.vtgate.Exceptions.ConnectionException;
|
||||
import com.youtube.vitess.vtgate.Exceptions.DatabaseException;
|
||||
import com.youtube.vitess.vtgate.Query;
|
||||
import com.youtube.vitess.vtgate.VtGate;
|
||||
import com.youtube.vitess.vtgate.hadoop.writables.KeyspaceIdWritable;
|
||||
import com.youtube.vitess.vtgate.hadoop.writables.RowWritable;
|
||||
import com.youtube.vitess.vtgate.rpcclient.RpcClientFactory;
|
||||
import org.apache.hadoop.io.NullWritable;
|
||||
import org.apache.hadoop.mapreduce.InputFormat;
|
||||
import org.apache.hadoop.mapreduce.InputSplit;
|
||||
import org.apache.hadoop.mapreduce.Job;
|
||||
import org.apache.hadoop.mapreduce.JobContext;
|
||||
import org.apache.hadoop.mapreduce.RecordReader;
|
||||
import org.apache.hadoop.mapreduce.TaskAttemptContext;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* {@link VitessInputFormat} is the {@link InputFormat} for tables in Vitess. Input splits (
|
||||
* {@link VitessInputSplit}) are fetched from VtGate via an RPC. map() calls are supplied with a
|
||||
* {@link KeyspaceIdWritable}, {@link RowWritable} pair.
|
||||
*/
|
||||
public class VitessInputFormat extends InputFormat<NullWritable, RowWritable> {
|
||||
|
||||
@Override
|
||||
public List<InputSplit> getSplits(JobContext context) {
|
||||
try {
|
||||
VitessConf conf = new VitessConf(context.getConfiguration());
|
||||
Class<? extends RpcClientFactory> rpcFactoryClass = null;
|
||||
VtGate vtgate;
|
||||
try {
|
||||
rpcFactoryClass = (Class<? extends RpcClientFactory>)Class.forName(conf.getRpcFactoryClass());
|
||||
vtgate = VtGate.connect(conf.getHosts(), conf.getTimeoutMs(), rpcFactoryClass.newInstance());
|
||||
} catch (ClassNotFoundException|InstantiationException|IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
Map<Query, Long> queries =
|
||||
vtgate.splitQuery(conf.getKeyspace(), conf.getInputQuery(), conf.getSplits(), conf.getSplitColumn());
|
||||
List<InputSplit> splits = new LinkedList<>();
|
||||
for (Query query : queries.keySet()) {
|
||||
Long size = queries.get(query);
|
||||
InputSplit split = new VitessInputSplit(query, size);
|
||||
splits.add(split);
|
||||
}
|
||||
|
||||
for (InputSplit split : splits) {
|
||||
((VitessInputSplit) split).setLocations(conf.getHosts().split(VitessConf.HOSTS_DELIM));
|
||||
}
|
||||
return splits;
|
||||
} catch (ConnectionException | DatabaseException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public RecordReader<NullWritable, RowWritable> createRecordReader(InputSplit split,
|
||||
TaskAttemptContext context) throws IOException, InterruptedException {
|
||||
return new VitessRecordReader();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the necessary configurations for Vitess table input source
|
||||
*/
|
||||
public static void setInput(Job job,
|
||||
String hosts,
|
||||
String keyspace,
|
||||
String query,
|
||||
int splits,
|
||||
Class<? extends RpcClientFactory> rpcFactoryClass) {
|
||||
job.setInputFormatClass(VitessInputFormat.class);
|
||||
VitessConf vtConf = new VitessConf(job.getConfiguration());
|
||||
vtConf.setHosts(hosts);
|
||||
vtConf.setKeyspace(keyspace);
|
||||
vtConf.setInputQuery(query);
|
||||
vtConf.setSplits(splits);
|
||||
vtConf.setRpcFactoryClass(rpcFactoryClass);
|
||||
}
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
package com.youtube.vitess.vtgate.hadoop;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import com.youtube.vitess.vtgate.Query;
|
||||
|
||||
import org.apache.hadoop.io.Writable;
|
||||
import org.apache.hadoop.mapreduce.InputSplit;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* {@link VitessInputSplit} has a Query corresponding to a set of rows from a Vitess table input
|
||||
* source. Locations point to the same VtGate hosts used to fetch the splits. Length information is
|
||||
* only approximate.
|
||||
*
|
||||
*/
|
||||
public class VitessInputSplit extends InputSplit implements Writable {
|
||||
private String[] locations;
|
||||
private Query query;
|
||||
private long length;
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
public VitessInputSplit(Query query, long length) {
|
||||
this.query = query;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
public VitessInputSplit() {
|
||||
|
||||
}
|
||||
|
||||
public Query getQuery() {
|
||||
return query;
|
||||
}
|
||||
|
||||
public void setLocations(String[] locations) {
|
||||
this.locations = locations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLength() throws IOException, InterruptedException {
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getLocations() throws IOException, InterruptedException {
|
||||
return locations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFields(DataInput input) throws IOException {
|
||||
query = gson.fromJson(input.readUTF(), Query.class);
|
||||
length = input.readLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(DataOutput output) throws IOException {
|
||||
output.writeUTF(gson.toJson(query));
|
||||
output.writeLong(length);
|
||||
}
|
||||
}
|
|
@ -1,93 +0,0 @@
|
|||
package com.youtube.vitess.vtgate.hadoop;
|
||||
|
||||
import com.youtube.vitess.vtgate.Exceptions.ConnectionException;
|
||||
import com.youtube.vitess.vtgate.Exceptions.DatabaseException;
|
||||
import com.youtube.vitess.vtgate.Row;
|
||||
import com.youtube.vitess.vtgate.VtGate;
|
||||
import com.youtube.vitess.vtgate.cursor.Cursor;
|
||||
import com.youtube.vitess.vtgate.hadoop.writables.RowWritable;
|
||||
|
||||
import com.youtube.vitess.vtgate.rpcclient.RpcClientFactory;
|
||||
import org.apache.hadoop.io.NullWritable;
|
||||
import org.apache.hadoop.mapreduce.InputSplit;
|
||||
import org.apache.hadoop.mapreduce.RecordReader;
|
||||
import org.apache.hadoop.mapreduce.TaskAttemptContext;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class VitessRecordReader extends RecordReader<NullWritable, RowWritable> {
|
||||
private VitessInputSplit split;
|
||||
private VtGate vtgate;
|
||||
private VitessConf conf;
|
||||
private RowWritable rowWritable;
|
||||
private long rowsProcessed = 0;
|
||||
private Cursor cursor;
|
||||
|
||||
/**
|
||||
* Fetch connection parameters from Configuraiton and open VtGate connection.
|
||||
*/
|
||||
@Override
|
||||
public void initialize(InputSplit split, TaskAttemptContext context) throws IOException,
|
||||
InterruptedException {
|
||||
this.split = (VitessInputSplit) split;
|
||||
conf = new VitessConf(context.getConfiguration());
|
||||
try {
|
||||
Class<? extends RpcClientFactory> rpcFactoryClass = (Class<? extends RpcClientFactory>)Class.forName(conf.getRpcFactoryClass());
|
||||
vtgate = VtGate.connect(conf.getHosts(), conf.getTimeoutMs(), rpcFactoryClass.newInstance());
|
||||
} catch (ConnectionException|ClassNotFoundException|InstantiationException|IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (vtgate != null) {
|
||||
try {
|
||||
vtgate.close();
|
||||
vtgate = null;
|
||||
} catch (ConnectionException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public NullWritable getCurrentKey() throws IOException, InterruptedException {
|
||||
return NullWritable.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RowWritable getCurrentValue() throws IOException, InterruptedException {
|
||||
return rowWritable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getProgress() throws IOException, InterruptedException {
|
||||
if (rowsProcessed > split.getLength()) {
|
||||
return 0.9f;
|
||||
}
|
||||
return rowsProcessed / split.getLength();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the next row. If this is the first invocation for the split, execute the streaming
|
||||
* query. Subsequent calls just advance the iterator.
|
||||
*/
|
||||
@Override
|
||||
public boolean nextKeyValue() throws IOException, InterruptedException {
|
||||
if (cursor == null) {
|
||||
try {
|
||||
cursor = vtgate.execute(split.getQuery());
|
||||
} catch (DatabaseException | ConnectionException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
if (!cursor.hasNext()) {
|
||||
return false;
|
||||
}
|
||||
Row row = cursor.next();
|
||||
rowWritable = new RowWritable(row);
|
||||
rowsProcessed++;
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
package com.youtube.vitess.vtgate.hadoop.utils;
|
||||
|
||||
import com.google.common.primitives.UnsignedLong;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
/**
|
||||
* Custom GSON adapters for {@link UnsignedLong} and {@link Class} types
|
||||
*/
|
||||
public class GsonAdapters {
|
||||
public static final TypeAdapter<UnsignedLong> UNSIGNED_LONG = new TypeAdapter<UnsignedLong>() {
|
||||
@Override
|
||||
public UnsignedLong read(JsonReader in) throws IOException {
|
||||
if (in.peek() == JsonToken.NULL) {
|
||||
in.nextNull();
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return UnsignedLong.valueOf(in.nextString());
|
||||
} catch (NumberFormatException e) {
|
||||
throw new JsonSyntaxException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(JsonWriter out, UnsignedLong value) throws IOException {
|
||||
out.value(value.toString());
|
||||
}
|
||||
};
|
||||
|
||||
public static final TypeAdapter<Class> CLASS = new TypeAdapter<Class>() {
|
||||
@Override
|
||||
public Class read(JsonReader in) throws IOException {
|
||||
if (in.peek() == JsonToken.NULL) {
|
||||
in.nextNull();
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Class.forName(in.nextString());
|
||||
} catch (NumberFormatException | ClassNotFoundException e) {
|
||||
throw new JsonSyntaxException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(JsonWriter out, Class value) throws IOException {
|
||||
out.value(value.getName());
|
||||
}
|
||||
};
|
||||
|
||||
public static final Object BYTE_ARRAY = new ByteArrayAdapter();
|
||||
|
||||
private static class ByteArrayAdapter implements JsonSerializer<byte[]>, JsonDeserializer<byte[]> {
|
||||
@Override
|
||||
public byte[] deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
return Base64.decodeBase64(json.getAsString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(byte[] src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
return new JsonPrimitive(Base64.encodeBase64String(src));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
package com.youtube.vitess.vtgate.hadoop.writables;
|
||||
|
||||
import com.google.common.primitives.UnsignedLong;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import com.youtube.vitess.vtgate.KeyspaceId;
|
||||
import com.youtube.vitess.vtgate.hadoop.utils.GsonAdapters;
|
||||
|
||||
import org.apache.hadoop.io.WritableComparable;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Serializable version of {@link KeyspaceId}
|
||||
*/
|
||||
public class KeyspaceIdWritable implements WritableComparable<KeyspaceIdWritable> {
|
||||
private KeyspaceId keyspaceId;
|
||||
// KeyspaceId can be UnsignedLong which needs a custom adapter
|
||||
private Gson gson = new GsonBuilder().registerTypeAdapter(UnsignedLong.class,
|
||||
GsonAdapters.UNSIGNED_LONG).create();
|
||||
|
||||
public KeyspaceIdWritable() {}
|
||||
|
||||
public KeyspaceIdWritable(KeyspaceId keyspaceId) {
|
||||
this.keyspaceId = keyspaceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(DataOutput out) throws IOException {
|
||||
if (keyspaceId.getId() instanceof UnsignedLong) {
|
||||
out.writeUTF("UnsignedLong");
|
||||
out.writeUTF(((UnsignedLong) (keyspaceId.getId())).toString());
|
||||
} else {
|
||||
out.writeUTF("String");
|
||||
out.writeUTF((String) (keyspaceId.getId()));
|
||||
}
|
||||
}
|
||||
|
||||
public KeyspaceId getKeyspaceId() {
|
||||
return keyspaceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFields(DataInput in) throws IOException {
|
||||
String type = in.readUTF();
|
||||
Object id;
|
||||
if ("UnsignedLong".equals(type)) {
|
||||
id = UnsignedLong.valueOf(in.readUTF());
|
||||
} else {
|
||||
id = in.readUTF();
|
||||
}
|
||||
keyspaceId = KeyspaceId.valueOf(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toJson();
|
||||
}
|
||||
|
||||
public String toJson() {
|
||||
return gson.toJson(keyspaceId, KeyspaceId.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(KeyspaceIdWritable o) {
|
||||
if (o == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
|
||||
return this.keyspaceId.compareTo(o.keyspaceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o instanceof KeyspaceIdWritable) {
|
||||
return this.compareTo((KeyspaceIdWritable) o) == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return keyspaceId.hashCode();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,125 +0,0 @@
|
|||
package com.youtube.vitess.vtgate.hadoop.writables;
|
||||
|
||||
import com.google.common.primitives.UnsignedLong;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import com.youtube.vitess.vtgate.Row;
|
||||
import com.youtube.vitess.vtgate.Row.Cell;
|
||||
import com.youtube.vitess.vtgate.hadoop.utils.GsonAdapters;
|
||||
|
||||
import org.apache.commons.net.util.Base64;
|
||||
import org.apache.hadoop.io.Writable;
|
||||
import org.apache.hadoop.io.WritableComparable;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
* Serializable version of {@link Row}. Only implements {@link Writable} and not
|
||||
* {@link WritableComparable} since this is not meant to be used as a key.
|
||||
*
|
||||
*/
|
||||
public class RowWritable implements Writable {
|
||||
private Row row;
|
||||
|
||||
// Row contains UnsignedLong and Class objects which need custom adapters
|
||||
private Gson gson = new GsonBuilder()
|
||||
.registerTypeHierarchyAdapter(byte[].class, GsonAdapters.BYTE_ARRAY)
|
||||
.registerTypeAdapter(UnsignedLong.class, GsonAdapters.UNSIGNED_LONG)
|
||||
.registerTypeAdapter(Class.class, GsonAdapters.CLASS)
|
||||
.create();
|
||||
|
||||
public RowWritable() {
|
||||
|
||||
}
|
||||
|
||||
public RowWritable(Row row) {
|
||||
this.row = row;
|
||||
}
|
||||
|
||||
public Row getRow() {
|
||||
return row;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(DataOutput out) throws IOException {
|
||||
out.writeInt(row.size());
|
||||
for (Cell cell : row) {
|
||||
writeCell(out, cell);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeCell(DataOutput out, Cell cell) throws IOException {
|
||||
out.writeUTF(cell.getName());
|
||||
out.writeUTF(cell.getType().getName());
|
||||
if (cell.getType().equals(byte[].class)){
|
||||
out.writeUTF(Base64.encodeBase64String((byte[])cell.getValue()));
|
||||
} else{
|
||||
out.writeUTF(cell.getValue().toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFields(DataInput in) throws IOException {
|
||||
int size = in.readInt();
|
||||
LinkedList<Cell> cells = new LinkedList<>();
|
||||
for (int i = 0; i < size; i++) {
|
||||
cells.add(readCell(in));
|
||||
}
|
||||
row = new Row(cells);
|
||||
}
|
||||
|
||||
public Cell readCell(DataInput in) throws IOException {
|
||||
String name = in.readUTF();
|
||||
String type = in.readUTF();
|
||||
String value = in.readUTF();
|
||||
Object val = null;
|
||||
Class clazz;
|
||||
try {
|
||||
clazz = Class.forName(type);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
if (clazz.equals(Double.class)) {
|
||||
val = Double.valueOf(value);
|
||||
}
|
||||
if (clazz.equals(Integer.class)) {
|
||||
val = Integer.valueOf(value);
|
||||
}
|
||||
if (clazz.equals(Long.class)) {
|
||||
val = Long.valueOf(value);
|
||||
}
|
||||
if (clazz.equals(Float.class)) {
|
||||
val = Float.valueOf(value);
|
||||
}
|
||||
if (clazz.equals(UnsignedLong.class)) {
|
||||
val = UnsignedLong.valueOf(value);
|
||||
}
|
||||
if (clazz.equals(Date.class)) {
|
||||
val = Date.parse(value);
|
||||
}
|
||||
if (clazz.equals(String.class)) {
|
||||
val = value;
|
||||
}
|
||||
if (clazz.equals(byte[].class)) {
|
||||
val = Base64.decodeBase64(value);
|
||||
}
|
||||
if (val == null) {
|
||||
throw new RuntimeException("unknown type in RowWritable: " + clazz);
|
||||
}
|
||||
return new Cell(name, val, clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toJson();
|
||||
}
|
||||
|
||||
public String toJson() {
|
||||
return gson.toJson(row, Row.class);
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
package com.youtube.vitess.vtgate.rpcclient;
|
||||
|
||||
import com.youtube.vitess.vtgate.Exceptions.ConnectionException;
|
||||
import com.youtube.vitess.vtgate.Field;
|
||||
import com.youtube.vitess.vtgate.Query;
|
||||
import com.youtube.vitess.vtgate.QueryResponse;
|
||||
import com.youtube.vitess.vtgate.QueryResult;
|
||||
import com.youtube.vitess.vtgate.SplitQueryRequest;
|
||||
import com.youtube.vitess.vtgate.SplitQueryResponse;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface RpcClient {
|
||||
|
||||
public Object begin() throws ConnectionException;
|
||||
|
||||
public void commit(Object session) throws ConnectionException;
|
||||
|
||||
public void rollback(Object session) throws ConnectionException;
|
||||
|
||||
public QueryResponse execute(Query query) throws ConnectionException;
|
||||
|
||||
public List<QueryResponse> executeBatchKeyspaceIds(
|
||||
List<Query> queries, String tabletType, Object session, boolean asTransaction)
|
||||
throws ConnectionException;
|
||||
|
||||
public QueryResult streamNext(List<Field> fields) throws ConnectionException;
|
||||
|
||||
public SplitQueryResponse splitQuery(SplitQueryRequest request) throws ConnectionException;
|
||||
|
||||
public void close() throws ConnectionException;
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
package com.youtube.vitess.vtgate.rpcclient;
|
||||
|
||||
import com.youtube.vitess.vtgate.Exceptions.ConnectionException;
|
||||
|
||||
public interface RpcClientFactory {
|
||||
RpcClient create(String address, int timeoutMs) throws ConnectionException;
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
package com.youtube.vitess.vtgate.rpcclient.gorpc;
|
||||
|
||||
import com.google.common.net.HostAndPort;
|
||||
import com.youtube.vitess.gorpc.Client;
|
||||
import com.youtube.vitess.gorpc.Exceptions.GoRpcException;
|
||||
import com.youtube.vitess.gorpc.codecs.bson.BsonClientCodecFactory;
|
||||
import com.youtube.vitess.vtgate.Exceptions.ConnectionException;
|
||||
import com.youtube.vitess.vtgate.rpcclient.RpcClient;
|
||||
import com.youtube.vitess.vtgate.rpcclient.RpcClientFactory;
|
||||
|
||||
public class BsonRpcClientFactory implements RpcClientFactory {
|
||||
@Override
|
||||
public RpcClient create(String address, int timeoutMs) throws ConnectionException {
|
||||
try {
|
||||
HostAndPort hostAndPort = HostAndPort.fromString(address);
|
||||
Client client = Client.dialHttp(hostAndPort.getHostText(), hostAndPort.getPort(),
|
||||
GoRpcClient.BSON_RPC_PATH, timeoutMs, new BsonClientCodecFactory());
|
||||
return new GoRpcClient(client);
|
||||
} catch (GoRpcException e) {
|
||||
GoRpcClient.LOGGER.error("vtgate connection exception: ", e);
|
||||
throw new ConnectionException(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,258 +0,0 @@
|
|||
package com.youtube.vitess.vtgate.rpcclient.gorpc;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
import com.google.common.primitives.UnsignedLong;
|
||||
|
||||
import com.youtube.vitess.vtgate.BatchQuery;
|
||||
import com.youtube.vitess.vtgate.BatchQueryResponse;
|
||||
import com.youtube.vitess.vtgate.BindVariable;
|
||||
import com.youtube.vitess.vtgate.Field;
|
||||
import com.youtube.vitess.vtgate.Field.Flag;
|
||||
import com.youtube.vitess.vtgate.KeyRange;
|
||||
import com.youtube.vitess.vtgate.KeyspaceId;
|
||||
import com.youtube.vitess.vtgate.Query;
|
||||
import com.youtube.vitess.vtgate.Query.QueryBuilder;
|
||||
import com.youtube.vitess.vtgate.QueryResponse;
|
||||
import com.youtube.vitess.vtgate.QueryResult;
|
||||
import com.youtube.vitess.vtgate.Row;
|
||||
import com.youtube.vitess.vtgate.Row.Cell;
|
||||
import com.youtube.vitess.vtgate.SplitQueryRequest;
|
||||
import com.youtube.vitess.vtgate.SplitQueryResponse;
|
||||
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
import org.bson.BSONObject;
|
||||
import org.bson.BasicBSONObject;
|
||||
import org.bson.types.BasicBSONList;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class Bsonify {
|
||||
public static BSONObject queryToBson(Query query) {
|
||||
BSONObject b = new BasicBSONObject();
|
||||
b.put("Sql", query.getSql());
|
||||
b.put("Keyspace", query.getKeyspace());
|
||||
b.put("TabletType", query.getTabletType());
|
||||
if (query.getBindVars() != null) {
|
||||
b.put("BindVariables", bindVarsToBSON(query.getBindVars()));
|
||||
}
|
||||
if (query.getKeyspaceIds() != null) {
|
||||
b.put("KeyspaceIds", query.getKeyspaceIds());
|
||||
} else {
|
||||
b.put("KeyRanges", query.getKeyRanges());
|
||||
}
|
||||
if (query.getSession() != null) {
|
||||
b.put("Session", query.getSession());
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
public static BSONObject bindVarsToBSON(List<BindVariable> bindVars) {
|
||||
BSONObject bindVariables = new BasicBSONObject();
|
||||
for (BindVariable b : bindVars) {
|
||||
if (b.getType().equals(BindVariable.Type.NULL)) {
|
||||
bindVariables.put(b.getName(), null);
|
||||
}
|
||||
if (b.getType().equals(BindVariable.Type.LONG)) {
|
||||
bindVariables.put(b.getName(), b.getLongVal());
|
||||
}
|
||||
if (b.getType().equals(BindVariable.Type.UNSIGNED_LONG)) {
|
||||
bindVariables.put(b.getName(), b.getULongVal());
|
||||
}
|
||||
if (b.getType().equals(BindVariable.Type.DOUBLE)) {
|
||||
bindVariables.put(b.getName(), b.getDoubleVal());
|
||||
}
|
||||
if (b.getType().equals(BindVariable.Type.BYTE_ARRAY)) {
|
||||
bindVariables.put(b.getName(), b.getByteArrayVal());
|
||||
}
|
||||
}
|
||||
return bindVariables;
|
||||
}
|
||||
|
||||
public static BSONObject batchQueryToBson(BatchQuery batchQuery) {
|
||||
BSONObject b = new BasicBSONObject();
|
||||
List<Map<String, Object>> queries = new LinkedList<>();
|
||||
Iterator<String> sqlIter = batchQuery.getSqls().iterator();
|
||||
Iterator<List<BindVariable>> bvIter = batchQuery.getBindVarsList().iterator();
|
||||
while (sqlIter.hasNext()) {
|
||||
Map<String, Object> query = new HashMap<>();
|
||||
query.put("Sql", sqlIter.next());
|
||||
List<BindVariable> bindVars = bvIter.next();
|
||||
if (bindVars != null) {
|
||||
query.put("BindVariables", bindVarsToBSON(bindVars));
|
||||
}
|
||||
queries.add(query);
|
||||
}
|
||||
b.put("Queries", queries);
|
||||
b.put("Keyspace", batchQuery.getKeyspace());
|
||||
b.put("TabletType", batchQuery.getTabletType());
|
||||
b.put("KeyspaceIds", batchQuery.getKeyspaceIds());
|
||||
if (batchQuery.getSession() != null) {
|
||||
b.put("Session", batchQuery.getSession());
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
public static QueryResponse bsonToQueryResponse(BSONObject reply) {
|
||||
String error = null;
|
||||
if (reply.containsField("Error")) {
|
||||
byte[] err = (byte[]) reply.get("Error");
|
||||
if (err.length > 0) {
|
||||
error = new String(err);
|
||||
}
|
||||
}
|
||||
|
||||
QueryResult queryResult = null;
|
||||
BSONObject result = (BSONObject) reply.get("Result");
|
||||
if (result != null) {
|
||||
queryResult = bsonToQueryResult(result, null);
|
||||
}
|
||||
|
||||
Object session = null;
|
||||
if (reply.containsField("Session")) {
|
||||
session = reply.get("Session");
|
||||
}
|
||||
return new QueryResponse(queryResult, session, error);
|
||||
}
|
||||
|
||||
public static BatchQueryResponse bsonToBatchQueryResponse(BSONObject reply) {
|
||||
String error = null;
|
||||
if (reply.containsField("Error")) {
|
||||
byte[] err = (byte[]) reply.get("Error");
|
||||
if (err.length > 0) {
|
||||
error = new String(err);
|
||||
}
|
||||
}
|
||||
Object session = null;
|
||||
if (reply.containsField("Session")) {
|
||||
session = reply.get("Session");
|
||||
}
|
||||
List<QueryResult> qrs = new LinkedList<>();
|
||||
BasicBSONList results = (BasicBSONList) reply.get("List");
|
||||
for (Object result : results) {
|
||||
qrs.add(bsonToQueryResult((BSONObject) result, null));
|
||||
}
|
||||
return new BatchQueryResponse(qrs, session, error);
|
||||
}
|
||||
|
||||
public static QueryResult bsonToQueryResult(BSONObject result, List<Field> fields) {
|
||||
if (fields == null) {
|
||||
fields = bsonToFields(result);
|
||||
}
|
||||
List<Row> rows = bsonToRows(result, fields);
|
||||
long rowsAffected = (long) result.get("RowsAffected");
|
||||
long lastRowId = (long) result.get("InsertId");
|
||||
return new QueryResult(rows, fields, rowsAffected, lastRowId);
|
||||
}
|
||||
|
||||
public static List<Field> bsonToFields(BSONObject result) {
|
||||
List<Field> fieldList = new LinkedList<>();
|
||||
BasicBSONList fields = (BasicBSONList) result.get("Fields");
|
||||
for (Object field : fields) {
|
||||
BSONObject fieldBson = (BSONObject) field;
|
||||
String fieldName = new String((byte[]) fieldBson.get("Name"));
|
||||
int mysqlType = Ints.checkedCast((Long) fieldBson.get("Type"));
|
||||
long mysqlFlags = Flag.VT_ZEROVALUE_FLAG.mysqlFlag;
|
||||
Object flags = fieldBson.get("Flags");
|
||||
if (flags != null) {
|
||||
mysqlFlags = (Long) flags;
|
||||
}
|
||||
fieldList.add(Field.newFieldFromMysql(fieldName, mysqlType, mysqlFlags));
|
||||
}
|
||||
return fieldList;
|
||||
}
|
||||
|
||||
public static List<Row> bsonToRows(BSONObject result, List<Field> fields) {
|
||||
List<Row> rowList = new LinkedList<>();
|
||||
BasicBSONList rows = (BasicBSONList) result.get("Rows");
|
||||
for (Object row : rows) {
|
||||
LinkedList<Cell> cells = new LinkedList<>();
|
||||
BasicBSONList cols = (BasicBSONList) row;
|
||||
Iterator<Field> fieldsIter = fields.iterator();
|
||||
for (Object col : cols) {
|
||||
byte[] val = col != null ? (byte[]) col : null;
|
||||
Field field = fieldsIter.next();
|
||||
cells.add(field.convertValueToCell(val));
|
||||
}
|
||||
rowList.add(new Row(cells));
|
||||
}
|
||||
return rowList;
|
||||
}
|
||||
|
||||
public static BSONObject splitQueryRequestToBson(SplitQueryRequest request) {
|
||||
BSONObject query = new BasicBSONObject();
|
||||
query.put("Sql", request.getSql());
|
||||
BSONObject b = new BasicBSONObject();
|
||||
b.put("Keyspace", request.getKeyspace());
|
||||
b.put("SplitColumn", request.getSplitColumn());
|
||||
b.put("Query", query);
|
||||
b.put("SplitCount", request.getSplitCount());
|
||||
return b;
|
||||
}
|
||||
|
||||
public static SplitQueryResponse bsonToSplitQueryResponse(BSONObject reply) {
|
||||
String error = null;
|
||||
if (reply.containsField("Error")) {
|
||||
byte[] err = (byte[]) reply.get("Error");
|
||||
if (err.length > 0) {
|
||||
error = new String(err);
|
||||
}
|
||||
}
|
||||
BasicBSONList result = (BasicBSONList) reply.get("Splits");
|
||||
Map<Query, Long> queries = new HashMap<>();
|
||||
for (Object split : result) {
|
||||
BSONObject splitObj = (BasicBSONObject) split;
|
||||
BSONObject query = (BasicBSONObject) (splitObj.get("Query"));
|
||||
String sql = new String((byte[]) query.get("Sql"));
|
||||
BSONObject bindVars = (BasicBSONObject) query.get("BindVariables");
|
||||
List<BindVariable> bindVariables = new LinkedList<>();
|
||||
for (String key : bindVars.keySet()) {
|
||||
BindVariable bv = null;
|
||||
BSONObject val = (BasicBSONObject) bindVars.get(key);
|
||||
|
||||
if (val == null) {
|
||||
bv = BindVariable.forNull(key);
|
||||
}
|
||||
|
||||
int type = (int)val.get("Type");
|
||||
|
||||
if (type == 3 /*unsigned long*/) {
|
||||
bv = BindVariable.forULong(key, (UnsignedLong) val.get("ValueUint"));
|
||||
}
|
||||
if (type == 2 /*long*/) {
|
||||
bv = BindVariable.forLong(key, (Long) val.get("ValueInt"));
|
||||
}
|
||||
if (type == 4 /*double*/) {
|
||||
bv = BindVariable.forDouble(key, (Double) val.get("ValueFloat"));
|
||||
}
|
||||
if (type == 1 /*byte[]*/) {
|
||||
bv = BindVariable.forBytes(key, (byte[]) val.get("ValueBytes"));
|
||||
}
|
||||
if (bv == null) {
|
||||
throw new RuntimeException("invalid bind variable type: " + val.getClass());
|
||||
}
|
||||
bindVariables.add(bv);
|
||||
}
|
||||
BSONObject keyrangePart = (BasicBSONObject)splitObj.get("KeyRangePart");
|
||||
String keyspace = new String((byte[]) keyrangePart.get("Keyspace"));
|
||||
List<KeyRange> keyranges = new ArrayList<>();
|
||||
for (Object o : (List<?>) keyrangePart.get("KeyRanges")) {
|
||||
BSONObject keyrange = (BasicBSONObject) o;
|
||||
String start = Hex.encodeHexString((byte[]) keyrange.get("Start"));
|
||||
String end = Hex.encodeHexString((byte[]) keyrange.get("End"));
|
||||
KeyRange kr = new KeyRange(KeyspaceId.valueOf(start), KeyspaceId.valueOf(end));
|
||||
keyranges.add(kr);
|
||||
}
|
||||
|
||||
Query q = new QueryBuilder(sql, keyspace, "rdonly").setKeyRanges(keyranges)
|
||||
.setBindVars(bindVariables).setStreaming(true).build();
|
||||
long size = (long) splitObj.get("Size");
|
||||
queries.put(q, size);
|
||||
}
|
||||
return new SplitQueryResponse(queries, error);
|
||||
}
|
||||
}
|
|
@ -1,134 +0,0 @@
|
|||
package com.youtube.vitess.vtgate.rpcclient.gorpc;
|
||||
|
||||
import com.youtube.vitess.gorpc.Client;
|
||||
import com.youtube.vitess.gorpc.Exceptions.ApplicationException;
|
||||
import com.youtube.vitess.gorpc.Exceptions.GoRpcException;
|
||||
import com.youtube.vitess.gorpc.Response;
|
||||
import com.youtube.vitess.vtgate.Exceptions.ConnectionException;
|
||||
import com.youtube.vitess.vtgate.Field;
|
||||
import com.youtube.vitess.vtgate.Query;
|
||||
import com.youtube.vitess.vtgate.QueryResponse;
|
||||
import com.youtube.vitess.vtgate.QueryResult;
|
||||
import com.youtube.vitess.vtgate.SplitQueryRequest;
|
||||
import com.youtube.vitess.vtgate.SplitQueryResponse;
|
||||
import com.youtube.vitess.vtgate.rpcclient.RpcClient;
|
||||
|
||||
import org.apache.log4j.LogManager;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.bson.BSONObject;
|
||||
import org.bson.BasicBSONObject;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GoRpcClient implements RpcClient {
|
||||
public static final Logger LOGGER = LogManager.getLogger(GoRpcClient.class.getName());
|
||||
public static final String BSON_RPC_PATH = "/_bson_rpc_";
|
||||
private Client client;
|
||||
|
||||
public GoRpcClient(Client client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object begin() throws ConnectionException {
|
||||
Response response = call("VTGate.Begin", new BasicBSONObject());
|
||||
return response.getReply();
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryResponse execute(Query query) throws ConnectionException {
|
||||
String callMethod = null;
|
||||
Response response;
|
||||
if (query.isStreaming()) {
|
||||
if (query.getKeyspaceIds() != null) {
|
||||
callMethod = "VTGate.StreamExecuteKeyspaceIds";
|
||||
} else {
|
||||
callMethod = "VTGate.StreamExecuteKeyRanges";
|
||||
}
|
||||
response = streamCall(callMethod, Bsonify.queryToBson(query));
|
||||
} else {
|
||||
if (query.getKeyspaceIds() != null) {
|
||||
callMethod = "VTGate.ExecuteKeyspaceIds";
|
||||
} else {
|
||||
callMethod = "VTGate.ExecuteKeyRanges";
|
||||
}
|
||||
response = call(callMethod, Bsonify.queryToBson(query));
|
||||
}
|
||||
return Bsonify.bsonToQueryResponse((BSONObject) response.getReply());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<QueryResponse> executeBatchKeyspaceIds(
|
||||
List<Query> queries, String tabletType, Object session, boolean asTransaction)
|
||||
throws ConnectionException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryResult streamNext(List<Field> fields) throws ConnectionException {
|
||||
Response response;
|
||||
try {
|
||||
response = client.streamNext();
|
||||
} catch (GoRpcException | ApplicationException e) {
|
||||
LOGGER.error("vtgate exception", e);
|
||||
throw new ConnectionException("vtgate exception: " + e.getMessage());
|
||||
}
|
||||
|
||||
if (response == null) {
|
||||
return null;
|
||||
}
|
||||
BSONObject reply = (BSONObject) response.getReply();
|
||||
if (reply.containsField("Result")) {
|
||||
BSONObject result = (BSONObject) reply.get("Result");
|
||||
return Bsonify.bsonToQueryResult(result, fields);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit(Object session) throws ConnectionException {
|
||||
call("VTGate.Commit", session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback(Object session) throws ConnectionException {
|
||||
call("VTGate.Rollback", session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws ConnectionException {
|
||||
try {
|
||||
client.close();
|
||||
} catch (GoRpcException e) {
|
||||
LOGGER.error("vtgate exception", e);
|
||||
throw new ConnectionException("vtgate exception: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SplitQueryResponse splitQuery(SplitQueryRequest request) throws ConnectionException {
|
||||
String callMethod = "VTGate.SplitQuery";
|
||||
Response response = call(callMethod, Bsonify.splitQueryRequestToBson(request));
|
||||
return Bsonify.bsonToSplitQueryResponse((BSONObject) response.getReply());
|
||||
}
|
||||
|
||||
private Response call(String methodName, Object args) throws ConnectionException {
|
||||
try {
|
||||
Response response = client.call(methodName, args);
|
||||
return response;
|
||||
} catch (GoRpcException | ApplicationException e) {
|
||||
LOGGER.error("vtgate exception", e);
|
||||
throw new ConnectionException("vtgate exception: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private Response streamCall(String methodName, Object args) throws ConnectionException {
|
||||
try {
|
||||
client.streamCall(methodName, args);
|
||||
return client.streamNext();
|
||||
} catch (GoRpcException | ApplicationException e) {
|
||||
LOGGER.error("vtgate exception", e);
|
||||
throw new ConnectionException("vtgate exception: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
log4j.rootLogger=ERROR, consoleAppender
|
||||
|
||||
log4j.appender.consoleAppender=org.apache.log4j.ConsoleAppender
|
||||
log4j.appender.consoleAppender.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.consoleAppender.layout.ConversionPattern=[%t] %-5p %c %x - %m%n
|
|
@ -1,46 +0,0 @@
|
|||
package com.youtube.vitess.vtgate;
|
||||
|
||||
import com.youtube.vitess.vtgate.Field.Flag;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
import java.text.ParseException;
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public class DateTypesTest {
|
||||
@Test
|
||||
public void testDateTimes() throws Exception {
|
||||
DateTime dt = DateTime.now();
|
||||
byte[] bytes = BindVariable.forDateTime("", dt).getByteArrayVal();
|
||||
check(FieldType.VT_TIMESTAMP, dt, bytes);
|
||||
check(FieldType.VT_DATETIME, dt, bytes);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDate() throws Exception {
|
||||
DateTime now = DateTime.now();
|
||||
byte[] bytes = BindVariable.forDate("", now).getByteArrayVal();
|
||||
DateTime date =
|
||||
now.withMillisOfSecond(0).withHourOfDay(0).withMinuteOfHour(0).withSecondOfMinute(0);
|
||||
check(FieldType.VT_DATE, date, bytes);
|
||||
check(FieldType.VT_NEWDATE, date, bytes);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTime() throws Exception {
|
||||
DateTime now = DateTime.now();
|
||||
byte[] bytes = BindVariable.forTime("", now).getByteArrayVal();
|
||||
DateTime time = now.withMillisOfSecond(0).withYear(1970).withMonthOfYear(1).withDayOfMonth(1);
|
||||
check(FieldType.VT_TIME, time, bytes);
|
||||
}
|
||||
|
||||
private void check(FieldType typeUnderTest, DateTime dt, byte[] bytes) throws ParseException {
|
||||
Field f = Field.newFieldForTest(typeUnderTest, Flag.VT_ZEROVALUE_FLAG);
|
||||
Object o = f.convert(bytes);
|
||||
Assert.assertEquals(DateTime.class, o.getClass());
|
||||
Assert.assertEquals(dt, (DateTime) o);
|
||||
}
|
||||
}
|
|
@ -1,97 +0,0 @@
|
|||
package com.youtube.vitess.vtgate;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.primitives.UnsignedLong;
|
||||
import com.youtube.vitess.vtgate.Field.Flag;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public class FieldTest {
|
||||
@Test
|
||||
public void testDouble() {
|
||||
List<FieldType> typesToTest = Lists.newArrayList(FieldType.VT_DOUBLE);
|
||||
String val = "1000.01";
|
||||
for (FieldType type : typesToTest) {
|
||||
Field f = Field.newFieldForTest(type, Flag.VT_ZEROVALUE_FLAG);
|
||||
Object o = f.convert(val.getBytes());
|
||||
Assert.assertEquals(Double.class, o.getClass());
|
||||
Assert.assertEquals(1000.01, ((Double) o).doubleValue(), 0.01);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBigDecimal() {
|
||||
List<FieldType> typesToTest = Lists.newArrayList(FieldType.VT_DECIMAL, FieldType.VT_NEWDECIMAL);
|
||||
String val = "1000.01";
|
||||
for (FieldType type : typesToTest) {
|
||||
Field f = Field.newFieldForTest(type, Flag.VT_ZEROVALUE_FLAG);
|
||||
Object o = f.convert(val.getBytes());
|
||||
Assert.assertEquals(BigDecimal.class, o.getClass());
|
||||
Assert.assertEquals(1000.01, ((BigDecimal) o).doubleValue(), 0.01);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInteger() {
|
||||
List<FieldType> typesToTest =
|
||||
Lists.newArrayList(FieldType.VT_TINY, FieldType.VT_SHORT, FieldType.VT_INT24);
|
||||
String val = "1000";
|
||||
for (FieldType type : typesToTest) {
|
||||
Field f = Field.newFieldForTest(type, Flag.VT_ZEROVALUE_FLAG);
|
||||
Object o = f.convert(val.getBytes());
|
||||
Assert.assertEquals(Integer.class, o.getClass());
|
||||
Assert.assertEquals(1000, ((Integer) o).intValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLong_LONG() {
|
||||
String val = "1000";
|
||||
Field f = Field.newFieldForTest(FieldType.VT_LONG, Flag.VT_ZEROVALUE_FLAG);
|
||||
Object o = f.convert(val.getBytes());
|
||||
Assert.assertEquals(Long.class, o.getClass());
|
||||
Assert.assertEquals(1000L, ((Long) o).longValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLong_LONGLONG() {
|
||||
String val = "10000000000000";
|
||||
Field f = Field.newFieldForTest(FieldType.VT_LONGLONG, Flag.VT_ZEROVALUE_FLAG);
|
||||
Object o = f.convert(val.getBytes());
|
||||
Assert.assertEquals(Long.class, o.getClass());
|
||||
Assert.assertEquals(10000000000000L, ((Long) o).longValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLong_LONGLONG_UNSIGNED() {
|
||||
String val = "10000000000000";
|
||||
Field f = Field.newFieldForTest(FieldType.VT_LONGLONG, Flag.VT_UNSIGNED_FLAG);
|
||||
Object o = f.convert(val.getBytes());
|
||||
Assert.assertEquals(UnsignedLong.class, o.getClass());
|
||||
Assert.assertEquals(10000000000000L, ((UnsignedLong) o).longValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNull() {
|
||||
Field f = Field.newFieldForTest(FieldType.VT_NULL, Flag.VT_ZEROVALUE_FLAG);
|
||||
Object o = f.convert(null);
|
||||
Assert.assertEquals(null, o);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFloat() {
|
||||
String val = "1000.01";
|
||||
Field f = Field.newFieldForTest(FieldType.VT_FLOAT, Flag.VT_ZEROVALUE_FLAG);
|
||||
Object o = f.convert(val.getBytes());
|
||||
Assert.assertEquals(Float.class, o.getClass());
|
||||
Assert.assertEquals(1000.01, ((Float) o).floatValue(), 0.1);
|
||||
}
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
package com.youtube.vitess.vtgate;
|
||||
|
||||
import com.youtube.vitess.vtgate.Query.QueryBuilder;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public class QueryBuilderTest {
|
||||
@Test
|
||||
public void testValidQueryWithKeyspaceIds() {
|
||||
String sql = "select 1 from dual";
|
||||
KeyspaceId kid = KeyspaceId.valueOf("80");
|
||||
QueryBuilder builder =
|
||||
new QueryBuilder("select 1 from dual", "test_keyspace", "master").addKeyspaceId(kid);
|
||||
Query query = builder.build();
|
||||
Assert.assertEquals(sql, query.getSql());
|
||||
Assert.assertEquals("test_keyspace", query.getKeyspace());
|
||||
Assert.assertEquals("master", query.getTabletType());
|
||||
Assert.assertEquals(null, query.getBindVars());
|
||||
Assert.assertEquals(1, query.getKeyspaceIds().size());
|
||||
Assert.assertTrue(Arrays.equals(kid.getBytes(), query.getKeyspaceIds().get(0)));
|
||||
Assert.assertEquals(null, query.getKeyRanges());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidQueryWithKeyRanges() {
|
||||
String sql = "select 1 from dual";
|
||||
QueryBuilder builder =
|
||||
new QueryBuilder("select 1 from dual", "test_keyspace", "master").addKeyRange(KeyRange.ALL);
|
||||
Query query = builder.build();
|
||||
Assert.assertEquals(sql, query.getSql());
|
||||
Assert.assertEquals("test_keyspace", query.getKeyspace());
|
||||
Assert.assertEquals("master", query.getTabletType());
|
||||
Assert.assertEquals(null, query.getBindVars());
|
||||
Assert.assertEquals(1, query.getKeyRanges().size());
|
||||
Assert.assertEquals(KeyRange.ALL.toMap(), query.getKeyRanges().get(0));
|
||||
Assert.assertEquals(null, query.getKeyspaceIds());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoKeyspaceIdOrKeyrange() {
|
||||
QueryBuilder builder = new QueryBuilder("select 1 from dual", "test_keyspace", "master");
|
||||
try {
|
||||
builder.build();
|
||||
Assert.fail("did not raise IllegalStateException");
|
||||
} catch (IllegalStateException e) {
|
||||
Assert.assertEquals("query must have either keyspaceIds or keyRanges", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBothKeyspaceIdAndKeyrange() {
|
||||
QueryBuilder builder = new QueryBuilder("select 1 from dual", "test_keyspace", "master")
|
||||
.addKeyRange(KeyRange.ALL).addKeyspaceId(KeyspaceId.valueOf("80"));
|
||||
try {
|
||||
builder.build();
|
||||
Assert.fail("did not raise IllegalStateException");
|
||||
} catch (IllegalStateException e) {
|
||||
Assert.assertEquals("query cannot have both keyspaceIds and keyRanges", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,234 +0,0 @@
|
|||
package com.youtube.vitess.vtgate.integration;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.primitives.UnsignedLong;
|
||||
|
||||
import com.youtube.vitess.vtgate.BindVariable;
|
||||
import com.youtube.vitess.vtgate.KeyRange;
|
||||
import com.youtube.vitess.vtgate.Query;
|
||||
import com.youtube.vitess.vtgate.Query.QueryBuilder;
|
||||
import com.youtube.vitess.vtgate.Row;
|
||||
import com.youtube.vitess.vtgate.VtGate;
|
||||
import com.youtube.vitess.vtgate.cursor.Cursor;
|
||||
import com.youtube.vitess.vtgate.TestEnv;
|
||||
import com.youtube.vitess.vtgate.integration.util.Util;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.format.ISODateTimeFormat;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Assert;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public class DataTypesIT {
|
||||
|
||||
public static TestEnv testEnv = getTestEnv();
|
||||
|
||||
@BeforeClass
|
||||
public static void setUpVtGate() throws Exception {
|
||||
Util.setupTestEnv(testEnv);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDownVtGate() throws Exception {
|
||||
Util.teardownTestEnv(testEnv);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInts() throws Exception {
|
||||
String createTable =
|
||||
"create table vtocc_ints(tiny tinyint, tinyu tinyint unsigned, small smallint, smallu smallint unsigned, medium mediumint, mediumu mediumint unsigned, normal int, normalu int unsigned, big bigint, bigu bigint unsigned, year year, primary key(tiny)) comment 'vtocc_nocache'\n" + "";
|
||||
VtGate vtgate = VtGate.connect("localhost:" + testEnv.getPort(), 0, testEnv.getRpcClientFactory());
|
||||
vtgate.begin();
|
||||
vtgate.execute(getQuery(createTable));
|
||||
vtgate.commit();
|
||||
|
||||
String insertSql =
|
||||
"insert into vtocc_ints values(:tiny, :tinyu, :small, :smallu, :medium, :mediumu, :normal, :normalu, :big, :bigu, :year)";
|
||||
Query insertQuery = new QueryBuilder(insertSql, testEnv.getKeyspace(), "master")
|
||||
.addBindVar(BindVariable.forInt("tiny", -128))
|
||||
.addBindVar(BindVariable.forInt("tinyu", 255))
|
||||
.addBindVar(BindVariable.forInt("small", -32768))
|
||||
.addBindVar(BindVariable.forInt("smallu", 65535))
|
||||
.addBindVar(BindVariable.forInt("medium", -8388608))
|
||||
.addBindVar(BindVariable.forInt("mediumu", 16777215))
|
||||
.addBindVar(BindVariable.forLong("normal", -2147483648L))
|
||||
.addBindVar(BindVariable.forLong("normalu", 4294967295L))
|
||||
.addBindVar(BindVariable.forLong("big", -9223372036854775808L))
|
||||
.addBindVar(BindVariable.forULong("bigu", UnsignedLong.valueOf("18446744073709551615")))
|
||||
.addBindVar(BindVariable.forShort("year", (short) 2012))
|
||||
.addKeyRange(KeyRange.ALL)
|
||||
.build();
|
||||
vtgate.begin();
|
||||
vtgate.execute(insertQuery);
|
||||
vtgate.commit();
|
||||
|
||||
String selectSql = "select * from vtocc_ints where tiny = -128";
|
||||
Query selectQuery =
|
||||
new QueryBuilder(selectSql, testEnv.getKeyspace(), "master").addKeyRange(KeyRange.ALL).build();
|
||||
Cursor cursor = vtgate.execute(selectQuery);
|
||||
Assert.assertEquals(1, cursor.getRowsAffected());
|
||||
Row row = cursor.next();
|
||||
Assert.assertTrue(row.getInt("tiny").equals(-128));
|
||||
Assert.assertTrue(row.getInt("tinyu").equals(255));
|
||||
Assert.assertTrue(row.getInt("small").equals(-32768));
|
||||
Assert.assertTrue(row.getInt("smallu").equals(65535));
|
||||
Assert.assertTrue(row.getInt("medium").equals(-8388608));
|
||||
Assert.assertTrue(row.getInt("mediumu").equals(16777215));
|
||||
Assert.assertTrue(row.getLong("normal").equals(-2147483648L));
|
||||
Assert.assertTrue(row.getLong("normalu").equals(4294967295L));
|
||||
Assert.assertTrue(row.getLong("big").equals(-9223372036854775808L));
|
||||
Assert.assertTrue(row.getULong("bigu").equals(UnsignedLong.valueOf("18446744073709551615")));
|
||||
Assert.assertTrue(row.getShort("year").equals((short) 2012));
|
||||
vtgate.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFracts() throws Exception {
|
||||
String createTable =
|
||||
"create table vtocc_fracts(id int, deci decimal(5,2), num numeric(5,2), f float, d double, primary key(id)) comment 'vtocc_nocache'\n" + "";
|
||||
VtGate vtgate = VtGate.connect("localhost:" + testEnv.getPort(), 0, testEnv.getRpcClientFactory());
|
||||
vtgate.begin();
|
||||
vtgate.execute(getQuery(createTable));
|
||||
vtgate.commit();
|
||||
|
||||
String insertSql = "insert into vtocc_fracts values(:id, :deci, :num, :f, :d)";
|
||||
Query insertQuery = new QueryBuilder(insertSql, testEnv.getKeyspace(), "master")
|
||||
.addBindVar(BindVariable.forInt("id", 1))
|
||||
.addBindVar(BindVariable.forDouble("deci", 1.99))
|
||||
.addBindVar(BindVariable.forDouble("num", 2.99))
|
||||
.addBindVar(BindVariable.forFloat("f", 3.99F))
|
||||
.addBindVar(BindVariable.forDouble("d", 4.99))
|
||||
.addKeyRange(KeyRange.ALL)
|
||||
.build();
|
||||
vtgate.begin();
|
||||
vtgate.execute(insertQuery);
|
||||
vtgate.commit();
|
||||
|
||||
String selectSql = "select * from vtocc_fracts where id = 1";
|
||||
Query selectQuery =
|
||||
new QueryBuilder(selectSql, testEnv.getKeyspace(), "master").addKeyRange(KeyRange.ALL).build();
|
||||
Cursor cursor = vtgate.execute(selectQuery);
|
||||
Assert.assertEquals(1, cursor.getRowsAffected());
|
||||
Row row = cursor.next();
|
||||
Assert.assertTrue(row.getLong("id").equals(1L));
|
||||
Assert.assertTrue(row.getBigDecimal("deci").equals(new BigDecimal("1.99")));
|
||||
Assert.assertTrue(row.getBigDecimal("num").equals(new BigDecimal("2.99")));
|
||||
Assert.assertTrue(row.getFloat("f").equals(3.99F));
|
||||
Assert.assertTrue(row.getDouble("d").equals(4.99));
|
||||
vtgate.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStrings() throws Exception {
|
||||
String createTable =
|
||||
"create table vtocc_strings(vb varbinary(16), c char(16), vc varchar(16), b binary(4), tb tinyblob, bl blob, ttx tinytext, tx text, en enum('a','b'), s set('a','b'), primary key(vb)) comment 'vtocc_nocache'\n" + "";
|
||||
VtGate vtgate = VtGate.connect("localhost:" + testEnv.getPort(), 0, testEnv.getRpcClientFactory());
|
||||
vtgate.begin();
|
||||
vtgate.execute(getQuery(createTable));
|
||||
vtgate.commit();
|
||||
|
||||
String insertSql =
|
||||
"insert into vtocc_strings values(:vb, :c, :vc, :b, :tb, :bl, :ttx, :tx, :en, :s)";
|
||||
Query insertQuery = new QueryBuilder(insertSql, testEnv.getKeyspace(), "master")
|
||||
.addBindVar(BindVariable.forBytes("vb", "a".getBytes()))
|
||||
.addBindVar(BindVariable.forBytes("c", "b".getBytes()))
|
||||
.addBindVar(BindVariable.forBytes("vc", "c".getBytes()))
|
||||
.addBindVar(BindVariable.forBytes("b", "d".getBytes()))
|
||||
.addBindVar(BindVariable.forBytes("tb", "e".getBytes()))
|
||||
.addBindVar(BindVariable.forBytes("bl", "f".getBytes()))
|
||||
.addBindVar(BindVariable.forBytes("ttx", "g".getBytes()))
|
||||
.addBindVar(BindVariable.forBytes("tx", "h".getBytes()))
|
||||
.addBindVar(BindVariable.forString("en", "a"))
|
||||
.addBindVar(BindVariable.forString("s", "a,b"))
|
||||
.addKeyRange(KeyRange.ALL)
|
||||
.build();
|
||||
vtgate.begin();
|
||||
vtgate.execute(insertQuery);
|
||||
vtgate.commit();
|
||||
|
||||
String selectSql = "select * from vtocc_strings where vb = 'a'";
|
||||
Query selectQuery =
|
||||
new QueryBuilder(selectSql, testEnv.getKeyspace(), "master").addKeyRange(KeyRange.ALL).build();
|
||||
Cursor cursor = vtgate.execute(selectQuery);
|
||||
Assert.assertEquals(1, cursor.getRowsAffected());
|
||||
Row row = cursor.next();
|
||||
Assert.assertTrue(Arrays.equals(row.getBytes("vb"), "a".getBytes()));
|
||||
Assert.assertTrue(Arrays.equals(row.getBytes("c"), "b".getBytes()));
|
||||
Assert.assertTrue(Arrays.equals(row.getBytes("vc"), "c".getBytes()));
|
||||
// binary(4) column will be suffixed with three 0 bytes
|
||||
Assert.assertTrue(
|
||||
Arrays.equals(row.getBytes("b"), ByteBuffer.allocate(4).put((byte) 'd').array()));
|
||||
Assert.assertTrue(Arrays.equals(row.getBytes("tb"), "e".getBytes()));
|
||||
Assert.assertTrue(Arrays.equals(row.getBytes("bl"), "f".getBytes()));
|
||||
Assert.assertTrue(Arrays.equals(row.getBytes("ttx"), "g".getBytes()));
|
||||
Assert.assertTrue(Arrays.equals(row.getBytes("tx"), "h".getBytes()));
|
||||
// Assert.assertTrue(row.getString("en").equals("a"));
|
||||
// Assert.assertTrue(row.getString("s").equals("a,b"));
|
||||
vtgate.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMisc() throws Exception {
|
||||
String createTable =
|
||||
"create table vtocc_misc(id int, b bit(8), d date, dt datetime, t time, primary key(id)) comment 'vtocc_nocache'\n" + "";
|
||||
VtGate vtgate = VtGate.connect("localhost:" + testEnv.getPort(), 0, testEnv.getRpcClientFactory());
|
||||
vtgate.begin();
|
||||
vtgate.execute(getQuery(createTable));
|
||||
vtgate.commit();
|
||||
|
||||
String insertSql = "insert into vtocc_misc values(:id, :b, :d, :dt, :t)";
|
||||
Query insertQuery = new QueryBuilder(insertSql, testEnv.getKeyspace(), "master")
|
||||
.addBindVar(BindVariable.forInt("id", 1))
|
||||
.addBindVar(BindVariable.forBytes("b", ByteBuffer.allocate(1).put((byte) 1).array()))
|
||||
.addBindVar(BindVariable.forDate("d", DateTime.parse("2012-01-01")))
|
||||
.addBindVar(BindVariable.forDateTime("dt", DateTime.parse("2012-01-01T15:45:45")))
|
||||
.addBindVar(BindVariable.forTime("t",
|
||||
DateTime.parse("15:45:45", ISODateTimeFormat.timeElementParser())))
|
||||
.addKeyRange(KeyRange.ALL)
|
||||
.build();
|
||||
vtgate.begin();
|
||||
vtgate.execute(insertQuery);
|
||||
vtgate.commit();
|
||||
|
||||
String selectSql = "select * from vtocc_misc where id = 1";
|
||||
Query selectQuery =
|
||||
new QueryBuilder(selectSql, testEnv.getKeyspace(), "master").addKeyRange(KeyRange.ALL).build();
|
||||
Cursor cursor = vtgate.execute(selectQuery);
|
||||
Assert.assertEquals(1, cursor.getRowsAffected());
|
||||
Row row = cursor.next();
|
||||
Assert.assertTrue(row.getLong("id").equals(1L));
|
||||
Assert.assertTrue(
|
||||
Arrays.equals(row.getBytes("b"), ByteBuffer.allocate(1).put((byte) 1).array()));
|
||||
Assert.assertTrue(row.getDateTime("d").equals(DateTime.parse("2012-01-01")));
|
||||
Assert.assertTrue(row.getDateTime("dt").equals(DateTime.parse("2012-01-01T15:45:45")));
|
||||
Assert.assertTrue(row.getDateTime("t").equals(
|
||||
DateTime.parse("15:45:45", ISODateTimeFormat.timeElementParser())));
|
||||
vtgate.close();
|
||||
}
|
||||
|
||||
private Query getQuery(String sql) {
|
||||
return new QueryBuilder(sql, testEnv.getKeyspace(), "master").addKeyRange(KeyRange.ALL).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create env with two shards each having a master and replica
|
||||
*/
|
||||
static TestEnv getTestEnv() {
|
||||
Map<String, List<String>> shardKidMap = new HashMap<>();
|
||||
shardKidMap.put("-", Lists.newArrayList("527875958493693904"));
|
||||
TestEnv env = Util.getTestEnv(shardKidMap, "test_keyspace");
|
||||
env.addTablet("replica", 1);
|
||||
return env;
|
||||
}
|
||||
}
|
|
@ -1,135 +0,0 @@
|
|||
package com.youtube.vitess.vtgate.integration;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.primitives.UnsignedLong;
|
||||
|
||||
import com.youtube.vitess.vtgate.BindVariable;
|
||||
import com.youtube.vitess.vtgate.Exceptions.ConnectionException;
|
||||
import com.youtube.vitess.vtgate.Exceptions.DatabaseException;
|
||||
import com.youtube.vitess.vtgate.Exceptions.IntegrityException;
|
||||
import com.youtube.vitess.vtgate.KeyRange;
|
||||
import com.youtube.vitess.vtgate.KeyspaceId;
|
||||
import com.youtube.vitess.vtgate.Query;
|
||||
import com.youtube.vitess.vtgate.Query.QueryBuilder;
|
||||
import com.youtube.vitess.vtgate.VtGate;
|
||||
import com.youtube.vitess.vtgate.TestEnv;
|
||||
import com.youtube.vitess.vtgate.integration.util.Util;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Test failures and exceptions
|
||||
*/
|
||||
@RunWith(JUnit4.class)
|
||||
public class FailuresIT {
|
||||
public static TestEnv testEnv = getTestEnv();
|
||||
|
||||
@BeforeClass
|
||||
public static void setUpVtGate() throws Exception {
|
||||
Util.setupTestEnv(testEnv);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDownVtGate() throws Exception {
|
||||
Util.teardownTestEnv(testEnv);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void createTable() throws Exception {
|
||||
Util.createTable(testEnv);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntegrityException() throws Exception {
|
||||
VtGate vtgate = VtGate.connect("localhost:" + testEnv.getPort(), 0, testEnv.getRpcClientFactory());
|
||||
String insertSql = "insert into vtgate_test(id, keyspace_id) values (:id, :keyspace_id)";
|
||||
KeyspaceId kid = testEnv.getAllKeyspaceIds().get(0);
|
||||
Query insertQuery = new QueryBuilder(insertSql, testEnv.getKeyspace(), "master")
|
||||
.addBindVar(BindVariable.forULong("id", UnsignedLong.valueOf("1")))
|
||||
.addBindVar(BindVariable.forULong("keyspace_id", ((UnsignedLong) kid.getId())))
|
||||
.addKeyspaceId(kid).build();
|
||||
vtgate.begin();
|
||||
vtgate.execute(insertQuery);
|
||||
vtgate.commit();
|
||||
vtgate.begin();
|
||||
try {
|
||||
vtgate.execute(insertQuery);
|
||||
Assert.fail("failed to throw exception");
|
||||
} catch (IntegrityException e) {
|
||||
} finally {
|
||||
vtgate.rollback();
|
||||
vtgate.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTimeout() throws ConnectionException, DatabaseException {
|
||||
VtGate vtgate = VtGate.connect("localhost:" + testEnv.getPort(), 200, testEnv.getRpcClientFactory());
|
||||
// Check timeout error raised for slow query
|
||||
Query sleepQuery = new QueryBuilder("select sleep(0.5) from dual", testEnv.getKeyspace(), "master")
|
||||
.setKeyspaceIds(testEnv.getAllKeyspaceIds()).build();
|
||||
try {
|
||||
vtgate.execute(sleepQuery);
|
||||
Assert.fail("did not raise timeout exception");
|
||||
} catch (ConnectionException e) {
|
||||
}
|
||||
vtgate.close();
|
||||
vtgate = VtGate.connect("localhost:" + testEnv.getPort(), 2000, testEnv.getRpcClientFactory());
|
||||
// Check no timeout error for fast query
|
||||
sleepQuery = new QueryBuilder("select sleep(0.01) from dual", testEnv.getKeyspace(), "master")
|
||||
.setKeyspaceIds(testEnv.getAllKeyspaceIds()).build();
|
||||
vtgate.execute(sleepQuery);
|
||||
vtgate.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTxPoolFull() throws Exception {
|
||||
List<VtGate> vtgates = new ArrayList<>();
|
||||
boolean failed = false;
|
||||
try {
|
||||
// Transaction cap is 20
|
||||
for (int i = 0; i < 25; i++) {
|
||||
VtGate vtgate = VtGate.connect("localhost:" + testEnv.getPort(), 0, testEnv.getRpcClientFactory());
|
||||
vtgates.add(vtgate);
|
||||
vtgate.begin();
|
||||
// Run a query to actually begin a transaction with the tablets
|
||||
Query query = new QueryBuilder("delete from vtgate_test", testEnv.getKeyspace(), "master")
|
||||
.addKeyRange(KeyRange.ALL).build();
|
||||
vtgate.execute(query);
|
||||
}
|
||||
} catch (DatabaseException e) {
|
||||
if (e.getMessage().contains("tx_pool_full")) {
|
||||
failed = true;
|
||||
}
|
||||
} finally {
|
||||
for (VtGate vtgate : vtgates) {
|
||||
vtgate.close();
|
||||
}
|
||||
}
|
||||
if (!failed) {
|
||||
Assert.fail("failed to raise tx_pool_full exception");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create env with two shards each having a master and replica
|
||||
*/
|
||||
static TestEnv getTestEnv() {
|
||||
Map<String, List<String>> shardKidMap = new HashMap<>();
|
||||
shardKidMap.put("-", Lists.newArrayList("527875958493693904"));
|
||||
TestEnv env = Util.getTestEnv(shardKidMap, "test_keyspace");
|
||||
env.addTablet("replica", 1);
|
||||
return env;
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
package com.youtube.vitess.vtgate.integration;
|
||||
|
||||
import com.youtube.vitess.vtgate.Query;
|
||||
import com.youtube.vitess.vtgate.Query.QueryBuilder;
|
||||
import com.youtube.vitess.vtgate.VtGate;
|
||||
import com.youtube.vitess.vtgate.cursor.Cursor;
|
||||
import com.youtube.vitess.vtgate.TestEnv;
|
||||
import com.youtube.vitess.vtgate.integration.util.Util;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public class StreamingServerShutdownIT {
|
||||
|
||||
static TestEnv testEnv = VtGateIT.getTestEnv();
|
||||
|
||||
@Before
|
||||
public void setUpVtGate() throws Exception {
|
||||
Util.setupTestEnv(testEnv);
|
||||
Util.createTable(testEnv);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDownVtGate() throws Exception {
|
||||
Util.teardownTestEnv(testEnv);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShutdownServerWhileStreaming() throws Exception {
|
||||
Util.insertRows(testEnv, 1, 2000);
|
||||
VtGate vtgate = VtGate.connect("localhost:" + testEnv.getPort(), 0, testEnv.getRpcClientFactory());
|
||||
String selectSql = "select A.* from vtgate_test A join vtgate_test B";
|
||||
Query joinQuery = new QueryBuilder(selectSql, testEnv.getKeyspace(), "master")
|
||||
.setKeyspaceIds(testEnv.getAllKeyspaceIds()).setStreaming(true).build();
|
||||
Cursor cursor = vtgate.execute(joinQuery);
|
||||
|
||||
int count = 0;
|
||||
try {
|
||||
while (cursor.hasNext()) {
|
||||
count++;
|
||||
if (count == 1) {
|
||||
Util.teardownTestEnv(testEnv);
|
||||
}
|
||||
cursor.next();
|
||||
}
|
||||
vtgate.close();
|
||||
Assert.fail("failed to raise exception");
|
||||
} catch (RuntimeException e) {
|
||||
Assert.assertTrue(e.getMessage().length() > 0);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,146 +0,0 @@
|
|||
package com.youtube.vitess.vtgate.integration;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.primitives.UnsignedLong;
|
||||
|
||||
import com.youtube.vitess.vtgate.BindVariable;
|
||||
import com.youtube.vitess.vtgate.KeyRange;
|
||||
import com.youtube.vitess.vtgate.KeyspaceId;
|
||||
import com.youtube.vitess.vtgate.Query;
|
||||
import com.youtube.vitess.vtgate.Query.QueryBuilder;
|
||||
import com.youtube.vitess.vtgate.TestEnv;
|
||||
import com.youtube.vitess.vtgate.VtGate;
|
||||
import com.youtube.vitess.vtgate.cursor.Cursor;
|
||||
import com.youtube.vitess.vtgate.cursor.StreamCursor;
|
||||
import com.youtube.vitess.vtgate.integration.util.Util;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Test cases for streaming queries in VtGate
|
||||
*
|
||||
*/
|
||||
@RunWith(JUnit4.class)
|
||||
public class StreamingVtGateIT {
|
||||
public static TestEnv testEnv = VtGateIT.getTestEnv();
|
||||
|
||||
@BeforeClass
|
||||
public static void setUpVtGate() throws Exception {
|
||||
Util.setupTestEnv(testEnv);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDownVtGate() throws Exception {
|
||||
Util.teardownTestEnv(testEnv);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void createTable() throws Exception {
|
||||
Util.createTable(testEnv);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStreamCursorType() throws Exception {
|
||||
VtGate vtgate = VtGate.connect("localhost:" + testEnv.getPort(), 0, testEnv.getRpcClientFactory());
|
||||
String selectSql = "select * from vtgate_test";
|
||||
Query allRowsQuery = new QueryBuilder(selectSql, testEnv.getKeyspace(), "master")
|
||||
.setKeyspaceIds(testEnv.getAllKeyspaceIds()).setStreaming(true).build();
|
||||
Cursor cursor = vtgate.execute(allRowsQuery);
|
||||
Assert.assertEquals(StreamCursor.class, cursor.getClass());
|
||||
vtgate.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test StreamExecuteKeyspaceIds query on a single shard
|
||||
*/
|
||||
@Test
|
||||
public void testStreamExecuteKeyspaceIds() throws Exception {
|
||||
int rowCount = 10;
|
||||
for (String shardName : testEnv.getShardKidMap().keySet()) {
|
||||
Util.insertRowsInShard(testEnv, shardName, rowCount);
|
||||
}
|
||||
VtGate vtgate = VtGate.connect("localhost:" + testEnv.getPort(), 0, testEnv.getRpcClientFactory());
|
||||
for (String shardName : testEnv.getShardKidMap().keySet()) {
|
||||
String selectSql = "select A.* from vtgate_test A join vtgate_test B join vtgate_test C";
|
||||
Query query = new QueryBuilder(selectSql, testEnv.getKeyspace(), "master")
|
||||
.setKeyspaceIds(testEnv.getKeyspaceIds(shardName)).setStreaming(true).build();
|
||||
Cursor cursor = vtgate.execute(query);
|
||||
int count = Iterables.size(cursor);
|
||||
Assert.assertEquals((int) Math.pow(rowCount, 3), count);
|
||||
}
|
||||
vtgate.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as testStreamExecuteKeyspaceIds but for StreamExecuteKeyRanges
|
||||
*/
|
||||
@Test
|
||||
public void testStreamExecuteKeyRanges() throws Exception {
|
||||
VtGate vtgate = VtGate.connect("localhost:" + testEnv.getPort(), 0, testEnv.getRpcClientFactory());
|
||||
int rowCount = 10;
|
||||
for (String shardName : testEnv.getShardKidMap().keySet()) {
|
||||
Util.insertRowsInShard(testEnv, shardName, rowCount);
|
||||
}
|
||||
for (String shardName : testEnv.getShardKidMap().keySet()) {
|
||||
List<KeyspaceId> kids = testEnv.getKeyspaceIds(shardName);
|
||||
KeyRange kr = new KeyRange(Collections.min(kids), Collections.max(kids));
|
||||
String selectSql = "select A.* from vtgate_test A join vtgate_test B join vtgate_test C";
|
||||
Query query = new QueryBuilder(selectSql, testEnv.getKeyspace(), "master").addKeyRange(kr)
|
||||
.setStreaming(true).build();
|
||||
Cursor cursor = vtgate.execute(query);
|
||||
int count = Iterables.size(cursor);
|
||||
Assert.assertEquals((int) Math.pow(rowCount, 3), count);
|
||||
}
|
||||
vtgate.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test scatter streaming queries fetch rows from all shards
|
||||
*/
|
||||
@Test
|
||||
public void testScatterStreamingQuery() throws Exception {
|
||||
int rowCount = 10;
|
||||
for (String shardName : testEnv.getShardKidMap().keySet()) {
|
||||
Util.insertRowsInShard(testEnv, shardName, rowCount);
|
||||
}
|
||||
String selectSql = "select A.* from vtgate_test A join vtgate_test B join vtgate_test C";
|
||||
Query query = new QueryBuilder(selectSql, testEnv.getKeyspace(), "master")
|
||||
.setKeyspaceIds(testEnv.getAllKeyspaceIds()).setStreaming(true).build();
|
||||
VtGate vtgate = VtGate.connect("localhost:" + testEnv.getPort(), 0, testEnv.getRpcClientFactory());
|
||||
Cursor cursor = vtgate.execute(query);
|
||||
int count = Iterables.size(cursor);
|
||||
Assert.assertEquals(2 * (int) Math.pow(rowCount, 3), count);
|
||||
vtgate.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("currently failing as vtgate doesn't set the error")
|
||||
public void testStreamingWrites() throws Exception {
|
||||
VtGate vtgate = VtGate.connect("localhost:" + testEnv.getPort(), 0, testEnv.getRpcClientFactory());
|
||||
|
||||
vtgate.begin();
|
||||
String insertSql = "insert into vtgate_test " + "(id, name, age, percent, keyspace_id) "
|
||||
+ "values (:id, :name, :age, :percent, :keyspace_id)";
|
||||
KeyspaceId kid = testEnv.getAllKeyspaceIds().get(0);
|
||||
Query query = new QueryBuilder(insertSql, testEnv.getKeyspace(), "master")
|
||||
.addBindVar(BindVariable.forULong("id", UnsignedLong.valueOf("" + 1)))
|
||||
.addBindVar(BindVariable.forString("name", ("name_" + 1)))
|
||||
.addBindVar(BindVariable.forULong("keyspace_id", (UnsignedLong) kid.getId()))
|
||||
.addKeyspaceId(kid)
|
||||
.setStreaming(true)
|
||||
.build();
|
||||
vtgate.execute(query);
|
||||
vtgate.commit();
|
||||
vtgate.close();
|
||||
}
|
||||
}
|
|
@ -1,370 +0,0 @@
|
|||
package com.youtube.vitess.vtgate.integration;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.primitives.UnsignedLong;
|
||||
import com.youtube.vitess.vtgate.BindVariable;
|
||||
import com.youtube.vitess.vtgate.Exceptions.ConnectionException;
|
||||
import com.youtube.vitess.vtgate.KeyRange;
|
||||
import com.youtube.vitess.vtgate.KeyspaceId;
|
||||
import com.youtube.vitess.vtgate.Query;
|
||||
import com.youtube.vitess.vtgate.Query.QueryBuilder;
|
||||
import com.youtube.vitess.vtgate.Row;
|
||||
import com.youtube.vitess.vtgate.Row.Cell;
|
||||
import com.youtube.vitess.vtgate.TestEnv;
|
||||
import com.youtube.vitess.vtgate.VtGate;
|
||||
import com.youtube.vitess.vtgate.cursor.Cursor;
|
||||
import com.youtube.vitess.vtgate.cursor.CursorImpl;
|
||||
import com.youtube.vitess.vtgate.integration.util.Util;
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public class VtGateIT {
|
||||
|
||||
public static TestEnv testEnv = getTestEnv();
|
||||
|
||||
@BeforeClass
|
||||
public static void setUpVtGate() throws Exception {
|
||||
Util.setupTestEnv(testEnv);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDownVtGate() throws Exception {
|
||||
Util.teardownTestEnv(testEnv);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void createTable() throws Exception {
|
||||
Util.createTable(testEnv);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test selects using ExecuteKeyspaceIds
|
||||
*/
|
||||
@Test
|
||||
public void testExecuteKeyspaceIds() throws Exception {
|
||||
VtGate vtgate = VtGate.connect("localhost:" + testEnv.getPort(), 0, testEnv.getRpcClientFactory());
|
||||
|
||||
// Ensure empty table
|
||||
String selectSql = "select * from vtgate_test";
|
||||
Query allRowsQuery = new QueryBuilder(selectSql, testEnv.getKeyspace(), "master").setKeyspaceIds(
|
||||
testEnv.getAllKeyspaceIds()).build();
|
||||
Cursor cursor = vtgate.execute(allRowsQuery);
|
||||
Assert.assertEquals(CursorImpl.class, cursor.getClass());
|
||||
Assert.assertEquals(0, cursor.getRowsAffected());
|
||||
Assert.assertEquals(0, cursor.getLastRowId());
|
||||
Assert.assertFalse(cursor.hasNext());
|
||||
vtgate.close();
|
||||
|
||||
// Insert 10 rows
|
||||
Util.insertRows(testEnv, 1000, 10);
|
||||
|
||||
vtgate = VtGate.connect("localhost:" + testEnv.getPort(), 0, testEnv.getRpcClientFactory());
|
||||
cursor = vtgate.execute(allRowsQuery);
|
||||
Assert.assertEquals(10, cursor.getRowsAffected());
|
||||
Assert.assertEquals(0, cursor.getLastRowId());
|
||||
Assert.assertTrue(cursor.hasNext());
|
||||
|
||||
// Fetch all rows from the first shard
|
||||
KeyspaceId firstKid = testEnv.getAllKeyspaceIds().get(0);
|
||||
Query query =
|
||||
new QueryBuilder(selectSql, testEnv.getKeyspace(), "master").addKeyspaceId(firstKid).build();
|
||||
cursor = vtgate.execute(query);
|
||||
|
||||
// Check field types and values
|
||||
Row row = cursor.next();
|
||||
Cell idCell = row.next();
|
||||
Assert.assertEquals("id", idCell.getName());
|
||||
Assert.assertEquals(Long.class, idCell.getType());
|
||||
Long id = row.getLong(idCell.getName());
|
||||
|
||||
Cell nameCell = row.next();
|
||||
Assert.assertEquals("name", nameCell.getName());
|
||||
Assert.assertEquals(byte[].class, nameCell.getType());
|
||||
Assert.assertTrue(
|
||||
Arrays.equals(("name_" + id.toString()).getBytes(), row.getBytes(nameCell.getName())));
|
||||
|
||||
Cell ageCell = row.next();
|
||||
Assert.assertEquals("age", ageCell.getName());
|
||||
Assert.assertEquals(Integer.class, ageCell.getType());
|
||||
Assert.assertEquals(Integer.valueOf(2 * id.intValue()), row.getInt(ageCell.getName()));
|
||||
|
||||
vtgate.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test queries are routed to the right shard based on based on keyspace ids
|
||||
*/
|
||||
@Test
|
||||
public void testQueryRouting() throws Exception {
|
||||
for (String shardName : testEnv.getShardKidMap().keySet()) {
|
||||
Util.insertRowsInShard(testEnv, shardName, 10);
|
||||
}
|
||||
|
||||
VtGate vtgate = VtGate.connect("localhost:" + testEnv.getPort(), 0, testEnv.getRpcClientFactory());
|
||||
String allRowsSql = "select * from vtgate_test";
|
||||
|
||||
for (String shardName : testEnv.getShardKidMap().keySet()) {
|
||||
Query shardRows = new QueryBuilder(allRowsSql, testEnv.getKeyspace(), "master").setKeyspaceIds(
|
||||
testEnv.getKeyspaceIds(shardName)).build();
|
||||
Cursor cursor = vtgate.execute(shardRows);
|
||||
Assert.assertEquals(10, cursor.getRowsAffected());
|
||||
}
|
||||
vtgate.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDateFieldTypes() throws Exception {
|
||||
DateTime dt = DateTime.now().minusDays(2).withMillisOfSecond(0);
|
||||
Util.insertRows(testEnv, 10, 1, dt);
|
||||
VtGate vtgate = VtGate.connect("localhost:" + testEnv.getPort(), 0, testEnv.getRpcClientFactory());
|
||||
Query allRowsQuery = new QueryBuilder("select * from vtgate_test", testEnv.getKeyspace(), "master")
|
||||
.setKeyspaceIds(testEnv.getAllKeyspaceIds()).build();
|
||||
Row row = vtgate.execute(allRowsQuery).next();
|
||||
Assert.assertTrue(dt.equals(row.getDateTime("timestamp_col")));
|
||||
Assert.assertTrue(dt.equals(row.getDateTime("datetime_col")));
|
||||
Assert.assertTrue(dt.withHourOfDay(0).withMinuteOfHour(0).withSecondOfMinute(0)
|
||||
.equals(row.getDateTime("date_col")));
|
||||
Assert.assertTrue(
|
||||
dt.withYear(1970).withMonthOfYear(1).withDayOfMonth(1).equals(row.getDateTime("time_col")));
|
||||
|
||||
vtgate.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test ALL keyrange fetches rows from all shards
|
||||
*/
|
||||
@Test
|
||||
public void testAllKeyRange() throws Exception {
|
||||
// Insert 10 rows across the shards
|
||||
Util.insertRows(testEnv, 1000, 10);
|
||||
VtGate vtgate = VtGate.connect("localhost:" + testEnv.getPort(), 0, testEnv.getRpcClientFactory());
|
||||
String selectSql = "select * from vtgate_test";
|
||||
Query allRowsQuery =
|
||||
new QueryBuilder(selectSql, testEnv.getKeyspace(), "master").addKeyRange(KeyRange.ALL).build();
|
||||
Cursor cursor = vtgate.execute(allRowsQuery);
|
||||
// Verify all rows returned
|
||||
Assert.assertEquals(10, cursor.getRowsAffected());
|
||||
vtgate.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test reads using Keyrange query
|
||||
*/
|
||||
@Test
|
||||
public void testKeyRangeReads() throws Exception {
|
||||
int rowsPerShard = 10;
|
||||
// insert rows in each shard using ExecuteKeyspaceIds
|
||||
for (String shardName : testEnv.getShardKidMap().keySet()) {
|
||||
Util.insertRowsInShard(testEnv, shardName, rowsPerShard);
|
||||
}
|
||||
|
||||
VtGate vtgate = VtGate.connect("localhost:" + testEnv.getPort(), 0, testEnv.getRpcClientFactory());
|
||||
String selectSql = "select * from vtgate_test";
|
||||
|
||||
// Check ALL KeyRange query returns rows from both shards
|
||||
Query allRangeQuery =
|
||||
new QueryBuilder(selectSql, testEnv.getKeyspace(), "master").addKeyRange(KeyRange.ALL).build();
|
||||
Cursor cursor = vtgate.execute(allRangeQuery);
|
||||
Assert.assertEquals(rowsPerShard * 2, cursor.getRowsAffected());
|
||||
|
||||
// Check KeyRange query limited to a single shard returns 10 rows each
|
||||
for (String shardName : testEnv.getShardKidMap().keySet()) {
|
||||
List<KeyspaceId> shardKids = testEnv.getKeyspaceIds(shardName);
|
||||
KeyspaceId minKid = Collections.min(shardKids);
|
||||
KeyspaceId maxKid = Collections.max(shardKids);
|
||||
KeyRange shardKeyRange = new KeyRange(minKid, maxKid);
|
||||
Query shardRangeQuery = new QueryBuilder(selectSql, testEnv.getKeyspace(), "master").addKeyRange(
|
||||
shardKeyRange).build();
|
||||
cursor = vtgate.execute(shardRangeQuery);
|
||||
Assert.assertEquals(rowsPerShard, cursor.getRowsAffected());
|
||||
}
|
||||
|
||||
// Now make a cross-shard KeyRange and check all rows are returned
|
||||
Iterator<String> shardNameIter = testEnv.getShardKidMap().keySet().iterator();
|
||||
KeyspaceId kidShard1 = testEnv.getKeyspaceIds(shardNameIter.next()).get(2);
|
||||
KeyspaceId kidShard2 = testEnv.getKeyspaceIds(shardNameIter.next()).get(2);
|
||||
KeyRange crossShardKeyrange;
|
||||
if (kidShard1.compareTo(kidShard2) < 0) {
|
||||
crossShardKeyrange = new KeyRange(kidShard1, kidShard2);
|
||||
} else {
|
||||
crossShardKeyrange = new KeyRange(kidShard2, kidShard1);
|
||||
}
|
||||
Query shardRangeQuery = new QueryBuilder(selectSql, testEnv.getKeyspace(), "master").addKeyRange(
|
||||
crossShardKeyrange).build();
|
||||
cursor = vtgate.execute(shardRangeQuery);
|
||||
Assert.assertEquals(rowsPerShard * 2, cursor.getRowsAffected());
|
||||
vtgate.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test inserts using KeyRange query
|
||||
*/
|
||||
@Test
|
||||
public void testKeyRangeWrites() throws Exception {
|
||||
Random random = new Random();
|
||||
VtGate vtgate = VtGate.connect("localhost:" + testEnv.getPort(), 0, testEnv.getRpcClientFactory());
|
||||
vtgate.begin();
|
||||
String sql = "insert into vtgate_test " + "(id, name, keyspace_id, age) "
|
||||
+ "values (:id, :name, :keyspace_id, :age)";
|
||||
int count = 20;
|
||||
// Insert 20 rows per shard
|
||||
for (String shardName : testEnv.getShardKidMap().keySet()) {
|
||||
List<KeyspaceId> kids = testEnv.getKeyspaceIds(shardName);
|
||||
KeyspaceId minKid = Collections.min(kids);
|
||||
KeyspaceId maxKid = Collections.max(kids);
|
||||
KeyRange kr = new KeyRange(minKid, maxKid);
|
||||
for (int i = 0; i < count; i++) {
|
||||
KeyspaceId kid = kids.get(i % kids.size());
|
||||
Query query = new QueryBuilder(sql, testEnv.getKeyspace(), "master")
|
||||
.addBindVar(
|
||||
BindVariable.forULong("id", UnsignedLong.valueOf("" + Math.abs(random.nextInt()))))
|
||||
.addBindVar(BindVariable.forString("name", ("name_" + i)))
|
||||
.addBindVar(BindVariable.forULong("keyspace_id", (UnsignedLong) kid.getId()))
|
||||
.addBindVar(BindVariable.forNull("age"))
|
||||
.addKeyRange(kr)
|
||||
.build();
|
||||
vtgate.execute(query);
|
||||
}
|
||||
}
|
||||
vtgate.commit();
|
||||
vtgate.close();
|
||||
|
||||
// Check 40 rows exist in total
|
||||
vtgate = VtGate.connect("localhost:" + testEnv.getPort(), 0, testEnv.getRpcClientFactory());
|
||||
String selectSql = "select * from vtgate_test";
|
||||
Query allRowsQuery = new QueryBuilder(selectSql, testEnv.getKeyspace(), "master").setKeyspaceIds(
|
||||
testEnv.getAllKeyspaceIds()).build();
|
||||
Cursor cursor = vtgate.execute(allRowsQuery);
|
||||
Assert.assertEquals(count * 2, cursor.getRowsAffected());
|
||||
|
||||
// Check 20 rows exist per shard
|
||||
for (String shardName : testEnv.getShardKidMap().keySet()) {
|
||||
Query shardRows = new QueryBuilder(selectSql, testEnv.getKeyspace(), "master").setKeyspaceIds(
|
||||
testEnv.getKeyspaceIds(shardName)).build();
|
||||
cursor = vtgate.execute(shardRows);
|
||||
Assert.assertEquals(count, cursor.getRowsAffected());
|
||||
}
|
||||
|
||||
vtgate.close();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSplitQuery() throws Exception {
|
||||
// Insert 20 rows per shard
|
||||
for (String shardName : testEnv.getShardKidMap().keySet()) {
|
||||
Util.insertRowsInShard(testEnv, shardName, 20);
|
||||
}
|
||||
Util.waitForTablet("rdonly", 40, 3, testEnv);
|
||||
VtGate vtgate = VtGate.connect("localhost:" + testEnv.getPort(), 0, testEnv.getRpcClientFactory());
|
||||
Map<Query, Long> queries =
|
||||
vtgate.splitQuery("test_keyspace", "select id,keyspace_id from vtgate_test", 1, "");
|
||||
vtgate.close();
|
||||
|
||||
// Verify 2 splits, one per shard
|
||||
Assert.assertEquals(2, queries.size());
|
||||
Set<String> shardsInSplits = new HashSet<>();
|
||||
for (Query q : queries.keySet()) {
|
||||
Assert.assertEquals("select id,keyspace_id from vtgate_test", q.getSql());
|
||||
Assert.assertEquals("test_keyspace", q.getKeyspace());
|
||||
Assert.assertEquals("rdonly", q.getTabletType());
|
||||
Assert.assertEquals(0, q.getBindVars().size());
|
||||
Assert.assertEquals(null, q.getKeyspaceIds());
|
||||
String start = Hex.encodeHexString(q.getKeyRanges().get(0).get("Start"));
|
||||
String end = Hex.encodeHexString(q.getKeyRanges().get(0).get("End"));
|
||||
shardsInSplits.add(start + "-" + end);
|
||||
}
|
||||
|
||||
// Verify the keyrange queries in splits cover the entire keyspace
|
||||
Assert.assertTrue(shardsInSplits.containsAll(testEnv.getShardKidMap().keySet()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSplitQueryMultipleSplitsPerShard() throws Exception {
|
||||
int rowCount = 30;
|
||||
Util.insertRows(testEnv, 1, 30);
|
||||
Map<String, List<BindVariable>> expectedSqls = Maps.newHashMap();
|
||||
expectedSqls.put("select id, keyspace_id from vtgate_test where id < :_splitquery_end",
|
||||
Lists.newArrayList(BindVariable.forInt("_splitquery_end", 10)));
|
||||
expectedSqls.put(
|
||||
"select id, keyspace_id from vtgate_test where id >= :_splitquery_start "
|
||||
+ "and id < :_splitquery_end",
|
||||
Lists.newArrayList(BindVariable.forInt("_splitquery_start", 10),
|
||||
BindVariable.forInt("_splitquery_end", 19)));
|
||||
expectedSqls.put("select id, keyspace_id from vtgate_test where id >= :_splitquery_start",
|
||||
Lists.newArrayList(BindVariable.forInt("_splitquery_start", 19)));
|
||||
Util.waitForTablet("rdonly", rowCount, 3, testEnv);
|
||||
VtGate vtgate = VtGate.connect("localhost:" + testEnv.getPort(), 0, testEnv.getRpcClientFactory());
|
||||
int splitCount = 6;
|
||||
Map<Query, Long> queries =
|
||||
vtgate.splitQuery("test_keyspace", "select id,keyspace_id from vtgate_test", splitCount, "");
|
||||
vtgate.close();
|
||||
|
||||
// Verify 6 splits, 3 per shard
|
||||
Assert.assertEquals(splitCount, queries.size());
|
||||
Set<String> shardsInSplits = new HashSet<>();
|
||||
for (Query q : queries.keySet()) {
|
||||
String sql = q.getSql();
|
||||
List<BindVariable> bindVars = expectedSqls.get(sql);
|
||||
Assert.assertNotNull(bindVars);
|
||||
Assert.assertEquals("test_keyspace", q.getKeyspace());
|
||||
Assert.assertEquals("rdonly", q.getTabletType());
|
||||
Assert.assertEquals(bindVars.size(), q.getBindVars().size());
|
||||
Assert.assertEquals(null, q.getKeyspaceIds());
|
||||
String start = Hex.encodeHexString(q.getKeyRanges().get(0).get("Start"));
|
||||
String end = Hex.encodeHexString(q.getKeyRanges().get(0).get("End"));
|
||||
shardsInSplits.add(start + "-" + end);
|
||||
}
|
||||
|
||||
// Verify the keyrange queries in splits cover the entire keyspace
|
||||
Assert.assertTrue(shardsInSplits.containsAll(testEnv.getShardKidMap().keySet()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSplitQueryInvalidTable() throws Exception {
|
||||
VtGate vtgate = VtGate.connect("localhost:" + testEnv.getPort(), 0, testEnv.getRpcClientFactory());
|
||||
try {
|
||||
vtgate.splitQuery("test_keyspace", "select id from invalid_table", 1, "");
|
||||
Assert.fail("failed to raise connection exception");
|
||||
} catch (ConnectionException e) {
|
||||
Assert.assertTrue(
|
||||
e.getMessage().contains("query validation error: can't find table in schema"));
|
||||
} finally {
|
||||
vtgate.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create env with two shards each having a master and replica
|
||||
*/
|
||||
static TestEnv getTestEnv() {
|
||||
Map<String, List<String>> shardKidMap = new LinkedHashMap<>();
|
||||
shardKidMap.put("80-",
|
||||
Lists.newArrayList("9767889778372766922", "9742070682920810358", "10296850775085416642"));
|
||||
shardKidMap.put("-80",
|
||||
Lists.newArrayList("527875958493693904", "626750931627689502", "345387386794260318"));
|
||||
TestEnv env = Util.getTestEnv(shardKidMap, "test_keyspace");
|
||||
env.addTablet("rdonly", 1);
|
||||
return env;
|
||||
}
|
||||
}
|
|
@ -1,251 +0,0 @@
|
|||
package com.youtube.vitess.vtgate.integration.hadoop;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.primitives.UnsignedLong;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import com.youtube.vitess.vtgate.Exceptions.InvalidFieldException;
|
||||
import com.youtube.vitess.vtgate.KeyspaceId;
|
||||
import com.youtube.vitess.vtgate.hadoop.VitessInputFormat;
|
||||
import com.youtube.vitess.vtgate.hadoop.writables.KeyspaceIdWritable;
|
||||
import com.youtube.vitess.vtgate.hadoop.writables.RowWritable;
|
||||
import com.youtube.vitess.vtgate.TestEnv;
|
||||
import com.youtube.vitess.vtgate.integration.util.Util;
|
||||
import com.youtube.vitess.vtgate.hadoop.utils.GsonAdapters;
|
||||
|
||||
import junit.extensions.TestSetup;
|
||||
import junit.framework.TestSuite;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.hadoop.conf.Configuration;
|
||||
import org.apache.hadoop.fs.FileSystem;
|
||||
import org.apache.hadoop.fs.Path;
|
||||
import org.apache.hadoop.io.LongWritable;
|
||||
import org.apache.hadoop.io.NullWritable;
|
||||
import org.apache.hadoop.mapred.HadoopTestCase;
|
||||
import org.apache.hadoop.mapreduce.Job;
|
||||
import org.apache.hadoop.mapreduce.MapReduceTestUtil;
|
||||
import org.apache.hadoop.mapreduce.Mapper;
|
||||
import org.apache.hadoop.mapreduce.Reducer;
|
||||
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
|
||||
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Integration tests for MapReductions in Vitess. These tests use an in-process Hadoop cluster via
|
||||
* {@link HadoopTestCase}. These tests are JUnit3 style because of this dependency. Vitess setup for
|
||||
* these tests require at least one rdonly instance per shard.
|
||||
*
|
||||
*/
|
||||
public class MapReduceIT extends HadoopTestCase {
|
||||
|
||||
public static TestEnv testEnv = getTestEnv();
|
||||
private static Path outputPath = new Path("/tmp");
|
||||
|
||||
public MapReduceIT() throws IOException {
|
||||
super(HadoopTestCase.LOCAL_MR, HadoopTestCase.LOCAL_FS, 1, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
Util.createTable(testEnv);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a mapper only MR job and verify all the rows in the source table were outputted into HDFS.
|
||||
*/
|
||||
public void testDumpTableToHDFS() throws Exception {
|
||||
// Insert 20 rows per shard
|
||||
int rowsPerShard = 20;
|
||||
for (String shardName : testEnv.getShardKidMap().keySet()) {
|
||||
Util.insertRowsInShard(testEnv, shardName, rowsPerShard);
|
||||
}
|
||||
Util.waitForTablet("rdonly", 40, 3, testEnv);
|
||||
// Configurations for the job, output from mapper as Text
|
||||
Configuration conf = createJobConf();
|
||||
Job job = new Job(conf);
|
||||
job.setJobName("table");
|
||||
job.setJarByClass(VitessInputFormat.class);
|
||||
job.setMapperClass(TableMapper.class);
|
||||
VitessInputFormat.setInput(job,
|
||||
"localhost:" + testEnv.getPort(),
|
||||
testEnv.getKeyspace(),
|
||||
"select keyspace_id, name from vtgate_test",
|
||||
4,
|
||||
testEnv.getRpcClientFactory().getClass());
|
||||
job.setOutputKeyClass(NullWritable.class);
|
||||
job.setOutputValueClass(RowWritable.class);
|
||||
job.setOutputFormatClass(TextOutputFormat.class);
|
||||
job.setNumReduceTasks(0);
|
||||
|
||||
Path outDir = new Path(outputPath, "mrvitess/output");
|
||||
FileSystem fs = FileSystem.get(conf);
|
||||
if (fs.exists(outDir)) {
|
||||
fs.delete(outDir, true);
|
||||
}
|
||||
FileOutputFormat.setOutputPath(job, outDir);
|
||||
|
||||
job.waitForCompletion(true);
|
||||
assertTrue(job.isSuccessful());
|
||||
|
||||
String[] outputLines = MapReduceTestUtil.readOutput(outDir, conf).split("\n");
|
||||
// there should be one line per row in the source table
|
||||
assertEquals(rowsPerShard * 2, outputLines.length);
|
||||
Set<String> actualKids = new HashSet<>();
|
||||
Set<String> actualNames = new HashSet<>();
|
||||
|
||||
// Rows are keyspace ids are written as JSON since this is
|
||||
// TextOutputFormat. Parse and verify we've gotten all the keyspace
|
||||
// ids and rows.
|
||||
Gson gson = new GsonBuilder()
|
||||
.registerTypeHierarchyAdapter(byte[].class, GsonAdapters.BYTE_ARRAY)
|
||||
.registerTypeAdapter(UnsignedLong.class, GsonAdapters.UNSIGNED_LONG)
|
||||
.registerTypeAdapter(Class.class, GsonAdapters.CLASS)
|
||||
.create();
|
||||
for (String line : outputLines) {
|
||||
String kidJson = line.split("\t")[0];
|
||||
Map<String, String> m = new HashMap<>();
|
||||
m = gson.fromJson(kidJson, m.getClass());
|
||||
actualKids.add(m.get("id"));
|
||||
|
||||
String rowJson = line.split("\t")[1];
|
||||
Map<String, Map<String, Map<String, String>>> map = new HashMap<>();
|
||||
map = gson.fromJson(rowJson, map.getClass());
|
||||
actualNames.add(
|
||||
new String(Base64.decodeBase64(map.get("contents").get("name").get("value"))));
|
||||
}
|
||||
|
||||
Set<String> expectedKids = new HashSet<>();
|
||||
for (KeyspaceId kid : testEnv.getAllKeyspaceIds()) {
|
||||
expectedKids.add(((UnsignedLong) kid.getId()).toString());
|
||||
}
|
||||
assertEquals(expectedKids.size(), actualKids.size());
|
||||
assertTrue(actualKids.containsAll(expectedKids));
|
||||
|
||||
Set<String> expectedNames = new HashSet<>();
|
||||
for (int i = 1; i <= rowsPerShard; i++) {
|
||||
expectedNames.add("name_" + i);
|
||||
}
|
||||
|
||||
assertEquals(rowsPerShard, actualNames.size());
|
||||
assertTrue(actualNames.containsAll(expectedNames));
|
||||
}
|
||||
|
||||
/**
|
||||
* Map all rows and aggregate by keyspace id at the reducer.
|
||||
*/
|
||||
public void testReducerAggregateRows() throws Exception {
|
||||
int rowsPerShard = 20;
|
||||
for (String shardName : testEnv.getShardKidMap().keySet()) {
|
||||
Util.insertRowsInShard(testEnv, shardName, rowsPerShard);
|
||||
}
|
||||
Util.waitForTablet("rdonly", 40, 3, testEnv);
|
||||
Configuration conf = createJobConf();
|
||||
|
||||
Job job = new Job(conf);
|
||||
job.setJobName("table");
|
||||
job.setJarByClass(VitessInputFormat.class);
|
||||
job.setMapperClass(TableMapper.class);
|
||||
VitessInputFormat.setInput(job,
|
||||
"localhost:" + testEnv.getPort(),
|
||||
testEnv.getKeyspace(),
|
||||
"select keyspace_id, name from vtgate_test",
|
||||
1,
|
||||
testEnv.getRpcClientFactory().getClass());
|
||||
|
||||
job.setMapOutputKeyClass(KeyspaceIdWritable.class);
|
||||
job.setMapOutputValueClass(RowWritable.class);
|
||||
|
||||
job.setReducerClass(CountReducer.class);
|
||||
job.setOutputKeyClass(NullWritable.class);
|
||||
job.setOutputValueClass(LongWritable.class);
|
||||
job.setOutputFormatClass(TextOutputFormat.class);
|
||||
|
||||
Path outDir = new Path(outputPath, "mrvitess/output");
|
||||
FileSystem fs = FileSystem.get(conf);
|
||||
if (fs.exists(outDir)) {
|
||||
fs.delete(outDir, true);
|
||||
}
|
||||
FileOutputFormat.setOutputPath(job, outDir);
|
||||
|
||||
job.waitForCompletion(true);
|
||||
assertTrue(job.isSuccessful());
|
||||
|
||||
String[] outputLines = MapReduceTestUtil.readOutput(outDir, conf).split("\n");
|
||||
assertEquals(6, outputLines.length);
|
||||
int totalRowsReduced = 0;
|
||||
for (String line : outputLines) {
|
||||
totalRowsReduced += Integer.parseInt(line);
|
||||
}
|
||||
assertEquals(rowsPerShard * 2, totalRowsReduced);
|
||||
}
|
||||
|
||||
public static class TableMapper extends
|
||||
Mapper<NullWritable, RowWritable, KeyspaceIdWritable, RowWritable> {
|
||||
@Override
|
||||
public void map(NullWritable key, RowWritable value, Context context) throws IOException,
|
||||
InterruptedException {
|
||||
try {
|
||||
KeyspaceId id = new KeyspaceId();
|
||||
id.setId(value.getRow().getULong("keyspace_id"));
|
||||
context.write(new KeyspaceIdWritable(id), value);
|
||||
} catch (InvalidFieldException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class CountReducer extends
|
||||
Reducer<KeyspaceIdWritable, RowWritable, NullWritable, LongWritable> {
|
||||
@Override
|
||||
public void reduce(KeyspaceIdWritable key, Iterable<RowWritable> values, Context context)
|
||||
throws IOException, InterruptedException {
|
||||
long count = 0;
|
||||
Iterator<RowWritable> iter = values.iterator();
|
||||
while (iter.hasNext()) {
|
||||
count++;
|
||||
iter.next();
|
||||
}
|
||||
context.write(NullWritable.get(), new LongWritable(count));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create env with two shards each having a master and rdonly
|
||||
*/
|
||||
static TestEnv getTestEnv() {
|
||||
Map<String, List<String>> shardKidMap = new HashMap<>();
|
||||
shardKidMap.put("-80",
|
||||
Lists.newArrayList("527875958493693904", "626750931627689502", "345387386794260318"));
|
||||
shardKidMap.put("80-",
|
||||
Lists.newArrayList("9767889778372766922", "9742070682920810358", "10296850775085416642"));
|
||||
TestEnv env = Util.getTestEnv(shardKidMap, "test_keyspace");
|
||||
env.addTablet("rdonly", 1);
|
||||
return env;
|
||||
}
|
||||
|
||||
public static TestSetup suite() {
|
||||
return new TestSetup(new TestSuite(MapReduceIT.class)) {
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
Util.setupTestEnv(testEnv);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tearDown() throws Exception {
|
||||
Util.teardownTestEnv(testEnv);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
|
@ -1,188 +0,0 @@
|
|||
package com.youtube.vitess.vtgate.integration.util;
|
||||
|
||||
import com.google.common.primitives.UnsignedLong;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import com.youtube.vitess.vtgate.BindVariable;
|
||||
import com.youtube.vitess.vtgate.Exceptions.ConnectionException;
|
||||
import com.youtube.vitess.vtgate.Exceptions.DatabaseException;
|
||||
import com.youtube.vitess.vtgate.KeyspaceId;
|
||||
import com.youtube.vitess.vtgate.Query;
|
||||
import com.youtube.vitess.vtgate.Query.QueryBuilder;
|
||||
import com.youtube.vitess.vtgate.TestEnv;
|
||||
import com.youtube.vitess.vtgate.VtGate;
|
||||
import com.youtube.vitess.vtgate.cursor.Cursor;
|
||||
|
||||
import org.apache.log4j.LogManager;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.Assert;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class Util {
|
||||
static final Logger logger = LogManager.getLogger(Util.class.getName());
|
||||
public static final String PROPERTY_KEY_VTGATE_TEST_ENV = "vtgate.test.env";
|
||||
/**
|
||||
* Setup MySQL, Vttablet and VtGate instances required for the tests. This uses a Python helper
|
||||
* script to start and stop instances.
|
||||
*/
|
||||
public static void setupTestEnv(TestEnv testEnv) throws Exception {
|
||||
ProcessBuilder pb = new ProcessBuilder(testEnv.getSetupCommand());
|
||||
pb.redirectErrorStream(true);
|
||||
Process p = pb.start();
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
|
||||
// The port for VtGate is dynamically assigned and written to
|
||||
// stdout as a JSON string.
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
logger.info("java_vtgate_test_helper: " + line);
|
||||
if (!line.startsWith("{")) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
Type mapType = new TypeToken<Map<String, Integer>>() {}.getType();
|
||||
Map<String, Integer> map = new Gson().fromJson(line, mapType);
|
||||
testEnv.setPythonScriptProcess(p);
|
||||
testEnv.setPort(map.get("port"));
|
||||
return;
|
||||
} catch (JsonSyntaxException e) {
|
||||
logger.error("JsonSyntaxException parsing setup command output: " + line, e);
|
||||
}
|
||||
}
|
||||
Assert.fail("setup script failed to parse vtgate port");
|
||||
}
|
||||
|
||||
/**
|
||||
* Teardown the test instances, if any.
|
||||
*/
|
||||
public static void teardownTestEnv(TestEnv testEnv) throws Exception {
|
||||
Process process = testEnv.getPythonScriptProcess();
|
||||
if (process != null) {
|
||||
logger.info("sending empty line to java_vtgate_test_helper to stop test setup");
|
||||
process.getOutputStream().write("\n".getBytes());
|
||||
process.getOutputStream().flush();
|
||||
process.waitFor();
|
||||
testEnv.setPythonScriptProcess(null);
|
||||
}
|
||||
}
|
||||
|
||||
public static void insertRows(TestEnv testEnv, int startId, int count) throws ConnectionException,
|
||||
DatabaseException {
|
||||
insertRows(testEnv, startId, count, new DateTime());
|
||||
}
|
||||
|
||||
public static void insertRows(TestEnv testEnv, int startId, int count, DateTime dateTime)
|
||||
throws ConnectionException, DatabaseException {
|
||||
VtGate vtgate = VtGate.connect("localhost:" + testEnv.getPort(), 0, testEnv.getRpcClientFactory());
|
||||
|
||||
vtgate.begin();
|
||||
String insertSql = "insert into vtgate_test "
|
||||
+ "(id, name, age, percent, datetime_col, timestamp_col, date_col, time_col, keyspace_id) "
|
||||
+ "values (:id, :name, :age, :percent, :datetime_col, :timestamp_col, :date_col, :time_col, :keyspace_id)";
|
||||
for (int i = startId; i < startId + count; i++) {
|
||||
KeyspaceId kid = testEnv.getAllKeyspaceIds().get(i % testEnv.getAllKeyspaceIds().size());
|
||||
Query query = new QueryBuilder(insertSql, testEnv.getKeyspace(), "master")
|
||||
.addBindVar(BindVariable.forULong("id", UnsignedLong.valueOf("" + i)))
|
||||
.addBindVar(BindVariable.forBytes("name", ("name_" + i).getBytes()))
|
||||
.addBindVar(BindVariable.forInt("age", i * 2))
|
||||
.addBindVar(BindVariable.forDouble("percent", new Double(i / 100.0)))
|
||||
.addBindVar(BindVariable.forULong("keyspace_id", (UnsignedLong) kid.getId()))
|
||||
.addBindVar(BindVariable.forDateTime("datetime_col", dateTime))
|
||||
.addBindVar(BindVariable.forDateTime("timestamp_col", dateTime))
|
||||
.addBindVar(BindVariable.forDate("date_col", dateTime))
|
||||
.addBindVar(BindVariable.forTime("time_col", dateTime))
|
||||
.addKeyspaceId(kid)
|
||||
.build();
|
||||
vtgate.execute(query);
|
||||
}
|
||||
vtgate.commit();
|
||||
vtgate.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert rows to a specific shard using ExecuteKeyspaceIds
|
||||
*/
|
||||
public static void insertRowsInShard(TestEnv testEnv, String shardName, int count)
|
||||
throws DatabaseException, ConnectionException {
|
||||
VtGate vtgate = VtGate.connect("localhost:" + testEnv.getPort(), 0, testEnv.getRpcClientFactory());
|
||||
vtgate.begin();
|
||||
String sql = "insert into vtgate_test " + "(id, name, keyspace_id) "
|
||||
+ "values (:id, :name, :keyspace_id)";
|
||||
List<KeyspaceId> kids = testEnv.getKeyspaceIds(shardName);
|
||||
for (int i = 1; i <= count; i++) {
|
||||
KeyspaceId kid = kids.get(i % kids.size());
|
||||
Query query = new QueryBuilder(sql, testEnv.getKeyspace(), "master")
|
||||
.addBindVar(BindVariable.forULong("id", UnsignedLong.valueOf("" + i)))
|
||||
.addBindVar(BindVariable.forBytes("name", ("name_" + i).getBytes()))
|
||||
.addBindVar(BindVariable.forULong("keyspace_id", (UnsignedLong) kid.getId()))
|
||||
.addKeyspaceId(kid)
|
||||
.build();
|
||||
vtgate.execute(query);
|
||||
}
|
||||
vtgate.commit();
|
||||
vtgate.close();
|
||||
}
|
||||
|
||||
public static void createTable(TestEnv testEnv) throws Exception {
|
||||
VtGate vtgate = VtGate.connect("localhost:" + testEnv.getPort(), 0, testEnv.getRpcClientFactory());
|
||||
vtgate.begin();
|
||||
vtgate.execute(new QueryBuilder("drop table if exists vtgate_test", testEnv.getKeyspace(), "master")
|
||||
.setKeyspaceIds(testEnv.getAllKeyspaceIds()).build());
|
||||
String createTable = "create table vtgate_test (id bigint auto_increment,"
|
||||
+ " name varchar(64), age SMALLINT, percent DECIMAL(5,2),"
|
||||
+ " keyspace_id bigint(20) unsigned NOT NULL, datetime_col DATETIME,"
|
||||
+ " timestamp_col TIMESTAMP, date_col DATE, time_col TIME, primary key (id))"
|
||||
+ " Engine=InnoDB";
|
||||
vtgate.execute(new QueryBuilder(createTable, testEnv.getKeyspace(), "master").setKeyspaceIds(
|
||||
testEnv.getAllKeyspaceIds()).build());
|
||||
vtgate.commit();
|
||||
vtgate.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until the specified tablet type has received at least rowCount rows in vtgate_test from
|
||||
* the master. If the criteria isn't met after the specified number of attempts raise an
|
||||
* exception.
|
||||
*/
|
||||
public static void waitForTablet(String tabletType, int rowCount, int attempts, TestEnv testEnv)
|
||||
throws Exception {
|
||||
String sql = "select * from vtgate_test";
|
||||
VtGate vtgate = VtGate.connect("localhost:" + testEnv.getPort(), 0, testEnv.getRpcClientFactory());
|
||||
for (int i = 0; i < attempts; i++) {
|
||||
try {
|
||||
Cursor cursor = vtgate.execute(new QueryBuilder(sql, testEnv.getKeyspace(), tabletType)
|
||||
.setKeyspaceIds(testEnv.getAllKeyspaceIds()).build());
|
||||
if (cursor.getRowsAffected() >= rowCount) {
|
||||
vtgate.close();
|
||||
return;
|
||||
}
|
||||
} catch (DatabaseException e) {
|
||||
|
||||
}
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
vtgate.close();
|
||||
throw new Exception(tabletType + " fails to catch up");
|
||||
}
|
||||
|
||||
public static TestEnv getTestEnv(Map<String, List<String>> shardKidMap, String keyspace) {
|
||||
String testEnvClass = System.getProperty(PROPERTY_KEY_VTGATE_TEST_ENV);
|
||||
try {
|
||||
Class<?> clazz = Class.forName(testEnvClass);
|
||||
TestEnv env = (TestEnv)clazz.newInstance();
|
||||
env.setKeyspace(keyspace);
|
||||
env.setShardKidMap(shardKidMap);
|
||||
return env;
|
||||
} catch (ClassNotFoundException|IllegalAccessException|InstantiationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,105 +0,0 @@
|
|||
package com.youtube.vitess.vtgate.rpcclient.gorpc;
|
||||
|
||||
import com.google.common.primitives.UnsignedLong;
|
||||
import com.youtube.vitess.vtgate.Exceptions.InvalidFieldException;
|
||||
import com.youtube.vitess.vtgate.Field.Flag;
|
||||
import com.youtube.vitess.vtgate.QueryResult;
|
||||
import com.youtube.vitess.vtgate.Row;
|
||||
import com.youtube.vitess.vtgate.Row.Cell;
|
||||
import com.youtube.vitess.vtgate.cursor.Cursor;
|
||||
import com.youtube.vitess.vtgate.cursor.CursorImpl;
|
||||
import com.youtube.vitess.vtgate.FieldType;
|
||||
import org.bson.BSONObject;
|
||||
import org.bson.BasicBSONObject;
|
||||
import org.bson.types.BasicBSONList;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public class BsonifyTest {
|
||||
|
||||
@Test
|
||||
public void testResultParse() throws InvalidFieldException {
|
||||
BSONObject result = new BasicBSONObject();
|
||||
result.put("RowsAffected", 12L);
|
||||
result.put("InsertId", 12345L);
|
||||
|
||||
BasicBSONList fields = new BasicBSONList();
|
||||
fields.add(newField("col_0", FieldType.VT_DECIMAL, Flag.VT_ZEROVALUE_FLAG));
|
||||
fields.add(newField("col_1", FieldType.VT_TINY, Flag.VT_ZEROVALUE_FLAG));
|
||||
fields.add(newField("col_2", FieldType.VT_SHORT, Flag.VT_ZEROVALUE_FLAG));
|
||||
fields.add(newField("col_3", FieldType.VT_LONG, Flag.VT_ZEROVALUE_FLAG));
|
||||
fields.add(newField("col_4", FieldType.VT_LONGLONG, Flag.VT_ZEROVALUE_FLAG));
|
||||
fields.add(newField("col_5", FieldType.VT_LONGLONG, Flag.VT_UNSIGNED_FLAG));
|
||||
result.put("Fields", fields);
|
||||
|
||||
// Fill each column with the following different values: 0, 1, 2
|
||||
BasicBSONList rows = new BasicBSONList();
|
||||
int rowCount = 2;
|
||||
for (int i = 0; i <= rowCount; i++) {
|
||||
BasicBSONList row = new BasicBSONList();
|
||||
row.add(new Double(i).toString().getBytes());
|
||||
row.add(String.valueOf(i).getBytes());
|
||||
row.add(String.valueOf(i).getBytes());
|
||||
row.add(new Long(i).toString().getBytes());
|
||||
row.add(new Long(i).toString().getBytes());
|
||||
row.add(new Long(i).toString().getBytes());
|
||||
Assert.assertEquals(fields.size(), row.size());
|
||||
rows.add(row);
|
||||
}
|
||||
result.put("Rows", rows);
|
||||
|
||||
QueryResult qr = Bsonify.bsonToQueryResult(result, null);
|
||||
Cursor cursor = new CursorImpl(qr);
|
||||
Assert.assertEquals(12L, cursor.getRowsAffected());
|
||||
Assert.assertEquals(12345L, cursor.getLastRowId());
|
||||
|
||||
for (int i = 0; i <= rowCount; i++) {
|
||||
Row row = cursor.next();
|
||||
Cell cell0 = row.next();
|
||||
Assert.assertEquals("col_0", cell0.getName());
|
||||
Assert.assertEquals(BigDecimal.class, cell0.getType());
|
||||
Assert.assertEquals(new BigDecimal(String.format("%d.0", i)), row.getBigDecimal(cell0.getName()));
|
||||
|
||||
Cell cell1 = row.next();
|
||||
Assert.assertEquals("col_1", cell1.getName());
|
||||
Assert.assertEquals(Integer.class, cell1.getType());
|
||||
Assert.assertEquals(new Integer(i), row.getInt(cell1.getName()));
|
||||
|
||||
Cell cell2 = row.next();
|
||||
Assert.assertEquals("col_2", cell2.getName());
|
||||
Assert.assertEquals(Integer.class, cell2.getType());
|
||||
Assert.assertEquals(new Integer(i), row.getInt(cell2.getName()));
|
||||
|
||||
Cell cell3 = row.next();
|
||||
Assert.assertEquals("col_3", cell3.getName());
|
||||
Assert.assertEquals(Long.class, cell3.getType());
|
||||
Assert.assertEquals(new Long(i), row.getLong(cell3.getName()));
|
||||
|
||||
Cell cell4 = row.next();
|
||||
Assert.assertEquals("col_4", cell4.getName());
|
||||
Assert.assertEquals(Long.class, cell4.getType());
|
||||
Assert.assertEquals(new Long(i), row.getLong(cell4.getName()));
|
||||
|
||||
Cell cell5 = row.next();
|
||||
Assert.assertEquals("col_5", cell5.getName());
|
||||
Assert.assertEquals(UnsignedLong.class, cell5.getType());
|
||||
Assert.assertEquals(UnsignedLong.valueOf(String.format("%d", i)), row.getULong(cell5.getName()));
|
||||
}
|
||||
// No more rows left.
|
||||
Assert.assertFalse(cursor.hasNext());
|
||||
}
|
||||
|
||||
private BSONObject newField(String name, FieldType fieldType, Flag flag) {
|
||||
BSONObject field = new BasicBSONObject();
|
||||
field.put("Name", name.getBytes());
|
||||
field.put("Type", (long) fieldType.mysqlType);
|
||||
field.put("Flags", (long) flag.mysqlFlag);
|
||||
|
||||
return field;
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
log4j.rootLogger=INFO, consoleAppender
|
||||
|
||||
log4j.appender.consoleAppender=org.apache.log4j.ConsoleAppender
|
||||
log4j.appender.consoleAppender.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.consoleAppender.layout.ConversionPattern=[%t] %-5p %c %x - %m%n
|
||||
OFF
|
|
@ -6,14 +6,27 @@ See `demo.php` for a simple example of using the API.
|
|||
|
||||
## Prerequisites
|
||||
|
||||
PHP 5.3+ is required.
|
||||
PHP 5.5+ is required.
|
||||
|
||||
This library uses the BSON codec library contained within the MongoDB plugin.
|
||||
For example, you can install this on Debian/Ubuntu like this:
|
||||
### gRPC Extension Module
|
||||
|
||||
Install the [gRPC extension module](https://pecl.php.net/package/gRPC).
|
||||
|
||||
For example, on Debian/Ubuntu:
|
||||
|
||||
``` sh
|
||||
$ sudo apt-get install php5-dev php5-cli php-pear
|
||||
$ sudo pecl install mongo
|
||||
$ sudo pecl install grpc-beta
|
||||
```
|
||||
|
||||
### gRPC Dependencies
|
||||
|
||||
To download the dependencies of the gRPC PHP library, run Composer:
|
||||
|
||||
``` sh
|
||||
$ cd vitess/php
|
||||
vitess/php$ curl -sS https://getcomposer.org/installer | php
|
||||
vitess/php$ php composer.phar install
|
||||
```
|
||||
|
||||
## Unit Tests
|
||||
|
@ -29,7 +42,8 @@ $ chmod +x $VTROOT/bin/phpunit
|
|||
Then run the tests like this:
|
||||
|
||||
``` sh
|
||||
vitess/php$ phpunit tests
|
||||
vitess$ . dev.env
|
||||
vitess$ make php_test
|
||||
```
|
||||
|
||||
### Coverage
|
||||
|
|
20
php/demo.php
20
php/demo.php
|
@ -4,13 +4,13 @@
|
|||
* This is a sample for using the PHP Vitess client.
|
||||
*
|
||||
* Before running this, start up a local demo cluster by running:
|
||||
* vitess$ test/demo.py
|
||||
* vitess$ examples/demo/run.py
|
||||
*
|
||||
* The demo.py script will print the vtgate port, which you pass in like this:
|
||||
* vitess/php$ php demo.php --server localhost:port
|
||||
* Then in another terminal:
|
||||
* vitess/php$ php demo.php --server=localhost:12346
|
||||
*/
|
||||
require_once ('./src/VTGateConn.php');
|
||||
require_once ('./src/BsonRpcClient.php');
|
||||
require_once ('./src/VTGrpcClient.php');
|
||||
|
||||
$opts = getopt('', array(
|
||||
'server:'
|
||||
|
@ -18,9 +18,7 @@ $opts = getopt('', array(
|
|||
|
||||
// Create a connection.
|
||||
$ctx = VTContext::getDefault();
|
||||
$client = new BsonRpcClient();
|
||||
$client->dial($ctx, $opts['server']);
|
||||
$conn = new VTGateConn($client);
|
||||
$conn = new VTGateConn(new VTGrpcClient($opts['server']));
|
||||
|
||||
// Insert something.
|
||||
$tx = $conn->begin($ctx);
|
||||
|
@ -30,5 +28,9 @@ $tx->execute($ctx, 'INSERT INTO user (name) VALUES (:name)', array(
|
|||
$tx->commit($ctx);
|
||||
|
||||
// Read it back.
|
||||
$result = $conn->execute($ctx, 'SELECT * FROM user', array(), VTTabletType::MASTER);
|
||||
print_r($result->rows);
|
||||
$cursor = $conn->execute($ctx, 'SELECT * FROM user', array(), \topodata\TabletType::MASTER);
|
||||
while (($row = $cursor->next()) !== FALSE) {
|
||||
print_r($row);
|
||||
}
|
||||
|
||||
$conn->close();
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* NOTE: This module requires bson_encode() and bson_decode(),
|
||||
* which can be obtained by installing the MongoDB driver.
|
||||
* For example, on Debian/Ubuntu:
|
||||
* $ sudo apt-get install php5-dev php5-cli php-pear
|
||||
* $ sudo pecl install mongo
|
||||
*/
|
||||
ini_set('mongo.native_long', 1);
|
||||
|
||||
require_once (dirname(__FILE__) . '/GoRpcClient.php');
|
||||
|
||||
class BsonRpcClient extends GoRpcClient {
|
||||
const LEN_PACK_FORMAT = 'V';
|
||||
const LEN_PACK_SIZE = 4;
|
||||
|
||||
public function dial(VTContext $ctx, $addr) {
|
||||
parent::dial($ctx, "tcp://$addr", '/_bson_rpc_');
|
||||
}
|
||||
|
||||
protected function sendRequest(VTContext $ctx, GoRpcRequest $req) {
|
||||
$this->write($ctx, bson_encode($req->header));
|
||||
if ($req->body === NULL)
|
||||
$this->write($ctx, bson_encode(array()));
|
||||
else
|
||||
$this->write($ctx, bson_encode($req->body));
|
||||
}
|
||||
|
||||
protected function readResponse(VTContext $ctx) {
|
||||
// Read the header.
|
||||
$data = $this->readN($ctx, self::LEN_PACK_SIZE);
|
||||
$unpack = unpack(self::LEN_PACK_FORMAT, $data);
|
||||
$len = $unpack[1];
|
||||
$header = $data . $this->readN($ctx, $len - self::LEN_PACK_SIZE);
|
||||
|
||||
// Read the body.
|
||||
$data = $this->readN($ctx, self::LEN_PACK_SIZE);
|
||||
$unpack = unpack(self::LEN_PACK_FORMAT, $data);
|
||||
$len = $unpack[1];
|
||||
$body = $data . $this->readN($ctx, $len - self::LEN_PACK_SIZE);
|
||||
|
||||
// Decode and return.
|
||||
return new GoRpcResponse(bson_decode($header), bson_decode($body));
|
||||
}
|
||||
}
|
|
@ -1,167 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This is an implementation of a Vitess-compatible Go-style RPC layer for PHP.
|
||||
*/
|
||||
|
||||
// TODO(enisoc): Implement timeouts.
|
||||
class GoRpcException extends Exception {
|
||||
}
|
||||
|
||||
class GoRpcRemoteError extends GoRpcException {
|
||||
}
|
||||
|
||||
class GoRpcRequest {
|
||||
public $header;
|
||||
public $body;
|
||||
|
||||
public function __construct($seq, $method, $body) {
|
||||
$this->header = array(
|
||||
'ServiceMethod' => $method,
|
||||
'Seq' => $seq
|
||||
);
|
||||
$this->body = $body;
|
||||
}
|
||||
|
||||
public function seq() {
|
||||
return $this->header['Seq'];
|
||||
}
|
||||
}
|
||||
|
||||
function unwrap_bin_data($value) {
|
||||
if (is_object($value) && get_class($value) == 'MongoBinData')
|
||||
return $value->bin;
|
||||
if (is_array($value)) {
|
||||
foreach ($value as $key => $child)
|
||||
$value[$key] = unwrap_bin_data($child);
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
class GoRpcResponse {
|
||||
public $header;
|
||||
public $reply;
|
||||
|
||||
public function __construct($header, $body) {
|
||||
$this->header = unwrap_bin_data($header);
|
||||
$this->reply = unwrap_bin_data($body);
|
||||
}
|
||||
|
||||
public function error() {
|
||||
return $this->header['Error'];
|
||||
}
|
||||
|
||||
public function seq() {
|
||||
return $this->header['Seq'];
|
||||
}
|
||||
|
||||
public function isEOS() {
|
||||
return $this->error() == self::END_OF_STREAM;
|
||||
}
|
||||
const END_OF_STREAM = 'EOS';
|
||||
}
|
||||
|
||||
abstract class GoRpcClient {
|
||||
protected $seq = 0;
|
||||
protected $stream = NULL;
|
||||
|
||||
abstract protected function sendRequest(VTContext $ctx, GoRpcRequest $req);
|
||||
|
||||
abstract protected function readResponse(VTContext $ctx);
|
||||
private $in_stream_call = FALSE;
|
||||
|
||||
public function dial(VTContext $ctx, $addr, $path) {
|
||||
// Connect to $addr.
|
||||
$fp = stream_socket_client($addr, $errno, $errstr);
|
||||
if ($fp === FALSE)
|
||||
throw new GoRpcException("can't connect to $addr: $errstr ($errno)");
|
||||
$this->stream = $fp;
|
||||
|
||||
// Initiate request for $path.
|
||||
$this->write($ctx, "CONNECT $path HTTP/1.0\n\n");
|
||||
|
||||
// Read until handshake is completed.
|
||||
$data = '';
|
||||
while (strpos($data, "\n\n") === FALSE)
|
||||
$data .= $this->read($ctx, 1024);
|
||||
}
|
||||
|
||||
public function close() {
|
||||
if ($this->stream !== NULL) {
|
||||
fclose($this->stream);
|
||||
$this->stream = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
public function call(VTContext $ctx, $method, $request) {
|
||||
if ($this->in_stream_call) {
|
||||
throw new Exception("GoRpcClient: can't make another call until results of streaming call are completely fetched.");
|
||||
}
|
||||
|
||||
$req = new GoRpcRequest($this->nextSeq(), $method, $request);
|
||||
$this->sendRequest($ctx, $req);
|
||||
|
||||
$resp = $this->readResponse($ctx);
|
||||
if ($resp->seq() != $req->seq())
|
||||
throw new GoRpcException("$method: request sequence mismatch");
|
||||
if ($resp->error())
|
||||
throw new GoRpcRemoteError("$method: " . $resp->error());
|
||||
|
||||
return $resp;
|
||||
}
|
||||
|
||||
public function streamCall(VTContext $ctx, $method, $request) {
|
||||
if ($this->in_stream_call) {
|
||||
throw new Exception("GoRpcClient: can't make another call until results of streaming call are completely fetched.");
|
||||
}
|
||||
|
||||
$req = new GoRpcRequest($this->nextSeq(), $method, $request);
|
||||
$this->sendRequest($ctx, $req);
|
||||
$this->in_stream_call = TRUE;
|
||||
}
|
||||
|
||||
public function streamNext(VTContext $ctx) {
|
||||
if (! $this->in_stream_call) {
|
||||
throw new Exception("GoRpcClient: streamNext called when not in streaming call.");
|
||||
}
|
||||
|
||||
$resp = $this->readResponse($ctx);
|
||||
if ($resp->seq() != $this->seq) {
|
||||
throw new GoRpcException("$method: request sequence mismatch");
|
||||
}
|
||||
if ($resp->isEOS()) {
|
||||
$this->in_stream_call = FALSE;
|
||||
return FALSE;
|
||||
}
|
||||
if ($resp->error()) {
|
||||
throw new GoRpcRemoteError("$method: " . $resp->error());
|
||||
}
|
||||
return $resp;
|
||||
}
|
||||
|
||||
protected function read(VTContext $ctx, $max_len) {
|
||||
if (feof($this->stream))
|
||||
throw new GoRpcException("unexpected EOF while reading from stream");
|
||||
$packet = fread($this->stream, $max_len);
|
||||
if ($packet === FALSE)
|
||||
throw new GoRpcException("can't read from stream");
|
||||
return $packet;
|
||||
}
|
||||
|
||||
protected function readN(VTContext $ctx, $target_len) {
|
||||
// Read exactly $target_len bytes or bust.
|
||||
$data = '';
|
||||
while (($len = strlen($data)) < $target_len)
|
||||
$data .= $this->read($ctx, $target_len - $len);
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function write(VTContext $ctx, $data) {
|
||||
if (fwrite($this->stream, $data) === FALSE)
|
||||
throw new GoRpcException("can't write to stream");
|
||||
}
|
||||
|
||||
protected function nextSeq() {
|
||||
return ++ $this->seq;
|
||||
}
|
||||
}
|
|
@ -1,13 +1,11 @@
|
|||
<?php
|
||||
|
||||
class VTDeadlineExceededException extends Exception {
|
||||
}
|
||||
require_once (dirname(__FILE__) . '/VTException.php');
|
||||
|
||||
/**
|
||||
* VTContext is an immutable object carrying request-specific deadlines and
|
||||
* credentials (callerId).
|
||||
*
|
||||
* Example usage:
|
||||
* <p>Example usage:
|
||||
* <pre>
|
||||
* $ctx = VTContext::getDefault()->withDeadlineAfter(0.5); // 500ms timeout
|
||||
* $conn->doSomething($ctx, ...);
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
require_once (dirname(__FILE__) . '/VTProto.php');
|
||||
require_once (dirname(__FILE__) . '/VTException.php');
|
||||
|
||||
class VTCursor {
|
||||
private $queryResult;
|
||||
private $pos = - 1;
|
||||
private $rows;
|
||||
|
||||
public function __construct(\query\QueryResult $query_result) {
|
||||
$this->queryResult = $query_result;
|
||||
if ($query_result->hasRows()) {
|
||||
$this->rows = $query_result->getRowsList();
|
||||
}
|
||||
}
|
||||
|
||||
public function getRowsAffected() {
|
||||
return $this->queryResult->getRowsAffected();
|
||||
}
|
||||
|
||||
public function getInsertId() {
|
||||
return $this->queryResult->getInsertId();
|
||||
}
|
||||
|
||||
public function getFields() {
|
||||
return $this->queryResult->getFieldsList();
|
||||
}
|
||||
|
||||
public function close() {
|
||||
}
|
||||
|
||||
public function next() {
|
||||
if ($this->rows && ++ $this->pos < count($this->rows)) {
|
||||
return $this->rows[$this->pos]->getValuesList();
|
||||
} else {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VTStreamCursor {
|
||||
private $pos = - 1;
|
||||
private $rows;
|
||||
private $fields;
|
||||
private $stream;
|
||||
|
||||
public function __construct($stream) {
|
||||
$this->stream = $stream;
|
||||
}
|
||||
|
||||
public function getFields() {
|
||||
if ($this->fields === NULL) {
|
||||
// The first QueryResult should have the fields.
|
||||
if (! $this->nextQueryResult()) {
|
||||
throw new VTException("stream ended before fields were received");
|
||||
}
|
||||
}
|
||||
return $this->fields;
|
||||
}
|
||||
|
||||
public function close() {
|
||||
if ($this->stream) {
|
||||
$this->stream->close();
|
||||
$this->stream = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
public function next() {
|
||||
// Get the next row from the current QueryResult.
|
||||
if ($this->rows && ++ $this->pos < count($this->rows)) {
|
||||
return $this->rows[$this->pos]->getValuesList();
|
||||
}
|
||||
|
||||
// Get the next QueryResult. Loop in case we get a QueryResult with no rows (e.g. only fields).
|
||||
while ($this->nextQueryResult()) {
|
||||
// Get the first row from the new QueryResult.
|
||||
if ($this->rows && ++ $this->pos < count($this->rows)) {
|
||||
return $this->rows[$this->pos]->getValuesList();
|
||||
}
|
||||
}
|
||||
|
||||
// No more rows and no more QueryResults.
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
private function nextQueryResult() {
|
||||
if (($response = $this->stream->next()) !== FALSE) {
|
||||
$query_result = $response->getResult();
|
||||
$this->rows = $query_result->getRowsList();
|
||||
$this->pos = - 1;
|
||||
|
||||
if ($this->fields === NULL) {
|
||||
// The first QueryResult should have the fields.
|
||||
$this->fields = $query_result->getFieldsList();
|
||||
}
|
||||
return TRUE;
|
||||
} else {
|
||||
// No more QueryResults.
|
||||
$this->rows = NULL;
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
class VTException extends Exception {
|
||||
}
|
||||
|
||||
class VTBadInputError extends VTException {
|
||||
}
|
||||
|
||||
class VTIntegrityError extends VTException {
|
||||
}
|
||||
|
||||
class VTTransientError extends VTException {
|
||||
}
|
||||
|
||||
class VTDeadlineExceededError extends VTTransientError {
|
||||
}
|
||||
|
||||
class VTUnauthenticatedError extends VTException {
|
||||
}
|
||||
|
|
@ -1,137 +1,8 @@
|
|||
<?php
|
||||
require_once (dirname(__FILE__) . '/VTProto.php');
|
||||
require_once (dirname(__FILE__) . '/VTContext.php');
|
||||
|
||||
class VTGateTx {
|
||||
protected $client;
|
||||
protected $session;
|
||||
|
||||
public function __construct($client, $session) {
|
||||
$this->client = $client;
|
||||
$this->session = $session;
|
||||
}
|
||||
|
||||
private function callExecute(VTContext $ctx, $query, array $bind_vars, $tablet_type, $not_in_transaction, $method, $req = array()) {
|
||||
if (is_null($this->session)) {
|
||||
throw new Exception('execute called while not in transaction.');
|
||||
}
|
||||
$req['Session'] = $this->session;
|
||||
$req['Query'] = VTBoundQuery::buildBsonP3($query, $bind_vars);
|
||||
$req['TabletType'] = $tablet_type;
|
||||
$req['NotInTransaction'] = $not_in_transaction;
|
||||
if ($ctx->getCallerId()) {
|
||||
$req['CallerId'] = $ctx->getCallerId()->toBsonP3();
|
||||
}
|
||||
|
||||
$resp = $this->client->call($ctx, $method, $req)->reply;
|
||||
if (array_key_exists('Session', $resp) && $resp['Session']) {
|
||||
$this->session = $resp['Session'];
|
||||
} else {
|
||||
$this->session = NULL;
|
||||
}
|
||||
VTProto::checkError($resp);
|
||||
|
||||
return VTQueryResult::fromBsonP3($resp['Result']);
|
||||
}
|
||||
|
||||
private function callExecuteBatch(VTContext $ctx, $queries, $tablet_type, $as_transaction, $method, $req = array()) {
|
||||
if (is_null($this->session)) {
|
||||
throw new Exception('execute called while not in transaction.');
|
||||
}
|
||||
$req['Session'] = $this->session;
|
||||
$req['Queries'] = $queries;
|
||||
$req['TabletType'] = $tablet_type;
|
||||
$req['AsTransaction'] = $as_transaction;
|
||||
if ($ctx->getCallerId()) {
|
||||
$req['CallerId'] = $ctx->getCallerId()->toBsonP3();
|
||||
}
|
||||
|
||||
$resp = $this->client->call($ctx, $method, $req)->reply;
|
||||
if (array_key_exists('Session', $resp) && $resp['Session']) {
|
||||
$this->session = $resp['Session'];
|
||||
} else {
|
||||
$this->session = NULL;
|
||||
}
|
||||
VTProto::checkError($resp);
|
||||
|
||||
$results = array();
|
||||
foreach ($resp['Results'] as $result) {
|
||||
$results[] = VTQueryResult::fromBsonP3($result);
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function execute(VTContext $ctx, $query, array $bind_vars, $tablet_type = VTTabletType::MASTER, $not_in_transaction = FALSE) {
|
||||
return $this->callExecute($ctx, $query, $bind_vars, $tablet_type, $not_in_transaction, 'VTGateP3.Execute');
|
||||
}
|
||||
|
||||
public function executeShards(VTContext $ctx, $query, $keyspace, array $shards, array $bind_vars, $tablet_type = VTTabletType::MASTER, $not_in_transaction = FALSE) {
|
||||
return $this->callExecute($ctx, $query, $bind_vars, $tablet_type, $not_in_transaction, 'VTGateP3.ExecuteShards', array(
|
||||
'Keyspace' => $keyspace,
|
||||
'Shards' => $shards
|
||||
));
|
||||
}
|
||||
|
||||
public function executeKeyspaceIds(VTContext $ctx, $query, $keyspace, array $keyspace_ids, array $bind_vars, $tablet_type = VTTabletType::MASTER, $not_in_transaction = FALSE) {
|
||||
return $this->callExecute($ctx, $query, $bind_vars, $tablet_type, $not_in_transaction, 'VTGateP3.ExecuteKeyspaceIds', array(
|
||||
'Keyspace' => $keyspace,
|
||||
'KeyspaceIds' => VTKeyspaceId::buildBsonP3Array($keyspace_ids)
|
||||
));
|
||||
}
|
||||
|
||||
public function executeKeyRanges(VTContext $ctx, $query, $keyspace, array $key_ranges, array $bind_vars, $tablet_type = VTTabletType::MASTER, $not_in_transaction = FALSE) {
|
||||
return $this->callExecute($ctx, $query, $bind_vars, $tablet_type, $not_in_transaction, 'VTGateP3.ExecuteKeyRanges', array(
|
||||
'Keyspace' => $keyspace,
|
||||
'KeyRanges' => VTKeyRange::buildBsonP3Array($key_ranges)
|
||||
));
|
||||
}
|
||||
|
||||
public function executeEntityIds(VTContext $ctx, $query, $keyspace, $entity_column_name, array $entity_keyspace_ids, array $bind_vars, $tablet_type = VTTabletType::MASTER, $not_in_transaction = FALSE) {
|
||||
return $this->callExecute($ctx, $query, $bind_vars, $tablet_type, $not_in_transaction, 'VTGateP3.ExecuteEntityIds', array(
|
||||
'Keyspace' => $keyspace,
|
||||
'EntityColumnName' => $entity_column_name,
|
||||
'EntityKeyspaceIds' => VTEntityId::buildBsonP3Array($entity_keyspace_ids)
|
||||
));
|
||||
}
|
||||
|
||||
public function executeBatchShards(VTContext $ctx, array $bound_shard_queries, $tablet_type = VTTabletType::MASTER, $as_transaction = TRUE) {
|
||||
return $this->callExecuteBatch($ctx, VTBoundShardQuery::buildBsonP3Array($bound_shard_queries), $tablet_type, $as_transaction, 'VTGateP3.ExecuteBatchShards');
|
||||
}
|
||||
|
||||
public function executeBatchKeyspaceIds(VTContext $ctx, array $bound_keyspace_id_queries, $tablet_type = VTTabletType::MASTER, $as_transaction = TRUE) {
|
||||
return $this->callExecuteBatch($ctx, VTBoundKeyspaceIdQuery::buildBsonP3Array($bound_keyspace_id_queries), $tablet_type, $as_transaction, 'VTGateP3.ExecuteBatchKeyspaceIds');
|
||||
}
|
||||
|
||||
public function commit(VTContext $ctx) {
|
||||
if (is_null($this->session)) {
|
||||
throw new Exception('commit called while not in transaction.');
|
||||
}
|
||||
$req = array(
|
||||
'Session' => $this->session
|
||||
);
|
||||
if ($ctx->getCallerId()) {
|
||||
$req['CallerId'] = $ctx->getCallerId()->toBsonP3();
|
||||
}
|
||||
|
||||
$resp = $this->client->call($ctx, 'VTGateP3.Commit', $req)->reply;
|
||||
$this->session = NULL;
|
||||
}
|
||||
|
||||
public function rollback(VTContext $ctx) {
|
||||
if (is_null($this->session)) {
|
||||
throw new Exception('rollback called while not in transaction.');
|
||||
}
|
||||
$req = array(
|
||||
'Session' => $this->session
|
||||
);
|
||||
if ($ctx->getCallerId()) {
|
||||
$req['CallerId'] = $ctx->getCallerId()->toBsonP3();
|
||||
}
|
||||
|
||||
$resp = $this->client->call($ctx, 'VTGateP3.Rollback', $req)->reply;
|
||||
$this->session = NULL;
|
||||
}
|
||||
}
|
||||
require_once (dirname(__FILE__) . '/VTCursor.php');
|
||||
require_once (dirname(__FILE__) . '/VTGateTx.php');
|
||||
|
||||
class VTGateConn {
|
||||
protected $client;
|
||||
|
@ -140,200 +11,191 @@ class VTGateConn {
|
|||
$this->client = $client;
|
||||
}
|
||||
|
||||
private function callExecute(VTContext $ctx, $query, array $bind_vars, $tablet_type, $method, $req = array()) {
|
||||
$req['Query'] = VTBoundQuery::buildBsonP3($query, $bind_vars);
|
||||
$req['TabletType'] = $tablet_type;
|
||||
if ($ctx->getCallerId()) {
|
||||
$req['CallerId'] = $ctx->getCallerId()->toBsonP3();
|
||||
}
|
||||
|
||||
$resp = $this->client->call($ctx, $method, $req)->reply;
|
||||
VTProto::checkError($resp);
|
||||
|
||||
return VTQueryResult::fromBsonP3($resp['Result']);
|
||||
}
|
||||
|
||||
private function callExecuteBatch(VTContext $ctx, $queries, $tablet_type, $as_transaction, $method, $req = array()) {
|
||||
$req['Queries'] = $queries;
|
||||
$req['TabletType'] = $tablet_type;
|
||||
$req['AsTransaction'] = $as_transaction;
|
||||
if ($ctx->getCallerId()) {
|
||||
$req['CallerId'] = $ctx->getCallerId()->toBsonP3();
|
||||
}
|
||||
|
||||
$resp = $this->client->call($ctx, $method, $req)->reply;
|
||||
VTProto::checkError($resp);
|
||||
|
||||
$results = array();
|
||||
if (array_key_exists('Results', $resp)) {
|
||||
foreach ($resp['Results'] as $result) {
|
||||
$results[] = VTQueryResult::fromBsonP3($result);
|
||||
}
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
private function callStreamExecute(VTContext $ctx, $query, array $bind_vars, $tablet_type, $method, $req = array()) {
|
||||
$req['Query'] = VTBoundQuery::buildBsonP3($query, $bind_vars);
|
||||
$req['TabletType'] = $tablet_type;
|
||||
if ($ctx->getCallerId()) {
|
||||
$req['CallerId'] = $ctx->getCallerId()->toBsonP3();
|
||||
}
|
||||
|
||||
$this->client->streamCall($ctx, $method, $req);
|
||||
|
||||
return new VTStreamResults($ctx, $this->client);
|
||||
}
|
||||
|
||||
public function execute(VTContext $ctx, $query, array $bind_vars, $tablet_type) {
|
||||
return $this->callExecute($ctx, $query, $bind_vars, $tablet_type, 'VTGateP3.Execute');
|
||||
$request = new \vtgate\ExecuteRequest();
|
||||
$request->setQuery(VTProto::BoundQuery($query, $bind_vars));
|
||||
$request->setTabletType($tablet_type);
|
||||
if ($ctx->getCallerId()) {
|
||||
$request->setCallerId($ctx->getCallerId());
|
||||
}
|
||||
$response = $this->client->execute($ctx, $request);
|
||||
VTProto::checkError($response);
|
||||
return new VTCursor($response->getResult());
|
||||
}
|
||||
|
||||
public function executeShards(VTContext $ctx, $query, $keyspace, array $shards, array $bind_vars, $tablet_type) {
|
||||
return $this->callExecute($ctx, $query, $bind_vars, $tablet_type, 'VTGateP3.ExecuteShards', array(
|
||||
'Keyspace' => $keyspace,
|
||||
'Shards' => $shards
|
||||
));
|
||||
$request = new \vtgate\ExecuteShardsRequest();
|
||||
$request->setQuery(VTProto::BoundQuery($query, $bind_vars));
|
||||
$request->setTabletType($tablet_type);
|
||||
$request->setKeyspace($keyspace);
|
||||
$request->setShards($shards);
|
||||
if ($ctx->getCallerId()) {
|
||||
$request->setCallerId($ctx->getCallerId());
|
||||
}
|
||||
$response = $this->client->executeShards($ctx, $request);
|
||||
VTProto::checkError($response);
|
||||
return new VTCursor($response->getResult());
|
||||
}
|
||||
|
||||
public function executeKeyspaceIds(VTContext $ctx, $query, $keyspace, array $keyspace_ids, array $bind_vars, $tablet_type) {
|
||||
return $this->callExecute($ctx, $query, $bind_vars, $tablet_type, 'VTGateP3.ExecuteKeyspaceIds', array(
|
||||
'Keyspace' => $keyspace,
|
||||
'KeyspaceIds' => VTKeyspaceId::buildBsonP3Array($keyspace_ids)
|
||||
));
|
||||
$request = new \vtgate\ExecuteKeyspaceIdsRequest();
|
||||
$request->setQuery(VTProto::BoundQuery($query, $bind_vars));
|
||||
$request->setTabletType($tablet_type);
|
||||
$request->setKeyspace($keyspace);
|
||||
$request->setKeyspaceIds($keyspace_ids);
|
||||
if ($ctx->getCallerId()) {
|
||||
$request->setCallerId($ctx->getCallerId());
|
||||
}
|
||||
$response = $this->client->executeKeyspaceIds($ctx, $request);
|
||||
VTProto::checkError($response);
|
||||
return new VTCursor($response->getResult());
|
||||
}
|
||||
|
||||
public function executeKeyRanges(VTContext $ctx, $query, $keyspace, array $key_ranges, array $bind_vars, $tablet_type) {
|
||||
return $this->callExecute($ctx, $query, $bind_vars, $tablet_type, 'VTGateP3.ExecuteKeyRanges', array(
|
||||
'Keyspace' => $keyspace,
|
||||
'KeyRanges' => VTKeyRange::buildBsonP3Array($key_ranges)
|
||||
));
|
||||
$request = new \vtgate\ExecuteKeyRangesRequest();
|
||||
$request->setQuery(VTProto::BoundQuery($query, $bind_vars));
|
||||
$request->setTabletType($tablet_type);
|
||||
$request->setKeyspace($keyspace);
|
||||
VTProto::addKeyRanges($request, $key_ranges);
|
||||
if ($ctx->getCallerId()) {
|
||||
$request->setCallerId($ctx->getCallerId());
|
||||
}
|
||||
$response = $this->client->executeKeyRanges($ctx, $request);
|
||||
VTProto::checkError($response);
|
||||
return new VTCursor($response->getResult());
|
||||
}
|
||||
|
||||
public function executeEntityIds(VTContext $ctx, $query, $keyspace, $entity_column_name, array $entity_keyspace_ids, array $bind_vars, $tablet_type) {
|
||||
return $this->callExecute($ctx, $query, $bind_vars, $tablet_type, 'VTGateP3.ExecuteEntityIds', array(
|
||||
'Keyspace' => $keyspace,
|
||||
'EntityColumnName' => $entity_column_name,
|
||||
'EntityKeyspaceIds' => VTEntityId::buildBsonP3Array($entity_keyspace_ids)
|
||||
));
|
||||
$request = new \vtgate\ExecuteEntityIdsRequest();
|
||||
$request->setQuery(VTProto::BoundQuery($query, $bind_vars));
|
||||
$request->setTabletType($tablet_type);
|
||||
$request->setKeyspace($keyspace);
|
||||
$request->setEntityColumnName($entity_column_name);
|
||||
VTProto::addEntityKeyspaceIds($request, $entity_keyspace_ids);
|
||||
if ($ctx->getCallerId()) {
|
||||
$request->setCallerId($ctx->getCallerId());
|
||||
}
|
||||
$response = $this->client->executeEntityIds($ctx, $request);
|
||||
VTProto::checkError($response);
|
||||
return new VTCursor($response->getResult());
|
||||
}
|
||||
|
||||
public function executeBatchShards(VTContext $ctx, array $bound_shard_queries, $tablet_type, $as_transaction) {
|
||||
return $this->callExecuteBatch($ctx, VTBoundShardQuery::buildBsonP3Array($bound_shard_queries), $tablet_type, $as_transaction, 'VTGateP3.ExecuteBatchShards');
|
||||
}
|
||||
|
||||
public function executeBatchKeyspaceIds(VTContext $ctx, array $bound_keyspace_id_queries, $tablet_type, $as_transaction) {
|
||||
return $this->callExecuteBatch($ctx, VTBoundKeyspaceIdQuery::buildBsonP3Array($bound_keyspace_id_queries), $tablet_type, $as_transaction, 'VTGateP3.ExecuteBatchKeyspaceIds');
|
||||
}
|
||||
|
||||
public function streamExecute(VTContext $ctx, $query, array $bind_vars, $tablet_type) {
|
||||
return $this->callStreamExecute($ctx, $query, $bind_vars, $tablet_type, 'VTGateP3.StreamExecute');
|
||||
}
|
||||
|
||||
public function streamExecuteShards(VTContext $ctx, $query, $keyspace, array $shards, array $bind_vars, $tablet_type) {
|
||||
return $this->callStreamExecute($ctx, $query, $bind_vars, $tablet_type, 'VTGateP3.StreamExecuteShards', array(
|
||||
'Keyspace' => $keyspace,
|
||||
'Shards' => $shards
|
||||
));
|
||||
}
|
||||
|
||||
public function streamExecuteKeyspaceIds(VTContext $ctx, $query, $keyspace, array $keyspace_ids, array $bind_vars, $tablet_type) {
|
||||
return $this->callStreamExecute($ctx, $query, $bind_vars, $tablet_type, 'VTGateP3.StreamExecuteKeyspaceIds', array(
|
||||
'Keyspace' => $keyspace,
|
||||
'KeyspaceIds' => VTKeyspaceId::buildBsonP3Array($keyspace_ids)
|
||||
));
|
||||
}
|
||||
|
||||
public function streamExecuteKeyRanges(VTContext $ctx, $query, $keyspace, array $key_ranges, array $bind_vars, $tablet_type) {
|
||||
return $this->callStreamExecute($ctx, $query, $bind_vars, $tablet_type, 'VTGateP3.StreamExecuteKeyRanges', array(
|
||||
'Keyspace' => $keyspace,
|
||||
'KeyRanges' => VTKeyRange::buildBsonP3Array($key_ranges)
|
||||
));
|
||||
}
|
||||
|
||||
public function begin(VTContext $ctx) {
|
||||
$req = array();
|
||||
$request = new \vtgate\ExecuteBatchShardsRequest();
|
||||
VTProto::addQueries($request, $bound_shard_queries);
|
||||
$request->setTabletType($tablet_type);
|
||||
$request->setAsTransaction($as_transaction);
|
||||
if ($ctx->getCallerId()) {
|
||||
$req['CallerId'] = $ctx->getCallerId()->toBsonP3();
|
||||
$request->setCallerId($ctx->getCallerId());
|
||||
}
|
||||
|
||||
$resp = $this->client->call($ctx, 'VTGateP3.Begin', $req)->reply;
|
||||
|
||||
return new VTGateTx($this->client, $resp['Session']);
|
||||
}
|
||||
|
||||
public function splitQuery(VTContext $ctx, $keyspace, $query, array $bind_vars, $split_column, $split_count) {
|
||||
$req = array(
|
||||
'Keyspace' => $keyspace,
|
||||
'Query' => VTBoundQuery::buildBsonP3($query, $bind_vars),
|
||||
'SplitColumn' => $split_column,
|
||||
'SplitCount' => $split_count
|
||||
);
|
||||
if ($ctx->getCallerId()) {
|
||||
$req['CallerId'] = $ctx->getCallerId()->toBsonP3();
|
||||
}
|
||||
|
||||
$resp = $this->client->call($ctx, 'VTGateP3.SplitQuery', $req)->reply;
|
||||
|
||||
$response = $this->client->executeBatchShards($ctx, $request);
|
||||
VTProto::checkError($response);
|
||||
$results = array();
|
||||
if (array_key_exists('Splits', $resp)) {
|
||||
foreach ($resp['Splits'] as $split) {
|
||||
$results[] = VTSplitQueryPart::fromBsonP3($split);
|
||||
}
|
||||
foreach ($response->getResultsList() as $result) {
|
||||
$results[] = new VTCursor($result);
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function getSrvKeyspace(VTContext $ctx, $keyspace) {
|
||||
$req = array(
|
||||
'Keyspace' => $keyspace
|
||||
);
|
||||
public function executeBatchKeyspaceIds(VTContext $ctx, array $bound_keyspace_id_queries, $tablet_type, $as_transaction) {
|
||||
$request = new \vtgate\ExecuteBatchKeyspaceIdsRequest();
|
||||
VTProto::addQueries($request, $bound_keyspace_id_queries);
|
||||
$request->setTabletType($tablet_type);
|
||||
$request->setAsTransaction($as_transaction);
|
||||
if ($ctx->getCallerId()) {
|
||||
$req['CallerId'] = $ctx->getCallerId()->toBsonP3();
|
||||
$request->setCallerId($ctx->getCallerId());
|
||||
}
|
||||
$response = $this->client->executeBatchKeyspaceIds($ctx, $request);
|
||||
VTProto::checkError($response);
|
||||
$results = array();
|
||||
foreach ($response->getResultsList() as $result) {
|
||||
$results[] = new VTCursor($result);
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
$resp = $this->client->call($ctx, 'VTGateP3.GetSrvKeyspace', $req)->reply;
|
||||
return VTSrvKeyspace::fromBsonP3($resp['SrvKeyspace']);
|
||||
public function streamExecute(VTContext $ctx, $query, array $bind_vars, $tablet_type) {
|
||||
$request = new \vtgate\StreamExecuteRequest();
|
||||
$request->setQuery(VTProto::BoundQuery($query, $bind_vars));
|
||||
$request->setTabletType($tablet_type);
|
||||
if ($ctx->getCallerId()) {
|
||||
$request->setCallerId($ctx->getCallerId());
|
||||
}
|
||||
$call = $this->client->streamExecute($ctx, $request);
|
||||
return new VTStreamCursor($call);
|
||||
}
|
||||
|
||||
public function streamExecuteShards(VTContext $ctx, $query, $keyspace, array $shards, array $bind_vars, $tablet_type) {
|
||||
$request = new \vtgate\StreamExecuteShardsRequest();
|
||||
$request->setQuery(VTProto::BoundQuery($query, $bind_vars));
|
||||
$request->setKeyspace($keyspace);
|
||||
$request->setShards($shards);
|
||||
$request->setTabletType($tablet_type);
|
||||
if ($ctx->getCallerId()) {
|
||||
$request->setCallerId($ctx->getCallerId());
|
||||
}
|
||||
$call = $this->client->streamExecuteShards($ctx, $request);
|
||||
return new VTStreamCursor($call);
|
||||
}
|
||||
|
||||
public function streamExecuteKeyspaceIds(VTContext $ctx, $query, $keyspace, array $keyspace_ids, array $bind_vars, $tablet_type) {
|
||||
$request = new \vtgate\StreamExecuteKeyspaceIdsRequest();
|
||||
$request->setQuery(VTProto::BoundQuery($query, $bind_vars));
|
||||
$request->setKeyspace($keyspace);
|
||||
$request->setKeyspaceIds($keyspace_ids);
|
||||
$request->setTabletType($tablet_type);
|
||||
if ($ctx->getCallerId()) {
|
||||
$request->setCallerId($ctx->getCallerId());
|
||||
}
|
||||
$call = $this->client->streamExecuteKeyspaceIds($ctx, $request);
|
||||
return new VTStreamCursor($call);
|
||||
}
|
||||
|
||||
public function streamExecuteKeyRanges(VTContext $ctx, $query, $keyspace, array $key_ranges, array $bind_vars, $tablet_type) {
|
||||
$request = new \vtgate\StreamExecuteKeyRangesRequest();
|
||||
$request->setQuery(VTProto::BoundQuery($query, $bind_vars));
|
||||
$request->setKeyspace($keyspace);
|
||||
VTProto::addKeyRanges($request, $key_ranges);
|
||||
$request->setTabletType($tablet_type);
|
||||
if ($ctx->getCallerId()) {
|
||||
$request->setCallerId($ctx->getCallerId());
|
||||
}
|
||||
$call = $this->client->streamExecuteKeyRanges($ctx, $request);
|
||||
return new VTStreamCursor($call);
|
||||
}
|
||||
|
||||
public function begin(VTContext $ctx) {
|
||||
$request = new \vtgate\BeginRequest();
|
||||
if ($ctx->getCallerId()) {
|
||||
$request->setCallerId($ctx->getCallerId());
|
||||
}
|
||||
|
||||
$response = $this->client->begin($ctx, $request);
|
||||
return new VTGateTx($this->client, $response->getSession());
|
||||
}
|
||||
|
||||
public function splitQuery(VTContext $ctx, $keyspace, $query, array $bind_vars, $split_column, $split_count) {
|
||||
$request = new \vtgate\SplitQueryRequest();
|
||||
$request->setKeyspace($keyspace);
|
||||
$request->setQuery(VTProto::BoundQuery($query, $bind_vars));
|
||||
$request->setSplitColumn($split_column);
|
||||
$request->setSplitCount($split_count);
|
||||
if ($ctx->getCallerId()) {
|
||||
$request->setCallerId($ctx->getCallerId());
|
||||
}
|
||||
|
||||
$response = $this->client->splitQuery($ctx, $request);
|
||||
return $response->getSplitsList();
|
||||
}
|
||||
|
||||
public function getSrvKeyspace(VTContext $ctx, $keyspace) {
|
||||
$request = new \vtgate\GetSrvKeyspaceRequest();
|
||||
$request->setKeyspace($keyspace);
|
||||
$response = $this->client->getSrvKeyspace($ctx, $request);
|
||||
return $response->getSrvKeyspace();
|
||||
}
|
||||
|
||||
public function close() {
|
||||
$this->client->close();
|
||||
}
|
||||
}
|
||||
|
||||
class VTStreamResults {
|
||||
private $ctx;
|
||||
private $client;
|
||||
|
||||
public function __construct($ctx, $client) {
|
||||
$this->ctx = $ctx;
|
||||
$this->client = $client;
|
||||
}
|
||||
|
||||
/**
|
||||
* fetch reads and returns the next VTQueryResult from the stream.
|
||||
*
|
||||
* If there are no more results and the stream has finished successfully,
|
||||
* it returns FALSE.
|
||||
*/
|
||||
public function fetch() {
|
||||
$resp = $this->client->streamNext($this->ctx);
|
||||
if ($resp === FALSE) {
|
||||
return FALSE;
|
||||
}
|
||||
VTProto::checkError($resp->reply);
|
||||
return VTQueryResult::fromBsonP3($resp->reply['Result']);
|
||||
}
|
||||
|
||||
/**
|
||||
* fetchAll calls fetch in a loop until it returns FALSE, and then returns the
|
||||
* results as an array.
|
||||
*/
|
||||
public function fetchAll() {
|
||||
$results = array();
|
||||
while (($result = $this->fetch()) !== FALSE) {
|
||||
$results[] = $result;
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,193 @@
|
|||
<?php
|
||||
|
||||
class VTGateTx {
|
||||
protected $client;
|
||||
protected $session;
|
||||
|
||||
public function __construct($client, $session) {
|
||||
$this->client = $client;
|
||||
$this->session = $session;
|
||||
}
|
||||
|
||||
private function inTransaction() {
|
||||
return ! is_null($this->session) && $this->session->getInTransaction();
|
||||
}
|
||||
|
||||
public function execute(VTContext $ctx, $query, array $bind_vars, $tablet_type = \topodata\TabletType::MASTER, $not_in_transaction = FALSE) {
|
||||
if (! $this->inTransaction()) {
|
||||
throw new VTException('execute called while not in transaction.');
|
||||
}
|
||||
|
||||
$request = new \vtgate\ExecuteRequest();
|
||||
$request->setSession($this->session);
|
||||
$request->setQuery(VTProto::BoundQuery($query, $bind_vars));
|
||||
$request->setTabletType($tablet_type);
|
||||
$request->setNotInTransaction($not_in_transaction);
|
||||
if ($ctx->getCallerId()) {
|
||||
$request->setCallerId($ctx->getCallerId());
|
||||
}
|
||||
$response = $this->client->execute($ctx, $request);
|
||||
$this->session = $response->getSession();
|
||||
VTProto::checkError($response);
|
||||
return new VTCursor($response->getResult());
|
||||
}
|
||||
|
||||
public function executeShards(VTContext $ctx, $query, $keyspace, array $shards, array $bind_vars, $tablet_type = \topodata\TabletType::MASTER, $not_in_transaction = FALSE) {
|
||||
if (! $this->inTransaction()) {
|
||||
throw new VTException('execute called while not in transaction.');
|
||||
}
|
||||
|
||||
$request = new \vtgate\ExecuteShardsRequest();
|
||||
$request->setSession($this->session);
|
||||
$request->setQuery(VTProto::BoundQuery($query, $bind_vars));
|
||||
$request->setTabletType($tablet_type);
|
||||
$request->setNotInTransaction($not_in_transaction);
|
||||
$request->setKeyspace($keyspace);
|
||||
$request->setShards($shards);
|
||||
if ($ctx->getCallerId()) {
|
||||
$request->setCallerId($ctx->getCallerId());
|
||||
}
|
||||
$response = $this->client->executeShards($ctx, $request);
|
||||
$this->session = $response->getSession();
|
||||
VTProto::checkError($response);
|
||||
return new VTCursor($response->getResult());
|
||||
}
|
||||
|
||||
public function executeKeyspaceIds(VTContext $ctx, $query, $keyspace, array $keyspace_ids, array $bind_vars, $tablet_type = \topodata\TabletType::MASTER, $not_in_transaction = FALSE) {
|
||||
if (! $this->inTransaction()) {
|
||||
throw new VTException('execute called while not in transaction.');
|
||||
}
|
||||
|
||||
$request = new \vtgate\ExecuteKeyspaceIdsRequest();
|
||||
$request->setSession($this->session);
|
||||
$request->setQuery(VTProto::BoundQuery($query, $bind_vars));
|
||||
$request->setTabletType($tablet_type);
|
||||
$request->setNotInTransaction($not_in_transaction);
|
||||
$request->setKeyspace($keyspace);
|
||||
$request->setKeyspaceIds($keyspace_ids);
|
||||
if ($ctx->getCallerId()) {
|
||||
$request->setCallerId($ctx->getCallerId());
|
||||
}
|
||||
$response = $this->client->executeKeyspaceIds($ctx, $request);
|
||||
$this->session = $response->getSession();
|
||||
VTProto::checkError($response);
|
||||
return new VTCursor($response->getResult());
|
||||
}
|
||||
|
||||
public function executeKeyRanges(VTContext $ctx, $query, $keyspace, array $key_ranges, array $bind_vars, $tablet_type = \topodata\TabletType::MASTER, $not_in_transaction = FALSE) {
|
||||
if (! $this->inTransaction()) {
|
||||
throw new VTException('execute called while not in transaction.');
|
||||
}
|
||||
|
||||
$request = new \vtgate\ExecuteKeyRangesRequest();
|
||||
$request->setSession($this->session);
|
||||
$request->setQuery(VTProto::BoundQuery($query, $bind_vars));
|
||||
$request->setTabletType($tablet_type);
|
||||
$request->setNotInTransaction($not_in_transaction);
|
||||
$request->setKeyspace($keyspace);
|
||||
VTProto::addKeyRanges($request, $key_ranges);
|
||||
if ($ctx->getCallerId()) {
|
||||
$request->setCallerId($ctx->getCallerId());
|
||||
}
|
||||
$response = $this->client->executeKeyRanges($ctx, $request);
|
||||
$this->session = $response->getSession();
|
||||
VTProto::checkError($response);
|
||||
return new VTCursor($response->getResult());
|
||||
}
|
||||
|
||||
public function executeEntityIds(VTContext $ctx, $query, $keyspace, $entity_column_name, array $entity_keyspace_ids, array $bind_vars, $tablet_type = \topodata\TabletType::MASTER, $not_in_transaction = FALSE) {
|
||||
if (! $this->inTransaction()) {
|
||||
throw new VTException('execute called while not in transaction.');
|
||||
}
|
||||
|
||||
$request = new \vtgate\ExecuteEntityIdsRequest();
|
||||
$request->setSession($this->session);
|
||||
$request->setQuery(VTProto::BoundQuery($query, $bind_vars));
|
||||
$request->setTabletType($tablet_type);
|
||||
$request->setNotInTransaction($not_in_transaction);
|
||||
$request->setKeyspace($keyspace);
|
||||
$request->setEntityColumnName($entity_column_name);
|
||||
VTProto::addEntityKeyspaceIds($request, $entity_keyspace_ids);
|
||||
if ($ctx->getCallerId()) {
|
||||
$request->setCallerId($ctx->getCallerId());
|
||||
}
|
||||
$response = $this->client->executeEntityIds($ctx, $request);
|
||||
$this->session = $response->getSession();
|
||||
VTProto::checkError($response);
|
||||
return new VTCursor($response->getResult());
|
||||
}
|
||||
|
||||
public function executeBatchShards(VTContext $ctx, array $bound_shard_queries, $tablet_type = \topodata\TabletType::MASTER, $as_transaction = TRUE) {
|
||||
if (! $this->inTransaction()) {
|
||||
throw new VTException('execute called while not in transaction.');
|
||||
}
|
||||
|
||||
$request = new \vtgate\ExecuteBatchShardsRequest();
|
||||
$request->setSession($this->session);
|
||||
VTProto::addQueries($request, $bound_shard_queries);
|
||||
$request->setTabletType($tablet_type);
|
||||
$request->setAsTransaction($as_transaction);
|
||||
if ($ctx->getCallerId()) {
|
||||
$request->setCallerId($ctx->getCallerId());
|
||||
}
|
||||
$response = $this->client->executeBatchShards($ctx, $request);
|
||||
$this->session = $response->getSession();
|
||||
VTProto::checkError($response);
|
||||
$results = array();
|
||||
foreach ($response->getResultsList() as $result) {
|
||||
$results[] = new VTCursor($result);
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function executeBatchKeyspaceIds(VTContext $ctx, array $bound_keyspace_id_queries, $tablet_type = \topodata\TabletType::MASTER, $as_transaction = TRUE) {
|
||||
if (! $this->inTransaction()) {
|
||||
throw new VTException('execute called while not in transaction.');
|
||||
}
|
||||
|
||||
$request = new \vtgate\ExecuteBatchKeyspaceIdsRequest();
|
||||
$request->setSession($this->session);
|
||||
VTProto::addQueries($request, $bound_keyspace_id_queries);
|
||||
$request->setTabletType($tablet_type);
|
||||
$request->setAsTransaction($as_transaction);
|
||||
if ($ctx->getCallerId()) {
|
||||
$request->setCallerId($ctx->getCallerId());
|
||||
}
|
||||
$response = $this->client->executeBatchKeyspaceIds($ctx, $request);
|
||||
$this->session = $response->getSession();
|
||||
VTProto::checkError($response);
|
||||
$results = array();
|
||||
foreach ($response->getResultsList() as $result) {
|
||||
$results[] = new VTCursor($result);
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function commit(VTContext $ctx) {
|
||||
if (! $this->inTransaction()) {
|
||||
throw new VTException('commit called while not in transaction.');
|
||||
}
|
||||
$request = new \vtgate\CommitRequest();
|
||||
$request->setSession($this->session);
|
||||
if ($ctx->getCallerId()) {
|
||||
$request->setCallerId($ctx->getCallerId());
|
||||
}
|
||||
|
||||
$response = $this->client->commit($ctx, $request);
|
||||
$this->session = NULL;
|
||||
}
|
||||
|
||||
public function rollback(VTContext $ctx) {
|
||||
if (! $this->inTransaction()) {
|
||||
throw new VTException('rollback called while not in transaction.');
|
||||
}
|
||||
$request = new \vtgate\RollbackRequest();
|
||||
$request->setSession($this->session);
|
||||
if ($ctx->getCallerId()) {
|
||||
$request->setCallerId($ctx->getCallerId());
|
||||
}
|
||||
|
||||
$response = $this->client->rollback($ctx, $request);
|
||||
$this->session = NULL;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
<?php
|
||||
require_once (dirname(__FILE__) . '/proto/vtgateservice.php');
|
||||
|
||||
require_once (dirname(__FILE__) . '/VTRpcClient.php');
|
||||
|
||||
class VTGrpcClient implements VTRpcClient {
|
||||
protected $stub;
|
||||
|
||||
public function __construct($addr, $opts = []) {
|
||||
$this->stub = new \vtgateservice\VitessClient($addr, $opts);
|
||||
}
|
||||
|
||||
public function execute(VTContext $ctx, \vtgate\ExecuteRequest $request) {
|
||||
list ( $response, $status ) = $this->stub->Execute($request)->wait();
|
||||
self::checkError($status);
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function executeShards(VTContext $ctx, \vtgate\ExecuteShardsRequest $request) {
|
||||
list ( $response, $status ) = $this->stub->ExecuteShards($request)->wait();
|
||||
self::checkError($status);
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function executeKeyspaceIds(VTContext $ctx, \vtgate\ExecuteKeyspaceIdsRequest $request) {
|
||||
list ( $response, $status ) = $this->stub->ExecuteKeyspaceIds($request)->wait();
|
||||
self::checkError($status);
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function executeKeyRanges(VTContext $ctx, \vtgate\ExecuteKeyRangesRequest $request) {
|
||||
list ( $response, $status ) = $this->stub->ExecuteKeyRanges($request)->wait();
|
||||
self::checkError($status);
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function executeEntityIds(VTContext $ctx, \vtgate\ExecuteEntityIdsRequest $request) {
|
||||
list ( $response, $status ) = $this->stub->ExecuteEntityIds($request)->wait();
|
||||
self::checkError($status);
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function executeBatchShards(VTContext $ctx, \vtgate\ExecuteBatchShardsRequest $request) {
|
||||
list ( $response, $status ) = $this->stub->ExecuteBatchShards($request)->wait();
|
||||
self::checkError($status);
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function executeBatchKeyspaceIds(VTContext $ctx, \vtgate\ExecuteBatchKeyspaceIdsRequest $request) {
|
||||
list ( $response, $status ) = $this->stub->ExecuteBatchKeyspaceIds($request)->wait();
|
||||
self::checkError($status);
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function streamExecute(VTContext $ctx, \vtgate\StreamExecuteRequest $request) {
|
||||
return new VTGrpcStreamResponse($this->stub->StreamExecute($request));
|
||||
}
|
||||
|
||||
public function streamExecuteShards(VTContext $ctx, \vtgate\StreamExecuteShardsRequest $request) {
|
||||
return new VTGrpcStreamResponse($this->stub->StreamExecuteShards($request));
|
||||
}
|
||||
|
||||
public function streamExecuteKeyspaceIds(VTContext $ctx, \vtgate\StreamExecuteKeyspaceIdsRequest $request) {
|
||||
return new VTGrpcStreamResponse($this->stub->StreamExecuteKeyspaceIds($request));
|
||||
}
|
||||
|
||||
public function streamExecuteKeyRanges(VTContext $ctx, \vtgate\StreamExecuteKeyRangesRequest $request) {
|
||||
return new VTGrpcStreamResponse($this->stub->StreamExecuteKeyRanges($request));
|
||||
}
|
||||
|
||||
public function begin(VTContext $ctx, \vtgate\BeginRequest $request) {
|
||||
list ( $response, $status ) = $this->stub->Begin($request)->wait();
|
||||
self::checkError($status);
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function commit(VTContext $ctx, \vtgate\CommitRequest $request) {
|
||||
list ( $response, $status ) = $this->stub->Commit($request)->wait();
|
||||
self::checkError($status);
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function rollback(VTContext $ctx, \vtgate\RollbackRequest $request) {
|
||||
list ( $response, $status ) = $this->stub->Rollback($request)->wait();
|
||||
self::checkError($status);
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function getSrvKeyspace(VTContext $ctx, \vtgate\GetSrvKeyspaceRequest $request) {
|
||||
list ( $response, $status ) = $this->stub->GetSrvKeyspace($request)->wait();
|
||||
self::checkError($status);
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function splitQuery(VTContext $ctx, \vtgate\SplitQueryRequest $request) {
|
||||
list ( $response, $status ) = $this->stub->SplitQuery($request)->wait();
|
||||
self::checkError($status);
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function close() {
|
||||
$this->stub->close();
|
||||
}
|
||||
|
||||
public static function checkError($status) {
|
||||
if ($status) {
|
||||
switch ($status->code) {
|
||||
case Grpc\STATUS_OK:
|
||||
break;
|
||||
case Grpc\STATUS_INVALID_ARGUMENT:
|
||||
throw new VTBadInputError($status->details);
|
||||
case Grpc\STATUS_DEADLINE_EXCEEDED:
|
||||
throw new VTDeadlineExceededError($status->details);
|
||||
case Grpc\STATUS_ALREADY_EXISTS:
|
||||
throw new VTIntegrityError($status->details);
|
||||
case Grpc\STATUS_UNAUTHENTICATED:
|
||||
throw new VTUnauthenticatedError($status->details);
|
||||
case Grpc\STATUS_UNAVAILABLE:
|
||||
throw new VTTransientError($status->details);
|
||||
default:
|
||||
throw new VTException("{$status->code}: {$status->details}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VTGrpcStreamResponse {
|
||||
private $call;
|
||||
private $iterator;
|
||||
|
||||
public function __construct($call) {
|
||||
$this->call = $call;
|
||||
$this->iterator = $call->responses();
|
||||
|
||||
if (! $this->iterator->valid()) {
|
||||
// No responses were returned. Check for error.
|
||||
VTGrpcClient::checkError($this->call->getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
public function next() {
|
||||
if ($this->iterator->valid()) {
|
||||
$value = $this->iterator->current();
|
||||
$this->iterator->next();
|
||||
return $value;
|
||||
} else {
|
||||
// Now that the responses are done, check for gRPC status.
|
||||
VTGrpcClient::checkError($this->call->getStatus());
|
||||
// If no exception is raised, indicate successful end of stream.
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
public function close() {
|
||||
$this->call->cancel();
|
||||
}
|
||||
}
|
|
@ -1,16 +1,13 @@
|
|||
<?php
|
||||
require_once (dirname(__FILE__) . '/../vendor/autoload.php');
|
||||
require_once (dirname(__FILE__) . '/proto/query.php');
|
||||
require_once (dirname(__FILE__) . '/proto/vtgate.php');
|
||||
require_once (dirname(__FILE__) . '/proto/topodata.php');
|
||||
require_once (dirname(__FILE__) . '/proto/vtrpc.php');
|
||||
|
||||
/*
|
||||
* This module is used for interacting with associative arrays that represent
|
||||
* the proto3 structures for Vitess. Currently there's no protobuf compiler
|
||||
* plugin for PHP that supports maps (a protobuf feature we use), so we can't
|
||||
* generate the code needed to connect through gRPC. An official PHP plugin for
|
||||
* proto3 is in the works, and the plan is to replace this with generated code
|
||||
* when that's ready.
|
||||
*
|
||||
* In the meantime, we produce associative arrays that encode into a BSON
|
||||
* representation that's compatible with our proto3 structures. We then send
|
||||
* them over our legacy BSON-based GoRPC+ protocol.
|
||||
* This module contains helper functions and classes for interacting with the
|
||||
* Vitess protobufs.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -20,627 +17,196 @@
|
|||
* This is necessary because PHP doesn't have a real unsigned int type.
|
||||
*/
|
||||
class VTUnsignedInt {
|
||||
public $bsonValue;
|
||||
public $value;
|
||||
|
||||
public function __construct($value) {
|
||||
if (is_int($value)) {
|
||||
$this->bsonValue = $value;
|
||||
} else if (is_string($value)) {
|
||||
$this->bsonValue = new MongoInt64($value);
|
||||
$this->value = $value;
|
||||
} else {
|
||||
throw new Exception('Unsupported type for VTUnsignedInt');
|
||||
throw new VTException('Unsupported type for VTUnsignedInt');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VTTabletType {
|
||||
const IDLE = 1;
|
||||
const MASTER = 2;
|
||||
const REPLICA = 3;
|
||||
const RDONLY = 4;
|
||||
const SPARE = 5;
|
||||
const EXPERIMENTAL = 6;
|
||||
const SCHEMA_UPGRADE = 7;
|
||||
const BACKUP = 8;
|
||||
const RESTORE = 9;
|
||||
const WORKER = 10;
|
||||
const SCRAP = 11;
|
||||
}
|
||||
|
||||
class VTBindVariable {
|
||||
const TYPE_NULL = 0;
|
||||
const TYPE_BYTES = 1;
|
||||
const TYPE_INT = 2;
|
||||
const TYPE_UINT = 3;
|
||||
const TYPE_FLOAT = 4;
|
||||
const TYPE_BYTES_LIST = 5;
|
||||
const TYPE_INT_LIST = 6;
|
||||
const TYPE_UINT_LIST = 7;
|
||||
const TYPE_FLOAT_LIST = 8;
|
||||
|
||||
/**
|
||||
* buildBsonP3 creates a BindVariable bsonp3 object.
|
||||
*/
|
||||
public static function buildBsonP3($value) {
|
||||
if (is_null($value)) {
|
||||
return array(
|
||||
'Type' => self::TYPE_NULL
|
||||
);
|
||||
} else if (is_string($value)) {
|
||||
return array(
|
||||
'Type' => self::TYPE_BYTES,
|
||||
'ValueBytes' => new MongoBinData($value)
|
||||
);
|
||||
} else if (is_int($value)) {
|
||||
return array(
|
||||
'Type' => self::TYPE_INT,
|
||||
'ValueInt' => $value
|
||||
);
|
||||
} else if (is_float($value)) {
|
||||
return array(
|
||||
'Type' => self::TYPE_FLOAT,
|
||||
'ValueFloat' => $value
|
||||
);
|
||||
} else if (is_object($value)) {
|
||||
switch (get_class($value)) {
|
||||
case 'VTUnsignedInt':
|
||||
return array(
|
||||
'Type' => self::TYPE_UINT,
|
||||
'ValueUint' => $value->bsonValue
|
||||
);
|
||||
}
|
||||
} else if (is_array($value)) {
|
||||
return self::buildBsonP3ForList($value);
|
||||
}
|
||||
|
||||
throw new Exception('Unknown bind variable type.');
|
||||
}
|
||||
|
||||
public static function buildBsonP3ForList(array $list) {
|
||||
if (count($list) == 0) {
|
||||
// The list is empty, so it has no type. VTTablet will reject an empty
|
||||
// list anyway, so we'll just pretend it was a list of bytes.
|
||||
return array(
|
||||
'Type' => self::TYPE_BYTES_LIST
|
||||
);
|
||||
}
|
||||
|
||||
// Check type of first item to determine type of list.
|
||||
// We only support lists whose elements have uniform types.
|
||||
if (is_string($list[0])) {
|
||||
$value = array();
|
||||
foreach ($list as $val) {
|
||||
$value[] = new MongoBinData($val);
|
||||
}
|
||||
return array(
|
||||
'Type' => self::TYPE_BYTES_LIST,
|
||||
'ValueBytesList' => $value
|
||||
);
|
||||
} else if (is_int($list[0])) {
|
||||
return array(
|
||||
'Type' => self::TYPE_INT_LIST,
|
||||
'ValueIntList' => $list
|
||||
);
|
||||
} else if (is_float($list[0])) {
|
||||
return array(
|
||||
'Type' => self::TYPE_FLOAT_LIST,
|
||||
'ValueFloatList' => $list
|
||||
);
|
||||
} else if (is_object($list[0])) {
|
||||
switch (get_class($list[0])) {
|
||||
case 'VTUnsignedInt':
|
||||
$value = array();
|
||||
foreach ($list as $val) {
|
||||
$value[] = $val->bsonValue;
|
||||
}
|
||||
return array(
|
||||
'Type' => self::TYPE_UINT_LIST,
|
||||
'ValueUintList' => $value
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception('Unknown list bind variable type.');
|
||||
}
|
||||
|
||||
/**
|
||||
* fromBsonP3 returns a PHP object from a bsonp3 BindVariable object.
|
||||
*/
|
||||
public static function fromBsonP3($bson) {
|
||||
if (! array_key_exists('Type', $bson)) {
|
||||
return NULL;
|
||||
}
|
||||
switch ($bson['Type']) {
|
||||
case self::TYPE_NULL:
|
||||
return NULL;
|
||||
case self::TYPE_BYTES:
|
||||
return $bson['ValueBytes'];
|
||||
case self::TYPE_INT:
|
||||
return $bson['ValueInt'];
|
||||
case self::TYPE_UINT:
|
||||
return new VTUnsignedInt($bson['ValueUint']);
|
||||
case self::TYPE_FLOAT:
|
||||
return $bson['ValueFloat'];
|
||||
case self::TYPE_BYTES_LIST:
|
||||
return $bson['ValueBytesList'];
|
||||
case self::TYPE_INT_LIST:
|
||||
return $bson['ValueIntList'];
|
||||
case self::TYPE_UINT_LIST:
|
||||
$result = array();
|
||||
foreach ($bson['ValueUintList'] as $val) {
|
||||
$result[] = new VTUnsignedInt($val);
|
||||
}
|
||||
return $result;
|
||||
case self::TYPE_FLOAT_LIST:
|
||||
return $bson['ValueFloatList'];
|
||||
}
|
||||
|
||||
throw new Exception('Unknown bind variable type.');
|
||||
}
|
||||
}
|
||||
|
||||
class VTBoundQuery {
|
||||
public $query;
|
||||
public $bindVars;
|
||||
|
||||
public function __construct($query = '', $bindVars = array()) {
|
||||
$this->query = $query;
|
||||
$this->bindVars = $bindVars;
|
||||
}
|
||||
|
||||
public static function fromBsonP3($bson) {
|
||||
$result = new VTBoundQuery();
|
||||
if (array_key_exists('Sql', $bson)) {
|
||||
$result->query = $bson['Sql'];
|
||||
}
|
||||
if (array_key_exists('BindVariables', $bson)) {
|
||||
foreach ($bson['BindVariables'] as $key => $val) {
|
||||
$result->bindVars[$key] = VTBindVariable::fromBsonP3($val);
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* buildBsonP3 creates a BoundQuery bsonp3 object.
|
||||
*/
|
||||
public static function buildBsonP3($query, $vars) {
|
||||
$bindVars = array();
|
||||
if ($vars) {
|
||||
foreach ($vars as $key => $value) {
|
||||
$bindVars[$key] = VTBindVariable::buildBsonP3($value);
|
||||
}
|
||||
}
|
||||
return array(
|
||||
'Sql' => $query,
|
||||
'BindVariables' => $bindVars
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class VTBoundShardQuery {
|
||||
public $query;
|
||||
public $vars;
|
||||
public $keyspace;
|
||||
public $shards;
|
||||
|
||||
public function __construct($query, $bind_vars, $keyspace, $shards) {
|
||||
$this->query = $query;
|
||||
$this->vars = $bind_vars;
|
||||
$this->keyspace = $keyspace;
|
||||
$this->shards = $shards;
|
||||
}
|
||||
|
||||
public function toBsonP3() {
|
||||
return array(
|
||||
'Query' => VTBoundQuery::buildBsonP3($this->query, $this->vars),
|
||||
'Keyspace' => $this->keyspace,
|
||||
'Shards' => $this->shards
|
||||
);
|
||||
}
|
||||
|
||||
public static function buildBsonP3Array($bound_queries) {
|
||||
$result = array();
|
||||
foreach ($bound_queries as $bound_query) {
|
||||
$result[] = $bound_query->toBsonP3();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
class VTBoundKeyspaceIdQuery {
|
||||
public $query;
|
||||
public $vars;
|
||||
public $keyspace;
|
||||
public $keyspaceIds;
|
||||
|
||||
public function __construct($query, $bind_vars, $keyspace, $keyspace_ids) {
|
||||
$this->query = $query;
|
||||
$this->vars = $bind_vars;
|
||||
$this->keyspace = $keyspace;
|
||||
$this->keyspaceIds = $keyspace_ids;
|
||||
}
|
||||
|
||||
public function toBsonP3() {
|
||||
return array(
|
||||
'Query' => VTBoundQuery::buildBsonP3($this->query, $this->vars),
|
||||
'Keyspace' => $this->keyspace,
|
||||
'KeyspaceIds' => VTKeyspaceId::buildBsonP3Array($this->keyspaceIds)
|
||||
);
|
||||
}
|
||||
|
||||
public static function buildBsonP3Array($bound_queries) {
|
||||
$result = array();
|
||||
foreach ($bound_queries as $bound_query) {
|
||||
$result[] = $bound_query->toBsonP3();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
class VTField {
|
||||
const TYPE_DECIMAL = 0;
|
||||
const TYPE_TINY = 1;
|
||||
const TYPE_SHORT = 2;
|
||||
const TYPE_LONG = 3;
|
||||
const TYPE_FLOAT = 4;
|
||||
const TYPE_DOUBLE = 5;
|
||||
const TYPE_NULL = 6;
|
||||
const TYPE_TIMESTAMP = 7;
|
||||
const TYPE_LONGLONG = 8;
|
||||
const TYPE_INT24 = 9;
|
||||
const TYPE_DATE = 10;
|
||||
const TYPE_TIME = 11;
|
||||
const TYPE_DATETIME = 12;
|
||||
const TYPE_YEAR = 13;
|
||||
const TYPE_NEWDATE = 14;
|
||||
const TYPE_VARCHAR = 15;
|
||||
const TYPE_BIT = 16;
|
||||
const TYPE_NEWDECIMAL = 246;
|
||||
const TYPE_ENUM = 247;
|
||||
const TYPE_SET = 248;
|
||||
const TYPE_TINY_BLOB = 249;
|
||||
const TYPE_MEDIUM_BLOB = 250;
|
||||
const TYPE_LONG_BLOB = 251;
|
||||
const TYPE_BLOB = 252;
|
||||
const TYPE_VAR_STRING = 253;
|
||||
const TYPE_STRING = 254;
|
||||
const TYPE_GEOMETRY = 255;
|
||||
const VT_ZEROVALUE_FLAG = 0;
|
||||
const VT_NOT_NULL_FLAG = 1;
|
||||
const VT_PRI_KEY_FLAG = 2;
|
||||
const VT_UNIQUE_KEY_FLAG = 4;
|
||||
const VT_MULTIPLE_KEY_FLAG = 8;
|
||||
const VT_BLOB_FLAG = 16;
|
||||
const VT_UNSIGNED_FLAG = 32;
|
||||
const VT_ZEROFILL_FLAG = 64;
|
||||
const VT_BINARY_FLAG = 128;
|
||||
const VT_ENUM_FLAG = 256;
|
||||
const VT_AUTO_INCREMENT_FLAG = 512;
|
||||
const VT_TIMESTAMP_FLAG = 1024;
|
||||
const VT_SET_FLAG = 2048;
|
||||
const VT_NO_DEFAULT_VALUE_FLAG = 4096;
|
||||
const VT_ON_UPDATE_NOW_FLAG = 8192;
|
||||
const VT_NUM_FLAG = 32768;
|
||||
public $name = '';
|
||||
public $type = 0;
|
||||
public $flags = 0;
|
||||
|
||||
public static function fromBsonP3($bson) {
|
||||
$result = new VTField();
|
||||
if (array_key_exists('Name', $bson)) {
|
||||
$result->name = $bson['Name'];
|
||||
}
|
||||
if (array_key_exists('Type', $bson)) {
|
||||
$result->type = $bson['Type'];
|
||||
}
|
||||
if (array_key_exists('Flags', $bson)) {
|
||||
$result->flags = $bson['Flags'];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
class VTQueryResult {
|
||||
public $fields = array();
|
||||
public $rowsAffected = 0;
|
||||
public $insertId = 0;
|
||||
public $rows = array();
|
||||
|
||||
public static function fromBsonP3($bson) {
|
||||
$result = new VTQueryResult();
|
||||
if (array_key_exists('Fields', $bson)) {
|
||||
foreach ($bson['Fields'] as $field) {
|
||||
$result->fields[] = VTField::fromBsonP3($field);
|
||||
}
|
||||
}
|
||||
if (array_key_exists('RowsAffected', $bson)) {
|
||||
$result->rowsAffected = $bson['RowsAffected'];
|
||||
}
|
||||
if (array_key_exists('InsertId', $bson)) {
|
||||
$result->insertId = $bson['InsertId'];
|
||||
}
|
||||
if (array_key_exists('Rows', $bson)) {
|
||||
foreach ($bson['Rows'] as $row) {
|
||||
$result->rows[] = $row['Values'];
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
class VTProto {
|
||||
|
||||
public static function checkError($resp) {
|
||||
// TODO(enisoc): Implement app-level error checking.
|
||||
}
|
||||
}
|
||||
|
||||
class VTCallerId {
|
||||
public $principal;
|
||||
public $component;
|
||||
public $subcomponent;
|
||||
|
||||
public function __construct($principal, $component, $subcomponent) {
|
||||
$this->principal = $principal;
|
||||
$this->component = $component;
|
||||
$this->subcomponent = $subcomponent;
|
||||
}
|
||||
|
||||
public function toBsonP3() {
|
||||
return array(
|
||||
'Principal' => $this->principal,
|
||||
'Component' => $this->component,
|
||||
'Subcomponent' => $this->subcomponent
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class VTKeyRange {
|
||||
|
||||
public static function buildBsonP3Array($key_ranges) {
|
||||
$result = array();
|
||||
foreach ($key_ranges as $key_range) {
|
||||
$result[] = array(
|
||||
'Start' => new MongoBinData($key_range[0]),
|
||||
'End' => new MongoBinData($key_range[1])
|
||||
);
|
||||
public static function checkError($response) {
|
||||
$error = $response->getError();
|
||||
if ($error) {
|
||||
switch ($error->getCode()) {
|
||||
case \vtrpc\ErrorCode::SUCCESS:
|
||||
break;
|
||||
case \vtrpc\ErrorCode::BAD_INPUT:
|
||||
throw new VTBadInputError($error->getMessage());
|
||||
case \vtrpc\ErrorCode::DEADLINE_EXCEEDED:
|
||||
throw new VTDeadlineExceededError($error->getMessage());
|
||||
case \vtrpc\ErrorCode::INTEGRITY_ERROR:
|
||||
throw new VTIntegrityError($error->getMessage());
|
||||
case \vtrpc\ErrorCode::TRANSIENT_ERROR:
|
||||
throw new VTTransientError($error->getMessage());
|
||||
case \vtrpc\ErrorCode::UNAUTHENTICATED:
|
||||
throw new VTUnauthenticatedError($error->getMessage());
|
||||
default:
|
||||
throw new VTException($error->getCode() . ': ' . $error->getMessage());
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function fromBsonP3($bson) {
|
||||
$result = array(
|
||||
'',
|
||||
''
|
||||
);
|
||||
if (array_key_exists('Start', $bson)) {
|
||||
$result[0] = $bson['Start'];
|
||||
public static function BoundQuery($query, $vars) {
|
||||
$bound_query = new \query\BoundQuery();
|
||||
$bound_query->setSql($query);
|
||||
if ($vars) {
|
||||
foreach ($vars as $key => $value) {
|
||||
$entry = new \query\BoundQuery\BindVariablesEntry();
|
||||
$entry->setKey($key);
|
||||
$entry->setValue(self::BindVariable($value));
|
||||
$bound_query->addBindVariables($entry);
|
||||
}
|
||||
}
|
||||
if (array_key_exists('End', $bson)) {
|
||||
$result[1] = $bson['End'];
|
||||
}
|
||||
return $result;
|
||||
return $bound_query;
|
||||
}
|
||||
}
|
||||
|
||||
class VTKeyspaceId {
|
||||
public static function BindVariable($value) {
|
||||
$bind_var = new \query\BindVariable();
|
||||
|
||||
if (is_null($value)) {
|
||||
$bind_var->setType(\query\BindVariable\Type::TYPE_NULL);
|
||||
} else if (is_string($value)) {
|
||||
$bind_var->setType(\query\BindVariable\Type::TYPE_BYTES);
|
||||
$bind_var->setValueBytes($value);
|
||||
} else if (is_int($value)) {
|
||||
$bind_var->setType(\query\BindVariable\Type::TYPE_INT);
|
||||
$bind_var->setValueInt($value);
|
||||
} else if (is_float($value)) {
|
||||
$bind_var->setType(\query\BindVariable\Type::TYPE_FLOAT);
|
||||
$bind_var->setValueFloat($value);
|
||||
} else if (is_object($value)) {
|
||||
switch (get_class($value)) {
|
||||
case 'VTUnsignedInt':
|
||||
$bind_var->setType(\query\BindVariable\Type::TYPE_UINT);
|
||||
$bind_var->setValueUint($value->value);
|
||||
break;
|
||||
default:
|
||||
throw new VTException('Unknown bind variable class: ' . get_class($value));
|
||||
}
|
||||
} else if (is_array($value)) {
|
||||
self::ListBindVariable($bind_var, $value);
|
||||
} else {
|
||||
throw new VTException('Unknown bind variable type.');
|
||||
}
|
||||
|
||||
return $bind_var;
|
||||
}
|
||||
|
||||
public static function fromHex($hex) {
|
||||
protected static function ListBindVariable(&$bind_var, array $list) {
|
||||
if (count($list) == 0) {
|
||||
// The list is empty, so it has no type. VTTablet will reject an empty
|
||||
// list anyway, so we'll just pretend it was a list of bytes.
|
||||
$bind_var->setType(\query\BindVariable\Type::TYPE_BYTES_LIST);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check type of first item to determine type of list.
|
||||
// We only support lists whose elements have uniform types.
|
||||
if (is_string($list[0])) {
|
||||
$bind_var->setType(\query\BindVariable\Type::TYPE_BYTES_LIST);
|
||||
$bind_var->setValueBytesList($list);
|
||||
} else if (is_int($list[0])) {
|
||||
$bind_var->setType(\query\BindVariable\Type::TYPE_INT_LIST);
|
||||
$bind_var->setValueIntList($list);
|
||||
} else if (is_float($list[0])) {
|
||||
$bind_var->setType(\query\BindVariable\Type::TYPE_FLOAT_LIST);
|
||||
$bind_var->setValueFloatList($list);
|
||||
} else if (is_object($list[0])) {
|
||||
switch (get_class($list[0])) {
|
||||
case 'VTUnsignedInt':
|
||||
$bind_var->setType(\query\BindVariable\Type::TYPE_UINT_LIST);
|
||||
$value = array();
|
||||
foreach ($list as $val) {
|
||||
$value[] = $val->value;
|
||||
}
|
||||
$bind_var->setValueUintList($value);
|
||||
break;
|
||||
default:
|
||||
throw new VTException('Unknown list bind variable class: ' . get_class($list[0]));
|
||||
}
|
||||
} else {
|
||||
throw new VTException('Unknown list bind variable type.');
|
||||
}
|
||||
}
|
||||
|
||||
public static function addQueries($proto, $queries) {
|
||||
foreach ($queries as $query) {
|
||||
$proto->addQueries($query);
|
||||
}
|
||||
}
|
||||
|
||||
public static function KeyspaceIdFromHex($hex) {
|
||||
return pack('H*', $hex);
|
||||
}
|
||||
|
||||
public static function buildBsonP3Array($keyspace_ids) {
|
||||
$result = array();
|
||||
foreach ($keyspace_ids as $kid) {
|
||||
$result[] = new MongoBinData($kid);
|
||||
}
|
||||
return $result;
|
||||
public static function KeyRangeFromHex($start, $end) {
|
||||
$value = new \topodata\KeyRange();
|
||||
$value->setStart(self::KeyspaceIdFromHex($start));
|
||||
$value->setEnd(self::KeyspaceIdFromHex($end));
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
class VTKeyspaceIdType {
|
||||
const UINT64 = 1;
|
||||
const BYTES = 2;
|
||||
}
|
||||
public static function addKeyRanges($proto, $key_ranges) {
|
||||
foreach ($key_ranges as $key_range) {
|
||||
$proto->addKeyRanges($key_range);
|
||||
}
|
||||
}
|
||||
|
||||
class VTEntityId {
|
||||
const TYPE_BYTES = 1;
|
||||
const TYPE_INT = 2;
|
||||
const TYPE_UINT = 3;
|
||||
const TYPE_FLOAT = 4;
|
||||
|
||||
public static function buildBsonP3($entity_id) {
|
||||
if (is_string($entity_id)) {
|
||||
return array(
|
||||
'XidType' => self::TYPE_BYTES,
|
||||
'XidBytes' => new MongoBinData($entity_id)
|
||||
);
|
||||
} else if (is_int($entity_id)) {
|
||||
return array(
|
||||
'XidType' => self::TYPE_INT,
|
||||
'XidInt' => $entity_id
|
||||
);
|
||||
} else if (is_float($entity_id)) {
|
||||
return array(
|
||||
'XidType' => self::TYPE_FLOAT,
|
||||
'XidFloat' => $entity_id
|
||||
);
|
||||
} else if (is_object($entity_id)) {
|
||||
switch (get_class($entity_id)) {
|
||||
public static function EntityId($keyspace_id, $value) {
|
||||
$eid = new \vtgate\ExecuteEntityIdsRequest\EntityId();
|
||||
$eid->setKeyspaceId($keyspace_id);
|
||||
|
||||
if (is_string($value)) {
|
||||
$eid->setXidType(\vtgate\ExecuteEntityIdsRequest\EntityId\Type::TYPE_BYTES);
|
||||
$eid->setXidBytes($value);
|
||||
} else if (is_int($value)) {
|
||||
$eid->setXidType(\vtgate\ExecuteEntityIdsRequest\EntityId\Type::TYPE_INT);
|
||||
$eid->setXidInt($value);
|
||||
} else if (is_float($value)) {
|
||||
$eid->setXidType(\vtgate\ExecuteEntityIdsRequest\EntityId\Type::TYPE_FLOAT);
|
||||
$eid->setXidFloat($value);
|
||||
} else if (is_object($value)) {
|
||||
switch (get_class($value)) {
|
||||
case 'VTUnsignedInt':
|
||||
return array(
|
||||
'XidType' => self::TYPE_UINT,
|
||||
'XidUint' => $entity_id->bsonValue
|
||||
);
|
||||
$eid->setXidType(\vtgate\ExecuteEntityIdsRequest\EntityId\Type::TYPE_UINT);
|
||||
$eid->setXidUint($value->value);
|
||||
break;
|
||||
default:
|
||||
throw new VTException('Unknown entity ID class: ' . get_class($value));
|
||||
}
|
||||
} else {
|
||||
throw new VTException('Unknown entity ID type.');
|
||||
}
|
||||
|
||||
throw new Exception('Unknown entity ID type.');
|
||||
return $eid;
|
||||
}
|
||||
|
||||
public static function buildBsonP3Array($entity_keyspace_ids) {
|
||||
$result = array();
|
||||
public static function addEntityKeyspaceIds($proto, $entity_keyspace_ids) {
|
||||
foreach ($entity_keyspace_ids as $keyspace_id => $entity_id) {
|
||||
$eid = self::buildBsonP3($entity_id);
|
||||
$eid['KeyspaceId'] = new MongoBinData($keyspace_id);
|
||||
$result[] = $eid;
|
||||
$proto->addEntityKeyspaceIds(self::EntityId($keyspace_id, $entity_id));
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function BoundShardQuery($query, $bind_vars, $keyspace, $shards) {
|
||||
$value = new \vtgate\BoundShardQuery();
|
||||
$value->setQuery(self::BoundQuery($query, $bind_vars));
|
||||
$value->setKeyspace($keyspace);
|
||||
$value->setShards($shards);
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function BoundKeyspaceIdQuery($query, $bind_vars, $keyspace, $keyspace_ids) {
|
||||
$value = new \vtgate\BoundKeyspaceIdQuery();
|
||||
$value->setQuery(self::BoundQuery($query, $bind_vars));
|
||||
$value->setKeyspace($keyspace);
|
||||
$value->setKeyspaceIds($keyspace_ids);
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
class VTSplitQueryKeyRangePart {
|
||||
public $keyspace = '';
|
||||
public $keyRanges = array();
|
||||
|
||||
public static function fromBsonP3($bson) {
|
||||
$result = new VTSplitQueryKeyRangePart();
|
||||
if (array_key_exists('Keyspace', $bson)) {
|
||||
$result->keyspace = $bson['Keyspace'];
|
||||
}
|
||||
if (array_key_exists('KeyRanges', $bson) && $bson['KeyRanges']) {
|
||||
foreach ($bson['KeyRanges'] as $val) {
|
||||
$result->keyRanges[] = VTKeyRange::fromBsonP3($val);
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
class VTSplitQueryShardPart {
|
||||
public $keyspace = '';
|
||||
public $shards = array();
|
||||
|
||||
public static function fromBsonP3($bson) {
|
||||
$result = new VTSplitQueryShardPart();
|
||||
if (array_key_exists('Keyspace', $bson)) {
|
||||
$result->keyspace = $bson['Keyspace'];
|
||||
}
|
||||
if (array_key_exists('Shards', $bson)) {
|
||||
$result->shards = $bson['Shards'];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
class VTSplitQueryPart {
|
||||
public $query = '';
|
||||
public $keyRangePart;
|
||||
public $shardPart;
|
||||
public $size = 0;
|
||||
|
||||
public static function fromBsonP3($bson) {
|
||||
$result = new VTSplitQueryPart();
|
||||
if (array_key_exists('Query', $bson)) {
|
||||
$result->query = VTBoundQuery::fromBsonP3($bson['Query']);
|
||||
}
|
||||
if (array_key_exists('KeyRangePart', $bson) && $bson['KeyRangePart']) {
|
||||
$result->keyRangePart = VTSplitQueryKeyRangePart::fromBsonP3($bson['KeyRangePart']);
|
||||
}
|
||||
if (array_key_exists('ShardPart', $bson) && $bson['ShardPart']) {
|
||||
$result->shardPart = VTSplitQueryShardPart::fromBsonP3($bson['ShardPart']);
|
||||
}
|
||||
if (array_key_exists('Size', $bson)) {
|
||||
$result->size = $bson['Size'];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
class VTShardReference {
|
||||
public $name;
|
||||
public $keyRange;
|
||||
|
||||
public function __construct($name = '', $keyRange = array('','')) {
|
||||
$this->name = $name;
|
||||
$this->keyRange = $keyRange;
|
||||
}
|
||||
|
||||
public static function fromBsonP3($bson) {
|
||||
$result = new VTShardReference();
|
||||
if (array_key_exists('Name', $bson)) {
|
||||
$result->name = $bson['Name'];
|
||||
}
|
||||
if (array_key_exists('KeyRange', $bson)) {
|
||||
$result->keyRange = VTKeyRange::fromBsonP3($bson['KeyRange']);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
class VTSrvKeyspacePartition {
|
||||
public $servedType;
|
||||
public $shardReferences;
|
||||
|
||||
public function __construct($servedType = 0, $shardReferences = array()) {
|
||||
$this->servedType = $servedType;
|
||||
$this->shardReferences = $shardReferences;
|
||||
}
|
||||
|
||||
public static function fromBsonP3($bson) {
|
||||
$result = new VTSrvKeyspacePartition();
|
||||
if (array_key_exists('ServedType', $bson)) {
|
||||
$result->servedType = $bson['ServedType'];
|
||||
}
|
||||
if (array_key_exists('ShardReferences', $bson) && $bson['ShardReferences']) {
|
||||
foreach ($bson['ShardReferences'] as $val) {
|
||||
$result->shardReferences[] = VTShardReference::fromBsonP3($val);
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
class VTSrvKeyspaceServedFrom {
|
||||
public $tabletType;
|
||||
public $keyspace;
|
||||
|
||||
public function __construct($tabletType = 0, $keyspace = '') {
|
||||
$this->tabletType = $tabletType;
|
||||
$this->keyspace = $keyspace;
|
||||
}
|
||||
|
||||
public static function fromBsonP3($bson) {
|
||||
$result = new VTSrvKeyspaceServedFrom();
|
||||
if (array_key_exists('TabletType', $bson)) {
|
||||
$result->tabletType = $bson['TabletType'];
|
||||
}
|
||||
if (array_key_exists('Keyspace', $bson)) {
|
||||
$result->keyspace = $bson['Keyspace'];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
class VTSrvKeyspace {
|
||||
public $partitions = array();
|
||||
public $shardingColumnName = '';
|
||||
public $shardingColumnType = 0;
|
||||
public $servedFrom = array();
|
||||
public $splitShardCount = 0;
|
||||
|
||||
public static function fromBsonP3($bson) {
|
||||
$result = new VTSrvKeyspace();
|
||||
if (array_key_exists('Partitions', $bson) && $bson['Partitions']) {
|
||||
foreach ($bson['Partitions'] as $val) {
|
||||
$result->partitions[] = VTSrvKeyspacePartition::fromBsonP3($val);
|
||||
}
|
||||
}
|
||||
if (array_key_exists('ShardingColumnName', $bson)) {
|
||||
$result->shardingColumnName = $bson['ShardingColumnName'];
|
||||
}
|
||||
if (array_key_exists('ShardingColumnType', $bson)) {
|
||||
$result->shardingColumnType = $bson['ShardingColumnType'];
|
||||
}
|
||||
if (array_key_exists('ServedFrom', $bson)) {
|
||||
foreach ($bson['ServedFrom'] as $val) {
|
||||
$result->servedFrom[] = VTSrvKeyspaceServedFrom::fromBsonP3($val);
|
||||
}
|
||||
}
|
||||
if (array_key_exists('SplitShardCount', $bson)) {
|
||||
$result->splitShardCount = $bson['SplitShardCount'];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
require_once (dirname(__FILE__) . '/proto/vtgate.php');
|
||||
require_once (dirname(__FILE__) . '/VTContext.php');
|
||||
|
||||
interface VTRpcClient {
|
||||
|
||||
public function execute(VTContext $ctx, \vtgate\ExecuteRequest $request);
|
||||
|
||||
public function executeShards(VTContext $ctx, \vtgate\ExecuteShardsRequest $request);
|
||||
|
||||
public function executeKeyspaceIds(VTContext $ctx, \vtgate\ExecuteKeyspaceIdsRequest $request);
|
||||
|
||||
public function executeKeyRanges(VTContext $ctx, \vtgate\ExecuteKeyRangesRequest $request);
|
||||
|
||||
public function executeEntityIds(VTContext $ctx, \vtgate\ExecuteEntityIdsRequest $request);
|
||||
|
||||
public function executeBatchShards(VTContext $ctx, \vtgate\ExecuteBatchShardsRequest $request);
|
||||
|
||||
public function executeBatchKeyspaceIds(VTContext $ctx, \vtgate\ExecuteBatchKeyspaceIdsRequest $request);
|
||||
|
||||
public function streamExecute(VTContext $ctx, \vtgate\StreamExecuteRequest $request);
|
||||
|
||||
public function streamExecuteShards(VTContext $ctx, \vtgate\StreamExecuteShardsRequest $request);
|
||||
|
||||
public function streamExecuteKeyspaceIds(VTContext $ctx, \vtgate\StreamExecuteKeyspaceIdsRequest $request);
|
||||
|
||||
public function streamExecuteKeyRanges(VTContext $ctx, \vtgate\StreamExecuteKeyRangesRequest $request);
|
||||
|
||||
public function begin(VTContext $ctx, \vtgate\BeginRequest $request);
|
||||
|
||||
public function commit(VTContext $ctx, \vtgate\CommitRequest $request);
|
||||
|
||||
public function rollback(VTContext $ctx, \vtgate\RollbackRequest $request);
|
||||
|
||||
public function getSrvKeyspace(VTContext $ctx, \vtgate\GetSrvKeyspaceRequest $request);
|
||||
|
||||
public function splitQuery(VTContext $ctx, \vtgate\SplitQueryRequest $request);
|
||||
|
||||
public function close();
|
||||
}
|
|
@ -1,17 +1,28 @@
|
|||
<?php
|
||||
require_once (dirname(__FILE__) . '/VTTestUtils.php');
|
||||
require_once (dirname(__FILE__) . '/../src/VTProto.php');
|
||||
require_once (dirname(__FILE__) . '/../src/VTGateConn.php');
|
||||
require_once (dirname(__FILE__) . '/../src/BsonRpcClient.php');
|
||||
require_once (dirname(__FILE__) . '/../src/VTGrpcClient.php');
|
||||
|
||||
class VTGateConnTest extends PHPUnit_Framework_TestCase {
|
||||
private static $proc;
|
||||
private static $client;
|
||||
private static $ECHO_QUERY = 'echo://test query';
|
||||
private static $ERROR_PREFIX = 'error://';
|
||||
private static $PARTIAL_ERROR_PREFIX = 'partialerror://';
|
||||
private static $EXECUTE_ERRORS = array(
|
||||
'bad input' => 'VTBadInputError',
|
||||
'deadline exceeded' => 'VTDeadlineExceededError',
|
||||
'integrity error' => 'VTIntegrityError',
|
||||
'transient error' => 'VTTransientError',
|
||||
'unauthenticated' => 'VTUnauthenticatedError',
|
||||
'unknown error' => 'VTException'
|
||||
);
|
||||
private static $BIND_VARS; // initialized in setUpBeforeClass()
|
||||
private static $BIND_VARS_ECHO = 'map[bytes:[104 101 108 108 111] float:1.5 int:123 uint_from_int:345 uint_from_string:678]';
|
||||
private static $BIND_VARS_ECHO = 'map[bytes:[104 101 108 108 111] float:1.5 int:123 uint_from_int:18446744073709551493]'; // 18446744073709551493 = uint64(-123)
|
||||
private static $CALLER_ID; // initialized in setUpBeforeClass()
|
||||
private static $CALLER_ID_ECHO = 'principal:"test_principal" component:"test_component" subcomponent:"test_subcomponent" ';
|
||||
private static $TABLET_TYPE = VTTabletType::REPLICA;
|
||||
private static $TABLET_TYPE = \topodata\TabletType::REPLICA;
|
||||
private static $TABLET_TYPE_ECHO = 'REPLICA';
|
||||
private static $KEYSPACE = 'test_keyspace';
|
||||
private static $SHARDS = array(
|
||||
|
@ -43,7 +54,7 @@ class VTGateConnTest extends PHPUnit_Framework_TestCase {
|
|||
}
|
||||
socket_close($sock);
|
||||
|
||||
$cmd = "$VTROOT/bin/vtgateclienttest -logtostderr -lameduck-period 0 -port $port -service_map bsonrpc-vt-vtgateservice";
|
||||
$cmd = "$VTROOT/bin/vtgateclienttest -logtostderr -lameduck-period 0 -grpc_port $port -service_map grpc-vtgateservice";
|
||||
|
||||
$proc = proc_open($cmd, array(), $pipes);
|
||||
if (! $proc) {
|
||||
|
@ -53,12 +64,11 @@ class VTGateConnTest extends PHPUnit_Framework_TestCase {
|
|||
|
||||
// Wait for connection to be accepted.
|
||||
$ctx = VTContext::getDefault()->withDeadlineAfter(5.0);
|
||||
$client = new BsonRpcClient();
|
||||
$level = error_reporting(error_reporting() & ~ E_WARNING);
|
||||
while (! $ctx->isCancelled()) {
|
||||
try {
|
||||
$client->dial($ctx, "$addr:$port");
|
||||
} catch (GoRpcException $e) {
|
||||
$client = new VTGrpcClient("$addr:$port");
|
||||
} catch (Exception $e) {
|
||||
usleep(100000);
|
||||
continue;
|
||||
}
|
||||
|
@ -71,30 +81,26 @@ class VTGateConnTest extends PHPUnit_Framework_TestCase {
|
|||
self::$BIND_VARS = array(
|
||||
'bytes' => 'hello',
|
||||
'int' => 123,
|
||||
'uint_from_int' => new VTUnsignedInt(345),
|
||||
'uint_from_string' => new VTUnsignedInt('678'),
|
||||
'uint_from_int' => new VTUnsignedInt(- 123),
|
||||
'float' => 1.5
|
||||
);
|
||||
self::$CALLER_ID = new VTCallerId('test_principal', 'test_component', 'test_subcomponent');
|
||||
self::$CALLER_ID = new \vtrpc\CallerID();
|
||||
self::$CALLER_ID->setPrincipal('test_principal');
|
||||
self::$CALLER_ID->setComponent('test_component');
|
||||
self::$CALLER_ID->setSubcomponent('test_subcomponent');
|
||||
self::$KEYSPACE_IDS = array(
|
||||
VTKeyspaceID::fromHex('8000000000000000'),
|
||||
VTKeyspaceID::fromHex('ff000000000000ef')
|
||||
VTProto::KeyspaceIdFromHex('8000000000000000'),
|
||||
VTProto::KeyspaceIdFromHex('ff000000000000ef')
|
||||
);
|
||||
self::$KEY_RANGES = array(
|
||||
array(
|
||||
'',
|
||||
VTKeyspaceID::fromHex('8000000000000000')
|
||||
),
|
||||
array(
|
||||
VTKeyspaceID::fromHex('8000000000000000'),
|
||||
''
|
||||
)
|
||||
VTProto::KeyRangeFromHex('', '8000000000000000'),
|
||||
VTProto::KeyRangeFromHex('8000000000000000', '')
|
||||
);
|
||||
self::$ENTITY_KEYSPACE_IDS = array(
|
||||
VTKeyspaceID::fromHex('1234567800000002') => 'hello',
|
||||
VTKeyspaceID::fromHex('1234567800000000') => 123,
|
||||
VTKeyspaceID::fromHex('1234567800000001') => new VTUnsignedInt('456'),
|
||||
VTKeyspaceID::fromHex('1234567800000002') => 1.5
|
||||
VTProto::KeyspaceIdFromHex('1234567800000002') => 'hello',
|
||||
VTProto::KeyspaceIdFromHex('1234567800000000') => 123,
|
||||
VTProto::KeyspaceIdFromHex('1234567800000001') => new VTUnsignedInt(456),
|
||||
VTProto::KeyspaceIdFromHex('1234567800000002') => 1.5
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -120,10 +126,11 @@ class VTGateConnTest extends PHPUnit_Framework_TestCase {
|
|||
$this->conn = new VTGateconn(self::$client);
|
||||
}
|
||||
|
||||
private static function getEcho(VTQueryResult $result) {
|
||||
private static function getEcho($result) {
|
||||
$echo = array();
|
||||
foreach ($result->fields as $i => $field) {
|
||||
$echo[$field->name] = $result->rows[0][$i];
|
||||
$row = $result->next();
|
||||
foreach ($result->getFields() as $i => $field) {
|
||||
$echo[$field->getName()] = $row[$i];
|
||||
}
|
||||
return $echo;
|
||||
}
|
||||
|
@ -172,7 +179,7 @@ class VTGateConnTest extends PHPUnit_Framework_TestCase {
|
|||
$this->assertEquals(self::$TABLET_TYPE_ECHO, $echo['tabletType']);
|
||||
|
||||
$results = $conn->executeBatchShards($ctx, array(
|
||||
new VTBoundShardQuery(self::$ECHO_QUERY, self::$BIND_VARS, self::$KEYSPACE, self::$SHARDS)
|
||||
VTProto::BoundShardQuery(self::$ECHO_QUERY, self::$BIND_VARS, self::$KEYSPACE, self::$SHARDS)
|
||||
), self::$TABLET_TYPE, TRUE);
|
||||
$echo = self::getEcho($results[0]);
|
||||
$this->assertEquals(self::$CALLER_ID_ECHO, $echo['callerId']);
|
||||
|
@ -184,7 +191,7 @@ class VTGateConnTest extends PHPUnit_Framework_TestCase {
|
|||
$this->assertEquals('true', $echo['asTransaction']);
|
||||
|
||||
$results = $conn->executeBatchKeyspaceIds($ctx, array(
|
||||
new VTBoundKeyspaceIdQuery(self::$ECHO_QUERY, self::$BIND_VARS, self::$KEYSPACE, self::$KEYSPACE_IDS)
|
||||
VTProto::BoundKeyspaceIdQuery(self::$ECHO_QUERY, self::$BIND_VARS, self::$KEYSPACE, self::$KEYSPACE_IDS)
|
||||
), self::$TABLET_TYPE, TRUE);
|
||||
$echo = self::getEcho($results[0]);
|
||||
$this->assertEquals(self::$CALLER_ID_ECHO, $echo['callerId']);
|
||||
|
@ -200,17 +207,13 @@ class VTGateConnTest extends PHPUnit_Framework_TestCase {
|
|||
$ctx = $this->ctx;
|
||||
$conn = $this->conn;
|
||||
|
||||
$sr = $conn->streamExecute($ctx, self::$ECHO_QUERY, self::$BIND_VARS, self::$TABLET_TYPE);
|
||||
$results = $sr->fetchAll();
|
||||
$echo = self::getEcho($results[0]);
|
||||
$echo = self::getEcho($conn->streamExecute($ctx, self::$ECHO_QUERY, self::$BIND_VARS, self::$TABLET_TYPE));
|
||||
$this->assertEquals(self::$CALLER_ID_ECHO, $echo['callerId']);
|
||||
$this->assertEquals(self::$ECHO_QUERY, $echo['query']);
|
||||
$this->assertEquals(self::$BIND_VARS_ECHO, $echo['bindVars']);
|
||||
$this->assertEquals(self::$TABLET_TYPE_ECHO, $echo['tabletType']);
|
||||
|
||||
$sr = $conn->streamExecuteShards($ctx, self::$ECHO_QUERY, self::$KEYSPACE, self::$SHARDS, self::$BIND_VARS, self::$TABLET_TYPE);
|
||||
$results = $sr->fetchAll();
|
||||
$echo = self::getEcho($results[0]);
|
||||
$echo = self::getEcho($conn->streamExecuteShards($ctx, self::$ECHO_QUERY, self::$KEYSPACE, self::$SHARDS, self::$BIND_VARS, self::$TABLET_TYPE));
|
||||
$this->assertEquals(self::$CALLER_ID_ECHO, $echo['callerId']);
|
||||
$this->assertEquals(self::$ECHO_QUERY, $echo['query']);
|
||||
$this->assertEquals(self::$KEYSPACE, $echo['keyspace']);
|
||||
|
@ -218,9 +221,7 @@ class VTGateConnTest extends PHPUnit_Framework_TestCase {
|
|||
$this->assertEquals(self::$BIND_VARS_ECHO, $echo['bindVars']);
|
||||
$this->assertEquals(self::$TABLET_TYPE_ECHO, $echo['tabletType']);
|
||||
|
||||
$sr = $conn->streamExecuteKeyspaceIds($ctx, self::$ECHO_QUERY, self::$KEYSPACE, self::$KEYSPACE_IDS, self::$BIND_VARS, self::$TABLET_TYPE);
|
||||
$results = $sr->fetchAll();
|
||||
$echo = self::getEcho($results[0]);
|
||||
$echo = self::getEcho($conn->streamExecuteKeyspaceIds($ctx, self::$ECHO_QUERY, self::$KEYSPACE, self::$KEYSPACE_IDS, self::$BIND_VARS, self::$TABLET_TYPE));
|
||||
$this->assertEquals(self::$CALLER_ID_ECHO, $echo['callerId']);
|
||||
$this->assertEquals(self::$ECHO_QUERY, $echo['query']);
|
||||
$this->assertEquals(self::$KEYSPACE, $echo['keyspace']);
|
||||
|
@ -228,9 +229,7 @@ class VTGateConnTest extends PHPUnit_Framework_TestCase {
|
|||
$this->assertEquals(self::$BIND_VARS_ECHO, $echo['bindVars']);
|
||||
$this->assertEquals(self::$TABLET_TYPE_ECHO, $echo['tabletType']);
|
||||
|
||||
$sr = $conn->streamExecuteKeyRanges($ctx, self::$ECHO_QUERY, self::$KEYSPACE, self::$KEY_RANGES, self::$BIND_VARS, self::$TABLET_TYPE);
|
||||
$results = $sr->fetchAll();
|
||||
$echo = self::getEcho($results[0]);
|
||||
$echo = self::getEcho($conn->streamExecuteKeyRanges($ctx, self::$ECHO_QUERY, self::$KEYSPACE, self::$KEY_RANGES, self::$BIND_VARS, self::$TABLET_TYPE));
|
||||
$this->assertEquals(self::$CALLER_ID_ECHO, $echo['callerId']);
|
||||
$this->assertEquals(self::$ECHO_QUERY, $echo['query']);
|
||||
$this->assertEquals(self::$KEYSPACE, $echo['keyspace']);
|
||||
|
@ -298,7 +297,7 @@ class VTGateConnTest extends PHPUnit_Framework_TestCase {
|
|||
$tx = $conn->begin($ctx);
|
||||
|
||||
$results = $tx->executeBatchShards($ctx, array(
|
||||
new VTBoundShardQuery(self::$ECHO_QUERY, self::$BIND_VARS, self::$KEYSPACE, self::$SHARDS)
|
||||
VTProto::BoundShardQuery(self::$ECHO_QUERY, self::$BIND_VARS, self::$KEYSPACE, self::$SHARDS)
|
||||
), self::$TABLET_TYPE, TRUE);
|
||||
$echo = self::getEcho($results[0]);
|
||||
$this->assertEquals(self::$CALLER_ID_ECHO, $echo['callerId']);
|
||||
|
@ -311,7 +310,7 @@ class VTGateConnTest extends PHPUnit_Framework_TestCase {
|
|||
$this->assertEquals('true', $echo['asTransaction']);
|
||||
|
||||
$results = $tx->executeBatchKeyspaceIds($ctx, array(
|
||||
new VTBoundKeyspaceIdQuery(self::$ECHO_QUERY, self::$BIND_VARS, self::$KEYSPACE, self::$KEYSPACE_IDS)
|
||||
VTProto::BoundKeyspaceIdQuery(self::$ECHO_QUERY, self::$BIND_VARS, self::$KEYSPACE, self::$KEYSPACE_IDS)
|
||||
), self::$TABLET_TYPE, TRUE);
|
||||
$echo = self::getEcho($results[0]);
|
||||
$this->assertEquals(self::$CALLER_ID_ECHO, $echo['callerId']);
|
||||
|
@ -332,24 +331,22 @@ class VTGateConnTest extends PHPUnit_Framework_TestCase {
|
|||
|
||||
$input_bind_vars = array(
|
||||
'bytes' => 'hello',
|
||||
'float' => 1.5,
|
||||
'int' => 123,
|
||||
'uint_from_int' => new VTUnsignedInt(345),
|
||||
'uint_from_string' => new VTUnsignedInt('678'),
|
||||
'float' => 1.5
|
||||
'uint_from_int' => new VTUnsignedInt(345)
|
||||
);
|
||||
$expected_bind_vars = array(
|
||||
'bytes' => 'hello',
|
||||
'float' => 1.5,
|
||||
'int' => 123,
|
||||
'uint_from_int' => new VTUnsignedInt(345),
|
||||
// uint_from_string will come back to us as an int.
|
||||
'uint_from_string' => new VTUnsignedInt(678),
|
||||
'float' => 1.5
|
||||
'uint_from_int' => new VTUnsignedInt(345)
|
||||
);
|
||||
|
||||
$expected = new VTSplitQueryPart();
|
||||
$expected->query = new VTBoundQuery(self::$ECHO_QUERY . ':split_column:123', $expected_bind_vars);
|
||||
$expected->keyRangePart = new VTSplitQueryKeyRangePart();
|
||||
$expected->keyRangePart->keyspace = self::$KEYSPACE;
|
||||
$expected = new \vtgate\SplitQueryResponse\Part();
|
||||
$expected->setQuery(VTProto::BoundQuery(self::$ECHO_QUERY . ':split_column:123', $expected_bind_vars));
|
||||
$krpart = new \vtgate\SplitQueryResponse\KeyRangePart();
|
||||
$krpart->setKeyspace(self::$KEYSPACE);
|
||||
$expected->setKeyRangePart($krpart);
|
||||
|
||||
$actual = $conn->splitQuery($ctx, self::$KEYSPACE, self::$ECHO_QUERY, $input_bind_vars, 'split_column', 123);
|
||||
$this->assertEquals($expected, $actual[0]);
|
||||
|
@ -359,19 +356,178 @@ class VTGateConnTest extends PHPUnit_Framework_TestCase {
|
|||
$ctx = $this->ctx;
|
||||
$conn = $this->conn;
|
||||
|
||||
$expected = new VTSrvKeyspace();
|
||||
$expected->partitions[] = new VTSrvKeyspacePartition(VTTabletType::REPLICA, array(
|
||||
new VTShardReference("shard0", array(
|
||||
VTKeyspaceID::fromHex('4000000000000000'),
|
||||
VTKeyspaceID::fromHex('8000000000000000')
|
||||
))
|
||||
));
|
||||
$expected->shardingColumnName = 'sharding_column_name';
|
||||
$expected->shardingColumnType = VTKeyspaceIDType::UINT64;
|
||||
$expected->servedFrom[] = new VTSrvKeyspaceServedFrom(VTTabletType::MASTER, 'other_keyspace');
|
||||
$expected->splitShardCount = 128;
|
||||
$expected = new \topodata\SrvKeyspace();
|
||||
$partition = new \topodata\SrvKeyspace\KeyspacePartition();
|
||||
$partition->setServedType(\topodata\TabletType::REPLICA);
|
||||
$shard_ref = new \topodata\ShardReference();
|
||||
$shard_ref->setName("shard0");
|
||||
$shard_ref->setKeyRange(VTProto::KeyRangeFromHex('4000000000000000', '8000000000000000'));
|
||||
$partition->addShardReferences($shard_ref);
|
||||
$expected->addPartitions($partition);
|
||||
$expected->setShardingColumnName('sharding_column_name');
|
||||
$expected->setShardingColumnType(\topodata\KeyspaceIdType::UINT64);
|
||||
$served_from = new \topodata\SrvKeyspace\ServedFrom();
|
||||
$served_from->setTabletType(\topodata\TabletType::MASTER);
|
||||
$served_from->setKeyspace('other_keyspace');
|
||||
$expected->addServedFrom($served_from);
|
||||
$expected->setSplitShardCount(128);
|
||||
|
||||
$actual = $conn->getSrvKeyspace($ctx, "big");
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
private function checkExecuteErrors($execute, $partial = TRUE) {
|
||||
foreach (self::$EXECUTE_ERRORS as $error => $class) {
|
||||
try {
|
||||
$query = self::$ERROR_PREFIX . $error;
|
||||
$execute($this->ctx, $this->conn, $query);
|
||||
$this->fail("no exception thrown for $query");
|
||||
} catch (Exception $e) {
|
||||
$this->assertEquals($class, get_class($e), $e->getMessage());
|
||||
}
|
||||
|
||||
if ($partial) {
|
||||
try {
|
||||
$query = self::$PARTIAL_ERROR_PREFIX . $error;
|
||||
$execute($this->ctx, $this->conn, $query);
|
||||
$this->fail("no exception thrown for $query");
|
||||
} catch (Exception $e) {
|
||||
$this->assertEquals($class, get_class($e), $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function checkTransactionExecuteErrors($execute) {
|
||||
foreach (self::$EXECUTE_ERRORS as $error => $class) {
|
||||
try {
|
||||
$tx = $this->conn->begin($this->ctx);
|
||||
$query = self::$ERROR_PREFIX . $error;
|
||||
$execute($this->ctx, $tx, $query);
|
||||
$this->fail("no exception thrown for $query");
|
||||
} catch (Exception $e) {
|
||||
$this->assertEquals($class, get_class($e), $e->getMessage());
|
||||
}
|
||||
|
||||
// Don't close the transaction on partial error.
|
||||
$tx = $this->conn->begin($this->ctx);
|
||||
try {
|
||||
$query = self::$PARTIAL_ERROR_PREFIX . $error;
|
||||
$execute($this->ctx, $tx, $query);
|
||||
$this->fail("no exception thrown for $query");
|
||||
} catch (Exception $e) {
|
||||
$this->assertEquals($class, get_class($e), $e->getMessage());
|
||||
}
|
||||
// The transaction should still be usable now.
|
||||
$tx->rollback($this->ctx);
|
||||
|
||||
// Close the transaction on partial error.
|
||||
$tx = $this->conn->begin($this->ctx);
|
||||
try {
|
||||
$query = self::$PARTIAL_ERROR_PREFIX . $error . '/close transaction';
|
||||
$execute($this->ctx, $tx, $query);
|
||||
$this->fail("no exception thrown for $query");
|
||||
} catch (Exception $e) {
|
||||
$this->assertEquals($class, get_class($e), $e->getMessage());
|
||||
}
|
||||
// The transaction should be unusable now.
|
||||
try {
|
||||
$tx->rollback($this->ctx);
|
||||
$this->fail("no exception thrown for rollback() after closed transaction");
|
||||
} catch (Exception $e) {
|
||||
$this->assertEquals('VTException', get_class($e), $e->getMessage());
|
||||
$this->assertEquals(TRUE, strpos($e->getMessage(), 'not in transaction') !== FALSE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function checkStreamExecuteErrors($execute) {
|
||||
$this->checkExecuteErrors($execute, FALSE);
|
||||
}
|
||||
|
||||
public function testExecuteErrors() {
|
||||
$this->checkExecuteErrors(function ($ctx, $conn, $query) {
|
||||
$conn->execute($ctx, $query, self::$BIND_VARS, self::$TABLET_TYPE);
|
||||
});
|
||||
|
||||
$this->checkExecuteErrors(function ($ctx, $conn, $query) {
|
||||
$conn->executeShards($ctx, $query, self::$KEYSPACE, self::$SHARDS, self::$BIND_VARS, self::$TABLET_TYPE);
|
||||
});
|
||||
|
||||
$this->checkExecuteErrors(function ($ctx, $conn, $query) {
|
||||
$conn->executeKeyspaceIds($ctx, $query, self::$KEYSPACE, self::$KEYSPACE_IDS, self::$BIND_VARS, self::$TABLET_TYPE);
|
||||
});
|
||||
|
||||
$this->checkExecuteErrors(function ($ctx, $conn, $query) {
|
||||
$conn->executeKeyRanges($ctx, $query, self::$KEYSPACE, self::$KEY_RANGES, self::$BIND_VARS, self::$TABLET_TYPE);
|
||||
});
|
||||
|
||||
$this->checkExecuteErrors(function ($ctx, $conn, $query) {
|
||||
$conn->executeEntityIds($ctx, $query, self::$KEYSPACE, self::$ENTITY_COLUMN_NAME, self::$ENTITY_KEYSPACE_IDS, self::$BIND_VARS, self::$TABLET_TYPE);
|
||||
});
|
||||
|
||||
$this->checkExecuteErrors(function ($ctx, $conn, $query) {
|
||||
$conn->executeBatchShards($ctx, array(
|
||||
VTProto::BoundShardQuery($query, self::$BIND_VARS, self::$KEYSPACE, self::$SHARDS)
|
||||
), self::$TABLET_TYPE, TRUE);
|
||||
});
|
||||
|
||||
$this->checkExecuteErrors(function ($ctx, $conn, $query) {
|
||||
$conn->executeBatchKeyspaceIds($ctx, array(
|
||||
VTProto::BoundKeyspaceIdQuery($query, self::$BIND_VARS, self::$KEYSPACE, self::$KEYSPACE_IDS)
|
||||
), self::$TABLET_TYPE, TRUE);
|
||||
});
|
||||
}
|
||||
|
||||
public function testTransactionExecuteErrors() {
|
||||
$this->checkTransactionExecuteErrors(function ($ctx, $tx, $query) {
|
||||
$tx->execute($ctx, $query, self::$BIND_VARS, self::$TABLET_TYPE, TRUE);
|
||||
});
|
||||
|
||||
$this->checkTransactionExecuteErrors(function ($ctx, $tx, $query) {
|
||||
$tx->executeShards($ctx, $query, self::$KEYSPACE, self::$SHARDS, self::$BIND_VARS, self::$TABLET_TYPE, TRUE);
|
||||
});
|
||||
|
||||
$this->checkTransactionExecuteErrors(function ($ctx, $tx, $query) {
|
||||
$tx->executeKeyspaceIds($ctx, $query, self::$KEYSPACE, self::$KEYSPACE_IDS, self::$BIND_VARS, self::$TABLET_TYPE, TRUE);
|
||||
});
|
||||
|
||||
$this->checkTransactionExecuteErrors(function ($ctx, $tx, $query) {
|
||||
$tx->executeKeyRanges($ctx, $query, self::$KEYSPACE, self::$KEY_RANGES, self::$BIND_VARS, self::$TABLET_TYPE, TRUE);
|
||||
});
|
||||
|
||||
$this->checkTransactionExecuteErrors(function ($ctx, $tx, $query) {
|
||||
$tx->executeEntityIds($ctx, $query, self::$KEYSPACE, self::$ENTITY_COLUMN_NAME, self::$ENTITY_KEYSPACE_IDS, self::$BIND_VARS, self::$TABLET_TYPE, TRUE);
|
||||
});
|
||||
|
||||
$this->checkTransactionExecuteErrors(function ($ctx, $tx, $query) {
|
||||
$tx->executeBatchShards($ctx, array(
|
||||
VTProto::BoundShardQuery($query, self::$BIND_VARS, self::$KEYSPACE, self::$SHARDS)
|
||||
), self::$TABLET_TYPE, TRUE);
|
||||
});
|
||||
|
||||
$this->checkTransactionExecuteErrors(function ($ctx, $tx, $query) {
|
||||
$tx->executeBatchKeyspaceIds($ctx, array(
|
||||
VTProto::BoundKeyspaceIdQuery($query, self::$BIND_VARS, self::$KEYSPACE, self::$KEYSPACE_IDS)
|
||||
), self::$TABLET_TYPE, TRUE);
|
||||
});
|
||||
}
|
||||
|
||||
public function testStreamExecuteErrors() {
|
||||
$this->checkStreamExecuteErrors(function ($ctx, $conn, $query) {
|
||||
$conn->streamExecute($ctx, $query, self::$BIND_VARS, self::$TABLET_TYPE)->next();
|
||||
});
|
||||
|
||||
$this->checkStreamExecuteErrors(function ($ctx, $conn, $query) {
|
||||
$conn->streamExecuteShards($ctx, $query, self::$KEYSPACE, self::$SHARDS, self::$BIND_VARS, self::$TABLET_TYPE)->next();
|
||||
});
|
||||
|
||||
$this->checkStreamExecuteErrors(function ($ctx, $conn, $query) {
|
||||
$conn->streamExecuteKeyspaceIds($ctx, $query, self::$KEYSPACE, self::$KEYSPACE_IDS, self::$BIND_VARS, self::$TABLET_TYPE)->next();
|
||||
});
|
||||
|
||||
$this->checkStreamExecuteErrors(function ($ctx, $conn, $query) {
|
||||
$conn->streamExecuteKeyRanges($ctx, $query, self::$KEYSPACE, self::$KEY_RANGES, self::$BIND_VARS, self::$TABLET_TYPE)->next();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,69 +1,38 @@
|
|||
<?php
|
||||
require_once (dirname(__FILE__) . '/VTTestUtils.php');
|
||||
require_once (dirname(__FILE__) . '/../src/VTProto.php');
|
||||
|
||||
class VTProtoTest extends PHPUnit_Framework_TestCase {
|
||||
|
||||
public function testBoundQuery() {
|
||||
$expected = array(
|
||||
'Sql' => 'test query',
|
||||
'BindVariables' => array(
|
||||
'bytes' => array(
|
||||
'Type' => VTBindVariable::TYPE_BYTES,
|
||||
'ValueBytes' => new MongoBinData('hello')
|
||||
),
|
||||
'int' => array(
|
||||
'Type' => VTBindVariable::TYPE_INT,
|
||||
'ValueInt' => 123
|
||||
),
|
||||
'uint_from_int' => array(
|
||||
'Type' => VTBindVariable::TYPE_UINT,
|
||||
'ValueUint' => 345
|
||||
),
|
||||
'uint_from_string' => array(
|
||||
'Type' => VTBindVariable::TYPE_UINT,
|
||||
'ValueUint' => new MongoInt64('678')
|
||||
),
|
||||
'float' => array(
|
||||
'Type' => VTBindVariable::TYPE_FLOAT,
|
||||
'ValueFloat' => 1.5
|
||||
),
|
||||
'bytes_list' => array(
|
||||
'Type' => VTBindVariable::TYPE_BYTES_LIST,
|
||||
'ValueBytesList' => array(
|
||||
new MongoBinData('one'),
|
||||
new MongoBinData('two')
|
||||
)
|
||||
),
|
||||
'int_list' => array(
|
||||
'Type' => VTBindVariable::TYPE_INT_LIST,
|
||||
'ValueIntList' => array(
|
||||
1,
|
||||
2,
|
||||
3
|
||||
)
|
||||
),
|
||||
'uint_list' => array(
|
||||
'Type' => VTBindVariable::TYPE_UINT_LIST,
|
||||
'ValueUintList' => array(
|
||||
123,
|
||||
456
|
||||
)
|
||||
),
|
||||
'float_list' => array(
|
||||
'Type' => VTBindVariable::TYPE_FLOAT_LIST,
|
||||
'ValueFloatList' => array(
|
||||
2.0,
|
||||
4.0
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
$expected = new \query\BoundQuery();
|
||||
$expected->setSql('test query');
|
||||
add_bind_var($expected, \query\BindVariable\Type::TYPE_BYTES, 'setValueBytes', 'bytes', 'hello');
|
||||
add_bind_var($expected, \query\BindVariable\Type::TYPE_INT, 'setValueInt', 'int', 123);
|
||||
add_bind_var($expected, \query\BindVariable\Type::TYPE_UINT, 'setValueUint', 'uint_from_int', - 123);
|
||||
add_bind_var($expected, \query\BindVariable\Type::TYPE_FLOAT, 'setValueFloat', 'float', 1.5);
|
||||
add_bind_var($expected, \query\BindVariable\Type::TYPE_BYTES_LIST, 'setValueBytesList', 'bytes_list', array(
|
||||
'one',
|
||||
'two'
|
||||
));
|
||||
add_bind_var($expected, \query\BindVariable\Type::TYPE_INT_LIST, 'setValueIntList', 'int_list', array(
|
||||
1,
|
||||
2,
|
||||
3
|
||||
));
|
||||
add_bind_var($expected, \query\BindVariable\Type::TYPE_UINT_LIST, 'setValueUintList', 'uint_list', array(
|
||||
123,
|
||||
456
|
||||
));
|
||||
add_bind_var($expected, \query\BindVariable\Type::TYPE_FLOAT_LIST, 'setValueFloatList', 'float_list', array(
|
||||
2.0,
|
||||
4.0
|
||||
));
|
||||
|
||||
$actual = VTBoundQuery::buildBsonP3('test query', array(
|
||||
$actual = VTProto::BoundQuery('test query', array(
|
||||
'bytes' => 'hello',
|
||||
'int' => 123,
|
||||
'uint_from_int' => new VTUnsignedInt(345),
|
||||
'uint_from_string' => new VTUnsignedInt('678'),
|
||||
'uint_from_int' => new VTUnsignedInt(- 123),
|
||||
'float' => 1.5,
|
||||
'bytes_list' => array(
|
||||
'one',
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
function add_bind_var($bound_query, $type, $method, $key, $value) {
|
||||
$bv = new \query\BindVariable();
|
||||
$bv->setType($type);
|
||||
$bv->$method($value);
|
||||
$entry = new \query\BoundQuery\BindVariablesEntry();
|
||||
$entry->setKey($key);
|
||||
$entry->setValue($bv);
|
||||
$bound_query->addBindVariables($entry);
|
||||
}
|
|
@ -94,7 +94,6 @@ class TestEnv(object):
|
|||
utils.run_vtctl(['ApplyVSchema', '-vschema_file', self.vschema])
|
||||
utils.VtGate(port=self.vtgate_port).start(
|
||||
cache_ttl='500s',
|
||||
rpc_error_only_in_reply=False
|
||||
)
|
||||
except:
|
||||
self.shutdown()
|
||||
|
@ -105,7 +104,8 @@ class TestEnv(object):
|
|||
# StreamingServerShutdownIT.java expects an EOF from the vtgate
|
||||
# client and not an error that vttablet killed the query (which is
|
||||
# seen when vtgate is killed last).
|
||||
utils.vtgate.kill()
|
||||
if utils.vtgate:
|
||||
utils.vtgate.kill()
|
||||
tablet.kill_tablets(self.tablets)
|
||||
teardown_procs = [t.teardown_mysql() for t in self.tablets]
|
||||
utils.wait_procs(teardown_procs, raise_on_error=False)
|
||||
|
|
|
@ -45,7 +45,6 @@ def setUpModule():
|
|||
args.extend(['-grpc_port', str(vtgateclienttest_grpc_port)])
|
||||
if protocols_flavor().service_map():
|
||||
args.extend(['-service_map', ','.join(protocols_flavor().service_map())])
|
||||
args.extend(['-rpc-error-only-in-reply=true'])
|
||||
|
||||
vtgateclienttest_process = utils.run_bg(args)
|
||||
utils.wait_for_vars('vtgateclienttest', vtgateclienttest_port)
|
||||
|
|
|
@ -493,7 +493,7 @@ class VtGate(object):
|
|||
def start(self, cell='test_nj', retry_delay=1, retry_count=2,
|
||||
topo_impl=None, cache_ttl='1s',
|
||||
timeout_total='4s', timeout_per_conn='2s',
|
||||
extra_args=None, rpc_error_only_in_reply=True):
|
||||
extra_args=None):
|
||||
"""Starts the process for this vtgate instance.
|
||||
|
||||
If no other instance has been started, saves it into the global
|
||||
|
@ -519,8 +519,6 @@ class VtGate(object):
|
|||
args.extend(['-topo_implementation', topo_impl])
|
||||
else:
|
||||
args.extend(environment.topo_server().flags())
|
||||
if rpc_error_only_in_reply:
|
||||
args.extend(['-rpc-error-only-in-reply=true'])
|
||||
if extra_args:
|
||||
args.extend(extra_args)
|
||||
|
||||
|
|
|
@ -23,9 +23,7 @@ from vtdb import keyrange
|
|||
from vtdb import keyrange_constants
|
||||
from vtdb import vtdb_logger
|
||||
from vtdb import vtgate_cursor
|
||||
from vtdb import vtgatev2
|
||||
|
||||
conn_class = vtgatev2
|
||||
from vtdb import vtgate_client
|
||||
|
||||
shard_0_master = tablet.Tablet()
|
||||
shard_0_replica1 = tablet.Tablet()
|
||||
|
@ -184,11 +182,10 @@ def setup_tablets():
|
|||
utils.VtGate().start()
|
||||
|
||||
|
||||
def get_connection(user=None, password=None, timeout=10.0):
|
||||
vtgate_addrs = {'vt': [utils.vtgate.addr(),]}
|
||||
def get_connection(timeout=10.0):
|
||||
protocol = protocols_flavor().vtgate_python_protocol()
|
||||
try:
|
||||
return conn_class.connect(vtgate_addrs, timeout,
|
||||
user=user, password=password)
|
||||
return vtgate_client.connect(protocol, utils.vtgate.addr(), timeout)
|
||||
except Exception:
|
||||
logging.exception('Connection to vtgate (timeout=%s) failed.', timeout)
|
||||
raise
|
||||
|
@ -914,7 +911,9 @@ class TestFailures(BaseTestCase):
|
|||
vtgate_conn.commit()
|
||||
self.fail('Failed to raise DatabaseError exception')
|
||||
except dbexceptions.DatabaseError:
|
||||
if conn_class == vtgatev2:
|
||||
# FIXME(alainjobart) add a method to get the session to vtgate_client,
|
||||
# instead of poking into it like this.
|
||||
if protocols_flavor().vtgate_python_protocol() == 'gorpc':
|
||||
logging.info(
|
||||
'SHARD SESSIONS: %s', vtgate_conn.session['ShardSessions'])
|
||||
transaction_id = (
|
||||
|
|
Загрузка…
Ссылка в новой задаче