├── Makefile ├── README.md ├── _example ├── person.proto └── person_sql.go ├── empty.go ├── go.mod ├── go.sum ├── protoc-gen-sql ├── generator.go └── main.go ├── sql.proto └── sql └── sql.pb.go /Makefile: -------------------------------------------------------------------------------- 1 | compile: 2 | protoc --gogo_out=Mgoogle/protobuf/descriptor.proto=github.com/gogo/protobuf/protoc-gen-gogo/descriptor:sql *.proto 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # proto-go-sql 2 | 3 | Generate sql.Scanner and driver.Valuer implementations for your Protobufs. 4 | 5 | ## Example 6 | 7 | We want the generated struct for this Person message to implement sql.Scanner and driver.Valuer so we can easily write and read it as JSON from Postgres. 8 | 9 | So we compile the person.proto file: 10 | 11 | ``` proto 12 | syntax = "proto3"; 13 | 14 | import "github.com/travisjeffery/proto-go-sql/sql.proto"; 15 | 16 | message Person { 17 | option (sql.all) = "json"; 18 | 19 | string id = 1; 20 | } 21 | ``` 22 | 23 | And run: 24 | 25 | ``` sh 26 | $ protoc --sql_out=. person.proto 27 | ``` 28 | 29 | Generating this person_sql.go: 30 | 31 | ``` go 32 | func (t *Person) Scan(val interface{}) error { 33 | return json.Unmarshal(val.([]byte), t) 34 | } 35 | 36 | func (t *Person) Value() (driver.Value, error) { 37 | return json.Marshal(t) 38 | } 39 | 40 | ``` 41 | 42 | And we're done! 43 | 44 | ## License 45 | 46 | MIT 47 | 48 | --- 49 | 50 | - [travisjeffery.com](http://travisjeffery.com) 51 | - GitHub [@travisjeffery](https://github.com/travisjeffery) 52 | - Twitter [@travisjeffery](https://twitter.com/travisjeffery) 53 | - Medium [@travisjeffery](https://medium.com/@travisjeffery) 54 | -------------------------------------------------------------------------------- /_example/person.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | import "github.com/travisjeffery/proto-go-sql/sql.proto"; 4 | 5 | message Person { 6 | option (sql.all) = "json"; 7 | 8 | string id = 1; 9 | } 10 | -------------------------------------------------------------------------------- /_example/person_sql.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-gogo. DO NOT EDIT. 2 | // source: person.proto 3 | 4 | package person 5 | 6 | import ( 7 | driver "database/sql/driver" 8 | json "encoding/json" 9 | fmt "fmt" 10 | proto "github.com/gogo/protobuf/proto" 11 | _ "github.com/travisjeffery/proto-go-sql" 12 | math "math" 13 | ) 14 | 15 | // Reference imports to suppress errors if they are not otherwise used. 16 | var _ = proto.Marshal 17 | var _ = fmt.Errorf 18 | var _ = math.Inf 19 | 20 | func (t *Person) Scan(val interface{}) error { 21 | return json.Unmarshal(val.([]byte), t) 22 | } 23 | 24 | func (t *Person) Value() (driver.Value, error) { 25 | return json.Marshal(t) 26 | } 27 | -------------------------------------------------------------------------------- /empty.go: -------------------------------------------------------------------------------- 1 | package protogosql 2 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/travisjeffery/proto-go-sql 2 | 3 | go 1.13 4 | 5 | require github.com/gogo/protobuf v1.2.1 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= 2 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 3 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 4 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 5 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 6 | -------------------------------------------------------------------------------- /protoc-gen-sql/generator.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "html/template" 7 | "os" 8 | "reflect" 9 | 10 | "github.com/gogo/protobuf/proto" 11 | "github.com/gogo/protobuf/protoc-gen-gogo/descriptor" 12 | "github.com/gogo/protobuf/protoc-gen-gogo/generator" 13 | sql "github.com/travisjeffery/proto-go-sql/sql" 14 | ) 15 | 16 | type Generator struct { 17 | *generator.Generator 18 | generator.PluginImports 19 | write bool 20 | } 21 | 22 | func NewGenerator() *Generator { 23 | return &Generator{} 24 | } 25 | 26 | func (p *Generator) Name() string { 27 | return "sql" 28 | } 29 | 30 | func (p *Generator) Init(g *generator.Generator) { 31 | p.Generator = g 32 | } 33 | 34 | func (p *Generator) GenerateImports(file *generator.FileDescriptor) { 35 | if len(file.Messages()) == 0 { 36 | return 37 | } 38 | msgs := p.msgs(file) 39 | if len(msgs.JSON) > 0 { 40 | p.PrintImport("json", "encoding/json") 41 | } 42 | if len(msgs.GoGoProto) > 0 { 43 | p.PrintImport("gogoproto", "github.com/gogo/protobuf/proto") 44 | } 45 | p.PrintImport("driver", "database/sql/driver") 46 | } 47 | 48 | func (p *Generator) Generate(file *generator.FileDescriptor) { 49 | p.write = false 50 | t := template.Must(template.New("sql").Parse(tmpl)) 51 | var buf bytes.Buffer 52 | t.Execute(&buf, p.msgs(file)) 53 | p.P(buf.String()) 54 | } 55 | 56 | func (p *Generator) Write() bool { 57 | return p.write 58 | } 59 | 60 | func forEachMessage(parent *descriptor.DescriptorProto, children []*descriptor.DescriptorProto, f func(parent *descriptor.DescriptorProto, child *descriptor.DescriptorProto)) { 61 | for _, child := range children { 62 | f(parent, child) 63 | forEachMessage(child, child.NestedType, f) 64 | } 65 | } 66 | 67 | func (p *Generator) msgs(file *generator.FileDescriptor) Msgs { 68 | var msgs Msgs 69 | 70 | forEachMessage(nil, file.MessageType, func(parent *descriptor.DescriptorProto, child *descriptor.DescriptorProto) { 71 | var name string 72 | if parent != nil { 73 | parentName := generator.CamelCase(*parent.Name) 74 | childName := generator.CamelCase(*child.Name) 75 | name = fmt.Sprintf("%s_%s", parentName, childName) 76 | } else { 77 | name = generator.CamelCase(*child.Name) 78 | } 79 | child.Name = &name 80 | 81 | if !reflect.ValueOf(child.Options).IsNil() { 82 | v, err := proto.GetExtension(child.Options, sql.E_All) 83 | if err == nil { 84 | ext := v.(*string) 85 | 86 | switch *ext { 87 | case "json": 88 | p.write = true 89 | msgs.JSON = append(msgs.JSON, child) 90 | case "gogoprotobuf": 91 | p.write = true 92 | msgs.GoGoProto = append(msgs.GoGoProto, child) 93 | default: 94 | fmt.Fprintf(os.Stderr, "Unsupported marshal type: %s", *ext) 95 | } 96 | } 97 | } 98 | }) 99 | 100 | return msgs 101 | } 102 | 103 | func init() { 104 | generator.RegisterPlugin(NewGenerator()) 105 | } 106 | 107 | type Msgs struct { 108 | JSON []*descriptor.DescriptorProto 109 | GoGoProto []*descriptor.DescriptorProto 110 | } 111 | 112 | var tmpl = ` 113 | {{ range $message := .JSON }} 114 | func (t *{{ $message.Name }}) Scan(val interface{}) error { 115 | return json.Unmarshal(val.([]byte), t) 116 | } 117 | 118 | func (t *{{ $message.Name }}) Value() (driver.Value, error) { 119 | return json.Marshal(t) 120 | } 121 | {{ end }} 122 | 123 | {{ range $message := .GoGoProto }} 124 | func (t *{{ $message.Name }}) Scan(val interface{}) error { 125 | return gogoproto.Unmarshal(val.([]byte), t) 126 | } 127 | 128 | func (t *{{ $message.Name }}) Value() (driver.Value, error) { 129 | return gogoproto.Marshal(t) 130 | } 131 | {{ end }} 132 | ` 133 | -------------------------------------------------------------------------------- /protoc-gen-sql/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gogo/protobuf/vanity/command" 5 | ) 6 | 7 | func main() { 8 | request := command.Read() 9 | plugin := NewGenerator() 10 | response := command.GeneratePlugin(request, plugin, "_sql.go") 11 | if !plugin.Write() { 12 | return 13 | } 14 | command.Write(response) 15 | } 16 | -------------------------------------------------------------------------------- /sql.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package sql; 4 | 5 | import "google/protobuf/descriptor.proto"; 6 | 7 | extend google.protobuf.MessageOptions { 8 | string all = 90001; 9 | } 10 | -------------------------------------------------------------------------------- /sql/sql.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-gogo. DO NOT EDIT. 2 | // source: sql.proto 3 | 4 | /* 5 | Package sql is a generated protocol buffer package. 6 | 7 | It is generated from these files: 8 | sql.proto 9 | 10 | It has these top-level messages: 11 | */ 12 | package sql 13 | 14 | import proto "github.com/gogo/protobuf/proto" 15 | import fmt "fmt" 16 | import math "math" 17 | import google_protobuf "github.com/gogo/protobuf/protoc-gen-gogo/descriptor" 18 | 19 | // Reference imports to suppress errors if they are not otherwise used. 20 | var _ = proto.Marshal 21 | var _ = fmt.Errorf 22 | var _ = math.Inf 23 | 24 | // This is a compile-time assertion to ensure that this generated file 25 | // is compatible with the proto package it is being compiled against. 26 | // A compilation error at this line likely means your copy of the 27 | // proto package needs to be updated. 28 | const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package 29 | 30 | var E_All = &proto.ExtensionDesc{ 31 | ExtendedType: (*google_protobuf.MessageOptions)(nil), 32 | ExtensionType: (*string)(nil), 33 | Field: 90001, 34 | Name: "sql.all", 35 | Tag: "bytes,90001,opt,name=all", 36 | Filename: "sql.proto", 37 | } 38 | 39 | func init() { 40 | proto.RegisterExtension(E_All) 41 | } 42 | 43 | func init() { proto.RegisterFile("sql.proto", fileDescriptorSql) } 44 | 45 | var fileDescriptorSql = []byte{ 46 | // 113 bytes of a gzipped FileDescriptorProto 47 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2c, 0x2e, 0xcc, 0xd1, 48 | 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x2e, 0x2e, 0xcc, 0x91, 0x52, 0x48, 0xcf, 0xcf, 0x4f, 49 | 0xcf, 0x49, 0xd5, 0x07, 0x0b, 0x25, 0x95, 0xa6, 0xe9, 0xa7, 0xa4, 0x16, 0x27, 0x17, 0x65, 0x16, 50 | 0x94, 0xe4, 0x17, 0x41, 0x94, 0x59, 0x19, 0x73, 0x31, 0x27, 0xe6, 0xe4, 0x08, 0xc9, 0xeb, 0x41, 51 | 0x54, 0xea, 0xc1, 0x54, 0xea, 0xf9, 0xa6, 0x16, 0x17, 0x27, 0xa6, 0xa7, 0xfa, 0x17, 0x94, 0x64, 52 | 0xe6, 0xe7, 0x15, 0x4b, 0x4c, 0xdc, 0xcf, 0xaa, 0xc0, 0xa8, 0xc1, 0x19, 0x04, 0x52, 0x9d, 0xc4, 53 | 0x06, 0x56, 0x65, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0xb4, 0x69, 0x82, 0xdf, 0x6f, 0x00, 0x00, 54 | 0x00, 55 | } 56 | --------------------------------------------------------------------------------