diff --git a/Makefile b/Makefile index 07fcb015..d740ff54 100644 --- a/Makefile +++ b/Makefile @@ -42,6 +42,9 @@ example/user/*.pb.go: example/user/*.proto example/postgres_arrays/*.pb.go: example/postgres_arrays/*.proto buf generate --template example/postgres_arrays/buf.gen.yaml --path example/postgres_arrays +example/clickhouse_arrays/*.pb.go: example/clickhouse_arrays/*.proto + buf generate --template example/clickhouse_arrays/buf.gen.yaml --path example/clickhouse_arrays + install: go install -v . diff --git a/example/clickhouse_arrays/Makefile b/example/clickhouse_arrays/Makefile new file mode 100644 index 00000000..2c2a25f0 --- /dev/null +++ b/example/clickhouse_arrays/Makefile @@ -0,0 +1,19 @@ +all: protos + +protos: + @protoc \ + -I /usr/local/include \ + -I ${GOPATH}/src \ + --go_out=${GOPATH}/src \ + --gorm_out=engine=clickhouse:${GOPATH}/src \ + --proto_path=. \ + clickhouse_arrays.proto + +protos_without_clickhouse: + @protoc \ + -I /usr/local/include \ + -I ${GOPATH}/src \ + --go_out=${GOPATH}/src \ + --gorm_out=${GOPATH}/src \ + --proto_path=. \ + clickhouse_arrays.proto diff --git a/example/clickhouse_arrays/buf.gen.yaml b/example/clickhouse_arrays/buf.gen.yaml new file mode 100644 index 00000000..587b01b5 --- /dev/null +++ b/example/clickhouse_arrays/buf.gen.yaml @@ -0,0 +1,8 @@ +version: v1 +plugins: + - plugin: buf.build/protocolbuffers/go:v1.30.0 + out: example + opt: paths=source_relative + - plugin: gorm + out: example + opt: engine=clickhouse,paths=source_relative,enums=string,gateway=true:./example/clickhouse_arrays diff --git a/example/clickhouse_arrays/clickhouse_arrays.pb.go b/example/clickhouse_arrays/clickhouse_arrays.pb.go new file mode 100644 index 00000000..38790a7f --- /dev/null +++ b/example/clickhouse_arrays/clickhouse_arrays.pb.go @@ -0,0 +1,205 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.30.0 +// protoc (unknown) +// source: clickhouse_arrays/clickhouse_arrays.proto + +package clickhouse_arrays + +import ( + _ "github.com/infobloxopen/protoc-gen-gorm/options" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Example struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // id for example + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` + ArrayOfBools []bool `protobuf:"varint,20,rep,packed,name=array_of_bools,json=arrayOfBools,proto3" json:"array_of_bools,omitempty"` + ArrayOfFloat64 []float64 `protobuf:"fixed64,30,rep,packed,name=array_of_float64,json=arrayOfFloat64,proto3" json:"array_of_float64,omitempty"` + ArrayOfInt64 []int64 `protobuf:"varint,40,rep,packed,name=array_of_int64,json=arrayOfInt64,proto3" json:"array_of_int64,omitempty"` + ArrayOfString []string `protobuf:"bytes,50,rep,name=array_of_string,json=arrayOfString,proto3" json:"array_of_string,omitempty"` +} + +func (x *Example) Reset() { + *x = Example{} + if protoimpl.UnsafeEnabled { + mi := &file_clickhouse_arrays_clickhouse_arrays_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Example) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Example) ProtoMessage() {} + +func (x *Example) ProtoReflect() protoreflect.Message { + mi := &file_clickhouse_arrays_clickhouse_arrays_proto_msgTypes[0] + 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 Example.ProtoReflect.Descriptor instead. +func (*Example) Descriptor() ([]byte, []int) { + return file_clickhouse_arrays_clickhouse_arrays_proto_rawDescGZIP(), []int{0} +} + +func (x *Example) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Example) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *Example) GetArrayOfBools() []bool { + if x != nil { + return x.ArrayOfBools + } + return nil +} + +func (x *Example) GetArrayOfFloat64() []float64 { + if x != nil { + return x.ArrayOfFloat64 + } + return nil +} + +func (x *Example) GetArrayOfInt64() []int64 { + if x != nil { + return x.ArrayOfInt64 + } + return nil +} + +func (x *Example) GetArrayOfString() []string { + if x != nil { + return x.ArrayOfString + } + return nil +} + +var File_clickhouse_arrays_clickhouse_arrays_proto protoreflect.FileDescriptor + +var file_clickhouse_arrays_clickhouse_arrays_proto_rawDesc = []byte{ + 0x0a, 0x29, 0x63, 0x6c, 0x69, 0x63, 0x6b, 0x68, 0x6f, 0x75, 0x73, 0x65, 0x5f, 0x61, 0x72, 0x72, + 0x61, 0x79, 0x73, 0x2f, 0x63, 0x6c, 0x69, 0x63, 0x6b, 0x68, 0x6f, 0x75, 0x73, 0x65, 0x5f, 0x61, + 0x72, 0x72, 0x61, 0x79, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x11, 0x63, 0x6c, 0x69, + 0x63, 0x6b, 0x68, 0x6f, 0x75, 0x73, 0x65, 0x2e, 0x61, 0x72, 0x72, 0x61, 0x79, 0x73, 0x1a, 0x12, + 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x67, 0x6f, 0x72, 0x6d, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x22, 0xf1, 0x01, 0x0a, 0x07, 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x12, 0x1e, + 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0e, 0xba, 0xb9, 0x19, 0x0a, + 0x0a, 0x08, 0x12, 0x04, 0x55, 0x55, 0x49, 0x44, 0x28, 0x01, 0x52, 0x02, 0x69, 0x64, 0x12, 0x20, + 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x24, 0x0a, 0x0e, 0x61, 0x72, 0x72, 0x61, 0x79, 0x5f, 0x6f, 0x66, 0x5f, 0x62, 0x6f, 0x6f, + 0x6c, 0x73, 0x18, 0x14, 0x20, 0x03, 0x28, 0x08, 0x52, 0x0c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x4f, + 0x66, 0x42, 0x6f, 0x6f, 0x6c, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x61, 0x72, 0x72, 0x61, 0x79, 0x5f, + 0x6f, 0x66, 0x5f, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x36, 0x34, 0x18, 0x1e, 0x20, 0x03, 0x28, 0x01, + 0x52, 0x0e, 0x61, 0x72, 0x72, 0x61, 0x79, 0x4f, 0x66, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x36, 0x34, + 0x12, 0x24, 0x0a, 0x0e, 0x61, 0x72, 0x72, 0x61, 0x79, 0x5f, 0x6f, 0x66, 0x5f, 0x69, 0x6e, 0x74, + 0x36, 0x34, 0x18, 0x28, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x4f, + 0x66, 0x49, 0x6e, 0x74, 0x36, 0x34, 0x12, 0x26, 0x0a, 0x0f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x5f, + 0x6f, 0x66, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x32, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x0d, 0x61, 0x72, 0x72, 0x61, 0x79, 0x4f, 0x66, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3a, 0x06, + 0xba, 0xb9, 0x19, 0x02, 0x08, 0x01, 0x42, 0x55, 0x5a, 0x53, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x69, 0x6e, 0x66, 0x6f, 0x62, 0x6c, 0x6f, 0x78, 0x6f, 0x70, 0x65, + 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2d, 0x67, 0x65, 0x6e, 0x2d, 0x67, 0x6f, 0x72, + 0x6d, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f, 0x63, 0x6c, 0x69, 0x63, 0x6b, 0x68, + 0x6f, 0x75, 0x73, 0x65, 0x5f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x73, 0x3b, 0x63, 0x6c, 0x69, 0x63, + 0x6b, 0x68, 0x6f, 0x75, 0x73, 0x65, 0x5f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x73, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_clickhouse_arrays_clickhouse_arrays_proto_rawDescOnce sync.Once + file_clickhouse_arrays_clickhouse_arrays_proto_rawDescData = file_clickhouse_arrays_clickhouse_arrays_proto_rawDesc +) + +func file_clickhouse_arrays_clickhouse_arrays_proto_rawDescGZIP() []byte { + file_clickhouse_arrays_clickhouse_arrays_proto_rawDescOnce.Do(func() { + file_clickhouse_arrays_clickhouse_arrays_proto_rawDescData = protoimpl.X.CompressGZIP(file_clickhouse_arrays_clickhouse_arrays_proto_rawDescData) + }) + return file_clickhouse_arrays_clickhouse_arrays_proto_rawDescData +} + +var file_clickhouse_arrays_clickhouse_arrays_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_clickhouse_arrays_clickhouse_arrays_proto_goTypes = []interface{}{ + (*Example)(nil), // 0: clickhouse.arrays.Example +} +var file_clickhouse_arrays_clickhouse_arrays_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_clickhouse_arrays_clickhouse_arrays_proto_init() } +func file_clickhouse_arrays_clickhouse_arrays_proto_init() { + if File_clickhouse_arrays_clickhouse_arrays_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_clickhouse_arrays_clickhouse_arrays_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Example); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_clickhouse_arrays_clickhouse_arrays_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_clickhouse_arrays_clickhouse_arrays_proto_goTypes, + DependencyIndexes: file_clickhouse_arrays_clickhouse_arrays_proto_depIdxs, + MessageInfos: file_clickhouse_arrays_clickhouse_arrays_proto_msgTypes, + }.Build() + File_clickhouse_arrays_clickhouse_arrays_proto = out.File + file_clickhouse_arrays_clickhouse_arrays_proto_rawDesc = nil + file_clickhouse_arrays_clickhouse_arrays_proto_goTypes = nil + file_clickhouse_arrays_clickhouse_arrays_proto_depIdxs = nil +} diff --git a/example/clickhouse_arrays/clickhouse_arrays.pb.gorm.go b/example/clickhouse_arrays/clickhouse_arrays.pb.gorm.go new file mode 100644 index 00000000..2688f34b --- /dev/null +++ b/example/clickhouse_arrays/clickhouse_arrays.pb.gorm.go @@ -0,0 +1,477 @@ +package clickhouse_arrays + +import ( + context "context" + fmt "fmt" + gateway "github.com/infobloxopen/atlas-app-toolkit/gateway" + errors "github.com/infobloxopen/protoc-gen-gorm/errors" + pq "github.com/lib/pq" + field_mask "google.golang.org/genproto/protobuf/field_mask" + gorm "gorm.io/gorm" +) + +type ExampleORM struct { + ArrayOfBools pq.BoolArray `gorm:"type:Array(Bool)"` + ArrayOfFloat64 pq.Float64Array `gorm:"type:Array(Double)"` + ArrayOfInt64 pq.Int64Array `gorm:"type:Array(Int64)"` + ArrayOfString pq.StringArray `gorm:"type:Array(String)"` + Description string + Id string `gorm:"type:UUID;primaryKey"` +} + +// TableName overrides the default tablename generated by GORM +func (ExampleORM) TableName() string { + return "examples" +} + +// ToORM runs the BeforeToORM hook if present, converts the fields of this +// object to ORM format, runs the AfterToORM hook, then returns the ORM object +func (m *Example) ToORM(ctx context.Context) (ExampleORM, error) { + to := ExampleORM{} + var err error + if prehook, ok := interface{}(m).(ExampleWithBeforeToORM); ok { + if err = prehook.BeforeToORM(ctx, &to); err != nil { + return to, err + } + } + to.Id = m.Id + to.Description = m.Description + if m.ArrayOfBools != nil { + to.ArrayOfBools = make(pq.BoolArray, len(m.ArrayOfBools)) + copy(to.ArrayOfBools, m.ArrayOfBools) + } + if m.ArrayOfFloat64 != nil { + to.ArrayOfFloat64 = make(pq.Float64Array, len(m.ArrayOfFloat64)) + copy(to.ArrayOfFloat64, m.ArrayOfFloat64) + } + if m.ArrayOfInt64 != nil { + to.ArrayOfInt64 = make(pq.Int64Array, len(m.ArrayOfInt64)) + copy(to.ArrayOfInt64, m.ArrayOfInt64) + } + if m.ArrayOfString != nil { + to.ArrayOfString = make(pq.StringArray, len(m.ArrayOfString)) + copy(to.ArrayOfString, m.ArrayOfString) + } + if posthook, ok := interface{}(m).(ExampleWithAfterToORM); ok { + err = posthook.AfterToORM(ctx, &to) + } + return to, err +} + +// ToPB runs the BeforeToPB hook if present, converts the fields of this +// object to PB format, runs the AfterToPB hook, then returns the PB object +func (m *ExampleORM) ToPB(ctx context.Context) (Example, error) { + to := Example{} + var err error + if prehook, ok := interface{}(m).(ExampleWithBeforeToPB); ok { + if err = prehook.BeforeToPB(ctx, &to); err != nil { + return to, err + } + } + to.Id = m.Id + to.Description = m.Description + if m.ArrayOfBools != nil { + to.ArrayOfBools = make(pq.BoolArray, len(m.ArrayOfBools)) + copy(to.ArrayOfBools, m.ArrayOfBools) + } + if m.ArrayOfFloat64 != nil { + to.ArrayOfFloat64 = make(pq.Float64Array, len(m.ArrayOfFloat64)) + copy(to.ArrayOfFloat64, m.ArrayOfFloat64) + } + if m.ArrayOfInt64 != nil { + to.ArrayOfInt64 = make(pq.Int64Array, len(m.ArrayOfInt64)) + copy(to.ArrayOfInt64, m.ArrayOfInt64) + } + if m.ArrayOfString != nil { + to.ArrayOfString = make(pq.StringArray, len(m.ArrayOfString)) + copy(to.ArrayOfString, m.ArrayOfString) + } + if posthook, ok := interface{}(m).(ExampleWithAfterToPB); ok { + err = posthook.AfterToPB(ctx, &to) + } + return to, err +} + +// The following are interfaces you can implement for special behavior during ORM/PB conversions +// of type Example the arg will be the target, the caller the one being converted from + +// ExampleBeforeToORM called before default ToORM code +type ExampleWithBeforeToORM interface { + BeforeToORM(context.Context, *ExampleORM) error +} + +// ExampleAfterToORM called after default ToORM code +type ExampleWithAfterToORM interface { + AfterToORM(context.Context, *ExampleORM) error +} + +// ExampleBeforeToPB called before default ToPB code +type ExampleWithBeforeToPB interface { + BeforeToPB(context.Context, *Example) error +} + +// ExampleAfterToPB called after default ToPB code +type ExampleWithAfterToPB interface { + AfterToPB(context.Context, *Example) error +} + +// DefaultCreateExample executes a basic gorm create call +func DefaultCreateExample(ctx context.Context, in *Example, db *gorm.DB) (*Example, error) { + if in == nil { + return nil, errors.NilArgumentError + } + ormObj, err := in.ToORM(ctx) + if err != nil { + return nil, err + } + if hook, ok := interface{}(&ormObj).(ExampleORMWithBeforeCreate_); ok { + if db, err = hook.BeforeCreate_(ctx, db); err != nil { + return nil, err + } + } + if err = db.Omit().Create(&ormObj).Error; err != nil { + return nil, err + } + if hook, ok := interface{}(&ormObj).(ExampleORMWithAfterCreate_); ok { + if err = hook.AfterCreate_(ctx, db); err != nil { + return nil, err + } + } + pbResponse, err := ormObj.ToPB(ctx) + return &pbResponse, err +} + +type ExampleORMWithBeforeCreate_ interface { + BeforeCreate_(context.Context, *gorm.DB) (*gorm.DB, error) +} +type ExampleORMWithAfterCreate_ interface { + AfterCreate_(context.Context, *gorm.DB) error +} + +func DefaultReadExample(ctx context.Context, in *Example, db *gorm.DB) (*Example, error) { + if in == nil { + return nil, errors.NilArgumentError + } + ormObj, err := in.ToORM(ctx) + if err != nil { + return nil, err + } + if ormObj.Id == "" { + return nil, errors.EmptyIdError + } + if hook, ok := interface{}(&ormObj).(ExampleORMWithBeforeReadApplyQuery); ok { + if db, err = hook.BeforeReadApplyQuery(ctx, db); err != nil { + return nil, err + } + } + if hook, ok := interface{}(&ormObj).(ExampleORMWithBeforeReadFind); ok { + if db, err = hook.BeforeReadFind(ctx, db); err != nil { + return nil, err + } + } + ormResponse := ExampleORM{} + if err = db.Where(&ormObj).First(&ormResponse).Error; err != nil { + return nil, err + } + if hook, ok := interface{}(&ormResponse).(ExampleORMWithAfterReadFind); ok { + if err = hook.AfterReadFind(ctx, db); err != nil { + return nil, err + } + } + pbResponse, err := ormResponse.ToPB(ctx) + return &pbResponse, err +} + +type ExampleORMWithBeforeReadApplyQuery interface { + BeforeReadApplyQuery(context.Context, *gorm.DB) (*gorm.DB, error) +} +type ExampleORMWithBeforeReadFind interface { + BeforeReadFind(context.Context, *gorm.DB) (*gorm.DB, error) +} +type ExampleORMWithAfterReadFind interface { + AfterReadFind(context.Context, *gorm.DB) error +} + +func DefaultDeleteExample(ctx context.Context, in *Example, db *gorm.DB) error { + if in == nil { + return errors.NilArgumentError + } + ormObj, err := in.ToORM(ctx) + if err != nil { + return err + } + if ormObj.Id == "" { + return errors.EmptyIdError + } + if hook, ok := interface{}(&ormObj).(ExampleORMWithBeforeDelete_); ok { + if db, err = hook.BeforeDelete_(ctx, db); err != nil { + return err + } + } + err = db.Where(&ormObj).Delete(&ExampleORM{}).Error + if err != nil { + return err + } + if hook, ok := interface{}(&ormObj).(ExampleORMWithAfterDelete_); ok { + err = hook.AfterDelete_(ctx, db) + } + return err +} + +type ExampleORMWithBeforeDelete_ interface { + BeforeDelete_(context.Context, *gorm.DB) (*gorm.DB, error) +} +type ExampleORMWithAfterDelete_ interface { + AfterDelete_(context.Context, *gorm.DB) error +} + +func DefaultDeleteExampleSet(ctx context.Context, in []*Example, db *gorm.DB) error { + if in == nil { + return errors.NilArgumentError + } + var err error + keys := []string{} + for _, obj := range in { + ormObj, err := obj.ToORM(ctx) + if err != nil { + return err + } + if ormObj.Id == "" { + return errors.EmptyIdError + } + keys = append(keys, ormObj.Id) + } + if hook, ok := (interface{}(&ExampleORM{})).(ExampleORMWithBeforeDeleteSet); ok { + if db, err = hook.BeforeDeleteSet(ctx, in, db); err != nil { + return err + } + } + err = db.Where("id in (?)", keys).Delete(&ExampleORM{}).Error + if err != nil { + return err + } + if hook, ok := (interface{}(&ExampleORM{})).(ExampleORMWithAfterDeleteSet); ok { + err = hook.AfterDeleteSet(ctx, in, db) + } + return err +} + +type ExampleORMWithBeforeDeleteSet interface { + BeforeDeleteSet(context.Context, []*Example, *gorm.DB) (*gorm.DB, error) +} +type ExampleORMWithAfterDeleteSet interface { + AfterDeleteSet(context.Context, []*Example, *gorm.DB) error +} + +// DefaultStrictUpdateExample clears / replaces / appends first level 1:many children and then executes a gorm update call +func DefaultStrictUpdateExample(ctx context.Context, in *Example, db *gorm.DB) (*Example, error) { + if in == nil { + return nil, fmt.Errorf("Nil argument to DefaultStrictUpdateExample") + } + ormObj, err := in.ToORM(ctx) + if err != nil { + return nil, err + } + var count int64 + lockedRow := &ExampleORM{} + count = db.Model(&ormObj).Set("gorm:query_option", "FOR UPDATE").Where("id=?", ormObj.Id).First(lockedRow).RowsAffected + if hook, ok := interface{}(&ormObj).(ExampleORMWithBeforeStrictUpdateCleanup); ok { + if db, err = hook.BeforeStrictUpdateCleanup(ctx, db); err != nil { + return nil, err + } + } + if hook, ok := interface{}(&ormObj).(ExampleORMWithBeforeStrictUpdateSave); ok { + if db, err = hook.BeforeStrictUpdateSave(ctx, db); err != nil { + return nil, err + } + } + if err = db.Omit().Save(&ormObj).Error; err != nil { + return nil, err + } + if hook, ok := interface{}(&ormObj).(ExampleORMWithAfterStrictUpdateSave); ok { + if err = hook.AfterStrictUpdateSave(ctx, db); err != nil { + return nil, err + } + } + pbResponse, err := ormObj.ToPB(ctx) + if err != nil { + return nil, err + } + if count == 0 { + err = gateway.SetCreated(ctx, "") + } + return &pbResponse, err +} + +type ExampleORMWithBeforeStrictUpdateCleanup interface { + BeforeStrictUpdateCleanup(context.Context, *gorm.DB) (*gorm.DB, error) +} +type ExampleORMWithBeforeStrictUpdateSave interface { + BeforeStrictUpdateSave(context.Context, *gorm.DB) (*gorm.DB, error) +} +type ExampleORMWithAfterStrictUpdateSave interface { + AfterStrictUpdateSave(context.Context, *gorm.DB) error +} + +// DefaultPatchExample executes a basic gorm update call with patch behavior +func DefaultPatchExample(ctx context.Context, in *Example, updateMask *field_mask.FieldMask, db *gorm.DB) (*Example, error) { + if in == nil { + return nil, errors.NilArgumentError + } + var pbObj Example + var err error + if hook, ok := interface{}(&pbObj).(ExampleWithBeforePatchRead); ok { + if db, err = hook.BeforePatchRead(ctx, in, updateMask, db); err != nil { + return nil, err + } + } + pbReadRes, err := DefaultReadExample(ctx, &Example{Id: in.GetId()}, db) + if err != nil { + return nil, err + } + pbObj = *pbReadRes + if hook, ok := interface{}(&pbObj).(ExampleWithBeforePatchApplyFieldMask); ok { + if db, err = hook.BeforePatchApplyFieldMask(ctx, in, updateMask, db); err != nil { + return nil, err + } + } + if _, err := DefaultApplyFieldMaskExample(ctx, &pbObj, in, updateMask, "", db); err != nil { + return nil, err + } + if hook, ok := interface{}(&pbObj).(ExampleWithBeforePatchSave); ok { + if db, err = hook.BeforePatchSave(ctx, in, updateMask, db); err != nil { + return nil, err + } + } + pbResponse, err := DefaultStrictUpdateExample(ctx, &pbObj, db) + if err != nil { + return nil, err + } + if hook, ok := interface{}(pbResponse).(ExampleWithAfterPatchSave); ok { + if err = hook.AfterPatchSave(ctx, in, updateMask, db); err != nil { + return nil, err + } + } + return pbResponse, nil +} + +type ExampleWithBeforePatchRead interface { + BeforePatchRead(context.Context, *Example, *field_mask.FieldMask, *gorm.DB) (*gorm.DB, error) +} +type ExampleWithBeforePatchApplyFieldMask interface { + BeforePatchApplyFieldMask(context.Context, *Example, *field_mask.FieldMask, *gorm.DB) (*gorm.DB, error) +} +type ExampleWithBeforePatchSave interface { + BeforePatchSave(context.Context, *Example, *field_mask.FieldMask, *gorm.DB) (*gorm.DB, error) +} +type ExampleWithAfterPatchSave interface { + AfterPatchSave(context.Context, *Example, *field_mask.FieldMask, *gorm.DB) error +} + +// DefaultPatchSetExample executes a bulk gorm update call with patch behavior +func DefaultPatchSetExample(ctx context.Context, objects []*Example, updateMasks []*field_mask.FieldMask, db *gorm.DB) ([]*Example, error) { + if len(objects) != len(updateMasks) { + return nil, fmt.Errorf(errors.BadRepeatedFieldMaskTpl, len(updateMasks), len(objects)) + } + + results := make([]*Example, 0, len(objects)) + for i, patcher := range objects { + pbResponse, err := DefaultPatchExample(ctx, patcher, updateMasks[i], db) + if err != nil { + return nil, err + } + + results = append(results, pbResponse) + } + + return results, nil +} + +// DefaultApplyFieldMaskExample patches an pbObject with patcher according to a field mask. +func DefaultApplyFieldMaskExample(ctx context.Context, patchee *Example, patcher *Example, updateMask *field_mask.FieldMask, prefix string, db *gorm.DB) (*Example, error) { + if patcher == nil { + return nil, nil + } else if patchee == nil { + return nil, errors.NilArgumentError + } + var err error + for _, f := range updateMask.Paths { + if f == prefix+"Id" { + patchee.Id = patcher.Id + continue + } + if f == prefix+"Description" { + patchee.Description = patcher.Description + continue + } + if f == prefix+"ArrayOfBools" { + patchee.ArrayOfBools = patcher.ArrayOfBools + continue + } + if f == prefix+"ArrayOfFloat64" { + patchee.ArrayOfFloat64 = patcher.ArrayOfFloat64 + continue + } + if f == prefix+"ArrayOfInt64" { + patchee.ArrayOfInt64 = patcher.ArrayOfInt64 + continue + } + if f == prefix+"ArrayOfString" { + patchee.ArrayOfString = patcher.ArrayOfString + continue + } + } + if err != nil { + return nil, err + } + return patchee, nil +} + +// DefaultListExample executes a gorm list call +func DefaultListExample(ctx context.Context, db *gorm.DB) ([]*Example, error) { + in := Example{} + ormObj, err := in.ToORM(ctx) + if err != nil { + return nil, err + } + if hook, ok := interface{}(&ormObj).(ExampleORMWithBeforeListApplyQuery); ok { + if db, err = hook.BeforeListApplyQuery(ctx, db); err != nil { + return nil, err + } + } + if hook, ok := interface{}(&ormObj).(ExampleORMWithBeforeListFind); ok { + if db, err = hook.BeforeListFind(ctx, db); err != nil { + return nil, err + } + } + db = db.Where(&ormObj) + db = db.Order("id") + ormResponse := []ExampleORM{} + if err := db.Find(&ormResponse).Error; err != nil { + return nil, err + } + if hook, ok := interface{}(&ormObj).(ExampleORMWithAfterListFind); ok { + if err = hook.AfterListFind(ctx, db, &ormResponse); err != nil { + return nil, err + } + } + pbResponse := []*Example{} + for _, responseEntry := range ormResponse { + temp, err := responseEntry.ToPB(ctx) + if err != nil { + return nil, err + } + pbResponse = append(pbResponse, &temp) + } + return pbResponse, nil +} + +type ExampleORMWithBeforeListApplyQuery interface { + BeforeListApplyQuery(context.Context, *gorm.DB) (*gorm.DB, error) +} +type ExampleORMWithBeforeListFind interface { + BeforeListFind(context.Context, *gorm.DB) (*gorm.DB, error) +} +type ExampleORMWithAfterListFind interface { + AfterListFind(context.Context, *gorm.DB, *[]ExampleORM) error +} diff --git a/example/clickhouse_arrays/clickhouse_arrays.proto b/example/clickhouse_arrays/clickhouse_arrays.proto new file mode 100644 index 00000000..b616f772 --- /dev/null +++ b/example/clickhouse_arrays/clickhouse_arrays.proto @@ -0,0 +1,19 @@ +syntax = "proto3"; + +package clickhouse.arrays; + +import "options/gorm.proto"; + +option go_package = "github.com/infobloxopen/protoc-gen-gorm/example/clickhouse_arrays;clickhouse_arrays"; + +message Example { + option (gorm.opts) = {ormable: true}; + + // id for example + string id = 1 [(gorm.field).tag = {type: "UUID" primary_key: true}]; + string description = 2; + repeated bool array_of_bools = 20; + repeated double array_of_float64 = 30; + repeated int64 array_of_int64 = 40; + repeated string array_of_string = 50; +} diff --git a/plugin/plugin.go b/plugin/plugin.go index 2d8000c5..c9287d74 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -119,6 +119,7 @@ const ( const ( ENGINE_UNSET = iota ENGINE_POSTGRES + ENGINE_CLICKHOUSE ) type ORMBuilder struct { @@ -151,6 +152,8 @@ func New(opts protogen.Options, request *pluginpb.CodeGeneratorRequest) (*ORMBui if strings.EqualFold(params["engine"], "postgres") { builder.dbEngine = ENGINE_POSTGRES + } else if strings.EqualFold(params["engine"], "clickhouse") { + builder.dbEngine = ENGINE_CLICKHOUSE } else { builder.dbEngine = ENGINE_UNSET } @@ -905,6 +908,23 @@ func (b *ORMBuilder) parseBasicFields(msg *protogen.Message, g *protogen.Generat default: continue } + } else if b.dbEngine == ENGINE_CLICKHOUSE && b.IsAbleToMakePQArray(fieldType) && field.Desc.IsList() { + switch fieldType { + case "bool": + fieldType = "[]bool" + gormOptions.Tag = tagWithType(tag, "Array(Bool)") + case "double": + fieldType = "[]float64" + gormOptions.Tag = tagWithType(tag, "Array(Double)") + case "int64": + fieldType = "[]int64" + gormOptions.Tag = tagWithType(tag, "Array(Int64)") + case "string": + fieldType = "[]string" + gormOptions.Tag = tagWithType(tag, "Array(String)") + default: + continue + } } else if (field.Message == nil || !b.isOrmable(fieldType)) && field.Desc.IsList() { // not implemented continue @@ -925,18 +945,27 @@ func (b *ORMBuilder) parseBasicFields(msg *protogen.Message, g *protogen.Generat if b.dbEngine == ENGINE_POSTGRES { gormOptions.Tag = tagWithType(tag, "numeric") } + if b.dbEngine == ENGINE_CLICKHOUSE { + gormOptions.Tag = tagWithType(tag, "Int64") + } } else if rawType == protoTypeUUID { typePackage = uuidImport fieldType = generateImport("UUID", uuidImport, g) if b.dbEngine == ENGINE_POSTGRES { gormOptions.Tag = tagWithType(tag, "uuid") } + if b.dbEngine == ENGINE_CLICKHOUSE { + gormOptions.Tag = tagWithType(tag, "UUID") + } } else if rawType == protoTypeUUIDValue { typePackage = uuidImport fieldType = "*" + generateImport("UUID", uuidImport, g) if b.dbEngine == ENGINE_POSTGRES { gormOptions.Tag = tagWithType(tag, "uuid") } + if b.dbEngine == ENGINE_CLICKHOUSE { + gormOptions.Tag = tagWithType(tag, "UUID") + } } else if rawType == protoTypeTimestamp { typePackage = stdTimeImport fieldType = "*" + generateImport("Time", stdTimeImport, g) @@ -948,6 +977,10 @@ func (b *ORMBuilder) parseBasicFields(msg *protogen.Message, g *protogen.Generat typePackage = gtypesImport fieldType = "*" + generateImport("Jsonb", gtypesImport, g) gormOptions.Tag = tagWithType(tag, "jsonb") + } else if b.dbEngine == ENGINE_CLICKHOUSE { + typePackage = gtypesImport + fieldType = "*" + generateImport("Jsonb", gtypesImport, g) + gormOptions.Tag = tagWithType(tag, "jsonb") } else { // Potential TODO: add types we want to use in other/default DB engine continue @@ -1416,6 +1449,20 @@ func (b *ORMBuilder) generateFieldConversion(message *protogen.Message, field *p } g.P(`copy(to.`, fieldName, `, m.`, fieldName, `)`) g.P(`}`) + } else if b.dbEngine == ENGINE_CLICKHOUSE && b.IsAbleToMakePQArray(fieldType) && field.Desc.IsList() { + g.P(`if m.`, fieldName, ` != nil {`) + switch fieldType { + case "bool": + g.P(`to.`, fieldName, ` = make(`, generateImport("BoolArray", pqImport, g), `, len(m.`, fieldName, `))`) + case "double": + g.P(`to.`, fieldName, ` = make(`, generateImport("Float64Array", pqImport, g), `, len(m.`, fieldName, `))`) + case "int64": + g.P(`to.`, fieldName, ` = make(`, generateImport("Int64Array", pqImport, g), `, len(m.`, fieldName, `))`) + case "string": + g.P(`to.`, fieldName, ` = make(`, generateImport("StringArray", pqImport, g), `, len(m.`, fieldName, `))`) + } + g.P(`copy(to.`, fieldName, `, m.`, fieldName, `)`) + g.P(`}`) } else if b.isOrmable(fieldType) { // Repeated ORMable type // fieldType = strings.Trim(fieldType, "[]*")