1
0
Fork 0

Merge pull request #1256 from rqlite/unified-endpoint

Support unified endpoint
master
Philip O'Toole 1 year ago committed by GitHub
commit e128333573
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,3 +1,6 @@
## 7.18.0 (unreleased)
- [PR 1256](https://github.com/rqlite/rqlite/pull/1256): Support a _Unified Endpoint_, which can accept both read and write requests. Fixes [issue #263](https://github.com/rqlite/rqlite/issues/263).
## 7.17.0 (May 9th 2023)
### New features
- [PR #1253](https://github.com/rqlite/rqlite/pull/1253): Node optionally removes itself from the cluster automatically when gracefully shutting down. See the [documentation](https://rqlite.io/docs/clustering/general-guidelines/#removing-a-node-automatically-on-shutdown) for full details.

@ -145,6 +145,32 @@ func (c *Client) Query(qr *command.QueryRequest, nodeAddr string, creds *Credent
return a.Rows, nil
}
// Request performs an ExecuteQuery on a remote node.
func (c *Client) Request(r *command.ExecuteQueryRequest, nodeAddr string, creds *Credentials, timeout time.Duration) ([]*command.ExecuteQueryResponse, error) {
command := &Command{
Type: Command_COMMAND_TYPE_REQUEST,
Request: &Command_ExecuteQueryRequest{
ExecuteQueryRequest: r,
},
Credentials: creds,
}
p, err := c.retry(command, nodeAddr, timeout)
if err != nil {
return nil, err
}
a := &CommandRequestResponse{}
err = proto.Unmarshal(p, a)
if err != nil {
return nil, err
}
if a.Error != "" {
return nil, errors.New(a.Error)
}
return a.Response, nil
}
// Backup retrieves a backup from a remote node and writes to the io.Writer
func (c *Client) Backup(br *command.BackupRequest, nodeAddr string, creds *Credentials, timeout time.Duration, w io.Writer) error {
command := &Command{

@ -133,6 +133,45 @@ func Test_ClientQuery(t *testing.T) {
}
}
func Test_ClientRequest(t *testing.T) {
srv := servicetest.NewService()
srv.Handler = func(conn net.Conn) {
var p []byte
var err error
c := readCommand(conn)
if c == nil {
// Error on connection, so give up, as normal
// test exit can cause that too.
return
}
if c.Type != Command_COMMAND_TYPE_REQUEST {
t.Fatalf("unexpected command type: %d", c.Type)
}
er := c.GetExecuteQueryRequest()
if er == nil {
t.Fatal("expected execute query request, got nil")
}
if er.Request.Statements[0].Sql != "SELECT * FROM foo" {
t.Fatalf("unexpected statement, got %s", er.Request.Statements[0])
}
p, err = proto.Marshal(&CommandRequestResponse{})
if err != nil {
conn.Close()
}
writeBytesWithLength(conn, p)
}
srv.Start()
defer srv.Close()
c := NewClient(&simpleDialer{}, 0)
_, err := c.Request(executeQueryRequestFromString("SELECT * FROM foo"),
srv.Addr(), nil, time.Second)
if err != nil {
t.Fatal(err)
}
}
func Test_ClientRemoveNode(t *testing.T) {
srv := servicetest.NewService()
srv.Handler = func(conn net.Conn) {

@ -33,6 +33,7 @@ const (
Command_COMMAND_TYPE_REMOVE_NODE Command_Type = 6
Command_COMMAND_TYPE_NOTIFY Command_Type = 7
Command_COMMAND_TYPE_JOIN Command_Type = 8
Command_COMMAND_TYPE_REQUEST Command_Type = 9
)
// Enum value maps for Command_Type.
@ -47,6 +48,7 @@ var (
6: "COMMAND_TYPE_REMOVE_NODE",
7: "COMMAND_TYPE_NOTIFY",
8: "COMMAND_TYPE_JOIN",
9: "COMMAND_TYPE_REQUEST",
}
Command_Type_value = map[string]int32{
"COMMAND_TYPE_UNKNOWN": 0,
@ -58,6 +60,7 @@ var (
"COMMAND_TYPE_REMOVE_NODE": 6,
"COMMAND_TYPE_NOTIFY": 7,
"COMMAND_TYPE_JOIN": 8,
"COMMAND_TYPE_REQUEST": 9,
}
)
@ -205,6 +208,7 @@ type Command struct {
// *Command_RemoveNodeRequest
// *Command_NotifyRequest
// *Command_JoinRequest
// *Command_ExecuteQueryRequest
Request isCommand_Request `protobuf_oneof:"request"`
Credentials *Credentials `protobuf:"bytes,4,opt,name=credentials,proto3" json:"credentials,omitempty"`
}
@ -304,6 +308,13 @@ func (x *Command) GetJoinRequest() *command.JoinRequest {
return nil
}
func (x *Command) GetExecuteQueryRequest() *command.ExecuteQueryRequest {
if x, ok := x.GetRequest().(*Command_ExecuteQueryRequest); ok {
return x.ExecuteQueryRequest
}
return nil
}
func (x *Command) GetCredentials() *Credentials {
if x != nil {
return x.Credentials
@ -343,6 +354,10 @@ type Command_JoinRequest struct {
JoinRequest *command.JoinRequest `protobuf:"bytes,9,opt,name=join_request,json=joinRequest,proto3,oneof"`
}
type Command_ExecuteQueryRequest struct {
ExecuteQueryRequest *command.ExecuteQueryRequest `protobuf:"bytes,10,opt,name=execute_query_request,json=executeQueryRequest,proto3,oneof"`
}
func (*Command_ExecuteRequest) isCommand_Request() {}
func (*Command_QueryRequest) isCommand_Request() {}
@ -357,6 +372,8 @@ func (*Command_NotifyRequest) isCommand_Request() {}
func (*Command_JoinRequest) isCommand_Request() {}
func (*Command_ExecuteQueryRequest) isCommand_Request() {}
type CommandExecuteResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@ -467,6 +484,61 @@ func (x *CommandQueryResponse) GetRows() []*command.QueryRows {
return nil
}
type CommandRequestResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Error string `protobuf:"bytes,1,opt,name=error,proto3" json:"error,omitempty"`
Response []*command.ExecuteQueryResponse `protobuf:"bytes,2,rep,name=response,proto3" json:"response,omitempty"`
}
func (x *CommandRequestResponse) Reset() {
*x = CommandRequestResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_message_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *CommandRequestResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CommandRequestResponse) ProtoMessage() {}
func (x *CommandRequestResponse) ProtoReflect() protoreflect.Message {
mi := &file_message_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use CommandRequestResponse.ProtoReflect.Descriptor instead.
func (*CommandRequestResponse) Descriptor() ([]byte, []int) {
return file_message_proto_rawDescGZIP(), []int{5}
}
func (x *CommandRequestResponse) GetError() string {
if x != nil {
return x.Error
}
return ""
}
func (x *CommandRequestResponse) GetResponse() []*command.ExecuteQueryResponse {
if x != nil {
return x.Response
}
return nil
}
type CommandBackupResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@ -479,7 +551,7 @@ type CommandBackupResponse struct {
func (x *CommandBackupResponse) Reset() {
*x = CommandBackupResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_message_proto_msgTypes[5]
mi := &file_message_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -492,7 +564,7 @@ func (x *CommandBackupResponse) String() string {
func (*CommandBackupResponse) ProtoMessage() {}
func (x *CommandBackupResponse) ProtoReflect() protoreflect.Message {
mi := &file_message_proto_msgTypes[5]
mi := &file_message_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -505,7 +577,7 @@ func (x *CommandBackupResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use CommandBackupResponse.ProtoReflect.Descriptor instead.
func (*CommandBackupResponse) Descriptor() ([]byte, []int) {
return file_message_proto_rawDescGZIP(), []int{5}
return file_message_proto_rawDescGZIP(), []int{6}
}
func (x *CommandBackupResponse) GetError() string {
@ -533,7 +605,7 @@ type CommandLoadResponse struct {
func (x *CommandLoadResponse) Reset() {
*x = CommandLoadResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_message_proto_msgTypes[6]
mi := &file_message_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -546,7 +618,7 @@ func (x *CommandLoadResponse) String() string {
func (*CommandLoadResponse) ProtoMessage() {}
func (x *CommandLoadResponse) ProtoReflect() protoreflect.Message {
mi := &file_message_proto_msgTypes[6]
mi := &file_message_proto_msgTypes[7]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -559,7 +631,7 @@ func (x *CommandLoadResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use CommandLoadResponse.ProtoReflect.Descriptor instead.
func (*CommandLoadResponse) Descriptor() ([]byte, []int) {
return file_message_proto_rawDescGZIP(), []int{6}
return file_message_proto_rawDescGZIP(), []int{7}
}
func (x *CommandLoadResponse) GetError() string {
@ -580,7 +652,7 @@ type CommandRemoveNodeResponse struct {
func (x *CommandRemoveNodeResponse) Reset() {
*x = CommandRemoveNodeResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_message_proto_msgTypes[7]
mi := &file_message_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -593,7 +665,7 @@ func (x *CommandRemoveNodeResponse) String() string {
func (*CommandRemoveNodeResponse) ProtoMessage() {}
func (x *CommandRemoveNodeResponse) ProtoReflect() protoreflect.Message {
mi := &file_message_proto_msgTypes[7]
mi := &file_message_proto_msgTypes[8]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -606,7 +678,7 @@ func (x *CommandRemoveNodeResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use CommandRemoveNodeResponse.ProtoReflect.Descriptor instead.
func (*CommandRemoveNodeResponse) Descriptor() ([]byte, []int) {
return file_message_proto_rawDescGZIP(), []int{7}
return file_message_proto_rawDescGZIP(), []int{8}
}
func (x *CommandRemoveNodeResponse) GetError() string {
@ -627,7 +699,7 @@ type CommandNotifyResponse struct {
func (x *CommandNotifyResponse) Reset() {
*x = CommandNotifyResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_message_proto_msgTypes[8]
mi := &file_message_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -640,7 +712,7 @@ func (x *CommandNotifyResponse) String() string {
func (*CommandNotifyResponse) ProtoMessage() {}
func (x *CommandNotifyResponse) ProtoReflect() protoreflect.Message {
mi := &file_message_proto_msgTypes[8]
mi := &file_message_proto_msgTypes[9]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -653,7 +725,7 @@ func (x *CommandNotifyResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use CommandNotifyResponse.ProtoReflect.Descriptor instead.
func (*CommandNotifyResponse) Descriptor() ([]byte, []int) {
return file_message_proto_rawDescGZIP(), []int{8}
return file_message_proto_rawDescGZIP(), []int{9}
}
func (x *CommandNotifyResponse) GetError() string {
@ -674,7 +746,7 @@ type CommandJoinResponse struct {
func (x *CommandJoinResponse) Reset() {
*x = CommandJoinResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_message_proto_msgTypes[9]
mi := &file_message_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -687,7 +759,7 @@ func (x *CommandJoinResponse) String() string {
func (*CommandJoinResponse) ProtoMessage() {}
func (x *CommandJoinResponse) ProtoReflect() protoreflect.Message {
mi := &file_message_proto_msgTypes[9]
mi := &file_message_proto_msgTypes[10]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -700,7 +772,7 @@ func (x *CommandJoinResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use CommandJoinResponse.ProtoReflect.Descriptor instead.
func (*CommandJoinResponse) Descriptor() ([]byte, []int) {
return file_message_proto_rawDescGZIP(), []int{9}
return file_message_proto_rawDescGZIP(), []int{10}
}
func (x *CommandJoinResponse) GetError() string {
@ -722,7 +794,7 @@ var file_message_proto_rawDesc = []byte{
0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61,
0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x1b, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73,
0x73, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03,
0x75, 0x72, 0x6c, 0x22, 0xb5, 0x06, 0x0a, 0x07, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12,
0x75, 0x72, 0x6c, 0x22, 0xa3, 0x07, 0x0a, 0x07, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12,
0x29, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e,
0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e,
0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x42, 0x0a, 0x0f, 0x65, 0x78,
@ -754,56 +826,70 @@ var file_message_proto_rawDesc = []byte{
0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e,
0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x4a, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0b, 0x6a, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x12, 0x36, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c,
0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65,
0x72, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x52, 0x0b, 0x63,
0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x22, 0xf3, 0x01, 0x0a, 0x04, 0x54,
0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x14, 0x43, 0x4f, 0x4d, 0x4d, 0x41, 0x4e, 0x44, 0x5f, 0x54,
0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x21, 0x0a,
0x1d, 0x43, 0x4f, 0x4d, 0x4d, 0x41, 0x4e, 0x44, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x47, 0x45,
0x54, 0x5f, 0x4e, 0x4f, 0x44, 0x45, 0x5f, 0x41, 0x50, 0x49, 0x5f, 0x55, 0x52, 0x4c, 0x10, 0x01,
0x12, 0x18, 0x0a, 0x14, 0x43, 0x4f, 0x4d, 0x4d, 0x41, 0x4e, 0x44, 0x5f, 0x54, 0x59, 0x50, 0x45,
0x5f, 0x45, 0x58, 0x45, 0x43, 0x55, 0x54, 0x45, 0x10, 0x02, 0x12, 0x16, 0x0a, 0x12, 0x43, 0x4f,
0x4d, 0x4d, 0x41, 0x4e, 0x44, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x51, 0x55, 0x45, 0x52, 0x59,
0x10, 0x03, 0x12, 0x17, 0x0a, 0x13, 0x43, 0x4f, 0x4d, 0x4d, 0x41, 0x4e, 0x44, 0x5f, 0x54, 0x59,
0x50, 0x45, 0x5f, 0x42, 0x41, 0x43, 0x4b, 0x55, 0x50, 0x10, 0x04, 0x12, 0x15, 0x0a, 0x11, 0x43,
0x4f, 0x4d, 0x4d, 0x41, 0x4e, 0x44, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4c, 0x4f, 0x41, 0x44,
0x10, 0x05, 0x12, 0x1c, 0x0a, 0x18, 0x43, 0x4f, 0x4d, 0x4d, 0x41, 0x4e, 0x44, 0x5f, 0x54, 0x59,
0x50, 0x45, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x56, 0x45, 0x5f, 0x4e, 0x4f, 0x44, 0x45, 0x10, 0x06,
0x12, 0x17, 0x0a, 0x13, 0x43, 0x4f, 0x4d, 0x4d, 0x41, 0x4e, 0x44, 0x5f, 0x54, 0x59, 0x50, 0x45,
0x5f, 0x4e, 0x4f, 0x54, 0x49, 0x46, 0x59, 0x10, 0x07, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x4f, 0x4d,
0x4d, 0x41, 0x4e, 0x44, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4a, 0x4f, 0x49, 0x4e, 0x10, 0x08,
0x42, 0x09, 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x60, 0x0a, 0x16, 0x43,
0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73,
0x73, 0x74, 0x12, 0x52, 0x0a, 0x15, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x5f, 0x71, 0x75,
0x65, 0x72, 0x79, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x1c, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x45, 0x78, 0x65, 0x63,
0x75, 0x74, 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48,
0x00, 0x52, 0x13, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x36, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e,
0x74, 0x69, 0x61, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x63, 0x6c,
0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c,
0x73, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x22, 0x8d,
0x02, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x14, 0x43, 0x4f, 0x4d, 0x4d, 0x41,
0x4e, 0x44, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10,
0x00, 0x12, 0x21, 0x0a, 0x1d, 0x43, 0x4f, 0x4d, 0x4d, 0x41, 0x4e, 0x44, 0x5f, 0x54, 0x59, 0x50,
0x45, 0x5f, 0x47, 0x45, 0x54, 0x5f, 0x4e, 0x4f, 0x44, 0x45, 0x5f, 0x41, 0x50, 0x49, 0x5f, 0x55,
0x52, 0x4c, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x43, 0x4f, 0x4d, 0x4d, 0x41, 0x4e, 0x44, 0x5f,
0x54, 0x59, 0x50, 0x45, 0x5f, 0x45, 0x58, 0x45, 0x43, 0x55, 0x54, 0x45, 0x10, 0x02, 0x12, 0x16,
0x0a, 0x12, 0x43, 0x4f, 0x4d, 0x4d, 0x41, 0x4e, 0x44, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x51,
0x55, 0x45, 0x52, 0x59, 0x10, 0x03, 0x12, 0x17, 0x0a, 0x13, 0x43, 0x4f, 0x4d, 0x4d, 0x41, 0x4e,
0x44, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x42, 0x41, 0x43, 0x4b, 0x55, 0x50, 0x10, 0x04, 0x12,
0x15, 0x0a, 0x11, 0x43, 0x4f, 0x4d, 0x4d, 0x41, 0x4e, 0x44, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f,
0x4c, 0x4f, 0x41, 0x44, 0x10, 0x05, 0x12, 0x1c, 0x0a, 0x18, 0x43, 0x4f, 0x4d, 0x4d, 0x41, 0x4e,
0x44, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x56, 0x45, 0x5f, 0x4e, 0x4f,
0x44, 0x45, 0x10, 0x06, 0x12, 0x17, 0x0a, 0x13, 0x43, 0x4f, 0x4d, 0x4d, 0x41, 0x4e, 0x44, 0x5f,
0x54, 0x59, 0x50, 0x45, 0x5f, 0x4e, 0x4f, 0x54, 0x49, 0x46, 0x59, 0x10, 0x07, 0x12, 0x15, 0x0a,
0x11, 0x43, 0x4f, 0x4d, 0x4d, 0x41, 0x4e, 0x44, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4a, 0x4f,
0x49, 0x4e, 0x10, 0x08, 0x12, 0x18, 0x0a, 0x14, 0x43, 0x4f, 0x4d, 0x4d, 0x41, 0x4e, 0x44, 0x5f,
0x54, 0x59, 0x50, 0x45, 0x5f, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x10, 0x09, 0x42, 0x09,
0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x60, 0x0a, 0x16, 0x43, 0x6f, 0x6d,
0x6d, 0x61, 0x6e, 0x64, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x30, 0x0a, 0x07, 0x72, 0x65, 0x73,
0x75, 0x6c, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x6f, 0x6d,
0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x75,
0x6c, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0x54, 0x0a, 0x14, 0x43,
0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x26, 0x0a, 0x04, 0x72, 0x6f, 0x77,
0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e,
0x64, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, 0x77, 0x73, 0x52, 0x04, 0x72, 0x6f, 0x77,
0x73, 0x22, 0x69, 0x0a, 0x16, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65,
0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f,
0x72, 0x12, 0x39, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x02, 0x20,
0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x45, 0x78,
0x65, 0x63, 0x75, 0x74, 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x52, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x41, 0x0a, 0x15,
0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x30, 0x0a, 0x07, 0x72,
0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63,
0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, 0x65,
0x73, 0x75, 0x6c, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0x54, 0x0a,
0x14, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73,
0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x64,
0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22,
0x2b, 0x0a, 0x13, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x4c, 0x6f, 0x61, 0x64, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x31, 0x0a, 0x19,
0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x4e, 0x6f, 0x64,
0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72,
0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22,
0x2d, 0x0a, 0x15, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f,
0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x2b,
0x0a, 0x13, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x4a, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x26, 0x0a, 0x04, 0x72,
0x6f, 0x77, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x6f, 0x6d, 0x6d,
0x61, 0x6e, 0x64, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, 0x77, 0x73, 0x52, 0x04, 0x72,
0x6f, 0x77, 0x73, 0x22, 0x41, 0x0a, 0x15, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x42, 0x61,
0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05,
0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72,
0x6f, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c,
0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x2b, 0x0a, 0x13, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e,
0x64, 0x4c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a,
0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72,
0x72, 0x6f, 0x72, 0x22, 0x31, 0x0a, 0x19, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65,
0x6d, 0x6f, 0x76, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x2d, 0x0a, 0x15, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e,
0x64, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05,
0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x2b, 0x0a, 0x13, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64,
0x4a, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05,
0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72,
0x6f, 0x72, 0x42, 0x22, 0x5a, 0x20, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
0x2f, 0x72, 0x71, 0x6c, 0x69, 0x74, 0x65, 0x2f, 0x72, 0x71, 0x6c, 0x69, 0x74, 0x65, 0x2f, 0x63,
0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x42, 0x22, 0x5a, 0x20, 0x67,
0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x71, 0x6c, 0x69, 0x74, 0x65,
0x2f, 0x72, 0x71, 0x6c, 0x69, 0x74, 0x65, 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x62,
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@ -819,46 +905,51 @@ func file_message_proto_rawDescGZIP() []byte {
}
var file_message_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_message_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
var file_message_proto_msgTypes = make([]protoimpl.MessageInfo, 11)
var file_message_proto_goTypes = []interface{}{
(Command_Type)(0), // 0: cluster.Command.Type
(*Credentials)(nil), // 1: cluster.Credentials
(*Address)(nil), // 2: cluster.Address
(*Command)(nil), // 3: cluster.Command
(*CommandExecuteResponse)(nil), // 4: cluster.CommandExecuteResponse
(*CommandQueryResponse)(nil), // 5: cluster.CommandQueryResponse
(*CommandBackupResponse)(nil), // 6: cluster.CommandBackupResponse
(*CommandLoadResponse)(nil), // 7: cluster.CommandLoadResponse
(*CommandRemoveNodeResponse)(nil), // 8: cluster.CommandRemoveNodeResponse
(*CommandNotifyResponse)(nil), // 9: cluster.CommandNotifyResponse
(*CommandJoinResponse)(nil), // 10: cluster.CommandJoinResponse
(*command.ExecuteRequest)(nil), // 11: command.ExecuteRequest
(*command.QueryRequest)(nil), // 12: command.QueryRequest
(*command.BackupRequest)(nil), // 13: command.BackupRequest
(*command.LoadRequest)(nil), // 14: command.LoadRequest
(*command.RemoveNodeRequest)(nil), // 15: command.RemoveNodeRequest
(*command.NotifyRequest)(nil), // 16: command.NotifyRequest
(*command.JoinRequest)(nil), // 17: command.JoinRequest
(*command.ExecuteResult)(nil), // 18: command.ExecuteResult
(*command.QueryRows)(nil), // 19: command.QueryRows
(Command_Type)(0), // 0: cluster.Command.Type
(*Credentials)(nil), // 1: cluster.Credentials
(*Address)(nil), // 2: cluster.Address
(*Command)(nil), // 3: cluster.Command
(*CommandExecuteResponse)(nil), // 4: cluster.CommandExecuteResponse
(*CommandQueryResponse)(nil), // 5: cluster.CommandQueryResponse
(*CommandRequestResponse)(nil), // 6: cluster.CommandRequestResponse
(*CommandBackupResponse)(nil), // 7: cluster.CommandBackupResponse
(*CommandLoadResponse)(nil), // 8: cluster.CommandLoadResponse
(*CommandRemoveNodeResponse)(nil), // 9: cluster.CommandRemoveNodeResponse
(*CommandNotifyResponse)(nil), // 10: cluster.CommandNotifyResponse
(*CommandJoinResponse)(nil), // 11: cluster.CommandJoinResponse
(*command.ExecuteRequest)(nil), // 12: command.ExecuteRequest
(*command.QueryRequest)(nil), // 13: command.QueryRequest
(*command.BackupRequest)(nil), // 14: command.BackupRequest
(*command.LoadRequest)(nil), // 15: command.LoadRequest
(*command.RemoveNodeRequest)(nil), // 16: command.RemoveNodeRequest
(*command.NotifyRequest)(nil), // 17: command.NotifyRequest
(*command.JoinRequest)(nil), // 18: command.JoinRequest
(*command.ExecuteQueryRequest)(nil), // 19: command.ExecuteQueryRequest
(*command.ExecuteResult)(nil), // 20: command.ExecuteResult
(*command.QueryRows)(nil), // 21: command.QueryRows
(*command.ExecuteQueryResponse)(nil), // 22: command.ExecuteQueryResponse
}
var file_message_proto_depIdxs = []int32{
0, // 0: cluster.Command.type:type_name -> cluster.Command.Type
11, // 1: cluster.Command.execute_request:type_name -> command.ExecuteRequest
12, // 2: cluster.Command.query_request:type_name -> command.QueryRequest
13, // 3: cluster.Command.backup_request:type_name -> command.BackupRequest
14, // 4: cluster.Command.load_request:type_name -> command.LoadRequest
15, // 5: cluster.Command.remove_node_request:type_name -> command.RemoveNodeRequest
16, // 6: cluster.Command.notify_request:type_name -> command.NotifyRequest
17, // 7: cluster.Command.join_request:type_name -> command.JoinRequest
1, // 8: cluster.Command.credentials:type_name -> cluster.Credentials
18, // 9: cluster.CommandExecuteResponse.results:type_name -> command.ExecuteResult
19, // 10: cluster.CommandQueryResponse.rows:type_name -> command.QueryRows
11, // [11:11] is the sub-list for method output_type
11, // [11:11] is the sub-list for method input_type
11, // [11:11] is the sub-list for extension type_name
11, // [11:11] is the sub-list for extension extendee
0, // [0:11] is the sub-list for field type_name
12, // 1: cluster.Command.execute_request:type_name -> command.ExecuteRequest
13, // 2: cluster.Command.query_request:type_name -> command.QueryRequest
14, // 3: cluster.Command.backup_request:type_name -> command.BackupRequest
15, // 4: cluster.Command.load_request:type_name -> command.LoadRequest
16, // 5: cluster.Command.remove_node_request:type_name -> command.RemoveNodeRequest
17, // 6: cluster.Command.notify_request:type_name -> command.NotifyRequest
18, // 7: cluster.Command.join_request:type_name -> command.JoinRequest
19, // 8: cluster.Command.execute_query_request:type_name -> command.ExecuteQueryRequest
1, // 9: cluster.Command.credentials:type_name -> cluster.Credentials
20, // 10: cluster.CommandExecuteResponse.results:type_name -> command.ExecuteResult
21, // 11: cluster.CommandQueryResponse.rows:type_name -> command.QueryRows
22, // 12: cluster.CommandRequestResponse.response:type_name -> command.ExecuteQueryResponse
13, // [13:13] is the sub-list for method output_type
13, // [13:13] is the sub-list for method input_type
13, // [13:13] is the sub-list for extension type_name
13, // [13:13] is the sub-list for extension extendee
0, // [0:13] is the sub-list for field type_name
}
func init() { file_message_proto_init() }
@ -928,7 +1019,7 @@ func file_message_proto_init() {
}
}
file_message_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*CommandBackupResponse); i {
switch v := v.(*CommandRequestResponse); i {
case 0:
return &v.state
case 1:
@ -940,7 +1031,7 @@ func file_message_proto_init() {
}
}
file_message_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*CommandLoadResponse); i {
switch v := v.(*CommandBackupResponse); i {
case 0:
return &v.state
case 1:
@ -952,7 +1043,7 @@ func file_message_proto_init() {
}
}
file_message_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*CommandRemoveNodeResponse); i {
switch v := v.(*CommandLoadResponse); i {
case 0:
return &v.state
case 1:
@ -964,7 +1055,7 @@ func file_message_proto_init() {
}
}
file_message_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*CommandNotifyResponse); i {
switch v := v.(*CommandRemoveNodeResponse); i {
case 0:
return &v.state
case 1:
@ -976,6 +1067,18 @@ func file_message_proto_init() {
}
}
file_message_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*CommandNotifyResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_message_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*CommandJoinResponse); i {
case 0:
return &v.state
@ -996,6 +1099,7 @@ func file_message_proto_init() {
(*Command_RemoveNodeRequest)(nil),
(*Command_NotifyRequest)(nil),
(*Command_JoinRequest)(nil),
(*Command_ExecuteQueryRequest)(nil),
}
type x struct{}
out := protoimpl.TypeBuilder{
@ -1003,7 +1107,7 @@ func file_message_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_message_proto_rawDesc,
NumEnums: 1,
NumMessages: 10,
NumMessages: 11,
NumExtensions: 0,
NumServices: 0,
},

@ -25,6 +25,7 @@ message Command {
COMMAND_TYPE_REMOVE_NODE = 6;
COMMAND_TYPE_NOTIFY = 7;
COMMAND_TYPE_JOIN = 8;
COMMAND_TYPE_REQUEST = 9;
}
Type type = 1;
@ -36,6 +37,7 @@ message Command {
command.RemoveNodeRequest remove_node_request = 7;
command.NotifyRequest notify_request = 8;
command.JoinRequest join_request = 9;
command.ExecuteQueryRequest execute_query_request = 10;
}
Credentials credentials = 4;
@ -51,6 +53,11 @@ message CommandQueryResponse {
repeated command.QueryRows rows = 2;
}
message CommandRequestResponse {
string error = 1;
repeated command.ExecuteQueryResponse response = 2;
}
message CommandBackupResponse {
string error = 1;
bytes data = 2;

@ -27,6 +27,7 @@ const (
numGetNodeAPIResponse = "num_get_node_api_resp"
numExecuteRequest = "num_execute_req"
numQueryRequest = "num_query_req"
numRequestRequest = "num_request_req"
numBackupRequest = "num_backup_req"
numLoadRequest = "num_load_req"
numRemoveNodeRequest = "num_remove_node_req"
@ -52,6 +53,7 @@ func init() {
stats.Add(numGetNodeAPIResponse, 0)
stats.Add(numExecuteRequest, 0)
stats.Add(numQueryRequest, 0)
stats.Add(numRequestRequest, 0)
stats.Add(numBackupRequest, 0)
stats.Add(numLoadRequest, 0)
stats.Add(numRemoveNodeRequest, 0)
@ -77,6 +79,9 @@ type Database interface {
// Query executes a slice of queries, each of which returns rows.
Query(qr *command.QueryRequest) ([]*command.QueryRows, error)
// Request processes a request that can both executes and queries.
Request(rr *command.ExecuteQueryRequest) ([]*command.ExecuteQueryResponse, error)
// Backup writes a backup of the database to the writer.
Backup(br *command.BackupRequest, dst io.Writer) error
@ -226,6 +231,25 @@ func (s *Service) checkCommandPerm(c *Command, perm string) bool {
return s.credentialStore.AA(username, password, perm)
}
func (s *Service) checkCommandPermAll(c *Command, perms ...string) bool {
if s.credentialStore == nil {
return true
}
username := ""
password := ""
if c.Credentials != nil {
username = c.Credentials.GetUsername()
password = c.Credentials.GetPassword()
}
for _, perm := range perms {
if !s.credentialStore.AA(username, password, perm) {
return false
}
}
return true
}
func (s *Service) handleConn(conn net.Conn) {
defer conn.Close()
@ -312,6 +336,32 @@ func (s *Service) handleConn(conn net.Conn) {
}
writeBytesWithLength(conn, p)
case Command_COMMAND_TYPE_REQUEST:
stats.Add(numRequestRequest, 1)
resp := &CommandRequestResponse{}
rr := c.GetExecuteQueryRequest()
if rr == nil {
resp.Error = "RequestRequest is nil"
} else if !s.checkCommandPermAll(c, auth.PermQuery, auth.PermExecute) {
resp.Error = "unauthorized"
} else {
res, err := s.db.Request(rr)
if err != nil {
resp.Error = err.Error()
} else {
resp.Response = make([]*command.ExecuteQueryResponse, len(res))
copy(resp.Response, res)
}
}
p, err = proto.Marshal(resp)
if err != nil {
return
}
writeBytesWithLength(conn, p)
case Command_COMMAND_TYPE_BACKUP:
stats.Add(numBackupRequest, 1)

@ -458,6 +458,24 @@ func queryRequestFromStrings(s []string) *command.QueryRequest {
}
}
func executeQueryRequestFromString(s string) *command.ExecuteQueryRequest {
return executeQueryRequestFromStrings([]string{s})
}
func executeQueryRequestFromStrings(s []string) *command.ExecuteQueryRequest {
stmts := make([]*command.Statement, len(s))
for i := range s {
stmts[i] = &command.Statement{
Sql: s[i],
}
}
return &command.ExecuteQueryRequest{
Request: &command.Request{
Statements: stmts,
},
}
}
func backupRequestBinary(leader bool) *command.BackupRequest {
return &command.BackupRequest{
Format: command.BackupRequest_BACKUP_REQUEST_FORMAT_BINARY,

@ -412,6 +412,7 @@ func mustNewMockTLSTransport() *mockTransport {
type mockDatabase struct {
executeFn func(er *command.ExecuteRequest) ([]*command.ExecuteResult, error)
queryFn func(qr *command.QueryRequest) ([]*command.QueryRows, error)
requestFn func(rr *command.ExecuteQueryRequest) ([]*command.ExecuteQueryResponse, error)
backupFn func(br *command.BackupRequest, dst io.Writer) error
loadFn func(lr *command.LoadRequest) error
}
@ -424,6 +425,13 @@ func (m *mockDatabase) Query(qr *command.QueryRequest) ([]*command.QueryRows, er
return m.queryFn(qr)
}
func (m *mockDatabase) Request(rr *command.ExecuteQueryRequest) ([]*command.ExecuteQueryResponse, error) {
if m.requestFn == nil {
return []*command.ExecuteQueryResponse{}, nil
}
return m.requestFn(rr)
}
func (m *mockDatabase) Backup(br *command.BackupRequest, dst io.Writer) error {
if m.backupFn == nil {
return nil

@ -69,6 +69,55 @@ func (QueryRequest_Level) EnumDescriptor() ([]byte, []int) {
return file_command_proto_rawDescGZIP(), []int{3, 0}
}
type ExecuteQueryRequest_Level int32
const (
ExecuteQueryRequest_QUERY_REQUEST_LEVEL_NONE ExecuteQueryRequest_Level = 0
ExecuteQueryRequest_QUERY_REQUEST_LEVEL_WEAK ExecuteQueryRequest_Level = 1
ExecuteQueryRequest_QUERY_REQUEST_LEVEL_STRONG ExecuteQueryRequest_Level = 2
)
// Enum value maps for ExecuteQueryRequest_Level.
var (
ExecuteQueryRequest_Level_name = map[int32]string{
0: "QUERY_REQUEST_LEVEL_NONE",
1: "QUERY_REQUEST_LEVEL_WEAK",
2: "QUERY_REQUEST_LEVEL_STRONG",
}
ExecuteQueryRequest_Level_value = map[string]int32{
"QUERY_REQUEST_LEVEL_NONE": 0,
"QUERY_REQUEST_LEVEL_WEAK": 1,
"QUERY_REQUEST_LEVEL_STRONG": 2,
}
)
func (x ExecuteQueryRequest_Level) Enum() *ExecuteQueryRequest_Level {
p := new(ExecuteQueryRequest_Level)
*p = x
return p
}
func (x ExecuteQueryRequest_Level) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (ExecuteQueryRequest_Level) Descriptor() protoreflect.EnumDescriptor {
return file_command_proto_enumTypes[1].Descriptor()
}
func (ExecuteQueryRequest_Level) Type() protoreflect.EnumType {
return &file_command_proto_enumTypes[1]
}
func (x ExecuteQueryRequest_Level) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use ExecuteQueryRequest_Level.Descriptor instead.
func (ExecuteQueryRequest_Level) EnumDescriptor() ([]byte, []int) {
return file_command_proto_rawDescGZIP(), []int{8, 0}
}
type BackupRequest_Format int32
const (
@ -102,11 +151,11 @@ func (x BackupRequest_Format) String() string {
}
func (BackupRequest_Format) Descriptor() protoreflect.EnumDescriptor {
return file_command_proto_enumTypes[1].Descriptor()
return file_command_proto_enumTypes[2].Descriptor()
}
func (BackupRequest_Format) Type() protoreflect.EnumType {
return &file_command_proto_enumTypes[1]
return &file_command_proto_enumTypes[2]
}
func (x BackupRequest_Format) Number() protoreflect.EnumNumber {
@ -115,18 +164,19 @@ func (x BackupRequest_Format) Number() protoreflect.EnumNumber {
// Deprecated: Use BackupRequest_Format.Descriptor instead.
func (BackupRequest_Format) EnumDescriptor() ([]byte, []int) {
return file_command_proto_rawDescGZIP(), []int{8, 0}
return file_command_proto_rawDescGZIP(), []int{10, 0}
}
type Command_Type int32
const (
Command_COMMAND_TYPE_UNKNOWN Command_Type = 0
Command_COMMAND_TYPE_QUERY Command_Type = 1
Command_COMMAND_TYPE_EXECUTE Command_Type = 2
Command_COMMAND_TYPE_NOOP Command_Type = 3
Command_COMMAND_TYPE_LOAD Command_Type = 4
Command_COMMAND_TYPE_JOIN Command_Type = 5
Command_COMMAND_TYPE_UNKNOWN Command_Type = 0
Command_COMMAND_TYPE_QUERY Command_Type = 1
Command_COMMAND_TYPE_EXECUTE Command_Type = 2
Command_COMMAND_TYPE_NOOP Command_Type = 3
Command_COMMAND_TYPE_LOAD Command_Type = 4
Command_COMMAND_TYPE_JOIN Command_Type = 5
Command_COMMAND_TYPE_EXECUTE_QUERY Command_Type = 6
)
// Enum value maps for Command_Type.
@ -138,14 +188,16 @@ var (
3: "COMMAND_TYPE_NOOP",
4: "COMMAND_TYPE_LOAD",
5: "COMMAND_TYPE_JOIN",
6: "COMMAND_TYPE_EXECUTE_QUERY",
}
Command_Type_value = map[string]int32{
"COMMAND_TYPE_UNKNOWN": 0,
"COMMAND_TYPE_QUERY": 1,
"COMMAND_TYPE_EXECUTE": 2,
"COMMAND_TYPE_NOOP": 3,
"COMMAND_TYPE_LOAD": 4,
"COMMAND_TYPE_JOIN": 5,
"COMMAND_TYPE_UNKNOWN": 0,
"COMMAND_TYPE_QUERY": 1,
"COMMAND_TYPE_EXECUTE": 2,
"COMMAND_TYPE_NOOP": 3,
"COMMAND_TYPE_LOAD": 4,
"COMMAND_TYPE_JOIN": 5,
"COMMAND_TYPE_EXECUTE_QUERY": 6,
}
)
@ -160,11 +212,11 @@ func (x Command_Type) String() string {
}
func (Command_Type) Descriptor() protoreflect.EnumDescriptor {
return file_command_proto_enumTypes[2].Descriptor()
return file_command_proto_enumTypes[3].Descriptor()
}
func (Command_Type) Type() protoreflect.EnumType {
return &file_command_proto_enumTypes[2]
return &file_command_proto_enumTypes[3]
}
func (x Command_Type) Number() protoreflect.EnumNumber {
@ -173,7 +225,7 @@ func (x Command_Type) Number() protoreflect.EnumNumber {
// Deprecated: Use Command_Type.Descriptor instead.
func (Command_Type) EnumDescriptor() ([]byte, []int) {
return file_command_proto_rawDescGZIP(), []int{14, 0}
return file_command_proto_rawDescGZIP(), []int{16, 0}
}
type Parameter struct {
@ -740,6 +792,158 @@ func (x *ExecuteResult) GetTime() float64 {
return 0
}
type ExecuteQueryRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Request *Request `protobuf:"bytes,1,opt,name=request,proto3" json:"request,omitempty"`
Timings bool `protobuf:"varint,2,opt,name=timings,proto3" json:"timings,omitempty"`
Level ExecuteQueryRequest_Level `protobuf:"varint,3,opt,name=level,proto3,enum=command.ExecuteQueryRequest_Level" json:"level,omitempty"`
Freshness int64 `protobuf:"varint,4,opt,name=freshness,proto3" json:"freshness,omitempty"`
}
func (x *ExecuteQueryRequest) Reset() {
*x = ExecuteQueryRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_command_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ExecuteQueryRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ExecuteQueryRequest) ProtoMessage() {}
func (x *ExecuteQueryRequest) ProtoReflect() protoreflect.Message {
mi := &file_command_proto_msgTypes[8]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ExecuteQueryRequest.ProtoReflect.Descriptor instead.
func (*ExecuteQueryRequest) Descriptor() ([]byte, []int) {
return file_command_proto_rawDescGZIP(), []int{8}
}
func (x *ExecuteQueryRequest) GetRequest() *Request {
if x != nil {
return x.Request
}
return nil
}
func (x *ExecuteQueryRequest) GetTimings() bool {
if x != nil {
return x.Timings
}
return false
}
func (x *ExecuteQueryRequest) GetLevel() ExecuteQueryRequest_Level {
if x != nil {
return x.Level
}
return ExecuteQueryRequest_QUERY_REQUEST_LEVEL_NONE
}
func (x *ExecuteQueryRequest) GetFreshness() int64 {
if x != nil {
return x.Freshness
}
return 0
}
type ExecuteQueryResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Types that are assignable to Result:
//
// *ExecuteQueryResponse_Q
// *ExecuteQueryResponse_E
Result isExecuteQueryResponse_Result `protobuf_oneof:"result"`
}
func (x *ExecuteQueryResponse) Reset() {
*x = ExecuteQueryResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_command_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ExecuteQueryResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ExecuteQueryResponse) ProtoMessage() {}
func (x *ExecuteQueryResponse) ProtoReflect() protoreflect.Message {
mi := &file_command_proto_msgTypes[9]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ExecuteQueryResponse.ProtoReflect.Descriptor instead.
func (*ExecuteQueryResponse) Descriptor() ([]byte, []int) {
return file_command_proto_rawDescGZIP(), []int{9}
}
func (m *ExecuteQueryResponse) GetResult() isExecuteQueryResponse_Result {
if m != nil {
return m.Result
}
return nil
}
func (x *ExecuteQueryResponse) GetQ() *QueryRows {
if x, ok := x.GetResult().(*ExecuteQueryResponse_Q); ok {
return x.Q
}
return nil
}
func (x *ExecuteQueryResponse) GetE() *ExecuteResult {
if x, ok := x.GetResult().(*ExecuteQueryResponse_E); ok {
return x.E
}
return nil
}
type isExecuteQueryResponse_Result interface {
isExecuteQueryResponse_Result()
}
type ExecuteQueryResponse_Q struct {
Q *QueryRows `protobuf:"bytes,1,opt,name=q,proto3,oneof"`
}
type ExecuteQueryResponse_E struct {
E *ExecuteResult `protobuf:"bytes,2,opt,name=e,proto3,oneof"`
}
func (*ExecuteQueryResponse_Q) isExecuteQueryResponse_Result() {}
func (*ExecuteQueryResponse_E) isExecuteQueryResponse_Result() {}
type BackupRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@ -752,7 +956,7 @@ type BackupRequest struct {
func (x *BackupRequest) Reset() {
*x = BackupRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_command_proto_msgTypes[8]
mi := &file_command_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -765,7 +969,7 @@ func (x *BackupRequest) String() string {
func (*BackupRequest) ProtoMessage() {}
func (x *BackupRequest) ProtoReflect() protoreflect.Message {
mi := &file_command_proto_msgTypes[8]
mi := &file_command_proto_msgTypes[10]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -778,7 +982,7 @@ func (x *BackupRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use BackupRequest.ProtoReflect.Descriptor instead.
func (*BackupRequest) Descriptor() ([]byte, []int) {
return file_command_proto_rawDescGZIP(), []int{8}
return file_command_proto_rawDescGZIP(), []int{10}
}
func (x *BackupRequest) GetFormat() BackupRequest_Format {
@ -806,7 +1010,7 @@ type LoadRequest struct {
func (x *LoadRequest) Reset() {
*x = LoadRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_command_proto_msgTypes[9]
mi := &file_command_proto_msgTypes[11]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -819,7 +1023,7 @@ func (x *LoadRequest) String() string {
func (*LoadRequest) ProtoMessage() {}
func (x *LoadRequest) ProtoReflect() protoreflect.Message {
mi := &file_command_proto_msgTypes[9]
mi := &file_command_proto_msgTypes[11]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -832,7 +1036,7 @@ func (x *LoadRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use LoadRequest.ProtoReflect.Descriptor instead.
func (*LoadRequest) Descriptor() ([]byte, []int) {
return file_command_proto_rawDescGZIP(), []int{9}
return file_command_proto_rawDescGZIP(), []int{11}
}
func (x *LoadRequest) GetData() []byte {
@ -855,7 +1059,7 @@ type JoinRequest struct {
func (x *JoinRequest) Reset() {
*x = JoinRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_command_proto_msgTypes[10]
mi := &file_command_proto_msgTypes[12]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -868,7 +1072,7 @@ func (x *JoinRequest) String() string {
func (*JoinRequest) ProtoMessage() {}
func (x *JoinRequest) ProtoReflect() protoreflect.Message {
mi := &file_command_proto_msgTypes[10]
mi := &file_command_proto_msgTypes[12]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -881,7 +1085,7 @@ func (x *JoinRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use JoinRequest.ProtoReflect.Descriptor instead.
func (*JoinRequest) Descriptor() ([]byte, []int) {
return file_command_proto_rawDescGZIP(), []int{10}
return file_command_proto_rawDescGZIP(), []int{12}
}
func (x *JoinRequest) GetId() string {
@ -917,7 +1121,7 @@ type NotifyRequest struct {
func (x *NotifyRequest) Reset() {
*x = NotifyRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_command_proto_msgTypes[11]
mi := &file_command_proto_msgTypes[13]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -930,7 +1134,7 @@ func (x *NotifyRequest) String() string {
func (*NotifyRequest) ProtoMessage() {}
func (x *NotifyRequest) ProtoReflect() protoreflect.Message {
mi := &file_command_proto_msgTypes[11]
mi := &file_command_proto_msgTypes[13]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -943,7 +1147,7 @@ func (x *NotifyRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use NotifyRequest.ProtoReflect.Descriptor instead.
func (*NotifyRequest) Descriptor() ([]byte, []int) {
return file_command_proto_rawDescGZIP(), []int{11}
return file_command_proto_rawDescGZIP(), []int{13}
}
func (x *NotifyRequest) GetId() string {
@ -971,7 +1175,7 @@ type RemoveNodeRequest struct {
func (x *RemoveNodeRequest) Reset() {
*x = RemoveNodeRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_command_proto_msgTypes[12]
mi := &file_command_proto_msgTypes[14]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -984,7 +1188,7 @@ func (x *RemoveNodeRequest) String() string {
func (*RemoveNodeRequest) ProtoMessage() {}
func (x *RemoveNodeRequest) ProtoReflect() protoreflect.Message {
mi := &file_command_proto_msgTypes[12]
mi := &file_command_proto_msgTypes[14]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -997,7 +1201,7 @@ func (x *RemoveNodeRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use RemoveNodeRequest.ProtoReflect.Descriptor instead.
func (*RemoveNodeRequest) Descriptor() ([]byte, []int) {
return file_command_proto_rawDescGZIP(), []int{12}
return file_command_proto_rawDescGZIP(), []int{14}
}
func (x *RemoveNodeRequest) GetId() string {
@ -1018,7 +1222,7 @@ type Noop struct {
func (x *Noop) Reset() {
*x = Noop{}
if protoimpl.UnsafeEnabled {
mi := &file_command_proto_msgTypes[13]
mi := &file_command_proto_msgTypes[15]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -1031,7 +1235,7 @@ func (x *Noop) String() string {
func (*Noop) ProtoMessage() {}
func (x *Noop) ProtoReflect() protoreflect.Message {
mi := &file_command_proto_msgTypes[13]
mi := &file_command_proto_msgTypes[15]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1044,7 +1248,7 @@ func (x *Noop) ProtoReflect() protoreflect.Message {
// Deprecated: Use Noop.ProtoReflect.Descriptor instead.
func (*Noop) Descriptor() ([]byte, []int) {
return file_command_proto_rawDescGZIP(), []int{13}
return file_command_proto_rawDescGZIP(), []int{15}
}
func (x *Noop) GetId() string {
@ -1067,7 +1271,7 @@ type Command struct {
func (x *Command) Reset() {
*x = Command{}
if protoimpl.UnsafeEnabled {
mi := &file_command_proto_msgTypes[14]
mi := &file_command_proto_msgTypes[16]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -1080,7 +1284,7 @@ func (x *Command) String() string {
func (*Command) ProtoMessage() {}
func (x *Command) ProtoReflect() protoreflect.Message {
mi := &file_command_proto_msgTypes[14]
mi := &file_command_proto_msgTypes[16]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1093,7 +1297,7 @@ func (x *Command) ProtoReflect() protoreflect.Message {
// Deprecated: Use Command.ProtoReflect.Descriptor instead.
func (*Command) Descriptor() ([]byte, []int) {
return file_command_proto_rawDescGZIP(), []int{14}
return file_command_proto_rawDescGZIP(), []int{16}
}
func (x *Command) GetType() Command_Type {
@ -1184,54 +1388,80 @@ var file_command_proto_rawDesc = []byte{
0x66, 0x66, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72,
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x12, 0x0a,
0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x04, 0x74, 0x69, 0x6d,
0x65, 0x22, 0xc9, 0x01, 0x0a, 0x0d, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x12, 0x35, 0x0a, 0x06, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0e, 0x32, 0x1d, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x42, 0x61,
0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, 0x6f, 0x72, 0x6d,
0x61, 0x74, 0x52, 0x06, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x4c, 0x65,
0x61, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x4c, 0x65, 0x61, 0x64,
0x65, 0x72, 0x22, 0x69, 0x0a, 0x06, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x12, 0x1e, 0x0a, 0x1a,
0x42, 0x41, 0x43, 0x4b, 0x55, 0x50, 0x5f, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x46,
0x4f, 0x52, 0x4d, 0x41, 0x54, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x1d, 0x0a, 0x19,
0x42, 0x41, 0x43, 0x4b, 0x55, 0x50, 0x5f, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x46,
0x4f, 0x52, 0x4d, 0x41, 0x54, 0x5f, 0x53, 0x51, 0x4c, 0x10, 0x01, 0x12, 0x20, 0x0a, 0x1c, 0x42,
0x41, 0x43, 0x4b, 0x55, 0x50, 0x5f, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x46, 0x4f,
0x52, 0x4d, 0x41, 0x54, 0x5f, 0x42, 0x49, 0x4e, 0x41, 0x52, 0x59, 0x10, 0x02, 0x22, 0x21, 0x0a,
0x0b, 0x4c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04,
0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61,
0x22, 0x4d, 0x0a, 0x0b, 0x4a, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12,
0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x6f, 0x74,
0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x76, 0x6f, 0x74, 0x65, 0x72, 0x22,
0x39, 0x0a, 0x0d, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x65, 0x22, 0x98, 0x02, 0x0a, 0x13, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x51, 0x75, 0x65,
0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x07, 0x72, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x63, 0x6f, 0x6d,
0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x07, 0x72, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x73,
0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x73, 0x12,
0x38, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x22,
0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65,
0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4c, 0x65, 0x76,
0x65, 0x6c, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x66, 0x72, 0x65,
0x73, 0x68, 0x6e, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x66, 0x72,
0x65, 0x73, 0x68, 0x6e, 0x65, 0x73, 0x73, 0x22, 0x63, 0x0a, 0x05, 0x4c, 0x65, 0x76, 0x65, 0x6c,
0x12, 0x1c, 0x0a, 0x18, 0x51, 0x55, 0x45, 0x52, 0x59, 0x5f, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53,
0x54, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x1c,
0x0a, 0x18, 0x51, 0x55, 0x45, 0x52, 0x59, 0x5f, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f,
0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x57, 0x45, 0x41, 0x4b, 0x10, 0x01, 0x12, 0x1e, 0x0a, 0x1a,
0x51, 0x55, 0x45, 0x52, 0x59, 0x5f, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x4c, 0x45,
0x56, 0x45, 0x4c, 0x5f, 0x53, 0x54, 0x52, 0x4f, 0x4e, 0x47, 0x10, 0x02, 0x22, 0x6c, 0x0a, 0x14,
0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x22, 0x0a, 0x01, 0x71, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x12, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52,
0x6f, 0x77, 0x73, 0x48, 0x00, 0x52, 0x01, 0x71, 0x12, 0x26, 0x0a, 0x01, 0x65, 0x18, 0x02, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x45, 0x78,
0x65, 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x48, 0x00, 0x52, 0x01, 0x65,
0x42, 0x08, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0xc9, 0x01, 0x0a, 0x0d, 0x42,
0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x35, 0x0a, 0x06,
0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1d, 0x2e, 0x63,
0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x52, 0x06, 0x66, 0x6f, 0x72,
0x6d, 0x61, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20,
0x01, 0x28, 0x08, 0x52, 0x06, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x22, 0x69, 0x0a, 0x06, 0x46,
0x6f, 0x72, 0x6d, 0x61, 0x74, 0x12, 0x1e, 0x0a, 0x1a, 0x42, 0x41, 0x43, 0x4b, 0x55, 0x50, 0x5f,
0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x46, 0x4f, 0x52, 0x4d, 0x41, 0x54, 0x5f, 0x4e,
0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x1d, 0x0a, 0x19, 0x42, 0x41, 0x43, 0x4b, 0x55, 0x50, 0x5f,
0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x46, 0x4f, 0x52, 0x4d, 0x41, 0x54, 0x5f, 0x53,
0x51, 0x4c, 0x10, 0x01, 0x12, 0x20, 0x0a, 0x1c, 0x42, 0x41, 0x43, 0x4b, 0x55, 0x50, 0x5f, 0x52,
0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x46, 0x4f, 0x52, 0x4d, 0x41, 0x54, 0x5f, 0x42, 0x49,
0x4e, 0x41, 0x52, 0x59, 0x10, 0x02, 0x22, 0x21, 0x0a, 0x0b, 0x4c, 0x6f, 0x61, 0x64, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x4d, 0x0a, 0x0b, 0x4a, 0x6f, 0x69,
0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72,
0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65,
0x73, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x6f, 0x74, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28,
0x08, 0x52, 0x05, 0x76, 0x6f, 0x74, 0x65, 0x72, 0x22, 0x39, 0x0a, 0x0d, 0x4e, 0x6f, 0x74, 0x69,
0x66, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64,
0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72,
0x65, 0x73, 0x73, 0x22, 0x23, 0x0a, 0x11, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x4e, 0x6f, 0x64,
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x16, 0x0a, 0x04, 0x4e, 0x6f, 0x6f, 0x70,
0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64,
0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28,
0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x23, 0x0a, 0x11, 0x52, 0x65,
0x6d, 0x6f, 0x76, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22,
0x16, 0x0a, 0x04, 0x4e, 0x6f, 0x6f, 0x70, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20,
0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x8f, 0x02, 0x0a, 0x07, 0x43, 0x6f, 0x6d, 0x6d,
0x61, 0x6e, 0x64, 0x12, 0x29, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0e, 0x32, 0x15, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x43, 0x6f, 0x6d, 0x6d,
0x61, 0x6e, 0x64, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1f,
0x0a, 0x0b, 0x73, 0x75, 0x62, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x02, 0x20,
0x01, 0x28, 0x0c, 0x52, 0x0a, 0x73, 0x75, 0x62, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12,
0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x18, 0x03, 0x20,
0x01, 0x28, 0x08, 0x52, 0x0a, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x22,
0x97, 0x01, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x14, 0x43, 0x4f, 0x4d, 0x4d,
0x41, 0x4e, 0x44, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e,
0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x43, 0x4f, 0x4d, 0x4d, 0x41, 0x4e, 0x44, 0x5f, 0x54, 0x59,
0x50, 0x45, 0x5f, 0x51, 0x55, 0x45, 0x52, 0x59, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x43, 0x4f,
0x4d, 0x4d, 0x41, 0x4e, 0x44, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x45, 0x58, 0x45, 0x43, 0x55,
0x54, 0x45, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x4f, 0x4d, 0x4d, 0x41, 0x4e, 0x44, 0x5f,
0x54, 0x59, 0x50, 0x45, 0x5f, 0x4e, 0x4f, 0x4f, 0x50, 0x10, 0x03, 0x12, 0x15, 0x0a, 0x11, 0x43,
0x4f, 0x4d, 0x4d, 0x41, 0x4e, 0x44, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4c, 0x4f, 0x41, 0x44,
0x10, 0x04, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x4f, 0x4d, 0x4d, 0x41, 0x4e, 0x44, 0x5f, 0x54, 0x59,
0x50, 0x45, 0x5f, 0x4a, 0x4f, 0x49, 0x4e, 0x10, 0x05, 0x42, 0x22, 0x5a, 0x20, 0x67, 0x69, 0x74,
0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x71, 0x6c, 0x69, 0x74, 0x65, 0x2f, 0x72,
0x71, 0x6c, 0x69, 0x74, 0x65, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x62, 0x06, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x33,
0x22, 0xaf, 0x02, 0x0a, 0x07, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x29, 0x0a, 0x04,
0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x63, 0x6f, 0x6d,
0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x54, 0x79, 0x70,
0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x75, 0x62, 0x5f, 0x63,
0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x73, 0x75,
0x62, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x70,
0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x63, 0x6f,
0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x22, 0xb7, 0x01, 0x0a, 0x04, 0x54, 0x79, 0x70,
0x65, 0x12, 0x18, 0x0a, 0x14, 0x43, 0x4f, 0x4d, 0x4d, 0x41, 0x4e, 0x44, 0x5f, 0x54, 0x59, 0x50,
0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x43,
0x4f, 0x4d, 0x4d, 0x41, 0x4e, 0x44, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x51, 0x55, 0x45, 0x52,
0x59, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x43, 0x4f, 0x4d, 0x4d, 0x41, 0x4e, 0x44, 0x5f, 0x54,
0x59, 0x50, 0x45, 0x5f, 0x45, 0x58, 0x45, 0x43, 0x55, 0x54, 0x45, 0x10, 0x02, 0x12, 0x15, 0x0a,
0x11, 0x43, 0x4f, 0x4d, 0x4d, 0x41, 0x4e, 0x44, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4e, 0x4f,
0x4f, 0x50, 0x10, 0x03, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x4f, 0x4d, 0x4d, 0x41, 0x4e, 0x44, 0x5f,
0x54, 0x59, 0x50, 0x45, 0x5f, 0x4c, 0x4f, 0x41, 0x44, 0x10, 0x04, 0x12, 0x15, 0x0a, 0x11, 0x43,
0x4f, 0x4d, 0x4d, 0x41, 0x4e, 0x44, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4a, 0x4f, 0x49, 0x4e,
0x10, 0x05, 0x12, 0x1e, 0x0a, 0x1a, 0x43, 0x4f, 0x4d, 0x4d, 0x41, 0x4e, 0x44, 0x5f, 0x54, 0x59,
0x50, 0x45, 0x5f, 0x45, 0x58, 0x45, 0x43, 0x55, 0x54, 0x45, 0x5f, 0x51, 0x55, 0x45, 0x52, 0x59,
0x10, 0x06, 0x42, 0x22, 0x5a, 0x20, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
0x2f, 0x72, 0x71, 0x6c, 0x69, 0x74, 0x65, 0x2f, 0x72, 0x71, 0x6c, 0x69, 0x74, 0x65, 0x2f, 0x63,
0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@ -1246,43 +1476,50 @@ func file_command_proto_rawDescGZIP() []byte {
return file_command_proto_rawDescData
}
var file_command_proto_enumTypes = make([]protoimpl.EnumInfo, 3)
var file_command_proto_msgTypes = make([]protoimpl.MessageInfo, 15)
var file_command_proto_enumTypes = make([]protoimpl.EnumInfo, 4)
var file_command_proto_msgTypes = make([]protoimpl.MessageInfo, 17)
var file_command_proto_goTypes = []interface{}{
(QueryRequest_Level)(0), // 0: command.QueryRequest.Level
(BackupRequest_Format)(0), // 1: command.BackupRequest.Format
(Command_Type)(0), // 2: command.Command.Type
(*Parameter)(nil), // 3: command.Parameter
(*Statement)(nil), // 4: command.Statement
(*Request)(nil), // 5: command.Request
(*QueryRequest)(nil), // 6: command.QueryRequest
(*Values)(nil), // 7: command.Values
(*QueryRows)(nil), // 8: command.QueryRows
(*ExecuteRequest)(nil), // 9: command.ExecuteRequest
(*ExecuteResult)(nil), // 10: command.ExecuteResult
(*BackupRequest)(nil), // 11: command.BackupRequest
(*LoadRequest)(nil), // 12: command.LoadRequest
(*JoinRequest)(nil), // 13: command.JoinRequest
(*NotifyRequest)(nil), // 14: command.NotifyRequest
(*RemoveNodeRequest)(nil), // 15: command.RemoveNodeRequest
(*Noop)(nil), // 16: command.Noop
(*Command)(nil), // 17: command.Command
(QueryRequest_Level)(0), // 0: command.QueryRequest.Level
(ExecuteQueryRequest_Level)(0), // 1: command.ExecuteQueryRequest.Level
(BackupRequest_Format)(0), // 2: command.BackupRequest.Format
(Command_Type)(0), // 3: command.Command.Type
(*Parameter)(nil), // 4: command.Parameter
(*Statement)(nil), // 5: command.Statement
(*Request)(nil), // 6: command.Request
(*QueryRequest)(nil), // 7: command.QueryRequest
(*Values)(nil), // 8: command.Values
(*QueryRows)(nil), // 9: command.QueryRows
(*ExecuteRequest)(nil), // 10: command.ExecuteRequest
(*ExecuteResult)(nil), // 11: command.ExecuteResult
(*ExecuteQueryRequest)(nil), // 12: command.ExecuteQueryRequest
(*ExecuteQueryResponse)(nil), // 13: command.ExecuteQueryResponse
(*BackupRequest)(nil), // 14: command.BackupRequest
(*LoadRequest)(nil), // 15: command.LoadRequest
(*JoinRequest)(nil), // 16: command.JoinRequest
(*NotifyRequest)(nil), // 17: command.NotifyRequest
(*RemoveNodeRequest)(nil), // 18: command.RemoveNodeRequest
(*Noop)(nil), // 19: command.Noop
(*Command)(nil), // 20: command.Command
}
var file_command_proto_depIdxs = []int32{
3, // 0: command.Statement.parameters:type_name -> command.Parameter
4, // 1: command.Request.statements:type_name -> command.Statement
5, // 2: command.QueryRequest.request:type_name -> command.Request
0, // 3: command.QueryRequest.level:type_name -> command.QueryRequest.Level
3, // 4: command.Values.parameters:type_name -> command.Parameter
7, // 5: command.QueryRows.values:type_name -> command.Values
5, // 6: command.ExecuteRequest.request:type_name -> command.Request
1, // 7: command.BackupRequest.format:type_name -> command.BackupRequest.Format
2, // 8: command.Command.type:type_name -> command.Command.Type
9, // [9:9] is the sub-list for method output_type
9, // [9:9] is the sub-list for method input_type
9, // [9:9] is the sub-list for extension type_name
9, // [9:9] is the sub-list for extension extendee
0, // [0:9] is the sub-list for field type_name
4, // 0: command.Statement.parameters:type_name -> command.Parameter
5, // 1: command.Request.statements:type_name -> command.Statement
6, // 2: command.QueryRequest.request:type_name -> command.Request
0, // 3: command.QueryRequest.level:type_name -> command.QueryRequest.Level
4, // 4: command.Values.parameters:type_name -> command.Parameter
8, // 5: command.QueryRows.values:type_name -> command.Values
6, // 6: command.ExecuteRequest.request:type_name -> command.Request
6, // 7: command.ExecuteQueryRequest.request:type_name -> command.Request
1, // 8: command.ExecuteQueryRequest.level:type_name -> command.ExecuteQueryRequest.Level
9, // 9: command.ExecuteQueryResponse.q:type_name -> command.QueryRows
11, // 10: command.ExecuteQueryResponse.e:type_name -> command.ExecuteResult
2, // 11: command.BackupRequest.format:type_name -> command.BackupRequest.Format
3, // 12: command.Command.type:type_name -> command.Command.Type
13, // [13:13] is the sub-list for method output_type
13, // [13:13] is the sub-list for method input_type
13, // [13:13] is the sub-list for extension type_name
13, // [13:13] is the sub-list for extension extendee
0, // [0:13] is the sub-list for field type_name
}
func init() { file_command_proto_init() }
@ -1388,7 +1625,7 @@ func file_command_proto_init() {
}
}
file_command_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*BackupRequest); i {
switch v := v.(*ExecuteQueryRequest); i {
case 0:
return &v.state
case 1:
@ -1400,7 +1637,7 @@ func file_command_proto_init() {
}
}
file_command_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*LoadRequest); i {
switch v := v.(*ExecuteQueryResponse); i {
case 0:
return &v.state
case 1:
@ -1412,7 +1649,7 @@ func file_command_proto_init() {
}
}
file_command_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*JoinRequest); i {
switch v := v.(*BackupRequest); i {
case 0:
return &v.state
case 1:
@ -1424,7 +1661,7 @@ func file_command_proto_init() {
}
}
file_command_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*NotifyRequest); i {
switch v := v.(*LoadRequest); i {
case 0:
return &v.state
case 1:
@ -1436,7 +1673,7 @@ func file_command_proto_init() {
}
}
file_command_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*RemoveNodeRequest); i {
switch v := v.(*JoinRequest); i {
case 0:
return &v.state
case 1:
@ -1448,7 +1685,7 @@ func file_command_proto_init() {
}
}
file_command_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Noop); i {
switch v := v.(*NotifyRequest); i {
case 0:
return &v.state
case 1:
@ -1460,6 +1697,30 @@ func file_command_proto_init() {
}
}
file_command_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*RemoveNodeRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_command_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Noop); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_command_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Command); i {
case 0:
return &v.state
@ -1479,13 +1740,17 @@ func file_command_proto_init() {
(*Parameter_Y)(nil),
(*Parameter_S)(nil),
}
file_command_proto_msgTypes[9].OneofWrappers = []interface{}{
(*ExecuteQueryResponse_Q)(nil),
(*ExecuteQueryResponse_E)(nil),
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_command_proto_rawDesc,
NumEnums: 3,
NumMessages: 15,
NumEnums: 4,
NumMessages: 17,
NumExtensions: 0,
NumServices: 0,
},

@ -60,6 +60,25 @@ message ExecuteResult {
double time = 4;
}
message ExecuteQueryRequest {
Request request = 1;
bool timings = 2;
enum Level {
QUERY_REQUEST_LEVEL_NONE = 0;
QUERY_REQUEST_LEVEL_WEAK = 1;
QUERY_REQUEST_LEVEL_STRONG = 2;
}
Level level = 3;
int64 freshness = 4;
}
message ExecuteQueryResponse {
oneof result {
QueryRows q = 1;
ExecuteResult e = 2;
}
}
message BackupRequest {
enum Format {
BACKUP_REQUEST_FORMAT_NONE = 0;
@ -101,6 +120,7 @@ message Command {
COMMAND_TYPE_NOOP = 3;
COMMAND_TYPE_LOAD = 4;
COMMAND_TYPE_JOIN = 5;
COMMAND_TYPE_EXECUTE_QUERY = 6;
}
Type type = 1;
bytes sub_command = 2;

@ -15,6 +15,27 @@ var (
ErrTypesColumnsLengthViolation = errors.New("types and columns are different lengths")
)
// ResultRows represents the outcome of an operation that might change rows or
// return query data.
type ResultRows struct {
LastInsertID int64 `json:"last_insert_id,omitempty"`
RowsAffected int64 `json:"rows_affected,omitempty"`
Columns []string `json:"columns,omitempty"`
Types []string `json:"types,omitempty"`
Values [][]interface{} `json:"values,omitempty"`
Error string `json:"error,omitempty"`
Time float64 `json:"time,omitempty"`
}
type AssociativeResultRows struct {
LastInsertID int64 `json:"last_insert_id,omitempty"`
RowsAffected int64 `json:"rows_affected,omitempty"`
Types map[string]string `json:"types,omitempty"`
Rows []map[string]interface{} `json:"rows,omitempty"`
Error string `json:"error,omitempty"`
Time float64 `json:"time,omitempty"`
}
// Result represents the outcome of an operation that changes rows.
type Result struct {
LastInsertID int64 `json:"last_insert_id,omitempty"`
@ -40,6 +61,83 @@ type AssociativeRows struct {
Time float64 `json:"time,omitempty"`
}
// NewResultRowsFromExecuteQueryResponse returns an API ResultRows object from an
// ExecuteQueryResponse.
func NewResultRowsFromExecuteQueryResponse(e *command.ExecuteQueryResponse) (*ResultRows, error) {
er := e.GetE()
qr := e.GetQ()
if er != nil {
return &ResultRows{
LastInsertID: er.LastInsertId,
RowsAffected: er.RowsAffected,
Error: er.Error,
Time: er.Time,
}, nil
} else if qr != nil {
if len(qr.Columns) != len(qr.Types) {
return nil, ErrTypesColumnsLengthViolation
}
values := make([][]interface{}, len(qr.Values))
if err := NewValuesFromQueryValues(values, qr.Values); err != nil {
return nil, err
}
return &ResultRows{
Columns: qr.Columns,
Types: qr.Types,
Values: values,
Error: qr.Error,
Time: qr.Time,
}, nil
}
return nil, errors.New("no ExecuteResult or QueryRows")
}
func NewAssociativeResultRowsFromExecuteQueryResponse(e *command.ExecuteQueryResponse) (*AssociativeResultRows, error) {
er := e.GetE()
qr := e.GetQ()
if er != nil {
return &AssociativeResultRows{
LastInsertID: er.LastInsertId,
RowsAffected: er.RowsAffected,
Error: er.Error,
Time: er.Time,
}, nil
} else if qr != nil {
if len(qr.Columns) != len(qr.Types) {
return nil, ErrTypesColumnsLengthViolation
}
values := make([][]interface{}, len(qr.Values))
if err := NewValuesFromQueryValues(values, qr.Values); err != nil {
return nil, err
}
rows := make([]map[string]interface{}, len(values))
for i := range rows {
m := make(map[string]interface{})
for ii, c := range qr.Columns {
m[c] = values[i][ii]
}
rows[i] = m
}
types := make(map[string]string)
for i := range qr.Types {
types[qr.Columns[i]] = qr.Types[i]
}
return &AssociativeResultRows{
Types: types,
Rows: rows,
Error: qr.Error,
Time: qr.Time,
}, nil
}
return nil, errors.New("no ExecuteResult or QueryRows")
}
// NewResultFromExecuteResult returns an API Result object from an ExecuteResult.
func NewResultFromExecuteResult(e *command.ExecuteResult) (*Result, error) {
return &Result{
@ -142,7 +240,8 @@ func NewValuesFromQueryValues(dest [][]interface{}, v []*command.Values) error {
return nil
}
// Encoder is used to JSON marshal ExecuteResults and QueryRows
// Encoder is used to JSON marshal ExecuteResults, QueryRows
// and ExecuteQueryRequests.
type Encoder struct {
Associative bool
}
@ -210,6 +309,12 @@ func jsonMarshal(i interface{}, f marshalFunc, assoc bool) ([]byte, error) {
}
return f(r)
}
case *command.ExecuteQueryResponse:
r, err := NewResultRowsFromExecuteQueryResponse(v)
if err != nil {
return nil, err
}
return f(r)
case []*command.QueryRows:
var err error
@ -232,6 +337,28 @@ func jsonMarshal(i interface{}, f marshalFunc, assoc bool) ([]byte, error) {
}
return f(rows)
}
case []*command.ExecuteQueryResponse:
if assoc {
res := make([]*AssociativeResultRows, len(v))
for j := range v {
r, err := NewAssociativeResultRowsFromExecuteQueryResponse(v[j])
if err != nil {
return nil, err
}
res[j] = r
}
return f(res)
} else {
res := make([]*ResultRows, len(v))
for j := range v {
r, err := NewResultRowsFromExecuteQueryResponse(v[j])
if err != nil {
return nil, err
}
res[j] = r
}
return f(res)
}
case []*command.Values:
values := make([][]interface{}, len(v))
if err := NewValuesFromQueryValues(values, v); err != nil {

@ -345,3 +345,231 @@ func Test_MarshalQueryAssociativeRowses(t *testing.T) {
t.Fatalf("failed to marshal QueryRows: exp %s, got %s", exp, got)
}
}
func Test_MarshalExecuteQueryResponse(t *testing.T) {
enc := Encoder{}
tests := []struct {
name string
responses []*command.ExecuteQueryResponse
expected string
}{
{
name: "Test with ExecuteResult",
responses: []*command.ExecuteQueryResponse{
{
Result: &command.ExecuteQueryResponse_E{
E: &command.ExecuteResult{
LastInsertId: 123,
RowsAffected: 456,
},
},
},
},
expected: `[{"last_insert_id":123,"rows_affected":456}]`,
},
{
name: "Test with QueryRows",
responses: []*command.ExecuteQueryResponse{
{
Result: &command.ExecuteQueryResponse_Q{
Q: &command.QueryRows{
Columns: []string{"column1", "column2"},
Types: []string{"type1", "type2"},
Values: []*command.Values{
{
Parameters: []*command.Parameter{
{
Value: &command.Parameter_I{
I: 123,
},
},
{
Value: &command.Parameter_S{
S: "fiona",
},
},
},
},
},
},
},
},
},
expected: `[{"columns":["column1","column2"],"types":["type1","type2"],"values":[[123,"fiona"]]}]`,
},
{
name: "Test with ExecuteResult and QueryRows",
responses: []*command.ExecuteQueryResponse{
{
Result: &command.ExecuteQueryResponse_E{
E: &command.ExecuteResult{
LastInsertId: 123,
RowsAffected: 456,
},
},
},
{
Result: &command.ExecuteQueryResponse_E{
E: &command.ExecuteResult{
Error: "unique constraint failed",
},
},
},
{
Result: &command.ExecuteQueryResponse_Q{
Q: &command.QueryRows{
Columns: []string{"column1", "column2"},
Types: []string{"int", "text"},
Values: []*command.Values{
{
Parameters: []*command.Parameter{
{
Value: &command.Parameter_I{
I: 456,
},
},
{
Value: &command.Parameter_S{
S: "declan",
},
},
},
},
},
},
},
},
},
expected: `[{"last_insert_id":123,"rows_affected":456},{"error":"unique constraint failed"},{"columns":["column1","column2"],"types":["int","text"],"values":[[456,"declan"]]}]`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
data, err := enc.JSONMarshal(tt.responses)
if err != nil {
t.Errorf("failed to marshal ExecuteQueryResponse: %v", err)
}
if string(data) != tt.expected {
t.Errorf("unexpected JSON output: got %v want %v", string(data), tt.expected)
}
})
}
}
func Test_MarshalExecuteQueryAssociativeResponse(t *testing.T) {
enc := Encoder{
Associative: true,
}
tests := []struct {
name string
responses []*command.ExecuteQueryResponse
expected string
}{
{
name: "Test with ExecuteResult",
responses: []*command.ExecuteQueryResponse{
{
Result: &command.ExecuteQueryResponse_E{
E: &command.ExecuteResult{
LastInsertId: 123,
RowsAffected: 456,
},
},
},
},
expected: `[{"last_insert_id":123,"rows_affected":456}]`,
},
{
name: "Test with QueryRows",
responses: []*command.ExecuteQueryResponse{
{
Result: &command.ExecuteQueryResponse_Q{
Q: &command.QueryRows{
Columns: []string{"column1", "column2"},
Types: []string{"type1", "type2"},
Values: []*command.Values{
{
Parameters: []*command.Parameter{
{
Value: &command.Parameter_I{
I: 123,
},
},
{
Value: &command.Parameter_S{
S: "fiona",
},
},
},
},
},
},
},
},
},
expected: `[{"types":{"column1":"type1","column2":"type2"},"rows":[{"column1":123,"column2":"fiona"}]}]`,
},
{
name: "Test with ExecuteResult and QueryRows",
responses: []*command.ExecuteQueryResponse{
{
Result: &command.ExecuteQueryResponse_E{
E: &command.ExecuteResult{
LastInsertId: 123,
RowsAffected: 456,
},
},
},
{
Result: &command.ExecuteQueryResponse_E{
E: &command.ExecuteResult{
Error: "unique constraint failed",
},
},
},
{
Result: &command.ExecuteQueryResponse_Q{
Q: &command.QueryRows{
Columns: []string{"column1", "column2"},
Types: []string{"int", "text"},
Values: []*command.Values{
{
Parameters: []*command.Parameter{
{
Value: &command.Parameter_I{
I: 456,
},
},
{
Value: &command.Parameter_S{
S: "declan",
},
},
},
},
},
},
},
},
},
expected: `[{"last_insert_id":123,"rows_affected":456},{"error":"unique constraint failed"},{"types":{"column1":"int","column2":"text"},"rows":[{"column1":456,"column2":"declan"}]}]`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
data, err := enc.JSONMarshal(tt.responses)
if err != nil {
t.Errorf("failed to marshal ExecuteQueryResponse: %v", err)
}
if string(data) != tt.expected {
t.Errorf("unexpected JSON output: got %v want %v", string(data), tt.expected)
}
})
}
}

@ -212,3 +212,16 @@ func gzUncompress(b []byte) ([]byte, error) {
}
return ub, nil
}
func MapConsistencyLevel(in QueryRequest_Level) ExecuteQueryRequest_Level {
switch in {
case QueryRequest_QUERY_REQUEST_LEVEL_NONE:
return ExecuteQueryRequest_QUERY_REQUEST_LEVEL_NONE
case QueryRequest_QUERY_REQUEST_LEVEL_WEAK:
return ExecuteQueryRequest_QUERY_REQUEST_LEVEL_WEAK
case QueryRequest_QUERY_REQUEST_LEVEL_STRONG:
return ExecuteQueryRequest_QUERY_REQUEST_LEVEL_STRONG
default:
return ExecuteQueryRequest_QUERY_REQUEST_LEVEL_WEAK
}
}

@ -21,15 +21,14 @@ import (
const bkDelay = 250
const (
onDiskMaxOpenConns = 32
onDiskMaxIdleTime = 120 * time.Second
numExecutions = "executions"
numExecutionErrors = "execution_errors"
numQueries = "queries"
numQueryErrors = "query_errors"
numRequests = "requests"
numETx = "execute_transactions"
numQTx = "query_transactions"
numRTx = "request_transactions"
)
// DBVersion is the SQLite version.
@ -52,8 +51,10 @@ func ResetStats() {
stats.Add(numExecutionErrors, 0)
stats.Add(numQueries, 0)
stats.Add(numQueryErrors, 0)
stats.Add(numRequests, 0)
stats.Add(numETx, 0)
stats.Add(numQTx, 0)
stats.Add(numRTx, 0)
}
// DB is the SQL database.
@ -416,18 +417,22 @@ func (db *DB) ExecuteStringStmt(query string) ([]*command.ExecuteResult, error)
// Execute executes queries that modify the database.
func (db *DB) Execute(req *command.Request, xTime bool) ([]*command.ExecuteResult, error) {
stats.Add(numExecutions, int64(len(req.Statements)))
conn, err := db.rwDB.Conn(context.Background())
if err != nil {
return nil, err
}
defer conn.Close()
return db.executeWithConn(req, xTime, conn)
}
type Execer interface {
ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
}
type execer interface {
ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
}
func (db *DB) executeWithConn(req *command.Request, xTime bool, conn *sql.Conn) ([]*command.ExecuteResult, error) {
var err error
var execer Execer
var execer execer
var tx *sql.Tx
if req.Transaction {
stats.Add(numETx, 1)
@ -468,49 +473,13 @@ func (db *DB) Execute(req *command.Request, xTime bool) ([]*command.ExecuteResul
continue
}
result := &command.ExecuteResult{}
start := time.Now()
parameters, err := parametersToValues(stmt.Parameters)
if err != nil {
if handleError(result, err) {
continue
}
break
}
r, err := execer.ExecContext(context.Background(), ss, parameters...)
result, err := db.executeStmtWithConn(stmt, xTime, execer)
if err != nil {
if handleError(result, err) {
continue
}
break
}
if r == nil {
continue
}
lid, err := r.LastInsertId()
if err != nil {
if handleError(result, err) {
continue
}
break
}
result.LastInsertId = lid
ra, err := r.RowsAffected()
if err != nil {
if handleError(result, err) {
continue
}
break
}
result.RowsAffected = ra
if xTime {
result.Time = time.Now().Sub(start).Seconds()
}
allResults = append(allResults, result)
}
@ -520,6 +489,45 @@ func (db *DB) Execute(req *command.Request, xTime bool) ([]*command.ExecuteResul
return allResults, err
}
func (db *DB) executeStmtWithConn(stmt *command.Statement, xTime bool, e execer) (*command.ExecuteResult, error) {
result := &command.ExecuteResult{}
start := time.Now()
parameters, err := parametersToValues(stmt.Parameters)
if err != nil {
result.Error = err.Error()
return result, nil
}
r, err := e.ExecContext(context.Background(), stmt.Sql, parameters...)
if err != nil {
result.Error = err.Error()
return result, err
}
if r == nil {
return result, nil
}
lid, err := r.LastInsertId()
if err != nil {
result.Error = err.Error()
return result, err
}
result.LastInsertId = lid
ra, err := r.RowsAffected()
if err != nil {
result.Error = err.Error()
return result, err
}
result.RowsAffected = ra
if xTime {
result.Time = time.Since(start).Seconds()
}
return result, nil
}
// QueryStringStmt executes a single query that return rows, but don't modify database.
func (db *DB) QueryStringStmt(query string) ([]*command.QueryRows, error) {
r := &command.Request{
@ -543,13 +551,14 @@ func (db *DB) Query(req *command.Request, xTime bool) ([]*command.QueryRows, err
return db.queryWithConn(req, xTime, conn)
}
type queryer interface {
QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
}
func (db *DB) queryWithConn(req *command.Request, xTime bool, conn *sql.Conn) ([]*command.QueryRows, error) {
var err error
type Queryer interface {
QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
}
var queryer Queryer
var queryer queryer
var tx *sql.Tx
if req.Transaction {
stats.Add(numQTx, 1)
@ -570,107 +579,196 @@ func (db *DB) queryWithConn(req *command.Request, xTime bool, conn *sql.Conn) ([
continue
}
rows := &command.QueryRows{}
start := time.Now()
var rows *command.QueryRows
var err error
// Do best-effort check that the statement won't try to change
// the database. As per the SQLite documentation, this will not
// cover 100% of possibilities, but should cover most.
var readOnly bool
f := func(driverConn interface{}) error {
c := driverConn.(*sqlite3.SQLiteConn)
drvStmt, err := c.Prepare(sql)
if err != nil {
return err
}
defer drvStmt.Close()
sqliteStmt := drvStmt.(*sqlite3.SQLiteStmt)
readOnly = sqliteStmt.Readonly()
return nil
}
if err := conn.Raw(f); err != nil {
readOnly, err := db.StmtReadOnly(sql)
if err != nil {
stats.Add(numQueryErrors, 1)
rows.Error = err.Error()
rows = &command.QueryRows{
Error: err.Error(),
}
allRows = append(allRows, rows)
continue
}
if !readOnly {
stats.Add(numQueryErrors, 1)
rows.Error = "attempt to change database via query operation"
rows = &command.QueryRows{
Error: "attempt to change database via query operation",
}
allRows = append(allRows, rows)
continue
}
parameters, err := parametersToValues(stmt.Parameters)
rows, err = db.queryStmtWithConn(stmt, xTime, queryer)
if err != nil {
stats.Add(numQueryErrors, 1)
rows.Error = err.Error()
allRows = append(allRows, rows)
continue
rows = &command.QueryRows{
Error: err.Error(),
}
}
allRows = append(allRows, rows)
}
rs, err := queryer.QueryContext(context.Background(), sql, parameters...)
if err != nil {
stats.Add(numQueryErrors, 1)
rows.Error = err.Error()
allRows = append(allRows, rows)
continue
}
defer rs.Close()
if tx != nil {
err = tx.Commit()
}
return allRows, err
}
func (db *DB) queryStmtWithConn(stmt *command.Statement, xTime bool, q queryer) (*command.QueryRows, error) {
rows := &command.QueryRows{}
start := time.Now()
columns, err := rs.Columns()
parameters, err := parametersToValues(stmt.Parameters)
if err != nil {
stats.Add(numQueryErrors, 1)
rows.Error = err.Error()
return rows, nil
}
rs, err := q.QueryContext(context.Background(), stmt.Sql, parameters...)
if err != nil {
stats.Add(numQueryErrors, 1)
rows.Error = err.Error()
return rows, nil
}
defer rs.Close()
columns, err := rs.Columns()
if err != nil {
return nil, err
}
types, err := rs.ColumnTypes()
if err != nil {
return nil, err
}
xTypes := make([]string, len(types))
for i := range types {
xTypes[i] = strings.ToLower(types[i].DatabaseTypeName())
}
for rs.Next() {
dest := make([]interface{}, len(columns))
ptrs := make([]interface{}, len(dest))
for i := range ptrs {
ptrs[i] = &dest[i]
}
if err := rs.Scan(ptrs...); err != nil {
return nil, err
}
params, err := normalizeRowValues(dest, xTypes)
if err != nil {
return nil, err
}
rows.Values = append(rows.Values, &command.Values{
Parameters: params,
})
}
// Check for errors from iterating over rows.
if err := rs.Err(); err != nil {
stats.Add(numQueryErrors, 1)
rows.Error = err.Error()
return rows, nil
}
if xTime {
rows.Time = time.Since(start).Seconds()
}
rows.Columns = columns
rows.Types = xTypes
return rows, nil
}
// RequestStringStmts processes a request that can contain both executes and queries.
func (db *DB) RequestStringStmts(stmts []string) ([]*command.ExecuteQueryResponse, error) {
req := &command.Request{}
for _, q := range stmts {
req.Statements = append(req.Statements, &command.Statement{
Sql: q,
})
}
return db.Request(req, false)
}
// Request processes a request that can contain both executes and queries.
func (db *DB) Request(req *command.Request, xTime bool) ([]*command.ExecuteQueryResponse, error) {
stats.Add(numRequests, int64(len(req.Statements)))
conn, err := db.rwDB.Conn(context.Background())
if err != nil {
return nil, err
}
defer conn.Close()
types, err := rs.ColumnTypes()
var queryer queryer
var execer execer
var tx *sql.Tx
if req.Transaction {
stats.Add(numRTx, 1)
tx, err = conn.BeginTx(context.Background(), nil)
if err != nil {
return nil, err
}
xTypes := make([]string, len(types))
for i := range types {
xTypes[i] = strings.ToLower(types[i].DatabaseTypeName())
}
defer tx.Rollback() // Will be ignored if tx is committed
queryer = tx
execer = tx
} else {
queryer = conn
execer = conn
}
for rs.Next() {
dest := make([]interface{}, len(columns))
ptrs := make([]interface{}, len(dest))
for i := range ptrs {
ptrs[i] = &dest[i]
}
if err := rs.Scan(ptrs...); err != nil {
return nil, err
}
params, err := normalizeRowValues(dest, xTypes)
if err != nil {
return nil, err
}
rows.Values = append(rows.Values, &command.Values{
Parameters: params,
})
// abortOnError indicates whether the caller should continue
// processing or break.
abortOnError := func(err error) bool {
if err != nil && tx != nil {
tx.Rollback()
tx = nil
return true
}
return false
}
// Check for errors from iterating over rows.
if err := rs.Err(); err != nil {
stats.Add(numQueryErrors, 1)
rows.Error = err.Error()
allRows = append(allRows, rows)
var eqResponse []*command.ExecuteQueryResponse
for _, stmt := range req.Statements {
ss := stmt.Sql
if ss == "" {
continue
}
if xTime {
rows.Time = time.Now().Sub(start).Seconds()
ro, err := db.StmtReadOnly(ss)
if err != nil {
eqResponse = append(eqResponse, &command.ExecuteQueryResponse{
Result: &command.ExecuteQueryResponse_Q{
Q: &command.QueryRows{
Error: err.Error(),
},
},
})
continue
}
rows.Columns = columns
rows.Types = xTypes
allRows = append(allRows, rows)
if ro {
rows, opErr := db.queryStmtWithConn(stmt, xTime, queryer)
eqResponse = append(eqResponse, createEQQueryResponse(rows, opErr))
if abortOnError(opErr) {
break
}
} else {
result, opErr := db.executeStmtWithConn(stmt, xTime, execer)
eqResponse = append(eqResponse, createEQExecuteResponse(result, opErr))
if abortOnError(opErr) {
break
}
}
}
if tx != nil {
err = tx.Commit()
}
return allRows, err
return eqResponse, err
}
// Backup writes a consistent snapshot of the database to the given file.
@ -827,6 +925,34 @@ func (db *DB) Dump(w io.Writer) error {
return nil
}
// StmtReadOnly returns whether the given SQL statement is read-only.
// As per https://www.sqlite.org/c3ref/stmt_readonly.html, this function
// may not return 100% correct results, but should cover most scenarios.
func (db *DB) StmtReadOnly(sql string) (bool, error) {
var readOnly bool
f := func(driverConn interface{}) error {
c := driverConn.(*sqlite3.SQLiteConn)
drvStmt, err := c.Prepare(sql)
if err != nil {
return err
}
defer drvStmt.Close()
sqliteStmt := drvStmt.(*sqlite3.SQLiteStmt)
readOnly = sqliteStmt.Readonly()
return nil
}
conn, err := db.roDB.Conn(context.Background())
if err != nil {
return false, err
}
defer conn.Close()
if err := conn.Raw(f); err != nil {
return false, err
}
return readOnly, nil
}
func (db *DB) memStats() (map[string]int64, error) {
ms := make(map[string]int64)
for _, p := range []string{
@ -847,6 +973,40 @@ func (db *DB) memStats() (map[string]int64, error) {
return ms, nil
}
func createEQQueryResponse(rows *command.QueryRows, err error) *command.ExecuteQueryResponse {
if err != nil {
return &command.ExecuteQueryResponse{
Result: &command.ExecuteQueryResponse_Q{
Q: &command.QueryRows{
Error: err.Error(),
},
},
}
}
return &command.ExecuteQueryResponse{
Result: &command.ExecuteQueryResponse_Q{
Q: rows,
},
}
}
func createEQExecuteResponse(execResult *command.ExecuteResult, err error) *command.ExecuteQueryResponse {
if err != nil {
return &command.ExecuteQueryResponse{
Result: &command.ExecuteQueryResponse_E{
E: &command.ExecuteResult{
Error: err.Error(),
},
},
}
}
return &command.ExecuteQueryResponse{
Result: &command.ExecuteQueryResponse_E{
E: execResult,
},
}
}
func copyDatabase(dst *DB, src *DB) error {
dstConn, err := dst.rwDB.Conn(context.Background())
if err != nil {

@ -2,6 +2,7 @@ package db
import (
"database/sql"
"errors"
"fmt"
"io/ioutil"
"os"
@ -747,7 +748,7 @@ func Test_SimplePragmaTableInfo(t *testing.T) {
}
}
func Test_WriteOnQueryOnDiskDatabase(t *testing.T) {
func Test_WriteOnQueryOnDiskDatabaseShouldFail(t *testing.T) {
db, path := mustCreateDatabase()
defer db.Close()
defer os.Remove(path)
@ -785,7 +786,7 @@ func Test_WriteOnQueryOnDiskDatabase(t *testing.T) {
}
}
func Test_WriteOnQueryInMemDatabase(t *testing.T) {
func Test_WriteOnQueryInMemDatabaseShouldFail(t *testing.T) {
db := mustCreateInMemoryDatabase()
defer db.Close()
@ -1163,6 +1164,110 @@ func Test_SimpleNamedParameterizedStatements(t *testing.T) {
}
}
func Test_SimpleRequest(t *testing.T) {
db, path := mustCreateDatabase()
defer db.Close()
defer os.Remove(path)
_, err := db.ExecuteStringStmt("CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, first TEXT, last TEXT)")
if err != nil {
t.Fatalf("failed to create table: %s", err.Error())
}
// create table-driven tests
tests := []struct {
name string
stmts []string
exp string
}{
{
name: "insert",
stmts: []string{
`INSERT INTO foo(first, last) VALUES("albert", "einstein")`,
`INSERT INTO foo(first, last) VALUES("isaac", "newton")`,
},
exp: `[{"last_insert_id":1,"rows_affected":1},{"last_insert_id":2,"rows_affected":1}]`,
},
{
name: "select",
stmts: []string{
`SELECT * FROM foo`,
},
exp: `[{"columns":["id","first","last"],"types":["integer","text","text"],"values":[[1,"albert","einstein"],[2,"isaac","newton"]]}]`,
},
{
name: "update",
stmts: []string{
`UPDATE foo SET first="isaac", last="asimov" WHERE id=2`,
},
exp: `[{"last_insert_id":2,"rows_affected":1}]`,
},
{
name: "insert and select",
stmts: []string{
`INSERT INTO foo(first, last) VALUES("richard", "feynman")`,
`SELECT COUNT(*) FROM foo`,
`SELECT last FROM foo WHERE first="richard"`,
},
exp: `[{"last_insert_id":3,"rows_affected":1},{"columns":["COUNT(*)"],"types":[""],"values":[[3]]},{"columns":["last"],"types":["text"],"values":[["feynman"]]}]`,
},
{
name: "insert and select non-existent table",
stmts: []string{
`INSERT INTO foo(first, last) VALUES("paul", "dirac")`,
`SELECT COUNT(*) FROM foo`,
`SELECT * FROM bar`,
},
exp: `[{"last_insert_id":4,"rows_affected":1},{"columns":["COUNT(*)"],"types":[""],"values":[[4]]},{"error":"no such table: bar"}]`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r, err := db.RequestStringStmts(tt.stmts)
if err != nil {
t.Fatalf("failed to request empty statements: %s", err.Error())
}
if exp, got := tt.exp, asJSON(r); exp != got {
t.Fatalf(`Test "%s" failed, unexpected results for request exp: %s got: %s`, tt.name, exp, got)
}
})
}
}
// Test_SimpleRequestTx tests that a transaction is rolled back when an error occurs, and that
// subsequent statements after the failed statement are not processed.
func Test_SimpleRequestTx(t *testing.T) {
db, path := mustCreateDatabase()
defer db.Close()
defer os.Remove(path)
mustExecute(db, `CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)`)
mustExecute(db, `INSERT INTO foo(id, name) VALUES(1, "fiona")`)
request := &command.Request{
Statements: []*command.Statement{
{
Sql: `INSERT INTO foo(id, name) VALUES(2, "declan")`,
},
{
Sql: `INSERT INTO foo(id, name) VALUES(1, "fiona")`,
},
{
Sql: `INSERT INTO foo(id, name) VALUES(3, "dana")`,
},
},
Transaction: true,
}
r, err := db.Request(request, false)
if err != nil {
t.Fatalf("failed to make request: %s", err.Error())
}
if exp, got := `[{"last_insert_id":2,"rows_affected":1},{"error":"UNIQUE constraint failed: foo.id"}]`, asJSON(r); exp != got {
t.Fatalf("unexpected results for request\nexp: %s\ngot: %s", exp, got)
}
}
func Test_CommonTableExpressions(t *testing.T) {
db, path := mustCreateDatabase()
defer db.Close()
@ -1250,6 +1355,9 @@ func Test_ConnectionIsolation(t *testing.T) {
if err != nil {
t.Fatalf("error executing insertion into table: %s", err.Error())
}
if exp, got := `[{"last_insert_id":1,"rows_affected":1}]`, asJSON(r); exp != got {
t.Fatalf("unexpected results for execute, expected %s, got %s", exp, got)
}
q, err := db.QueryStringStmt("SELECT * FROM foo")
if err != nil {
@ -1259,7 +1367,7 @@ func Test_ConnectionIsolation(t *testing.T) {
t.Fatalf("unexpected results for query, expected %s, got %s", exp, got)
}
r, err = db.ExecuteStringStmt("COMMIT")
_, err = db.ExecuteStringStmt("COMMIT")
if err != nil {
t.Fatalf("error executing insertion into table: %s", err.Error())
}
@ -1295,7 +1403,7 @@ func Test_ConnectionIsolationMemory(t *testing.T) {
t.Fatalf("unexpected results for query, expected %s, got %s", exp, got)
}
r, err = db.ExecuteStringStmt(`INSERT INTO foo(name) VALUES("fiona")`)
_, err = db.ExecuteStringStmt(`INSERT INTO foo(name) VALUES("fiona")`)
if err != nil {
t.Fatalf("error executing insertion into table: %s", err.Error())
}
@ -1983,6 +2091,117 @@ func Test_TableCreationInMemoryLoadRaw(t *testing.T) {
}
}
func Test_StmtReadOnly(t *testing.T) {
db := mustCreateInMemoryDatabase()
defer db.Close()
r, err := db.ExecuteStringStmt(`CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)`)
if err != nil {
t.Fatalf("failed to create table: %s", err.Error())
}
if exp, got := `[{}]`, asJSON(r); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
tests := []struct {
name string
sql string
ro bool
err error
}{
{
name: "CREATE TABLE statement",
sql: "CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)",
err: errors.New(`table foo already exists`),
},
{
name: "CREATE TABLE statement",
sql: "CREATE TABLE bar (id INTEGER NOT NULL PRIMARY KEY, name TEXT)",
ro: false,
},
{
name: "SELECT statement",
sql: "SELECT * FROM foo",
ro: true,
},
{
name: "SELECT statement",
sql: "SELECT * FROM non_existent_table",
err: errors.New(`no such table: non_existent_table`),
},
{
name: "INSERT statement",
sql: "INSERT INTO foo VALUES (1, 'test')",
ro: false,
},
{
name: "UPDATE statement",
sql: "UPDATE foo SET name='test' WHERE id=1",
ro: false,
},
{
name: "DELETE statement",
sql: "DELETE FROM foo WHERE id=1",
ro: false,
},
{
name: "SELECT statement with positional parameters",
sql: "SELECT * FROM foo WHERE id = ?",
ro: true,
},
{
name: "SELECT statement with named parameters",
sql: "SELECT * FROM foo WHERE id = @id AND name = @name",
ro: true,
},
{
name: "INSERT statement with positional parameters",
sql: "INSERT INTO foo VALUES (?, ?)",
ro: false,
},
{
name: "INSERT statement with named parameters",
sql: "INSERT INTO foo VALUES (@id, @name)",
ro: false,
},
{
name: "WITH clause, read-only",
sql: "WITH bar AS (SELECT * FROM foo WHERE id = ?) SELECT * FROM bar",
ro: true,
},
{
name: "WITH clause, not read-only",
sql: "WITH bar AS (SELECT * FROM foo WHERE id = ?) DELETE FROM foo WHERE id IN (SELECT id FROM bar)",
ro: false,
},
{
name: "Invalid statement",
sql: "INVALID SQL STATEMENT",
err: errors.New(`near "INVALID": syntax error`),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
readOnly, err := db.StmtReadOnly(tt.sql)
// Check if error is as expected
if err != nil && tt.err == nil {
t.Fatalf("unexpected error: got %v", err)
} else if err == nil && tt.err != nil {
t.Fatalf("expected error: got nil")
} else if err != nil && tt.err != nil && err.Error() != tt.err.Error() {
t.Fatalf("unexpected error: expected %v, got %v", tt.err, err)
}
// Check if result is as expected
if readOnly != tt.ro {
t.Fatalf("unexpected readOnly: expected %v, got %v", tt.ro, readOnly)
}
})
}
}
func mustCreateDatabase() (*DB, string) {
var err error
f := mustTempFile()
@ -2010,36 +2229,15 @@ func mustCreateInMemoryDatabaseFK() *DB {
return db
}
func mustWriteAndOpenDatabase(b []byte) (*DB, string) {
var err error
f := mustTempFile()
err = ioutil.WriteFile(f, b, 0660)
if err != nil {
panic("failed to write file")
}
db, err := Open(f, false)
if err != nil {
panic("failed to open database")
}
return db, f
}
// mustExecute executes a statement, and panics on failure. Used for statements
// that should never fail, even taking into account test setup.
func mustExecute(db *DB, stmt string) {
_, err := db.ExecuteStringStmt(stmt)
r, err := db.ExecuteStringStmt(stmt)
if err != nil {
panic(fmt.Sprintf("failed to execute statement: %s", err.Error()))
}
}
// mustQuery executes a statement, and panics on failure. Used for statements
// that should never fail, even taking into account test setup.
func mustQuery(db *DB, stmt string) {
_, err := db.QueryStringStmt(stmt)
if err != nil {
panic(fmt.Sprintf("failed to query: %s", err.Error()))
if r[0].Error != "" {
panic(fmt.Sprintf("failed to execute statement: %s", r[0].Error))
}
}

@ -54,6 +54,10 @@ type Database interface {
// is held on the database.
Query(qr *command.QueryRequest) ([]*command.QueryRows, error)
// Request processes a slice of requests, each of which can be either
// an Execute or Query request.
Request(eqr *command.ExecuteQueryRequest) ([]*command.ExecuteQueryResponse, error)
// Load loads a SQLite file into the system
Load(lr *command.LoadRequest) error
}
@ -98,6 +102,9 @@ type Cluster interface {
// Query performs an Query Request on a remote node.
Query(qr *command.QueryRequest, nodeAddr string, creds *cluster.Credentials, timeout time.Duration) ([]*command.QueryRows, error)
// Request performs an ExecuteQuery Request on a remote node.
Request(eqr *command.ExecuteQueryRequest, nodeAddr string, creds *cluster.Credentials, timeout time.Duration) ([]*command.ExecuteQueryResponse, error)
// Backup retrieves a backup from a remote node and writes to the io.Writer.
Backup(br *command.BackupRequest, nodeAddr string, creds *cluster.Credentials, timeout time.Duration, w io.Writer) error
@ -122,10 +129,12 @@ type StatusReporter interface {
Stats() (map[string]interface{}, error)
}
// DBResults stores either an Execute result or a Query result
// DBResults stores either an Execute result, a Query result, or
// an ExecuteQuery result.
type DBResults struct {
ExecuteResult []*command.ExecuteResult
QueryRows []*command.QueryRows
ExecuteResult []*command.ExecuteResult
QueryRows []*command.QueryRows
ExecuteQueryResponse []*command.ExecuteQueryResponse
AssociativeJSON bool // Render in associative form
}
@ -145,6 +154,8 @@ func (d *DBResults) MarshalJSON() ([]byte, error) {
return enc.JSONMarshal(d.ExecuteResult)
} else if d.QueryRows != nil {
return enc.JSONMarshal(d.QueryRows)
} else if d.ExecuteQueryResponse != nil {
return enc.JSONMarshal(d.ExecuteQueryResponse)
}
return json.Marshal(make([]interface{}, 0))
}
@ -191,8 +202,10 @@ const (
numQueuedExecutionsFailed = "queued_executions_failed"
numQueuedExecutionsWait = "queued_executions_wait"
numQueries = "queries"
numRequests = "requests"
numRemoteExecutions = "remote_executions"
numRemoteQueries = "remote_queries"
numRemoteRequests = "remote_requests"
numRemoteBackups = "remote_backups"
numRemoteLoads = "remote_loads"
numRemoteRemoveNode = "remote_remove_node"
@ -238,8 +251,10 @@ func ResetStats() {
stats.Add(numQueuedExecutionsFailed, 0)
stats.Add(numQueuedExecutionsWait, 0)
stats.Add(numQueries, 0)
stats.Add(numRequests, 0)
stats.Add(numRemoteExecutions, 0)
stats.Add(numRemoteQueries, 0)
stats.Add(numRemoteRequests, 0)
stats.Add(numRemoteBackups, 0)
stats.Add(numRemoteLoads, 0)
stats.Add(numRemoteRemoveNode, 0)
@ -404,6 +419,9 @@ func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
case strings.HasPrefix(r.URL.Path, "/db/query"):
stats.Add(numQueries, 1)
s.handleQuery(w, r)
case strings.HasPrefix(r.URL.Path, "/db/request"):
stats.Add(numRequests, 1)
s.handleRequest(w, r)
case strings.HasPrefix(r.URL.Path, "/db/backup"):
stats.Add(numBackups, 1)
s.handleBackup(w, r)
@ -1400,25 +1418,7 @@ func (s *Service) handleQuery(w http.ResponseWriter, r *http.Request) {
return
}
timeout, isTx, timings, redirect, noRewriteRandom, err := reqParams(r, defaultTimeout)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
lvl, err := level(r)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
frsh, err := freshness(r)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
assoc, err := isAssociative(r)
timeout, frsh, lvl, isTx, timings, redirect, noRewriteRandom, isAssoc, err := queryReqParams(r, defaultTimeout)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
@ -1441,7 +1441,7 @@ func (s *Service) handleQuery(w http.ResponseWriter, r *http.Request) {
}
resp := NewResponse()
resp.Results.AssociativeJSON = assoc
resp.Results.AssociativeJSON = isAssoc
qr := &command.QueryRequest{
Request: &command.Request{
@ -1500,6 +1500,103 @@ func (s *Service) handleQuery(w http.ResponseWriter, r *http.Request) {
s.writeResponse(w, r, resp)
}
func (s *Service) handleRequest(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
if !s.CheckRequestPermAll(r, auth.PermQuery, auth.PermExecute) {
w.WriteHeader(http.StatusUnauthorized)
return
}
if r.Method != "POST" {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
timeout, frsh, lvl, isTx, timings, redirect, noRewriteRandom, isAssoc, err := executeQueryReqParams(r, defaultTimeout)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
b, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
r.Body.Close()
stmts, err := ParseRequest(b)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if err := command.Rewrite(stmts, noRewriteRandom); err != nil {
http.Error(w, fmt.Sprintf("SQL rewrite: %s", err.Error()), http.StatusInternalServerError)
return
}
resp := NewResponse()
resp.Results.AssociativeJSON = isAssoc
eqr := &command.ExecuteQueryRequest{
Request: &command.Request{
Transaction: isTx,
Statements: stmts,
},
Timings: timings,
Level: lvl,
Freshness: frsh.Nanoseconds(),
}
results, resultErr := s.store.Request(eqr)
if resultErr != nil && resultErr == store.ErrNotLeader {
if redirect {
leaderAPIAddr := s.LeaderAPIAddr()
if leaderAPIAddr == "" {
stats.Add(numLeaderNotFound, 1)
http.Error(w, ErrLeaderNotFound.Error(), http.StatusServiceUnavailable)
return
}
loc := s.FormRedirect(r, leaderAPIAddr)
http.Redirect(w, r, loc, http.StatusMovedPermanently)
return
}
addr, err := s.store.LeaderAddr()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if addr == "" {
stats.Add(numLeaderNotFound, 1)
http.Error(w, ErrLeaderNotFound.Error(), http.StatusServiceUnavailable)
return
}
username, password, ok := r.BasicAuth()
if !ok {
username = ""
}
w.Header().Add(ServedByHTTPHeader, addr)
results, resultErr = s.cluster.Request(eqr, addr, makeCredentials(username, password), timeout)
if resultErr != nil && resultErr.Error() == "unauthorized" {
http.Error(w, "remote request not authorized", http.StatusUnauthorized)
return
}
stats.Add(numRemoteRequests, 1)
}
if resultErr != nil {
resp.Error = resultErr.Error()
} else {
resp.Results.ExecuteQueryResponse = results
}
resp.end = time.Now()
s.writeResponse(w, r, resp)
}
// handleExpvar serves registered expvar information over HTTP.
func (s *Service) handleExpvar(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
@ -1581,6 +1678,35 @@ func (s *Service) CheckRequestPerm(r *http.Request, perm string) (b bool) {
return s.credentialStore.AA(username, password, perm)
}
// CheckRequestPermAll checksif the request is authenticated and authorized
// with all the given Perms.
func (s *Service) CheckRequestPermAll(r *http.Request, perms ...string) (b bool) {
defer func() {
if b {
stats.Add(numAuthOK, 1)
} else {
stats.Add(numAuthFail, 1)
}
}()
// No auth store set, so no checking required.
if s.credentialStore == nil {
return true
}
username, password, ok := r.BasicAuth()
if !ok {
username = ""
}
for _, perm := range perms {
if !s.credentialStore.AA(username, password, perm) {
return false
}
}
return true
}
// LeaderAPIAddr returns the API address of the leader, as known by this node.
func (s *Service) LeaderAPIAddr() string {
nodeAddr, err := s.store.LeaderAddr()
@ -1874,6 +2000,39 @@ func reqParams(req *http.Request, def time.Duration) (timeout time.Duration, tx,
return timeout, tx, timings, redirect, noRwRandom, nil
}
// queryReqParams is a convenience function to get a bunch of query params
// in one function call.
func queryReqParams(req *http.Request, def time.Duration) (timeout, frsh time.Duration, lvl command.QueryRequest_Level, isTx, timings, redirect, noRwRandom, isAssoc bool, err error) {
timeout, isTx, timings, redirect, noRwRandom, err = reqParams(req, defaultTimeout)
if err != nil {
return 0, 0, command.QueryRequest_QUERY_REQUEST_LEVEL_WEAK, false, false, false, false, false, err
}
lvl, err = level(req)
if err != nil {
return 0, 0, command.QueryRequest_QUERY_REQUEST_LEVEL_WEAK, false, false, false, false, false, err
}
frsh, err = freshness(req)
if err != nil {
return 0, 0, command.QueryRequest_QUERY_REQUEST_LEVEL_WEAK, false, false, false, false, false, err
}
isAssoc, err = isAssociative(req)
if err != nil {
return 0, 0, command.QueryRequest_QUERY_REQUEST_LEVEL_WEAK, false, false, false, false, false, err
}
return
}
func executeQueryReqParams(req *http.Request, def time.Duration) (timeout, frsh time.Duration, lvl command.ExecuteQueryRequest_Level, isTx, timings, redirect, noRwRandom, isAssoc bool, err error) {
timeout, frsh, qLvl, isTx, timings, redirect, noRwRandom, isAssoc, err := queryReqParams(req, defaultTimeout)
if err != nil {
return 0, 0, command.ExecuteQueryRequest_QUERY_REQUEST_LEVEL_WEAK, false, false, false, false, false, err
}
return timeout, frsh, command.MapConsistencyLevel(qLvl), isTx, timings, redirect, noRwRandom, isAssoc, nil
}
// noLeader returns whether processing should skip the leader check.
func noLeader(req *http.Request) (bool, error) {
return queryParam(req, "noleader")

@ -354,6 +354,7 @@ func Test_401Routes_NoBasicAuth(t *testing.T) {
for _, path := range []string{
"/db/execute",
"/db/query",
"/db/request",
"/db/backup",
"/db/load",
"/join",
@ -396,6 +397,7 @@ func Test_401Routes_BasicAuthBadPassword(t *testing.T) {
for _, path := range []string{
"/db/execute",
"/db/query",
"/db/request",
"/db/backup",
"/db/load",
"/join",
@ -444,6 +446,7 @@ func Test_401Routes_BasicAuthBadPerm(t *testing.T) {
"/db/execute",
"/db/query",
"/db/backup",
"/db/request",
"/db/load",
"/join",
"/notify",
@ -1146,6 +1149,70 @@ func Test_ForwardingRedirectExecute(t *testing.T) {
}
}
func Test_ForwardingRedirectExecuteQuery(t *testing.T) {
m := &MockStore{
leaderAddr: "foo:1234",
}
m.requestFn = func(er *command.ExecuteQueryRequest) ([]*command.ExecuteQueryResponse, error) {
return nil, store.ErrNotLeader
}
c := &mockClusterService{
apiAddr: "https://bar:5678",
}
c.requestFn = func(er *command.ExecuteQueryRequest, addr string, timeout time.Duration) ([]*command.ExecuteQueryResponse, error) {
resp := &command.ExecuteQueryResponse{
Result: &command.ExecuteQueryResponse_E{
E: &command.ExecuteResult{
LastInsertId: 1234,
RowsAffected: 5678,
},
},
}
return []*command.ExecuteQueryResponse{resp}, nil
}
s := New("127.0.0.1:0", m, c, nil)
if err := s.Start(); err != nil {
t.Fatalf("failed to start service")
}
defer s.Close()
// Check ExecuteQuery.
client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
host := fmt.Sprintf("http://%s", s.Addr().String())
resp, err := client.Post(host+"/db/request", "application/json", strings.NewReader(`["Some SQL"]`))
if err != nil {
t.Fatalf("failed to make ExecuteQuery request")
}
if resp.StatusCode != http.StatusOK {
t.Fatalf("failed to get expected StatusOK for ExecuteQuery, got %d", resp.StatusCode)
}
resp, err = client.Post(host+"/db/request?redirect", "application/json", strings.NewReader(`["Some SQL"]`))
if err != nil {
t.Fatalf("failed to make redirected ExecuteQuery request: %s", err)
}
if resp.StatusCode != http.StatusMovedPermanently {
t.Fatalf("failed to get expected StatusMovedPermanently for execute, got %d", resp.StatusCode)
}
// Check leader failure case.
m.leaderAddr = ""
resp, err = client.Post(host+"/db/request", "application/json", strings.NewReader(`["Some SQL"]`))
if err != nil {
t.Fatalf("failed to make ExecuteQuery request")
}
if resp.StatusCode != http.StatusServiceUnavailable {
t.Fatalf("failed to get expected StatusServiceUnavailable for node with no leader, got %d", resp.StatusCode)
}
}
func Test_timeoutQueryParam(t *testing.T) {
var req http.Request
@ -1197,6 +1264,7 @@ func Test_timeoutQueryParam(t *testing.T) {
type MockStore struct {
executeFn func(er *command.ExecuteRequest) ([]*command.ExecuteResult, error)
queryFn func(qr *command.QueryRequest) ([]*command.QueryRows, error)
requestFn func(eqr *command.ExecuteQueryRequest) ([]*command.ExecuteQueryResponse, error)
backupFn func(br *command.BackupRequest, dst io.Writer) error
loadFn func(lr *command.LoadRequest) error
leaderAddr string
@ -1217,6 +1285,13 @@ func (m *MockStore) Query(qr *command.QueryRequest) ([]*command.QueryRows, error
return nil, nil
}
func (m *MockStore) Request(eqr *command.ExecuteQueryRequest) ([]*command.ExecuteQueryResponse, error) {
if m.requestFn != nil {
return m.requestFn(eqr)
}
return nil, nil
}
func (m *MockStore) Join(jr *command.JoinRequest) error {
return nil
}
@ -1263,6 +1338,7 @@ type mockClusterService struct {
apiAddr string
executeFn func(er *command.ExecuteRequest, addr string, t time.Duration) ([]*command.ExecuteResult, error)
queryFn func(qr *command.QueryRequest, addr string, t time.Duration) ([]*command.QueryRows, error)
requestFn func(eqr *command.ExecuteQueryRequest, nodeAddr string, timeout time.Duration) ([]*command.ExecuteQueryResponse, error)
backupFn func(br *command.BackupRequest, addr string, t time.Duration, w io.Writer) error
loadFn func(lr *command.LoadRequest, addr string, t time.Duration) error
removeNodeFn func(rn *command.RemoveNodeRequest, nodeAddr string, t time.Duration) error
@ -1286,6 +1362,13 @@ func (m *mockClusterService) Query(qr *command.QueryRequest, addr string, creds
return nil, nil
}
func (m *mockClusterService) Request(eqr *command.ExecuteQueryRequest, nodeAddr string, creds *cluster.Credentials, timeout time.Duration) ([]*command.ExecuteQueryResponse, error) {
if m.requestFn != nil {
return m.requestFn(eqr, nodeAddr, timeout)
}
return nil, nil
}
func (m *mockClusterService) Backup(br *command.BackupRequest, addr string, creds *cluster.Credentials, t time.Duration, w io.Writer) error {
if m.backupFn != nil {
return m.backupFn(br, addr, t, w)

@ -26,7 +26,6 @@ import (
"github.com/hashicorp/raft"
"github.com/rqlite/rqlite/command"
"github.com/rqlite/rqlite/db"
sql "github.com/rqlite/rqlite/db"
rlog "github.com/rqlite/rqlite/log"
)
@ -300,7 +299,7 @@ func (s *Store) SetRestorePath(path string) error {
return ErrOpen
}
if !db.IsValidSQLiteFile(path) {
if !sql.IsValidSQLiteFile(path) {
return fmt.Errorf("file %s is not a valid SQLite file", path)
}
s.RegisterReadyChannel(s.restoreDoneCh)
@ -864,15 +863,10 @@ func (s *Store) Execute(ex *command.ExecuteRequest) ([]*command.ExecuteResult, e
}
func (s *Store) execute(ex *command.ExecuteRequest) ([]*command.ExecuteResult, error) {
b, compressed, err := s.reqMarshaller.Marshal(ex)
b, compressed, err := s.tryCompress(ex)
if err != nil {
return nil, err
}
if compressed {
stats.Add(numCompressedCommands, 1)
} else {
stats.Add(numUncompressedCommands, 1)
}
c := &command.Command{
Type: command.Command_COMMAND_TYPE_EXECUTE,
@ -915,16 +909,10 @@ func (s *Store) Query(qr *command.QueryRequest) ([]*command.QueryRows, error) {
return nil, ErrNotReady
}
b, compressed, err := s.reqMarshaller.Marshal(qr)
b, compressed, err := s.tryCompress(qr)
if err != nil {
return nil, err
}
if compressed {
stats.Add(numCompressedCommands, 1)
} else {
stats.Add(numUncompressedCommands, 1)
}
c := &command.Command{
Type: command.Command_COMMAND_TYPE_QUERY,
SubCommand: b,
@ -970,6 +958,61 @@ func (s *Store) Query(qr *command.QueryRequest) ([]*command.QueryRows, error) {
return s.db.Query(qr.Request, qr.Timings)
}
// Request processes a request that may contain both Executes and Queries.
func (s *Store) Request(eqr *command.ExecuteQueryRequest) ([]*command.ExecuteQueryResponse, error) {
if !s.open {
return nil, ErrNotOpen
}
if !s.RequiresLeader(eqr) {
if eqr.Request.Transaction {
// Transaction requested during query, but not going through consensus. This means
// we need to block any database serialization during the query.
s.queryTxMu.RLock()
defer s.queryTxMu.RUnlock()
}
return s.db.Request(eqr.Request, eqr.Timings)
}
if s.raft.State() != raft.Leader {
return nil, ErrNotLeader
}
if !s.Ready() {
return nil, ErrNotReady
}
b, compressed, err := s.tryCompress(eqr)
if err != nil {
return nil, err
}
c := &command.Command{
Type: command.Command_COMMAND_TYPE_EXECUTE_QUERY,
SubCommand: b,
Compressed: compressed,
}
b, err = command.Marshal(c)
if err != nil {
return nil, err
}
af := s.raft.Apply(b, s.ApplyTimeout)
if af.Error() != nil {
if af.Error() == raft.ErrNotLeader {
return nil, ErrNotLeader
}
return nil, af.Error()
}
s.dbAppliedIndexMu.Lock()
s.dbAppliedIndex = af.Index()
s.dbAppliedIndexMu.Unlock()
r := af.Response().(*fsmExecuteQueryResponse)
return r.results, r.error
}
// Backup writes a snapshot of the underlying database to dst
//
// If Leader is true for the request, this operation is performed with a read consistency
@ -1244,6 +1287,26 @@ func (s *Store) Noop(id string) error {
return nil
}
// RequiresLeader returns whether the given ExecuteQueryRequest must be
// processed on the cluster Leader.
func (s *Store) RequiresLeader(eqr *command.ExecuteQueryRequest) bool {
if eqr.Level != command.ExecuteQueryRequest_QUERY_REQUEST_LEVEL_NONE {
return true
}
for _, stmt := range eqr.Request.Statements {
sql := stmt.Sql
if sql == "" {
continue
}
ro, err := s.db.StmtReadOnly(sql)
if !ro || err != nil {
return true
}
}
return false
}
// setLogInfo records some key indexs about the log.
func (s *Store) setLogInfo() error {
var err error
@ -1305,6 +1368,11 @@ type fsmQueryResponse struct {
error error
}
type fsmExecuteQueryResponse struct {
results []*command.ExecuteQueryResponse
error error
}
type fsmGenericResponse struct {
error error
}
@ -1597,6 +1665,24 @@ func (s *Store) logSize() (int64, error) {
return fi.Size(), nil
}
// tryCompress attempts to compress the given command. If the command is
// successfully compressed, the compressed byte slice is returned, along with
// a boolean true. If the command cannot be compressed, the uncompressed byte
// slice is returned, along with a boolean false. The stats are updated
// accordingly.
func (s *Store) tryCompress(rq command.Requester) ([]byte, bool, error) {
b, compressed, err := s.reqMarshaller.Marshal(rq)
if err != nil {
return nil, false, err
}
if compressed {
stats.Add(numCompressedCommands, 1)
} else {
stats.Add(numUncompressedCommands, 1)
}
return b, compressed, nil
}
type fsmSnapshot struct {
startT time.Time
logger *log.Logger
@ -1903,6 +1989,13 @@ func applyCommand(data []byte, pDB **sql.DB) (command.Command_Type, interface{})
}
r, err := db.Execute(er.Request, er.Timings)
return c.Type, &fsmExecuteResponse{results: r, error: err}
case command.Command_COMMAND_TYPE_EXECUTE_QUERY:
var eqr command.ExecuteQueryRequest
if err := command.UnmarshalSubCommand(&c, &eqr); err != nil {
panic(fmt.Sprintf("failed to unmarshal execute-query subcommand: %s", err.Error()))
}
r, err := db.Request(eqr.Request, eqr.Timings)
return c.Type, &fsmExecuteQueryResponse{results: r, error: err}
case command.Command_COMMAND_TYPE_LOAD:
var lr command.LoadRequest
if err := command.UnmarshalLoadRequest(c.SubCommand, &lr); err != nil {

@ -446,6 +446,247 @@ func Test_SingleNodeExecuteQueryTx(t *testing.T) {
}
}
// Test_SingleNodeInMemRequest tests simple requests that contain both
// queries and execute statements.
func Test_SingleNodeInMemRequest(t *testing.T) {
s, ln := mustNewStore(t, true)
defer ln.Close()
if err := s.Open(); err != nil {
t.Fatalf("failed to open single-node store: %s", err.Error())
}
if err := s.Bootstrap(NewServer(s.ID(), s.Addr(), true)); err != nil {
t.Fatalf("failed to bootstrap single-node store: %s", err.Error())
}
defer s.Close(true)
_, err := s.WaitForLeader(10 * time.Second)
if err != nil {
t.Fatalf("Error waiting for leader: %s", err)
}
er := executeRequestFromStrings([]string{
`CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)`,
`INSERT INTO foo(id, name) VALUES(1, "fiona")`,
}, false, false)
_, err = s.Execute(er)
if err != nil {
t.Fatalf("failed to execute on single node: %s", err.Error())
}
tests := []struct {
stmts []string
expected string
associative bool
}{
{
stmts: []string{},
expected: `[]`,
},
{
stmts: []string{
`SELECT * FROM foo`,
},
expected: `[{"columns":["id","name"],"types":["integer","text"],"values":[[1,"fiona"]]}]`,
},
{
stmts: []string{
`SELECT * FROM foo`,
`INSERT INTO foo(id, name) VALUES(66, "declan")`,
},
expected: `[{"columns":["id","name"],"types":["integer","text"],"values":[[1,"fiona"]]},{"last_insert_id":66,"rows_affected":1}]`,
},
{
stmts: []string{
`INSERT INTO foo(id, name) VALUES(77, "fiona")`,
`SELECT COUNT(*) FROM foo`,
},
expected: `[{"last_insert_id":77,"rows_affected":1},{"columns":["COUNT(*)"],"types":[""],"values":[[3]]}]`,
},
{
stmts: []string{
`INSERT INTO foo(id, name) VALUES(88, "fiona")`,
`nonsense SQL`,
`SELECT COUNT(*) FROM foo WHERE name='fiona'`,
`SELECT * FROM foo WHERE name='declan'`,
},
expected: `[{"last_insert_id":88,"rows_affected":1},{"error":"near \"nonsense\": syntax error"},{"types":{"COUNT(*)":""},"rows":[{"COUNT(*)":3}]},{"types":{"id":"integer","name":"text"},"rows":[{"id":66,"name":"declan"}]}]`,
associative: true,
},
}
for _, tt := range tests {
eqr := executeQueryRequestFromStrings(tt.stmts, command.ExecuteQueryRequest_QUERY_REQUEST_LEVEL_WEAK, false, false)
r, err := s.Request(eqr)
if err != nil {
t.Fatalf("failed to execute request on single node: %s", err.Error())
}
var exp string
var got string
if tt.associative {
exp, got = tt.expected, asJSONAssociative(r)
} else {
exp, got = tt.expected, asJSON(r)
}
if exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
}
}
func Test_SingleNodeInMemRequestTx(t *testing.T) {
s, ln := mustNewStore(t, true)
defer ln.Close()
if err := s.Open(); err != nil {
t.Fatalf("failed to open single-node store: %s", err.Error())
}
if err := s.Bootstrap(NewServer(s.ID(), s.Addr(), true)); err != nil {
t.Fatalf("failed to bootstrap single-node store: %s", err.Error())
}
defer s.Close(true)
_, err := s.WaitForLeader(10 * time.Second)
if err != nil {
t.Fatalf("Error waiting for leader: %s", err)
}
er := executeRequestFromStrings([]string{
`CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)`,
}, false, false)
_, err = s.Execute(er)
if err != nil {
t.Fatalf("failed to execute on single node: %s", err.Error())
}
tests := []struct {
stmts []string
expected string
tx bool
}{
{
stmts: []string{},
expected: `[]`,
},
{
stmts: []string{
`INSERT INTO foo(id, name) VALUES(1, "declan")`,
`SELECT * FROM foo`,
},
expected: `[{"last_insert_id":1,"rows_affected":1},{"columns":["id","name"],"types":["integer","text"],"values":[[1,"declan"]]}]`,
},
{
stmts: []string{
`INSERT INTO foo(id, name) VALUES(2, "fiona")`,
`INSERT INTO foo(id, name) VALUES(1, "fiona")`,
`SELECT COUNT(*) FROM foo`,
},
expected: `[{"last_insert_id":2,"rows_affected":1},{"error":"UNIQUE constraint failed: foo.id"}]`,
tx: true,
},
{
// Since the above transaction should be rolled back, there will be only one row in the table.
stmts: []string{
`SELECT COUNT(*) FROM foo`,
},
expected: `[{"columns":["COUNT(*)"],"types":[""],"values":[[1]]}]`,
},
}
for _, tt := range tests {
eqr := executeQueryRequestFromStrings(tt.stmts, command.ExecuteQueryRequest_QUERY_REQUEST_LEVEL_WEAK, false, tt.tx)
r, err := s.Request(eqr)
if err != nil {
t.Fatalf("failed to execute request on single node: %s", err.Error())
}
if exp, got := tt.expected, asJSON(r); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
}
}
func Test_SingleNodeInMemRequestParameters(t *testing.T) {
s, ln := mustNewStore(t, true)
defer ln.Close()
if err := s.Open(); err != nil {
t.Fatalf("failed to open single-node store: %s", err.Error())
}
if err := s.Bootstrap(NewServer(s.ID(), s.Addr(), true)); err != nil {
t.Fatalf("failed to bootstrap single-node store: %s", err.Error())
}
defer s.Close(true)
_, err := s.WaitForLeader(10 * time.Second)
if err != nil {
t.Fatalf("Error waiting for leader: %s", err)
}
er := executeRequestFromStrings([]string{
`CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)`,
`INSERT INTO foo(id, name) VALUES(1, "fiona")`,
}, false, false)
_, err = s.Execute(er)
if err != nil {
t.Fatalf("failed to execute on single node: %s", err.Error())
}
tests := []struct {
request *command.ExecuteQueryRequest
expected string
}{
{
request: &command.ExecuteQueryRequest{
Request: &command.Request{
Statements: []*command.Statement{
{
Sql: "SELECT * FROM foo WHERE id = ?",
Parameters: []*command.Parameter{
{
Value: &command.Parameter_I{
I: 1,
},
},
},
},
},
},
},
expected: `[{"columns":["id","name"],"types":["integer","text"],"values":[[1,"fiona"]]}]`,
},
{
request: &command.ExecuteQueryRequest{
Request: &command.Request{
Statements: []*command.Statement{
{
Sql: "SELECT id FROM foo WHERE name = :qux",
Parameters: []*command.Parameter{
{
Value: &command.Parameter_S{
S: "fiona",
},
Name: "qux",
},
},
},
},
},
},
expected: `[{"columns":["id"],"types":["integer"],"values":[[1]]}]`,
},
}
for _, tt := range tests {
r, err := s.Request(tt.request)
if err != nil {
t.Fatalf("failed to execute request on single node: %s", err.Error())
}
if exp, got := tt.expected, asJSON(r); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
}
}
// Test_SingleNodeInMemFK tests that basic foreign-key related functionality works.
func Test_SingleNodeInMemFK(t *testing.T) {
s, ln := mustNewStoreFK(t, true)
@ -2406,6 +2647,124 @@ func Test_IsVoter(t *testing.T) {
}
}
func Test_RequiresLeader(t *testing.T) {
s, ln := mustNewStore(t, true)
defer ln.Close()
if err := s.Open(); err != nil {
t.Fatalf("failed to open single-node store: %s", err.Error())
}
defer s.Close(true)
if err := s.Bootstrap(NewServer(s.ID(), s.Addr(), true)); err != nil {
t.Fatalf("failed to bootstrap single-node store: %s", err.Error())
}
if _, err := s.WaitForLeader(10 * time.Second); err != nil {
t.Fatalf("Error waiting for leader: %s", err)
}
er := executeRequestFromString(`CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)`,
false, false)
_, err := s.Execute(er)
if err != nil {
t.Fatalf("failed to execute on single node: %s", err.Error())
}
tests := []struct {
name string
stmts []string
lvl command.ExecuteQueryRequest_Level
requires bool
}{
{
name: "Empty SQL",
stmts: []string{""},
requires: false,
},
{
name: "Junk SQL",
stmts: []string{"asdkflj asgkdj"},
requires: true,
},
{
name: "CREATE TABLE statement, already exists",
stmts: []string{"CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"},
requires: true,
},
{
name: "CREATE TABLE statement, does not exists",
stmts: []string{"CREATE TABLE bar (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"},
requires: true,
},
{
name: "Single INSERT",
stmts: []string{"INSERT INTO foo(id, name) VALUES(1, 'fiona')"},
requires: true,
},
{
name: "Single INSERT, non-existent table",
stmts: []string{"INSERT INTO qux(id, name) VALUES(1, 'fiona')"},
requires: true,
},
{
name: "Single SELECT with implicit NONE",
stmts: []string{"SELECT * FROM foo"},
requires: false,
},
{
name: "Single SELECT with NONE",
stmts: []string{"SELECT * FROM foo"},
lvl: command.ExecuteQueryRequest_QUERY_REQUEST_LEVEL_NONE,
requires: false,
},
{
name: "Single SELECT from non-existent table with NONE",
stmts: []string{"SELECT * FROM qux"},
lvl: command.ExecuteQueryRequest_QUERY_REQUEST_LEVEL_NONE,
requires: true,
},
{
name: "Double SELECT with NONE",
stmts: []string{"SELECT * FROM foo", "SELECT * FROM foo WHERE id = 1"},
lvl: command.ExecuteQueryRequest_QUERY_REQUEST_LEVEL_NONE,
requires: false,
},
{
name: "Single SELECT with STRONG",
stmts: []string{"SELECT * FROM foo"},
lvl: command.ExecuteQueryRequest_QUERY_REQUEST_LEVEL_STRONG,
requires: true,
},
{
name: "Single SELECT with WEAK",
stmts: []string{"SELECT * FROM foo"},
lvl: command.ExecuteQueryRequest_QUERY_REQUEST_LEVEL_WEAK,
requires: true,
},
{
name: "Mix queries and executes with NONE",
stmts: []string{"SELECT * FROM foo", "INSERT INTO foo(id, name) VALUES(1, 'fiona')"},
lvl: command.ExecuteQueryRequest_QUERY_REQUEST_LEVEL_NONE,
requires: true,
},
{
name: "Mix queries and executes with WEAK",
stmts: []string{"SELECT * FROM foo", "INSERT INTO foo(id, name) VALUES(1, 'fiona')"},
lvl: command.ExecuteQueryRequest_QUERY_REQUEST_LEVEL_WEAK,
requires: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
requires := s.RequiresLeader(executeQueryRequestFromStrings(tt.stmts, tt.lvl, false, false))
if requires != tt.requires {
t.Fatalf(" test %s failed, unexpected requires: expected %v, got %v", tt.name, tt.requires, requires)
}
})
}
}
func Test_State(t *testing.T) {
s, ln := mustNewStore(t, true)
defer ln.Close()
@ -2536,7 +2895,7 @@ func executeRequestFromString(s string, timings, tx bool) *command.ExecuteReques
return executeRequestFromStrings([]string{s}, timings, tx)
}
// queryRequestFromStrings converts a slice of strings into a command.ExecuteRequest
// executeRequestFromStrings converts a slice of strings into a command.ExecuteRequest
func executeRequestFromStrings(s []string, timings, tx bool) *command.ExecuteRequest {
stmts := make([]*command.Statement, len(s))
for i := range s {
@ -2574,6 +2933,23 @@ func queryRequestFromStrings(s []string, timings, tx bool) *command.QueryRequest
}
}
func executeQueryRequestFromStrings(s []string, lvl command.ExecuteQueryRequest_Level, timings, tx bool) *command.ExecuteQueryRequest {
stmts := make([]*command.Statement, len(s))
for i := range s {
stmts[i] = &command.Statement{
Sql: s[i],
}
}
return &command.ExecuteQueryRequest{
Request: &command.Request{
Statements: stmts,
Transaction: tx,
},
Timings: timings,
Level: lvl,
}
}
func backupRequestSQL(leader bool) *command.BackupRequest {
return &command.BackupRequest{
Format: command.BackupRequest_BACKUP_REQUEST_FORMAT_SQL,
@ -2649,6 +3025,17 @@ func asJSON(v interface{}) string {
return string(b)
}
func asJSONAssociative(v interface{}) string {
enc := encoding.Encoder{
Associative: true,
}
b, err := enc.JSONMarshal(v)
if err != nil {
panic(fmt.Sprintf("failed to JSON marshal value: %s", err.Error()))
}
return string(b)
}
func randomString() string {
var output strings.Builder
chars := "abcdedfghijklmnopqrstABCDEFGHIJKLMNOP"

@ -463,6 +463,29 @@ class Node(object):
raise_for_status(r)
return r.json()
def request(self, statement, params=None, level='weak', pretty=False, associative=False):
body = [statement]
if params is not None:
try:
body = body + params
except TypeError:
# Presumably not a list, so append as an object.
body.append(params)
reqParams = {'level': level}
if pretty:
reqParams['pretty'] = "yes"
if associative:
reqParams['associative'] = "yes"
r = requests.post(self._request_url(), params=reqParams, data=json.dumps([body]))
raise_for_status(r)
return r.json()
def request_raw(self, body):
r = requests.post(self._request_url(), data=body)
raise_for_status(r)
return r.json()
def backup(self, file):
with open(file, 'wb') as fd:
r = requests.get(self._backup_url())
@ -532,6 +555,11 @@ class Node(object):
if wait:
u = u + '&wait'
return 'http://' + self.APIAddr() + u
def _request_url(self, redirect=False):
rd = ""
if redirect:
rd = "?redirect"
return 'http://' + self.APIAddr() + '/db/request' + rd
def _backup_url(self):
return 'http://' + self.APIAddr() + '/db/backup'
def _load_url(self):

@ -142,6 +142,24 @@ class TestSingleNode(unittest.TestCase):
j = n.query('SELECT * from bar')
self.assertEqual(j, d_("{'results': [{'values': [[1, 'fiona', 20], [2, 'sinead', 25]], 'types': ['integer', 'text', 'integer'], 'columns': ['id', 'name', 'age']}]}"))
def test_simple_parameterized_mixed_queries_via_request(self):
'''Test a mix of parameterized and non-parameterized queries work as expected with unified endpoint'''
n = self.cluster.wait_for_leader()
j = n.execute('CREATE TABLE bar (id INTEGER NOT NULL PRIMARY KEY, name TEXT, age INTEGER)')
self.assertEqual(j, d_("{'results': [{}]}"))
body = json.dumps([
["INSERT INTO bar(name, age) VALUES(?,?)", "fiona", 20],
['INSERT INTO bar(name, age) VALUES("sinead", 25)']
])
j = n.request_raw(body)
applied = n.wait_for_all_fsm()
self.assertEqual(j, d_("{'results': [{'last_insert_id': 1, 'rows_affected': 1}, {'last_insert_id': 2, 'rows_affected': 1}]}"))
j = n.request('SELECT * from bar')
self.assertEqual(j, d_("{'results': [{'values': [[1, 'fiona', 20], [2, 'sinead', 25]], 'types': ['integer', 'text', 'integer'], 'columns': ['id', 'name', 'age']}]}"))
j = n.request('SELECT * from bar', associative=True)
self.assertEqual(j, d_("{'results': [{'types': {'age': 'integer', 'id': 'integer', 'name': 'text'}, 'rows': [{'age': 20, 'id': 1, 'name': 'fiona'}, {'age': 25, 'id': 2, 'name': 'sinead'}]}]}"))
def test_snapshot(self):
''' Test that a node peforms at least 1 snapshot'''
n = self.cluster.wait_for_leader()

@ -157,6 +157,32 @@ func (n *Node) QueryParameterized(stmt []interface{}) (string, error) {
return n.postQuery(string(j))
}
// Request runs a single request against the node.
func (n *Node) Request(stmt string) (string, error) {
return n.RequestMulti([]string{stmt})
}
// RequestMulti runs multiple statements in a single request against the node.
func (n *Node) RequestMulti(stmts []string) (string, error) {
j, err := json.Marshal(stmts)
if err != nil {
return "", err
}
return n.postRequest(string(j))
}
// RequestMultiParameterized runs a single paramterized request against the node
func (n *Node) RequestMultiParameterized(stmt []interface{}) (string, error) {
m := make([][]interface{}, 1)
m[0] = stmt
j, err := json.Marshal(m)
if err != nil {
return "", err
}
return n.postRequest(string(j))
}
// Noop inserts a noop command into the Store's Raft log.
func (n *Node) Noop(id string) error {
return n.Store.Noop(id)
@ -426,6 +452,19 @@ func (n *Node) postQuery(stmt string) (string, error) {
return string(body), nil
}
func (n *Node) postRequest(stmt string) (string, error) {
resp, err := http.Post("http://"+n.APIAddr+"/db/request", "application/json", strings.NewReader(stmt))
if err != nil {
return "", err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
return string(body), nil
}
// PostExecuteStmt performs a HTTP execute request
func PostExecuteStmt(apiAddr string, stmt string) (string, error) {
return PostExecuteStmtMulti(apiAddr, []string{stmt})

@ -38,74 +38,120 @@ func Test_StoreClientSideBySide(t *testing.T) {
t.Fatalf("failed to execute on local: %s", err.Error())
}
if exp, got := "[{}]", asJSON(res); exp != got {
t.Fatalf("unexpected results, expt %s, got %s", exp, got)
t.Fatalf("unexpected results, exp %s, got %s", exp, got)
}
res, err = client.Execute(executeRequestFromString("CREATE TABLE bar (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"), leaderAddr, NO_CREDS, shortWait)
if err != nil {
t.Fatalf("failed to execute via remote: %s", err.Error())
}
if exp, got := "[{}]", asJSON(res); exp != got {
t.Fatalf("unexpected results, expt %s, got %s", exp, got)
t.Fatalf("unexpected results, exp %s, got %s", exp, got)
}
// ==============================================================================
res, err = node.Store.Execute(executeRequestFromString(`INSERT INTO foo(name) VALUES("fiona")`))
if err != nil {
t.Fatalf("failed to execute on local: %s", err.Error())
}
if exp, got := `[{"last_insert_id":1,"rows_affected":1}]`, asJSON(res); exp != got {
t.Fatalf("unexpected results, expt %s, got %s", exp, got)
t.Fatalf("unexpected results, exp %s, got %s", exp, got)
}
res, err = client.Execute(executeRequestFromString(`INSERT INTO bar(name) VALUES("fiona")`), leaderAddr, NO_CREDS, shortWait)
if err != nil {
t.Fatalf("failed to execute via remote: %s", err.Error())
}
if exp, got := `[{"last_insert_id":1,"rows_affected":1}]`, asJSON(res); exp != got {
t.Fatalf("unexpected results, expt %s, got %s", exp, got)
t.Fatalf("unexpected results, exp %s, got %s", exp, got)
}
// ==============================================================================
rows, err := node.Store.Query(queryRequestFromString(`SELECT * FROM foo`))
if err != nil {
t.Fatalf("failed to query on local: %s", err.Error())
}
if exp, got := `[{"columns":["id","name"],"types":["integer","text"],"values":[[1,"fiona"]]}]`, asJSON(rows); exp != got {
t.Fatalf("unexpected results, expt %s, got %s", exp, got)
t.Fatalf("unexpected results, exp %s, got %s", exp, got)
}
rows, err = node.Store.Query(queryRequestFromString(`SELECT * FROM bar`))
results, err := node.Store.Request(executeQueryRequestFromString(`SELECT * FROM foo`))
if err != nil {
t.Fatalf("failed to query on local: %s", err.Error())
t.Fatalf("failed to request on local: %s", err.Error())
}
if exp, got := `[{"columns":["id","name"],"types":["integer","text"],"values":[[1,"fiona"]]}]`, asJSON(rows); exp != got {
t.Fatalf("unexpected results, expt %s, got %s", exp, got)
if exp, got := `[{"columns":["id","name"],"types":["integer","text"],"values":[[1,"fiona"]]}]`, asJSON(results); exp != got {
t.Fatalf("unexpected results, exp %s, got %s", exp, got)
}
rows, err = client.Query(queryRequestFromString(`SELECT * FROM foo`), leaderAddr, NO_CREDS, shortWait)
if err != nil {
t.Fatalf("failed to query via remote: %s", err.Error())
}
if exp, got := `[{"columns":["id","name"],"types":["integer","text"],"values":[[1,"fiona"]]}]`, asJSON(rows); exp != got {
t.Fatalf("unexpected results, expt %s, got %s", exp, got)
t.Fatalf("unexpected results, exp %s, got %s", exp, got)
}
results, err = client.Request(executeQueryRequestFromString(`SELECT * FROM foo`), leaderAddr, NO_CREDS, shortWait)
if err != nil {
t.Fatalf("failed to query via remote: %s", err.Error())
}
if exp, got := `[{"columns":["id","name"],"types":["integer","text"],"values":[[1,"fiona"]]}]`, asJSON(results); exp != got {
t.Fatalf("unexpected results, exp %s, got %s", exp, got)
}
// ==============================================================================
rows, err = node.Store.Query(queryRequestFromString(`SELECT * FROM bar`))
if err != nil {
t.Fatalf("failed to query on local: %s", err.Error())
}
if exp, got := `[{"columns":["id","name"],"types":["integer","text"],"values":[[1,"fiona"]]}]`, asJSON(rows); exp != got {
t.Fatalf("unexpected results, exp %s, got %s", exp, got)
}
results, err = node.Store.Request(executeQueryRequestFromString(`SELECT * FROM bar`))
if err != nil {
t.Fatalf("failed to request on local: %s", err.Error())
}
if exp, got := `[{"columns":["id","name"],"types":["integer","text"],"values":[[1,"fiona"]]}]`, asJSON(results); exp != got {
t.Fatalf("unexpected results, exp %s, got %s", exp, got)
}
rows, err = client.Query(queryRequestFromString(`SELECT * FROM bar`), leaderAddr, NO_CREDS, shortWait)
if err != nil {
t.Fatalf("failed to query via remote: %s", err.Error())
}
if exp, got := `[{"columns":["id","name"],"types":["integer","text"],"values":[[1,"fiona"]]}]`, asJSON(rows); exp != got {
t.Fatalf("unexpected results, expt %s, got %s", exp, got)
t.Fatalf("unexpected results, exp %s, got %s", exp, got)
}
results, err = client.Request(executeQueryRequestFromString(`SELECT * FROM bar`), leaderAddr, NO_CREDS, shortWait)
if err != nil {
t.Fatalf("failed to query via remote: %s", err.Error())
}
if exp, got := `[{"columns":["id","name"],"types":["integer","text"],"values":[[1,"fiona"]]}]`, asJSON(results); exp != got {
t.Fatalf("unexpected results, exp %s, got %s", exp, got)
}
// ==============================================================================
rows, err = node.Store.Query(queryRequestFromString(`SELECT * FROM qux`))
if err != nil {
t.Fatalf("failed to query on local: %s", err.Error())
}
if exp, got := `[{"error":"no such table: qux"}]`, asJSON(rows); exp != got {
t.Fatalf("unexpected results, expt %s, got %s", exp, got)
t.Fatalf("unexpected results, exp %s, got %s", exp, got)
}
results, err = node.Store.Request(executeQueryRequestFromString(`SELECT * FROM qux`))
if err != nil {
t.Fatalf("failed to request on local: %s", err.Error())
}
if exp, got := `[{"error":"no such table: qux"}]`, asJSON(results); exp != got {
t.Fatalf("unexpected results, exp %s, got %s", exp, got)
}
rows, err = client.Query(queryRequestFromString(`SELECT * FROM qux`), leaderAddr, NO_CREDS, shortWait)
if err != nil {
t.Fatalf("failed to query via remote: %s", err.Error())
}
if exp, got := `[{"error":"no such table: qux"}]`, asJSON(rows); exp != got {
t.Fatalf("unexpected results, expt %s, got %s", exp, got)
t.Fatalf("unexpected results, exp %s, got %s", exp, got)
}
results, err = client.Request(executeQueryRequestFromString(`SELECT * FROM qux`), leaderAddr, NO_CREDS, shortWait)
if err != nil {
t.Fatalf("failed to query via remote: %s", err.Error())
}
if exp, got := `[{"error":"no such table: qux"}]`, asJSON(results); exp != got {
t.Fatalf("unexpected results, exp %s, got %s", exp, got)
}
}
@ -173,7 +219,7 @@ func Test_MultiNodeClusterRequestForwardOK(t *testing.T) {
t.Fatalf("got incorrect response from follower exp: %s, got: %s", exp, got)
}
res, err = leader.Execute(`INSERT INTO foo(name) VALUES("fiona")`)
res, err = followers[1].Request(`INSERT INTO foo(name) VALUES("fiona")`)
if err != nil {
t.Fatalf("failed to create table: %s", err.Error())
}
@ -181,11 +227,19 @@ func Test_MultiNodeClusterRequestForwardOK(t *testing.T) {
t.Fatalf("got incorrect response from follower exp: %s, got: %s", exp, got)
}
res, err = leader.Execute(`INSERT INTO foo(name) VALUES("fiona")`)
if err != nil {
t.Fatalf("failed to create table: %s", err.Error())
}
if exp, got := `{"results":[{"last_insert_id":3,"rows_affected":1}]}`, res; exp != got {
t.Fatalf("got incorrect response from follower exp: %s, got: %s", exp, got)
}
rows, err := followers[0].Query(`SELECT COUNT(*) FROM foo`)
if err != nil {
t.Fatalf("failed to create table: %s", err.Error())
}
if exp, got := `{"results":[{"columns":["COUNT(*)"],"types":[""],"values":[[2]]}]}`, rows; exp != got {
if exp, got := `{"results":[{"columns":["COUNT(*)"],"types":[""],"values":[[3]]}]}`, rows; exp != got {
t.Fatalf("got incorrect response from follower exp: %s, got: %s", exp, got)
}
}
@ -302,6 +356,27 @@ func queryRequestFromStrings(s []string) *command.QueryRequest {
}
}
func executeQueryRequestFromString(s string) *command.ExecuteQueryRequest {
return executeQueryRequestFromStrings([]string{s})
}
// executeQueryRequestFromStrings converts a slice of strings into a command.ExecuteQueryRequest
func executeQueryRequestFromStrings(s []string) *command.ExecuteQueryRequest {
stmts := make([]*command.Statement, len(s))
for i := range s {
stmts[i] = &command.Statement{
Sql: s[i],
}
}
return &command.ExecuteQueryRequest{
Request: &command.Request{
Statements: stmts,
Transaction: false,
},
Timings: false,
}
}
func mustNewDialer(header byte, remoteEncrypted, skipVerify bool) *tcp.Dialer {
var tlsConfig *tls.Config
var err error

@ -156,6 +156,55 @@ func Test_SingleNode(t *testing.T) {
}
}
func Test_SingleNodeRequest(t *testing.T) {
node := mustNewLeaderNode()
defer node.Deprovision()
tests := []struct {
stmt string
expected string
}{
{
stmt: `CREATE TABLE foo (id integer not null primary key, name text)`,
expected: `{"results":[{}]}`,
},
{
stmt: `INSERT INTO foo(name) VALUES("fiona")`,
expected: `{"results":[{"last_insert_id":1,"rows_affected":1}]}`,
},
{
stmt: `INSERT INTO bar(name) VALUES("fiona")`,
expected: `{"results":[{"error":"no such table: bar"}]}`,
},
{
stmt: `INSERT blah blah`,
expected: `{"results":[{"error":"near \"blah\": syntax error"}]}`,
},
{
stmt: `SELECT * FROM foo`,
expected: `{"results":[{"columns":["id","name"],"types":["integer","text"],"values":[[1,"fiona"]]}]}`,
},
{
stmt: `DROP TABLE bar`,
expected: `{"results":[{"error":"no such table: bar"}]}`,
},
{
stmt: `DROP TABLE foo`,
expected: `{"results":[{"last_insert_id":1,"rows_affected":1}]}`,
},
}
for i, tt := range tests {
r, err := node.Request(tt.stmt)
if err != nil {
t.Fatalf(`test %d failed "%s": %s`, i, tt.stmt, err.Error())
}
if r != tt.expected {
t.Fatalf(`test %d received wrong result "%s" got: %s exp: %s`, i, tt.stmt, r, tt.expected)
}
}
}
func Test_SingleNodeMulti(t *testing.T) {
node := mustNewLeaderNode()
defer node.Deprovision()
@ -326,6 +375,39 @@ func Test_SingleNodeParameterized(t *testing.T) {
}
}
func Test_SingleNodeRequestParameterized(t *testing.T) {
node := mustNewLeaderNode()
defer node.Deprovision()
tests := []struct {
stmt []interface{}
expected string
}{
{
stmt: []interface{}{"CREATE TABLE foo (id integer not null primary key, name text, age integer)"},
expected: `{"results":[{}]}`,
},
{
stmt: []interface{}{"INSERT INTO foo(name, age) VALUES(?, ?)", "fiona", 20},
expected: `{"results":[{"last_insert_id":1,"rows_affected":1}]}`,
},
{
stmt: []interface{}{"SELECT * FROM foo WHERE NAME=?", "fiona"},
expected: `{"results":[{"columns":["id","name","age"],"types":["integer","text","integer"],"values":[[1,"fiona",20]]}]}`,
},
}
for i, tt := range tests {
r, err := node.RequestMultiParameterized(tt.stmt)
if err != nil {
t.Fatalf(`test %d failed "%s": %s`, i, tt.stmt, err.Error())
}
if r != tt.expected {
t.Fatalf(`test %d received wrong result "%s" got: %s exp: %s`, i, tt.stmt, r, tt.expected)
}
}
}
func Test_SingleNodeParameterizedNull(t *testing.T) {
node := mustNewLeaderNode()
defer node.Deprovision()

Loading…
Cancel
Save