Skip to content

Commit

Permalink
proto: add functionality to marshal protobuf binary from file descriptor
Browse files Browse the repository at this point in the history
Part of cockroachdb#47534

Cockroachdb cannot currently marshal a protobuf binary into JSON based on a provided FileDescriptor.

The newest version of the `google.golang.org/protobuf` package allows for this functionality. gogo/protobuf does not.

Since cockroachdb uses gogo/protobuf for its better performance, I imported `google.golang.org/protobuf` under an alias.

Using that libary, I created `NewJSONMessageFromFileDescriptorn` to take a protobuf binary and a FileDescriptorand return JSON.

To handle linting errors, temporary wrapper functions `TODOMarshal` and `TODOUnmarshal`; these will need to be renamed if this workaround is acceptable

Release note: none
  • Loading branch information
cpustejovsky committed Dec 14, 2023
1 parent da553e3 commit 0fbde84
Show file tree
Hide file tree
Showing 9 changed files with 128 additions and 1 deletion.
1 change: 1 addition & 0 deletions pkg/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -1987,6 +1987,7 @@ GO_TARGETS = [
"//pkg/sql/plpgsql:plpgsql",
"//pkg/sql/privilege:privilege",
"//pkg/sql/privilege:privilege_test",
"//pkg/sql/protoreflect/gprototest:gprototest",
"//pkg/sql/protoreflect/test:test",
"//pkg/sql/protoreflect:protoreflect",
"//pkg/sql/protoreflect:protoreflect_test",
Expand Down
1 change: 1 addition & 0 deletions pkg/gen/protobuf.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ PROTOBUF_SRCS = [
"//pkg/sql/inverted:inverted_go_proto",
"//pkg/sql/lex:lex_go_proto",
"//pkg/sql/pgwire/pgerror:pgerror_go_proto",
"//pkg/sql/protoreflect/gprototest:gprototest_go_proto",
"//pkg/sql/protoreflect/test:protoreflecttest_go_proto",
"//pkg/sql/rowenc/rowencpb:rowencpb_go_proto",
"//pkg/sql/schemachanger/scpb:scpb_go_proto",
Expand Down
5 changes: 5 additions & 0 deletions pkg/sql/protoreflect/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ go_library(
"@com_github_cockroachdb_errors//:errors",
"@com_github_gogo_protobuf//jsonpb",
"@com_github_gogo_protobuf//proto",
"@org_golang_google_protobuf//encoding/protojson",
"@org_golang_google_protobuf//reflect/protodesc",
"@org_golang_google_protobuf//reflect/protoreflect",
"@org_golang_google_protobuf//types/dynamicpb",
],
)

Expand All @@ -33,6 +37,7 @@ go_test(
"//pkg/security/securitytest",
"//pkg/sql/catalog/catenumpb",
"//pkg/sql/catalog/descpb",
"//pkg/sql/protoreflect/gprototest",
"//pkg/sql/protoreflect/test",
"//pkg/sql/types",
"//pkg/util/json",
Expand Down
26 changes: 26 additions & 0 deletions pkg/sql/protoreflect/gprototest/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
load("@rules_proto//proto:defs.bzl", "proto_library")
load("@io_bazel_rules_go//go:def.bzl", "go_library")
load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")

proto_library(
name = "gprototest_proto",
srcs = ["gprototest.proto"],
strip_import_prefix = "/pkg",
visibility = ["//visibility:public"],
)

go_proto_library(
name = "gprototest_go_proto",
compilers = ["@io_bazel_rules_go//proto:go_proto"], #keep
importpath = "github.com/cockroachdb/cockroach/pkg/sql/protoreflect/gprototest",
proto = ":gprototest_proto",
visibility = ["//visibility:public"],
)

go_library(
name = "gprototest",
# srcs = ["wrap.go"],
embed = [":gprototest_go_proto"], # keep
importpath = "github.com/cockroachdb/cockroach/pkg/sql/protoreflect/gprototest",
visibility = ["//visibility:public"],
)
22 changes: 22 additions & 0 deletions pkg/sql/protoreflect/gprototest/gprototest.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright 2021 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

syntax = "proto3";
package cockroach.sql.gprototest;
option go_package = "github.com/cockroachdb/cockroach/pkg/sql/protoreflect/gprototest";

message Inner {
string value = 1;
}

message Outer {
string value = 1;
Inner inner = 2;
}
40 changes: 40 additions & 0 deletions pkg/sql/protoreflect/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ import (
"github.com/cockroachdb/errors"
"github.com/gogo/protobuf/jsonpb"
"github.com/gogo/protobuf/proto"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/reflect/protodesc"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/types/dynamicpb"
)

var shorthands map[string]protoutil.Message = map[string]protoutil.Message{}
Expand Down Expand Up @@ -78,6 +82,42 @@ func NewMessage(name string) (protoutil.Message, error) {
return msg, nil
}

// NewJSONMessageFromFileDescriptor decodes a protobuf message from binary to JSON
// based on the provided protoreflect.FileDescriptor
func NewJSONMessageFromFileDescriptor(
name string, fd protoreflect.FileDescriptor, data []byte, resolver protodesc.Resolver,
) (jsonb.JSON, error) {
//convert FileDescriptor to FileDescriptorProto
fdp := protodesc.ToFileDescriptorProto(fd)

//create new proto File from FileDescriptorProto
f, err := protodesc.NewFile(fdp, resolver)
if err != nil {
return nil, errors.Wrap(err, "error creating protodesc.NewFile: %w")
}

//get MessageDescriptor from File based on provided name
md := f.Messages().ByName(protoreflect.Name(name))
if md == nil {
return nil, errors.Newf("message descriptor was nil for name %s", name)
}

//Get message to unmarshall protobuf binary from the MessageDescriptor
mt := dynamicpb.NewMessageType(md)
msg := mt.New().Interface()

//Unmarshal
err = protoutil.TODOUnmarshal(data, msg)
if err != nil {
return nil, err
}

//Format Protobuf message to JSON string
json := protojson.Format(msg)

return jsonb.ParseJSON(json)
}

// DecodeMessage decodes protocol message specified as its fully
// qualified name, and it's marshaled data, into a protoutil.Message.
func DecodeMessage(name string, data []byte) (protoutil.Message, error) {
Expand Down
24 changes: 23 additions & 1 deletion pkg/sql/protoreflect/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import (
"github.com/cockroachdb/cockroach/pkg/sql/catalog/catenumpb"
"github.com/cockroachdb/cockroach/pkg/sql/catalog/descpb"
"github.com/cockroachdb/cockroach/pkg/sql/protoreflect"
protoreflecttest "github.com/cockroachdb/cockroach/pkg/sql/protoreflect/test"
gprotoreflecttest "github.com/cockroachdb/cockroach/pkg/sql/protoreflect/gprototest"
"github.com/cockroachdb/cockroach/pkg/sql/protoreflect/test"
"github.com/cockroachdb/cockroach/pkg/sql/types"
jsonb "github.com/cockroachdb/cockroach/pkg/util/json"
"github.com/cockroachdb/cockroach/pkg/util/leaktest"
Expand Down Expand Up @@ -261,3 +262,24 @@ func TestInvalidConversions(t *testing.T) {
require.Error(t, err)
})
}

func TestNewMessageFromFileDescriptor(t *testing.T) {
msg := "Hello, World"
in := gprotoreflecttest.Inner{
Value: msg,
}
fd := gprotoreflecttest.File_sql_protoreflect_gprototest_gprototest_proto
bin, err := protoutil.TODOMarshal(&in)
require.Nil(t, err)

t.Run("successfully gets message from FileDescriptor", func(t *testing.T) {
out, err := protoreflect.NewJSONMessageFromFileDescriptor("Inner", fd, bin, nil)
require.Nil(t, err)
require.Equal(t, msg, fetchPath(t, out, "value"))
})
t.Run("fails if name is incorrect", func(t *testing.T) {
out, err := protoreflect.NewJSONMessageFromFileDescriptor("foo", fd, bin, nil)
require.Nil(t, out)
require.Error(t, err)
})
}
1 change: 1 addition & 0 deletions pkg/util/protoutil/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ go_library(
"@com_github_gogo_protobuf//jsonpb",
"@com_github_gogo_protobuf//proto",
"@com_github_grpc_ecosystem_grpc_gateway//runtime:go_default_library",
"@org_golang_google_protobuf//proto",
],
)

Expand Down
9 changes: 9 additions & 0 deletions pkg/util/protoutil/marshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (

"github.com/cockroachdb/cockroach/pkg/util/buildutil"
"github.com/gogo/protobuf/proto"
gproto "google.golang.org/protobuf/proto"
)

// Message extends the proto.Message interface with the MarshalTo and Size
Expand Down Expand Up @@ -74,3 +75,11 @@ func Unmarshal(data []byte, pb Message) error {
pb.Reset()
return pb.Unmarshal(data)
}

func TODOMarshal(pb gproto.Message) ([]byte, error) {
return gproto.Marshal(pb)
}

func TODOUnmarshal(data []byte, pb gproto.Message) error {
return gproto.Unmarshal(data, pb)
}

0 comments on commit 0fbde84

Please sign in to comment.