From c25d38cfcd2a860584d8c3b204314bb0ea9d92c8 Mon Sep 17 00:00:00 2001 From: Rohit Kumar Date: Fri, 24 Apr 2026 16:02:13 +0000 Subject: [PATCH 1/2] feat: add support for multi-entity observations in v2 API and Spanner queries --- internal/merger/merger.go | 10 + internal/proto/v2/observation.pb.go | 581 +++++++++++++----- internal/server/spanner/client_selector.go | 17 + internal/server/spanner/datasource.go | 7 + internal/server/spanner/model_normalized.go | 14 + .../spanner/multi_entity_observation.go | 271 ++++++++ .../spanner/multi_entity_observation_test.go | 156 +++++ .../spanner/query_builder_normalized.go | 46 ++ .../spanner/query_builder_normalized_test.go | 60 ++ internal/server/spanner/query_normalized.go | 41 ++ .../server/spanner/statements_normalized.go | 29 +- internal/server/v2/observation/observation.go | 9 + proto/v2/observation.proto | 29 +- 13 files changed, 1128 insertions(+), 142 deletions(-) create mode 100644 internal/server/spanner/multi_entity_observation.go create mode 100644 internal/server/spanner/multi_entity_observation_test.go create mode 100644 internal/server/spanner/query_builder_normalized_test.go diff --git a/internal/merger/merger.go b/internal/merger/merger.go index c602b79bc..bfa68ef0d 100644 --- a/internal/merger/merger.go +++ b/internal/merger/merger.go @@ -337,6 +337,10 @@ func MergeObservation(main, aux *pbv2.ObservationResponse) *pbv2.ObservationResp if main.ByVariable[v].ByEntity == nil { main.ByVariable[v].ByEntity = map[string]*pbv2.EntityObservation{} } + main.ByVariable[v].MultiEntityObservations = append( + main.ByVariable[v].MultiEntityObservations, + vData.MultiEntityObservations..., + ) for e, eData := range vData.ByEntity { if _, ok := main.ByVariable[v].ByEntity[e]; !ok { main.ByVariable[v].ByEntity[e] = &pbv2.EntityObservation{ @@ -370,6 +374,12 @@ func MergeObservation(main, aux *pbv2.ObservationResponse) *pbv2.ObservationResp for facetID, facet := range aux.Facets { main.Facets[facetID] = facet } + if main.Nodes == nil { + main.Nodes = map[string]*pbv2.LinkedGraph{} + } + for dcid, node := range aux.Nodes { + main.Nodes[dcid] = node + } return main } diff --git a/internal/proto/v2/observation.pb.go b/internal/proto/v2/observation.pb.go index cac40ba51..4c72b3fea 100644 --- a/internal/proto/v2/observation.pb.go +++ b/internal/proto/v2/observation.pb.go @@ -115,6 +115,116 @@ func (x *DcidOrExpression) GetFormula() string { return "" } +type ObservationDimension struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Property string `protobuf:"bytes,1,opt,name=property,proto3" json:"property,omitempty"` + Dcid string `protobuf:"bytes,2,opt,name=dcid,proto3" json:"dcid,omitempty"` +} + +func (x *ObservationDimension) Reset() { + *x = ObservationDimension{} + if protoimpl.UnsafeEnabled { + mi := &file_v2_observation_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ObservationDimension) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ObservationDimension) ProtoMessage() {} + +func (x *ObservationDimension) ProtoReflect() protoreflect.Message { + mi := &file_v2_observation_proto_msgTypes[1] + 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 ObservationDimension.ProtoReflect.Descriptor instead. +func (*ObservationDimension) Descriptor() ([]byte, []int) { + return file_v2_observation_proto_rawDescGZIP(), []int{1} +} + +func (x *ObservationDimension) GetProperty() string { + if x != nil { + return x.Property + } + return "" +} + +func (x *ObservationDimension) GetDcid() string { + if x != nil { + return x.Dcid + } + return "" +} + +type MultiEntityObservation struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ObservationDimensions []*ObservationDimension `protobuf:"bytes,1,rep,name=observation_dimensions,json=observationDimensions,proto3" json:"observation_dimensions,omitempty"` + Observation *EntityObservation `protobuf:"bytes,2,opt,name=observation,proto3" json:"observation,omitempty"` +} + +func (x *MultiEntityObservation) Reset() { + *x = MultiEntityObservation{} + if protoimpl.UnsafeEnabled { + mi := &file_v2_observation_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MultiEntityObservation) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MultiEntityObservation) ProtoMessage() {} + +func (x *MultiEntityObservation) ProtoReflect() protoreflect.Message { + mi := &file_v2_observation_proto_msgTypes[2] + 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 MultiEntityObservation.ProtoReflect.Descriptor instead. +func (*MultiEntityObservation) Descriptor() ([]byte, []int) { + return file_v2_observation_proto_rawDescGZIP(), []int{2} +} + +func (x *MultiEntityObservation) GetObservationDimensions() []*ObservationDimension { + if x != nil { + return x.ObservationDimensions + } + return nil +} + +func (x *MultiEntityObservation) GetObservation() *EntityObservation { + if x != nil { + return x.Observation + } + return nil +} + // Holds all observations of a particular variable. type VariableObservation struct { state protoimpl.MessageState @@ -122,13 +232,14 @@ type VariableObservation struct { unknownFields protoimpl.UnknownFields // Keyed by entity DCID - ByEntity map[string]*EntityObservation `protobuf:"bytes,1,rep,name=by_entity,json=byEntity,proto3" json:"by_entity,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + ByEntity map[string]*EntityObservation `protobuf:"bytes,1,rep,name=by_entity,json=byEntity,proto3" json:"by_entity,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + MultiEntityObservations []*MultiEntityObservation `protobuf:"bytes,2,rep,name=multi_entity_observations,json=multiEntityObservations,proto3" json:"multi_entity_observations,omitempty"` } func (x *VariableObservation) Reset() { *x = VariableObservation{} if protoimpl.UnsafeEnabled { - mi := &file_v2_observation_proto_msgTypes[1] + mi := &file_v2_observation_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -141,7 +252,7 @@ func (x *VariableObservation) String() string { func (*VariableObservation) ProtoMessage() {} func (x *VariableObservation) ProtoReflect() protoreflect.Message { - mi := &file_v2_observation_proto_msgTypes[1] + mi := &file_v2_observation_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -154,7 +265,7 @@ func (x *VariableObservation) ProtoReflect() protoreflect.Message { // Deprecated: Use VariableObservation.ProtoReflect.Descriptor instead. func (*VariableObservation) Descriptor() ([]byte, []int) { - return file_v2_observation_proto_rawDescGZIP(), []int{1} + return file_v2_observation_proto_rawDescGZIP(), []int{3} } func (x *VariableObservation) GetByEntity() map[string]*EntityObservation { @@ -164,6 +275,13 @@ func (x *VariableObservation) GetByEntity() map[string]*EntityObservation { return nil } +func (x *VariableObservation) GetMultiEntityObservations() []*MultiEntityObservation { + if x != nil { + return x.MultiEntityObservations + } + return nil +} + type EntityObservation struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -180,7 +298,7 @@ type EntityObservation struct { func (x *EntityObservation) Reset() { *x = EntityObservation{} if protoimpl.UnsafeEnabled { - mi := &file_v2_observation_proto_msgTypes[2] + mi := &file_v2_observation_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -193,7 +311,7 @@ func (x *EntityObservation) String() string { func (*EntityObservation) ProtoMessage() {} func (x *EntityObservation) ProtoReflect() protoreflect.Message { - mi := &file_v2_observation_proto_msgTypes[2] + mi := &file_v2_observation_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -206,7 +324,7 @@ func (x *EntityObservation) ProtoReflect() protoreflect.Message { // Deprecated: Use EntityObservation.ProtoReflect.Descriptor instead. func (*EntityObservation) Descriptor() ([]byte, []int) { - return file_v2_observation_proto_rawDescGZIP(), []int{2} + return file_v2_observation_proto_rawDescGZIP(), []int{4} } func (x *EntityObservation) GetOrderedFacets() []*FacetObservation { @@ -242,7 +360,7 @@ type FacetObservation struct { func (x *FacetObservation) Reset() { *x = FacetObservation{} if protoimpl.UnsafeEnabled { - mi := &file_v2_observation_proto_msgTypes[3] + mi := &file_v2_observation_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -255,7 +373,7 @@ func (x *FacetObservation) String() string { func (*FacetObservation) ProtoMessage() {} func (x *FacetObservation) ProtoReflect() protoreflect.Message { - mi := &file_v2_observation_proto_msgTypes[3] + mi := &file_v2_observation_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -268,7 +386,7 @@ func (x *FacetObservation) ProtoReflect() protoreflect.Message { // Deprecated: Use FacetObservation.ProtoReflect.Descriptor instead. func (*FacetObservation) Descriptor() ([]byte, []int) { - return file_v2_observation_proto_rawDescGZIP(), []int{3} + return file_v2_observation_proto_rawDescGZIP(), []int{5} } func (x *FacetObservation) GetFacetId() string { @@ -321,7 +439,7 @@ type FacetFilter struct { func (x *FacetFilter) Reset() { *x = FacetFilter{} if protoimpl.UnsafeEnabled { - mi := &file_v2_observation_proto_msgTypes[4] + mi := &file_v2_observation_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -334,7 +452,7 @@ func (x *FacetFilter) String() string { func (*FacetFilter) ProtoMessage() {} func (x *FacetFilter) ProtoReflect() protoreflect.Message { - mi := &file_v2_observation_proto_msgTypes[4] + mi := &file_v2_observation_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -347,7 +465,7 @@ func (x *FacetFilter) ProtoReflect() protoreflect.Message { // Deprecated: Use FacetFilter.ProtoReflect.Descriptor instead. func (*FacetFilter) Descriptor() ([]byte, []int) { - return file_v2_observation_proto_rawDescGZIP(), []int{4} + return file_v2_observation_proto_rawDescGZIP(), []int{6} } func (x *FacetFilter) GetDomains() []string { @@ -364,6 +482,61 @@ func (x *FacetFilter) GetFacetIds() []string { return nil } +type ObservationDimensionConstraint struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Property string `protobuf:"bytes,1,opt,name=property,proto3" json:"property,omitempty"` + Value *DcidOrExpression `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` +} + +func (x *ObservationDimensionConstraint) Reset() { + *x = ObservationDimensionConstraint{} + if protoimpl.UnsafeEnabled { + mi := &file_v2_observation_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ObservationDimensionConstraint) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ObservationDimensionConstraint) ProtoMessage() {} + +func (x *ObservationDimensionConstraint) ProtoReflect() protoreflect.Message { + mi := &file_v2_observation_proto_msgTypes[7] + 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 ObservationDimensionConstraint.ProtoReflect.Descriptor instead. +func (*ObservationDimensionConstraint) Descriptor() ([]byte, []int) { + return file_v2_observation_proto_rawDescGZIP(), []int{7} +} + +func (x *ObservationDimensionConstraint) GetProperty() string { + if x != nil { + return x.Property + } + return "" +} + +func (x *ObservationDimensionConstraint) GetValue() *DcidOrExpression { + if x != nil { + return x.Value + } + return nil +} + // Generic observation request type ObservationRequest struct { state protoimpl.MessageState @@ -372,6 +545,9 @@ type ObservationRequest struct { // A list of statistical variable DCIDs, or a formula expression. Variable *DcidOrExpression `protobuf:"bytes,1,opt,name=variable,proto3" json:"variable,omitempty"` + // Exactly one of entity or observation_dimensions may be set. + // This is enforced in request validation because observation_dimensions is + // repeated and cannot be placed directly in a proto oneof. // A list of entity DCIDs, or an arrow expression that covers a list of entity // DCIDs. Entity *DcidOrExpression `protobuf:"bytes,2,opt,name=entity,proto3" json:"entity,omitempty"` @@ -387,12 +563,17 @@ type ObservationRequest struct { Filter *FacetFilter `protobuf:"bytes,5,opt,name=filter,proto3" json:"filter,omitempty"` // Fields to return, valid values are: "variable", "entity", "date", "value", "facet" Select []string `protobuf:"bytes,6,rep,name=select,proto3" json:"select,omitempty"` + // Multi-entity dimension filters. Each present property is constrained, and + // omitted dimension properties are unconstrained. + ObservationDimensions []*ObservationDimensionConstraint `protobuf:"bytes,7,rep,name=observation_dimensions,json=observationDimensions,proto3" json:"observation_dimensions,omitempty"` + // Node properties to include for multi-entity dimension dcids. + NodeProperties []string `protobuf:"bytes,8,rep,name=node_properties,json=nodeProperties,proto3" json:"node_properties,omitempty"` } func (x *ObservationRequest) Reset() { *x = ObservationRequest{} if protoimpl.UnsafeEnabled { - mi := &file_v2_observation_proto_msgTypes[5] + mi := &file_v2_observation_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -405,7 +586,7 @@ func (x *ObservationRequest) String() string { func (*ObservationRequest) ProtoMessage() {} func (x *ObservationRequest) ProtoReflect() protoreflect.Message { - mi := &file_v2_observation_proto_msgTypes[5] + mi := &file_v2_observation_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -418,7 +599,7 @@ func (x *ObservationRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ObservationRequest.ProtoReflect.Descriptor instead. func (*ObservationRequest) Descriptor() ([]byte, []int) { - return file_v2_observation_proto_rawDescGZIP(), []int{5} + return file_v2_observation_proto_rawDescGZIP(), []int{8} } func (x *ObservationRequest) GetVariable() *DcidOrExpression { @@ -463,6 +644,20 @@ func (x *ObservationRequest) GetSelect() []string { return nil } +func (x *ObservationRequest) GetObservationDimensions() []*ObservationDimensionConstraint { + if x != nil { + return x.ObservationDimensions + } + return nil +} + +func (x *ObservationRequest) GetNodeProperties() []string { + if x != nil { + return x.NodeProperties + } + return nil +} + type ObservationResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -472,12 +667,14 @@ type ObservationResponse struct { ByVariable map[string]*VariableObservation `protobuf:"bytes,1,rep,name=by_variable,json=byVariable,proto3" json:"by_variable,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` // Keyed by facet ID Facets map[string]*proto.Facet `protobuf:"bytes,2,rep,name=facets,proto3" json:"facets,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + // Keyed by dimension dcid when node_properties is requested. + Nodes map[string]*LinkedGraph `protobuf:"bytes,3,rep,name=nodes,proto3" json:"nodes,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (x *ObservationResponse) Reset() { *x = ObservationResponse{} if protoimpl.UnsafeEnabled { - mi := &file_v2_observation_proto_msgTypes[6] + mi := &file_v2_observation_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -490,7 +687,7 @@ func (x *ObservationResponse) String() string { func (*ObservationResponse) ProtoMessage() {} func (x *ObservationResponse) ProtoReflect() protoreflect.Message { - mi := &file_v2_observation_proto_msgTypes[6] + mi := &file_v2_observation_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -503,7 +700,7 @@ func (x *ObservationResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ObservationResponse.ProtoReflect.Descriptor instead. func (*ObservationResponse) Descriptor() ([]byte, []int) { - return file_v2_observation_proto_rawDescGZIP(), []int{6} + return file_v2_observation_proto_rawDescGZIP(), []int{9} } func (x *ObservationResponse) GetByVariable() map[string]*VariableObservation { @@ -520,99 +717,156 @@ func (x *ObservationResponse) GetFacets() map[string]*proto.Facet { return nil } +func (x *ObservationResponse) GetNodes() map[string]*LinkedGraph { + if x != nil { + return x.Nodes + } + return nil +} + var File_v2_observation_proto protoreflect.FileDescriptor var file_v2_observation_proto_rawDesc = []byte{ 0x0a, 0x14, 0x76, 0x32, 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x32, 0x1a, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x22, 0x62, 0x0a, 0x10, 0x44, 0x63, 0x69, 0x64, 0x4f, 0x72, 0x45, 0x78, 0x70, 0x72, - 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x64, 0x63, 0x69, 0x64, 0x73, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x64, 0x63, 0x69, 0x64, 0x73, 0x12, 0x1e, 0x0a, 0x0a, - 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, - 0x66, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x66, - 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x22, 0xc5, 0x01, 0x0a, 0x13, 0x56, 0x61, 0x72, 0x69, 0x61, - 0x62, 0x6c, 0x65, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x4e, - 0x0a, 0x09, 0x62, 0x79, 0x5f, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x31, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, - 0x76, 0x32, 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x4f, 0x62, 0x73, 0x65, 0x72, - 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x42, 0x79, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x62, 0x79, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x1a, 0x5e, - 0x0a, 0x0d, 0x42, 0x79, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, - 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, - 0x79, 0x12, 0x37, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x21, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x76, - 0x32, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x7d, - 0x0a, 0x11, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x47, 0x0a, 0x0e, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x65, 0x64, 0x5f, 0x66, - 0x61, 0x63, 0x65, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x64, 0x61, - 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x46, 0x61, 0x63, - 0x65, 0x74, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0d, 0x6f, - 0x72, 0x64, 0x65, 0x72, 0x65, 0x64, 0x46, 0x61, 0x63, 0x65, 0x74, 0x73, 0x12, 0x1f, 0x0a, 0x0b, - 0x70, 0x6c, 0x61, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, - 0x09, 0x52, 0x0a, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x73, 0x22, 0xcc, 0x01, - 0x0a, 0x10, 0x46, 0x61, 0x63, 0x65, 0x74, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x61, 0x63, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x66, 0x61, 0x63, 0x65, 0x74, 0x49, 0x64, 0x12, 0x3a, 0x0a, - 0x0c, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, - 0x73, 0x2e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x52, 0x0c, 0x6f, 0x62, 0x73, - 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x6f, 0x62, 0x73, - 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6f, 0x62, - 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x61, 0x72, 0x6c, 0x69, 0x65, - 0x73, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, - 0x61, 0x72, 0x6c, 0x69, 0x65, 0x73, 0x74, 0x44, 0x61, 0x74, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6c, - 0x61, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x44, 0x61, 0x74, 0x65, 0x22, 0x44, 0x0a, 0x0b, - 0x46, 0x61, 0x63, 0x65, 0x74, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x64, - 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x64, 0x6f, - 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x61, 0x63, 0x65, 0x74, 0x5f, 0x69, - 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x66, 0x61, 0x63, 0x65, 0x74, 0x49, - 0x64, 0x73, 0x22, 0x83, 0x02, 0x0a, 0x12, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3c, 0x0a, 0x08, 0x76, 0x61, 0x72, - 0x69, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x64, 0x61, - 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x63, 0x69, - 0x64, 0x4f, 0x72, 0x45, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x76, - 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x38, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, - 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, + 0x74, 0x6f, 0x1a, 0x0d, 0x76, 0x32, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x22, 0x62, 0x0a, 0x10, 0x44, 0x63, 0x69, 0x64, 0x4f, 0x72, 0x45, 0x78, 0x70, 0x72, 0x65, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x64, 0x63, 0x69, 0x64, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x64, 0x63, 0x69, 0x64, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x65, + 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x66, + 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x66, 0x6f, + 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x22, 0x46, 0x0a, 0x14, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, + 0x08, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x63, 0x69, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x63, 0x69, 0x64, 0x22, 0xba, 0x01, + 0x0a, 0x16, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x4f, 0x62, 0x73, + 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x5b, 0x0a, 0x16, 0x6f, 0x62, 0x73, 0x65, + 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, + 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, + 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x15, + 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x69, 0x6d, 0x65, 0x6e, + 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x43, 0x0a, 0x0b, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x64, 0x61, 0x74, + 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x45, 0x6e, 0x74, 0x69, + 0x74, 0x79, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x6f, + 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xa9, 0x02, 0x0a, 0x13, 0x56, + 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x4e, 0x0a, 0x09, 0x62, 0x79, 0x5f, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, + 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x4f, + 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x42, 0x79, 0x45, 0x6e, 0x74, + 0x69, 0x74, 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x62, 0x79, 0x45, 0x6e, 0x74, 0x69, + 0x74, 0x79, 0x12, 0x62, 0x0a, 0x19, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x5f, 0x65, 0x6e, 0x74, 0x69, + 0x74, 0x79, 0x5f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, + 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, + 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x45, 0x6e, 0x74, 0x69, + 0x74, 0x79, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x17, 0x6d, + 0x75, 0x6c, 0x74, 0x69, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x5e, 0x0a, 0x0d, 0x42, 0x79, 0x45, 0x6e, 0x74, 0x69, + 0x74, 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x37, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, + 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, + 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x7d, 0x0a, 0x11, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, + 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x47, 0x0a, 0x0e, 0x6f, + 0x72, 0x64, 0x65, 0x72, 0x65, 0x64, 0x5f, 0x66, 0x61, 0x63, 0x65, 0x74, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, + 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x46, 0x61, 0x63, 0x65, 0x74, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0d, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x65, 0x64, 0x46, 0x61, + 0x63, 0x65, 0x74, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x5f, 0x74, 0x79, + 0x70, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x6c, 0x61, 0x63, 0x65, + 0x54, 0x79, 0x70, 0x65, 0x73, 0x22, 0xcc, 0x01, 0x0a, 0x10, 0x46, 0x61, 0x63, 0x65, 0x74, 0x4f, + 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x61, + 0x63, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x66, 0x61, + 0x63, 0x65, 0x74, 0x49, 0x64, 0x12, 0x3a, 0x0a, 0x0c, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x64, 0x61, + 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x53, + 0x74, 0x61, 0x74, 0x52, 0x0c, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x6f, 0x62, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6f, 0x62, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, + 0x0a, 0x0d, 0x65, 0x61, 0x72, 0x6c, 0x69, 0x65, 0x73, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x61, 0x72, 0x6c, 0x69, 0x65, 0x73, 0x74, 0x44, + 0x61, 0x74, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x64, 0x61, + 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, + 0x44, 0x61, 0x74, 0x65, 0x22, 0x44, 0x0a, 0x0b, 0x46, 0x61, 0x63, 0x65, 0x74, 0x46, 0x69, 0x6c, + 0x74, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x1b, 0x0a, + 0x09, 0x66, 0x61, 0x63, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x08, 0x66, 0x61, 0x63, 0x65, 0x74, 0x49, 0x64, 0x73, 0x22, 0x74, 0x0a, 0x1e, 0x4f, 0x62, + 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, + 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, + 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x12, 0x36, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x63, 0x69, 0x64, 0x4f, 0x72, 0x45, - 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, - 0x79, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x64, 0x61, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x33, 0x0a, 0x06, 0x66, - 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x64, 0x61, - 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x46, 0x61, 0x63, - 0x65, 0x74, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, - 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, - 0x52, 0x06, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x22, 0xe7, 0x02, 0x0a, 0x13, 0x4f, 0x62, 0x73, - 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x54, 0x0a, 0x0b, 0x62, 0x79, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, - 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x42, 0x79, 0x56, 0x61, 0x72, - 0x69, 0x61, 0x62, 0x6c, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x62, 0x79, 0x56, 0x61, - 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x47, 0x0a, 0x06, 0x66, 0x61, 0x63, 0x65, 0x74, 0x73, - 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, + 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x22, 0x93, 0x03, 0x0a, 0x12, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3c, 0x0a, 0x08, 0x76, 0x61, 0x72, 0x69, 0x61, + 0x62, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x64, 0x61, 0x74, 0x61, + 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x63, 0x69, 0x64, 0x4f, + 0x72, 0x45, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x76, 0x61, 0x72, + 0x69, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x38, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, + 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x63, 0x69, 0x64, 0x4f, 0x72, 0x45, 0x78, 0x70, + 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, + 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, + 0x61, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x33, 0x0a, 0x06, 0x66, 0x69, 0x6c, + 0x74, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x64, 0x61, 0x74, 0x61, + 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x46, 0x61, 0x63, 0x65, 0x74, + 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x16, + 0x0a, 0x06, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, + 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x12, 0x65, 0x0a, 0x16, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, + 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x46, 0x61, 0x63, 0x65, - 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x66, 0x61, 0x63, 0x65, 0x74, 0x73, 0x1a, - 0x62, 0x0a, 0x0f, 0x42, 0x79, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x45, 0x6e, 0x74, - 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x03, 0x6b, 0x65, 0x79, 0x12, 0x39, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, - 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x4f, 0x62, 0x73, - 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, - 0x02, 0x38, 0x01, 0x1a, 0x4d, 0x0a, 0x0b, 0x46, 0x61, 0x63, 0x65, 0x74, 0x73, 0x45, 0x6e, 0x74, - 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x03, 0x6b, 0x65, 0x79, 0x12, 0x28, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, - 0x73, 0x2e, 0x46, 0x61, 0x63, 0x65, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, - 0x38, 0x01, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x6f, 0x72, 0x67, 0x2f, - 0x6d, 0x69, 0x78, 0x65, 0x72, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x32, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x69, 0x6f, 0x6e, 0x44, 0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x73, + 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x52, 0x15, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x44, 0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x27, 0x0a, + 0x0f, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, + 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x6e, 0x6f, 0x64, 0x65, 0x50, 0x72, 0x6f, 0x70, + 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x22, 0x84, 0x04, 0x0a, 0x13, 0x4f, 0x62, 0x73, 0x65, 0x72, + 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, + 0x0a, 0x0b, 0x62, 0x79, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, + 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x42, 0x79, 0x56, 0x61, 0x72, 0x69, 0x61, + 0x62, 0x6c, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x62, 0x79, 0x56, 0x61, 0x72, 0x69, + 0x61, 0x62, 0x6c, 0x65, 0x12, 0x47, 0x0a, 0x06, 0x66, 0x61, 0x63, 0x65, 0x74, 0x73, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, + 0x6e, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x46, 0x61, 0x63, 0x65, 0x74, 0x73, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x66, 0x61, 0x63, 0x65, 0x74, 0x73, 0x12, 0x44, 0x0a, + 0x05, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x64, + 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x4f, 0x62, + 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x05, 0x6e, 0x6f, + 0x64, 0x65, 0x73, 0x1a, 0x62, 0x0a, 0x0f, 0x42, 0x79, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, + 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x39, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, + 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, + 0x65, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x4d, 0x0a, 0x0b, 0x46, 0x61, 0x63, 0x65, 0x74, + 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x28, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, + 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x46, 0x61, 0x63, 0x65, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x55, 0x0a, 0x0a, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x31, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, + 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x47, 0x72, 0x61, + 0x70, 0x68, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x33, 0x5a, + 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x64, 0x61, 0x74, 0x61, + 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x6f, 0x72, 0x67, 0x2f, 0x6d, 0x69, 0x78, 0x65, 0x72, + 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, + 0x76, 0x32, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -627,38 +881,50 @@ func file_v2_observation_proto_rawDescGZIP() []byte { return file_v2_observation_proto_rawDescData } -var file_v2_observation_proto_msgTypes = make([]protoimpl.MessageInfo, 10) +var file_v2_observation_proto_msgTypes = make([]protoimpl.MessageInfo, 14) var file_v2_observation_proto_goTypes = []interface{}{ - (*DcidOrExpression)(nil), // 0: datacommons.v2.DcidOrExpression - (*VariableObservation)(nil), // 1: datacommons.v2.VariableObservation - (*EntityObservation)(nil), // 2: datacommons.v2.EntityObservation - (*FacetObservation)(nil), // 3: datacommons.v2.FacetObservation - (*FacetFilter)(nil), // 4: datacommons.v2.FacetFilter - (*ObservationRequest)(nil), // 5: datacommons.v2.ObservationRequest - (*ObservationResponse)(nil), // 6: datacommons.v2.ObservationResponse - nil, // 7: datacommons.v2.VariableObservation.ByEntityEntry - nil, // 8: datacommons.v2.ObservationResponse.ByVariableEntry - nil, // 9: datacommons.v2.ObservationResponse.FacetsEntry - (*proto.PointStat)(nil), // 10: datacommons.PointStat - (*proto.Facet)(nil), // 11: datacommons.Facet + (*DcidOrExpression)(nil), // 0: datacommons.v2.DcidOrExpression + (*ObservationDimension)(nil), // 1: datacommons.v2.ObservationDimension + (*MultiEntityObservation)(nil), // 2: datacommons.v2.MultiEntityObservation + (*VariableObservation)(nil), // 3: datacommons.v2.VariableObservation + (*EntityObservation)(nil), // 4: datacommons.v2.EntityObservation + (*FacetObservation)(nil), // 5: datacommons.v2.FacetObservation + (*FacetFilter)(nil), // 6: datacommons.v2.FacetFilter + (*ObservationDimensionConstraint)(nil), // 7: datacommons.v2.ObservationDimensionConstraint + (*ObservationRequest)(nil), // 8: datacommons.v2.ObservationRequest + (*ObservationResponse)(nil), // 9: datacommons.v2.ObservationResponse + nil, // 10: datacommons.v2.VariableObservation.ByEntityEntry + nil, // 11: datacommons.v2.ObservationResponse.ByVariableEntry + nil, // 12: datacommons.v2.ObservationResponse.FacetsEntry + nil, // 13: datacommons.v2.ObservationResponse.NodesEntry + (*proto.PointStat)(nil), // 14: datacommons.PointStat + (*proto.Facet)(nil), // 15: datacommons.Facet + (*LinkedGraph)(nil), // 16: datacommons.v2.LinkedGraph } var file_v2_observation_proto_depIdxs = []int32{ - 7, // 0: datacommons.v2.VariableObservation.by_entity:type_name -> datacommons.v2.VariableObservation.ByEntityEntry - 3, // 1: datacommons.v2.EntityObservation.ordered_facets:type_name -> datacommons.v2.FacetObservation - 10, // 2: datacommons.v2.FacetObservation.observations:type_name -> datacommons.PointStat - 0, // 3: datacommons.v2.ObservationRequest.variable:type_name -> datacommons.v2.DcidOrExpression - 0, // 4: datacommons.v2.ObservationRequest.entity:type_name -> datacommons.v2.DcidOrExpression - 4, // 5: datacommons.v2.ObservationRequest.filter:type_name -> datacommons.v2.FacetFilter - 8, // 6: datacommons.v2.ObservationResponse.by_variable:type_name -> datacommons.v2.ObservationResponse.ByVariableEntry - 9, // 7: datacommons.v2.ObservationResponse.facets:type_name -> datacommons.v2.ObservationResponse.FacetsEntry - 2, // 8: datacommons.v2.VariableObservation.ByEntityEntry.value:type_name -> datacommons.v2.EntityObservation - 1, // 9: datacommons.v2.ObservationResponse.ByVariableEntry.value:type_name -> datacommons.v2.VariableObservation - 11, // 10: datacommons.v2.ObservationResponse.FacetsEntry.value:type_name -> datacommons.Facet - 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 + 1, // 0: datacommons.v2.MultiEntityObservation.observation_dimensions:type_name -> datacommons.v2.ObservationDimension + 4, // 1: datacommons.v2.MultiEntityObservation.observation:type_name -> datacommons.v2.EntityObservation + 10, // 2: datacommons.v2.VariableObservation.by_entity:type_name -> datacommons.v2.VariableObservation.ByEntityEntry + 2, // 3: datacommons.v2.VariableObservation.multi_entity_observations:type_name -> datacommons.v2.MultiEntityObservation + 5, // 4: datacommons.v2.EntityObservation.ordered_facets:type_name -> datacommons.v2.FacetObservation + 14, // 5: datacommons.v2.FacetObservation.observations:type_name -> datacommons.PointStat + 0, // 6: datacommons.v2.ObservationDimensionConstraint.value:type_name -> datacommons.v2.DcidOrExpression + 0, // 7: datacommons.v2.ObservationRequest.variable:type_name -> datacommons.v2.DcidOrExpression + 0, // 8: datacommons.v2.ObservationRequest.entity:type_name -> datacommons.v2.DcidOrExpression + 6, // 9: datacommons.v2.ObservationRequest.filter:type_name -> datacommons.v2.FacetFilter + 7, // 10: datacommons.v2.ObservationRequest.observation_dimensions:type_name -> datacommons.v2.ObservationDimensionConstraint + 11, // 11: datacommons.v2.ObservationResponse.by_variable:type_name -> datacommons.v2.ObservationResponse.ByVariableEntry + 12, // 12: datacommons.v2.ObservationResponse.facets:type_name -> datacommons.v2.ObservationResponse.FacetsEntry + 13, // 13: datacommons.v2.ObservationResponse.nodes:type_name -> datacommons.v2.ObservationResponse.NodesEntry + 4, // 14: datacommons.v2.VariableObservation.ByEntityEntry.value:type_name -> datacommons.v2.EntityObservation + 3, // 15: datacommons.v2.ObservationResponse.ByVariableEntry.value:type_name -> datacommons.v2.VariableObservation + 15, // 16: datacommons.v2.ObservationResponse.FacetsEntry.value:type_name -> datacommons.Facet + 16, // 17: datacommons.v2.ObservationResponse.NodesEntry.value:type_name -> datacommons.v2.LinkedGraph + 18, // [18:18] is the sub-list for method output_type + 18, // [18:18] is the sub-list for method input_type + 18, // [18:18] is the sub-list for extension type_name + 18, // [18:18] is the sub-list for extension extendee + 0, // [0:18] is the sub-list for field type_name } func init() { file_v2_observation_proto_init() } @@ -666,6 +932,7 @@ func file_v2_observation_proto_init() { if File_v2_observation_proto != nil { return } + file_v2_node_proto_init() if !protoimpl.UnsafeEnabled { file_v2_observation_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*DcidOrExpression); i { @@ -680,7 +947,7 @@ func file_v2_observation_proto_init() { } } file_v2_observation_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*VariableObservation); i { + switch v := v.(*ObservationDimension); i { case 0: return &v.state case 1: @@ -692,7 +959,7 @@ func file_v2_observation_proto_init() { } } file_v2_observation_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*EntityObservation); i { + switch v := v.(*MultiEntityObservation); i { case 0: return &v.state case 1: @@ -704,7 +971,7 @@ func file_v2_observation_proto_init() { } } file_v2_observation_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FacetObservation); i { + switch v := v.(*VariableObservation); i { case 0: return &v.state case 1: @@ -716,7 +983,7 @@ func file_v2_observation_proto_init() { } } file_v2_observation_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FacetFilter); i { + switch v := v.(*EntityObservation); i { case 0: return &v.state case 1: @@ -728,7 +995,7 @@ func file_v2_observation_proto_init() { } } file_v2_observation_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ObservationRequest); i { + switch v := v.(*FacetObservation); i { case 0: return &v.state case 1: @@ -740,6 +1007,42 @@ func file_v2_observation_proto_init() { } } file_v2_observation_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FacetFilter); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_v2_observation_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ObservationDimensionConstraint); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_v2_observation_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ObservationRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_v2_observation_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ObservationResponse); i { case 0: return &v.state @@ -758,7 +1061,7 @@ func file_v2_observation_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_v2_observation_proto_rawDesc, NumEnums: 0, - NumMessages: 10, + NumMessages: 14, NumExtensions: 0, NumServices: 0, }, diff --git a/internal/server/spanner/client_selector.go b/internal/server/spanner/client_selector.go index e3004a22c..1cbd555ac 100644 --- a/internal/server/spanner/client_selector.go +++ b/internal/server/spanner/client_selector.go @@ -16,9 +16,11 @@ package spanner import ( "context" + "fmt" "log/slog" pb "github.com/datacommonsorg/mixer/internal/proto" + pbv2 "github.com/datacommonsorg/mixer/internal/proto/v2" v2 "github.com/datacommonsorg/mixer/internal/server/v2" "github.com/datacommonsorg/mixer/internal/util" "google.golang.org/grpc/metadata" @@ -76,6 +78,21 @@ func (s *schemaSelectorClient) GetSdmxObservations(ctx context.Context, req *pb. return s.normalized.GetSdmxObservations(ctx, req) } +func (s *schemaSelectorClient) GetMultiEntityObservations( + ctx context.Context, + variables []string, + dimensions []*pbv2.ObservationDimensionConstraint, +) ([]*multiEntityObservation, error) { + if !useNormalizedSchema(ctx) { + return nil, fmt.Errorf("multi-entity observations require normalized Spanner schema") + } + logNormalizedInvocation("GetMultiEntityObservations", + "num_variables", len(variables), + "num_dimensions", len(dimensions), + ) + return s.normalized.GetMultiEntityObservations(ctx, variables, dimensions) +} + // NewSchemaSelectorClient creates a new SpannerClient that dispatches calls to either default or normalized client. func NewSchemaSelectorClient(baseClient SpannerClient) (SpannerClient, error) { normalizedClient, err := NewNormalizedClient(baseClient) diff --git a/internal/server/spanner/datasource.go b/internal/server/spanner/datasource.go index 30c1197e4..b1c7aa61c 100644 --- a/internal/server/spanner/datasource.go +++ b/internal/server/spanner/datasource.go @@ -137,6 +137,13 @@ func (sds *SpannerDataSource) Node(ctx context.Context, req *pbv2.NodeRequest, p // Observation retrieves observation data from Spanner. func (sds *SpannerDataSource) Observation(ctx context.Context, req *pbv2.ObservationRequest) (*pbv2.ObservationResponse, error) { + if len(req.GetObservationDimensions()) > 0 { + return sds.multiEntityObservation(ctx, req) + } + if len(req.GetNodeProperties()) > 0 { + return nil, fmt.Errorf("node_properties are only supported for multi-entity observation requests") + } + if req.Entity == nil { return nil, fmt.Errorf("entity must be specified") } diff --git a/internal/server/spanner/model_normalized.go b/internal/server/spanner/model_normalized.go index 3785183bc..b49caf841 100644 --- a/internal/server/spanner/model_normalized.go +++ b/internal/server/spanner/model_normalized.go @@ -22,6 +22,20 @@ type rawObservation struct { Attributes []*spannerAttribute `spanner:"attributes"` } +type rawMultiEntityObservation struct { + VariableMeasured string `spanner:"variable_measured"` + Provenance string `spanner:"provenance"` + DatesAndValues []*spannerObservation `spanner:"dates_and_values"` + Attributes []*spannerAttribute `spanner:"attributes"` +} + +type multiEntityObservation struct { + VariableMeasured string + Provenance string + Observations TimeSeries + Attributes []*spannerAttribute +} + // spannerObservation represents the STRUCT returned in dates_and_values array. type spannerObservation struct { Date string `spanner:"date"` diff --git a/internal/server/spanner/multi_entity_observation.go b/internal/server/spanner/multi_entity_observation.go new file mode 100644 index 000000000..7d1b148f2 --- /dev/null +++ b/internal/server/spanner/multi_entity_observation.go @@ -0,0 +1,271 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package spanner + +import ( + "context" + "fmt" + "sort" + "strings" + + pb "github.com/datacommonsorg/mixer/internal/proto" + pbv2 "github.com/datacommonsorg/mixer/internal/proto/v2" + "github.com/datacommonsorg/mixer/internal/server/datasources" +) + +type multiEntityObservationClient interface { + GetMultiEntityObservations( + ctx context.Context, + variables []string, + dimensions []*pbv2.ObservationDimensionConstraint, + ) ([]*multiEntityObservation, error) +} + +var multiEntityDimensionExcludedProperties = map[string]struct{}{ + "facetId": {}, + "importName": {}, + "isDcAggregate": {}, + "measurementMethod": {}, + "observationAbout": {}, + "observationPeriod": {}, + "provenanceUrl": {}, + "scalingFactor": {}, + "unit": {}, +} + +func (sds *SpannerDataSource) multiEntityObservation( + ctx context.Context, + req *pbv2.ObservationRequest, +) (*pbv2.ObservationResponse, error) { + variables, dimensions, err := validateMultiEntityObservationRequest(req) + if err != nil { + return nil, err + } + + client, ok := sds.client.(multiEntityObservationClient) + if !ok { + return nil, fmt.Errorf("multi-entity observations require normalized Spanner schema") + } + + observations, err := client.GetMultiEntityObservations(ctx, variables, dimensions) + if err != nil { + return nil, err + } + + resp := multiEntityObservationsToResponse(variables, observations) + if len(req.GetNodeProperties()) == 0 { + return resp, nil + } + if err := sds.hydrateMultiEntityObservationNodes(ctx, resp, req.GetNodeProperties()); err != nil { + return nil, err + } + return resp, nil +} + +func validateMultiEntityObservationRequest( + req *pbv2.ObservationRequest, +) ([]string, []*pbv2.ObservationDimensionConstraint, error) { + if hasDcidOrExpressionValue(req.GetEntity()) { + return nil, nil, fmt.Errorf("only one of entity and observation_dimensions can be specified") + } + + variable := req.GetVariable() + if variable == nil || len(variable.GetDcids()) == 0 { + return nil, nil, fmt.Errorf("variable.dcids must be specified for multi-entity observations") + } + if variable.GetExpression() != "" || variable.GetFormula() != "" { + return nil, nil, fmt.Errorf("variable expressions and formulas are not supported for multi-entity observations") + } + for _, dcid := range variable.GetDcids() { + if dcid == "" { + return nil, nil, fmt.Errorf("variable.dcids cannot contain empty values") + } + } + + dimensions := req.GetObservationDimensions() + if len(dimensions) == 0 { + return nil, nil, fmt.Errorf("observation_dimensions must be specified") + } + seenProperties := map[string]struct{}{} + for _, dimension := range dimensions { + property := dimension.GetProperty() + if property == "" { + return nil, nil, fmt.Errorf("observation_dimensions property must be specified") + } + if _, ok := seenProperties[property]; ok { + return nil, nil, fmt.Errorf("duplicate observation_dimensions property: %s", property) + } + seenProperties[property] = struct{}{} + + value := dimension.GetValue() + if value == nil || len(value.GetDcids()) == 0 { + return nil, nil, fmt.Errorf("observation_dimensions value.dcids must be specified") + } + if value.GetExpression() != "" || value.GetFormula() != "" { + return nil, nil, fmt.Errorf("observation_dimensions expressions and formulas are not supported") + } + for _, dcid := range value.GetDcids() { + if dcid == "" { + return nil, nil, fmt.Errorf("observation_dimensions value.dcids cannot contain empty values") + } + } + } + + for _, property := range req.GetNodeProperties() { + if property == "" { + return nil, nil, fmt.Errorf("node_properties cannot contain empty values") + } + } + + return variable.GetDcids(), dimensions, nil +} + +func hasDcidOrExpressionValue(value *pbv2.DcidOrExpression) bool { + return value != nil && + (len(value.GetDcids()) > 0 || value.GetExpression() != "" || value.GetFormula() != "") +} + +func multiEntityObservationsToResponse( + variables []string, + observations []*multiEntityObservation, +) *pbv2.ObservationResponse { + resp := &pbv2.ObservationResponse{ + ByVariable: map[string]*pbv2.VariableObservation{}, + Facets: map[string]*pb.Facet{}, + } + for _, variable := range variables { + resp.ByVariable[variable] = &pbv2.VariableObservation{ + ByEntity: map[string]*pbv2.EntityObservation{}, + } + } + + for _, observation := range observations { + facetObservation := multiEntityFacetObservation(observation) + if facetObservation == nil { + continue + } + variableObservation, ok := resp.ByVariable[observation.VariableMeasured] + if !ok { + variableObservation = &pbv2.VariableObservation{ + ByEntity: map[string]*pbv2.EntityObservation{}, + } + resp.ByVariable[observation.VariableMeasured] = variableObservation + } + variableObservation.MultiEntityObservations = append( + variableObservation.MultiEntityObservations, + &pbv2.MultiEntityObservation{ + ObservationDimensions: multiEntityObservationDimensions(observation.Attributes), + Observation: &pbv2.EntityObservation{ + OrderedFacets: []*pbv2.FacetObservation{facetObservation}, + }, + }, + ) + if observation.Provenance != "" { + resp.Facets[observation.Provenance] = &pb.Facet{ImportName: observation.Provenance} + } + } + + return resp +} + +func multiEntityFacetObservation(observation *multiEntityObservation) *pbv2.FacetObservation { + pointStats := []*pb.PointStat{} + for _, dateValue := range observation.Observations { + pointStat, err := dateValueToPointStat(dateValue) + if err != nil { + continue + } + pointStats = append(pointStats, pointStat) + } + if len(pointStats) == 0 { + return nil + } + sort.Slice(pointStats, func(i, j int) bool { + return pointStats[i].GetDate() < pointStats[j].GetDate() + }) + return &pbv2.FacetObservation{ + FacetId: observation.Provenance, + Observations: pointStats, + ObsCount: int32(len(pointStats)), + EarliestDate: pointStats[0].GetDate(), + LatestDate: pointStats[len(pointStats)-1].GetDate(), + } +} + +func multiEntityObservationDimensions(attributes []*spannerAttribute) []*pbv2.ObservationDimension { + dimensions := []*pbv2.ObservationDimension{} + for _, attr := range attributes { + if _, excluded := multiEntityDimensionExcludedProperties[attr.Property]; excluded { + continue + } + dimensions = append(dimensions, &pbv2.ObservationDimension{ + Property: attr.Property, + Dcid: attr.Value, + }) + } + sort.Slice(dimensions, func(i, j int) bool { + if dimensions[i].GetProperty() == dimensions[j].GetProperty() { + return dimensions[i].GetDcid() < dimensions[j].GetDcid() + } + return dimensions[i].GetProperty() < dimensions[j].GetProperty() + }) + return dimensions +} + +func (sds *SpannerDataSource) hydrateMultiEntityObservationNodes( + ctx context.Context, + resp *pbv2.ObservationResponse, + nodeProperties []string, +) error { + nodeDcids := multiEntityObservationDimensionDcids(resp) + if len(nodeDcids) == 0 { + return nil + } + nodeResp, err := sds.Node(ctx, &pbv2.NodeRequest{ + Nodes: nodeDcids, + Property: nodePropertyExpression(nodeProperties), + }, datasources.DefaultPageSize) + if err != nil { + return err + } + resp.Nodes = nodeResp.GetData() + return nil +} + +func multiEntityObservationDimensionDcids(resp *pbv2.ObservationResponse) []string { + dcidSet := map[string]struct{}{} + for _, variableObservation := range resp.GetByVariable() { + for _, observation := range variableObservation.GetMultiEntityObservations() { + for _, dimension := range observation.GetObservationDimensions() { + if dimension.GetDcid() != "" { + dcidSet[dimension.GetDcid()] = struct{}{} + } + } + } + } + dcids := make([]string, 0, len(dcidSet)) + for dcid := range dcidSet { + dcids = append(dcids, dcid) + } + sort.Strings(dcids) + return dcids +} + +func nodePropertyExpression(properties []string) string { + if len(properties) == 1 { + return "->" + properties[0] + } + return "->[" + strings.Join(properties, ",") + "]" +} diff --git a/internal/server/spanner/multi_entity_observation_test.go b/internal/server/spanner/multi_entity_observation_test.go new file mode 100644 index 000000000..bfff14985 --- /dev/null +++ b/internal/server/spanner/multi_entity_observation_test.go @@ -0,0 +1,156 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package spanner + +import ( + "strings" + "testing" + + pbv2 "github.com/datacommonsorg/mixer/internal/proto/v2" +) + +func TestValidateMultiEntityObservationRequest(t *testing.T) { + validDimension := &pbv2.ObservationDimensionConstraint{ + Property: "recipient", + Value: &pbv2.DcidOrExpression{ + Dcids: []string{"country/GHA"}, + }, + } + + for _, c := range []struct { + name string + req *pbv2.ObservationRequest + wantErr string + }{ + { + name: "valid", + req: &pbv2.ObservationRequest{ + Variable: &pbv2.DcidOrExpression{Dcids: []string{"dcid:Amount_EconomicActivity_GrossODA"}}, + ObservationDimensions: []*pbv2.ObservationDimensionConstraint{validDimension}, + NodeProperties: []string{"name"}, + }, + }, + { + name: "entity and dimensions", + req: &pbv2.ObservationRequest{ + Variable: &pbv2.DcidOrExpression{Dcids: []string{"dcid:Amount_EconomicActivity_GrossODA"}}, + Entity: &pbv2.DcidOrExpression{Dcids: []string{"country/GHA"}}, + ObservationDimensions: []*pbv2.ObservationDimensionConstraint{validDimension}, + }, + wantErr: "only one of entity and observation_dimensions", + }, + { + name: "variable formula", + req: &pbv2.ObservationRequest{ + Variable: &pbv2.DcidOrExpression{Formula: "Count_Person + Count_Farm"}, + ObservationDimensions: []*pbv2.ObservationDimensionConstraint{validDimension}, + }, + wantErr: "variable.dcids must be specified", + }, + { + name: "dimension expression", + req: &pbv2.ObservationRequest{ + Variable: &pbv2.DcidOrExpression{Dcids: []string{"dcid:Amount_EconomicActivity_GrossODA"}}, + ObservationDimensions: []*pbv2.ObservationDimensionConstraint{ + { + Property: "recipient", + Value: &pbv2.DcidOrExpression{Expression: "country/GHA<-containedInPlace+{typeOf:Country}"}, + }, + }, + }, + wantErr: "value.dcids must be specified", + }, + { + name: "duplicate dimension", + req: &pbv2.ObservationRequest{ + Variable: &pbv2.DcidOrExpression{Dcids: []string{"dcid:Amount_EconomicActivity_GrossODA"}}, + ObservationDimensions: []*pbv2.ObservationDimensionConstraint{ + validDimension, + { + Property: "recipient", + Value: &pbv2.DcidOrExpression{Dcids: []string{"country/NER"}}, + }, + }, + }, + wantErr: "duplicate observation_dimensions property", + }, + { + name: "empty node property", + req: &pbv2.ObservationRequest{ + Variable: &pbv2.DcidOrExpression{Dcids: []string{"dcid:Amount_EconomicActivity_GrossODA"}}, + ObservationDimensions: []*pbv2.ObservationDimensionConstraint{validDimension}, + NodeProperties: []string{""}, + }, + wantErr: "node_properties cannot contain empty values", + }, + } { + t.Run(c.name, func(t *testing.T) { + _, _, err := validateMultiEntityObservationRequest(c.req) + if c.wantErr == "" { + if err != nil { + t.Fatalf("validateMultiEntityObservationRequest() = %v, want nil", err) + } + return + } + if err == nil || !strings.Contains(err.Error(), c.wantErr) { + t.Fatalf("validateMultiEntityObservationRequest() = %v, want error containing %q", err, c.wantErr) + } + }) + } +} + +func TestMultiEntityObservationsToResponse(t *testing.T) { + resp := multiEntityObservationsToResponse( + []string{"dcid:Amount_EconomicActivity_GrossODA"}, + []*multiEntityObservation{ + { + VariableMeasured: "dcid:Amount_EconomicActivity_GrossODA", + Provenance: "provenance/OECD", + Attributes: []*spannerAttribute{ + {Property: "recipient", Value: "country/GHA"}, + {Property: "unit", Value: "dcs:USDollar"}, + {Property: "donor", Value: "country/IRL"}, + }, + Observations: TimeSeries{ + {Date: "1998", Value: "520000"}, + {Date: "1997", Value: "510000"}, + }, + }, + }, + ) + + varObs := resp.GetByVariable()["dcid:Amount_EconomicActivity_GrossODA"] + if varObs == nil || len(varObs.GetMultiEntityObservations()) != 1 { + t.Fatalf("multi_entity_observations size = %d, want 1", len(varObs.GetMultiEntityObservations())) + } + obs := varObs.GetMultiEntityObservations()[0] + if got := len(obs.GetObservationDimensions()); got != 2 { + t.Fatalf("observation_dimensions size = %d, want 2", got) + } + if obs.GetObservationDimensions()[0].GetProperty() != "donor" || obs.GetObservationDimensions()[1].GetProperty() != "recipient" { + t.Fatalf("observation_dimensions = %v, want donor then recipient", obs.GetObservationDimensions()) + } + + facet := obs.GetObservation().GetOrderedFacets()[0] + if facet.GetFacetId() != "provenance/OECD" { + t.Fatalf("facet_id = %q, want provenance/OECD", facet.GetFacetId()) + } + if facet.GetEarliestDate() != "1997" || facet.GetLatestDate() != "1998" { + t.Fatalf("date range = %s-%s, want 1997-1998", facet.GetEarliestDate(), facet.GetLatestDate()) + } + if resp.GetFacets()["provenance/OECD"].GetImportName() != "provenance/OECD" { + t.Fatalf("facet import_name = %q, want provenance/OECD", resp.GetFacets()["provenance/OECD"].GetImportName()) + } +} diff --git a/internal/server/spanner/query_builder_normalized.go b/internal/server/spanner/query_builder_normalized.go index 9d4920e62..6934324f7 100644 --- a/internal/server/spanner/query_builder_normalized.go +++ b/internal/server/spanner/query_builder_normalized.go @@ -21,6 +21,7 @@ import ( "cloud.google.com/go/spanner" pb "github.com/datacommonsorg/mixer/internal/proto" + pbv2 "github.com/datacommonsorg/mixer/internal/proto/v2" "github.com/datacommonsorg/mixer/internal/server/sdmx" v2 "github.com/datacommonsorg/mixer/internal/server/v2" ) @@ -171,3 +172,48 @@ func GetSdmxObservationsQuery(req *pb.SdmxDataQuery) *spanner.Statement { return stmt } + +// GetNormalizedMultiEntityObservationsQuery returns a query to fetch observations +// based on variable dcids and multi-entity dimension constraints. +func GetNormalizedMultiEntityObservationsQuery( + variables []string, + dimensions []*pbv2.ObservationDimensionConstraint, +) (*spanner.Statement, error) { + if len(variables) == 0 { + return nil, fmt.Errorf("variables must be specified") + } + if len(dimensions) == 0 { + return nil, fmt.Errorf("observation_dimensions must be specified") + } + + stmt := &spanner.Statement{ + SQL: statementsNormalized.getMultiEntityObs, + Params: map[string]interface{}{}, + } + + filters := []string{} + variableFilter, variableVal := getParamStatement("variables", variables) + stmt.Params["variables"] = variableVal + filters = append(filters, fmt.Sprintf(statementsNormalized.selectVariableDcids, variableFilter)) + + sortedDimensions := append([]*pbv2.ObservationDimensionConstraint(nil), dimensions...) + sort.Slice(sortedDimensions, func(i, j int) bool { + return sortedDimensions[i].GetProperty() < sortedDimensions[j].GetProperty() + }) + + for i, dimension := range sortedDimensions { + propertyParam := fmt.Sprintf("dimension_property_%d", i) + valueParam := fmt.Sprintf("dimension_values_%d", i) + valueFilter, valueVal := getParamStatement(valueParam, dimension.GetValue().GetDcids()) + stmt.Params[propertyParam] = dimension.GetProperty() + stmt.Params[valueParam] = valueVal + filters = append(filters, fmt.Sprintf( + statementsNormalized.selectMultiEntityDimension, + propertyParam, + valueFilter, + )) + } + + stmt.SQL += "\n\t\tWHERE " + strings.Join(filters, " AND ") + return stmt, nil +} diff --git a/internal/server/spanner/query_builder_normalized_test.go b/internal/server/spanner/query_builder_normalized_test.go new file mode 100644 index 000000000..af9a55cbc --- /dev/null +++ b/internal/server/spanner/query_builder_normalized_test.go @@ -0,0 +1,60 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package spanner + +import ( + "strings" + "testing" + + pbv2 "github.com/datacommonsorg/mixer/internal/proto/v2" +) + +func TestGetNormalizedMultiEntityObservationsQuery(t *testing.T) { + stmt, err := GetNormalizedMultiEntityObservationsQuery( + []string{"dcid:Amount_EconomicActivity_GrossODA"}, + []*pbv2.ObservationDimensionConstraint{ + { + Property: "recipient", + Value: &pbv2.DcidOrExpression{Dcids: []string{"country/GHA"}}, + }, + { + Property: "donor", + Value: &pbv2.DcidOrExpression{Dcids: []string{"country/IRL", "country/USA"}}, + }, + }, + ) + if err != nil { + t.Fatalf("GetNormalizedMultiEntityObservationsQuery() error = %v", err) + } + + for _, want := range []string{ + "ts.variable_measured = @variables", + "property = @dimension_property_0", + "value IN UNNEST(@dimension_values_0)", + "property = @dimension_property_1", + "value = @dimension_values_1", + } { + if !strings.Contains(stmt.SQL, want) { + t.Fatalf("SQL missing %q:\n%s", want, stmt.SQL) + } + } + + if got := stmt.Params["dimension_property_0"]; got != "donor" { + t.Fatalf("dimension_property_0 = %v, want donor", got) + } + if got := stmt.Params["dimension_property_1"]; got != "recipient" { + t.Fatalf("dimension_property_1 = %v, want recipient", got) + } +} diff --git a/internal/server/spanner/query_normalized.go b/internal/server/spanner/query_normalized.go index 4fefbba66..4698292fd 100644 --- a/internal/server/spanner/query_normalized.go +++ b/internal/server/spanner/query_normalized.go @@ -20,6 +20,7 @@ import ( "strconv" pb "github.com/datacommonsorg/mixer/internal/proto" + pbv2 "github.com/datacommonsorg/mixer/internal/proto/v2" v2 "github.com/datacommonsorg/mixer/internal/server/v2" ) @@ -42,6 +43,26 @@ func (nc *normalizedClient) GetObservations(ctx context.Context, variables []str return reconstructObservations(rawObs), nil } +func (nc *normalizedClient) GetMultiEntityObservations( + ctx context.Context, + variables []string, + dimensions []*pbv2.ObservationDimensionConstraint, +) ([]*multiEntityObservation, error) { + stmt, err := GetNormalizedMultiEntityObservationsQuery(variables, dimensions) + if err != nil { + return nil, err + } + + var rawObs []*rawMultiEntityObservation + err = queryStructs(ctx, nc.sc, *stmt, func() interface{} { return &rawMultiEntityObservation{} }, func(row interface{}) { + rawObs = append(rawObs, row.(*rawMultiEntityObservation)) + }) + if err != nil { + return nil, err + } + return reconstructMultiEntityObservations(rawObs), nil +} + // fetchRawObservations fetches data from TimeSeries and StatVarObservation tables. func (nc *normalizedClient) fetchRawObservations(ctx context.Context, variables []string, entities []string) ([]*rawObservation, error) { stmt := GetNormalizedObservationsQuery(variables, entities) @@ -89,6 +110,26 @@ func reconstructObservations(rawObs []*rawObservation) []*Observation { return result } +func reconstructMultiEntityObservations(rawObs []*rawMultiEntityObservation) []*multiEntityObservation { + var result []*multiEntityObservation + + for _, r := range rawObs { + obs := &multiEntityObservation{ + VariableMeasured: r.VariableMeasured, + Provenance: r.Provenance, + Attributes: r.Attributes, + Observations: TimeSeries{}, + } + + for _, dv := range r.DatesAndValues { + obs.Observations = append(obs.Observations, &DateValue{Date: dv.Date, Value: dv.Value}) + } + result = append(result, obs) + } + + return result +} + // CheckVariableExistence checks which variables exist for which entities using the normalized schema. func (nc *normalizedClient) CheckVariableExistence(ctx context.Context, variables []string, entities []string) ([][]string, error) { stmt, err := GetNormalizedStatVarsByEntityQuery(variables, entities) diff --git a/internal/server/spanner/statements_normalized.go b/internal/server/spanner/statements_normalized.go index c68e2721e..5de7edccb 100644 --- a/internal/server/spanner/statements_normalized.go +++ b/internal/server/spanner/statements_normalized.go @@ -34,6 +34,12 @@ var statementsNormalized = struct { // Fetch observations for entities contained in a place. getObsByContainedInPlace string + + // Fetch observations with multi-entity dimension filters. + getMultiEntityObs string + + // Filter by one multi-entity dimension. + selectMultiEntityDimension string }{ getObs: ` SELECT ts.variable_measured, @@ -48,7 +54,7 @@ var statementsNormalized = struct { FROM TimeSeriesAttribute WHERE id = ts.id ) as attributes - FROM + FROM TimeSeries@{FORCE_INDEX=TimeSeriesByVariableMeasured} ts`, getSdmxObs: ` SELECT ts.variable_measured, @@ -64,7 +70,7 @@ var statementsNormalized = struct { FROM TimeSeriesAttribute WHERE id = ts.id ) as attributes - FROM + FROM TimeSeries@{FORCE_INDEX=TimeSeriesByVariableMeasured} ts`, selectVariableDcids: "ts.variable_measured %s", @@ -113,4 +119,23 @@ var statementsNormalized = struct { TimeSeries@{FORCE_INDEX=TimeSeriesByVariableMeasured} ts ON ts.id = tsa.id`, + + getMultiEntityObs: ` SELECT + ts.variable_measured, + ts.provenance, + ARRAY( + SELECT STRUCT(date, value) + FROM StatVarObservation + WHERE id = ts.id + ORDER BY date ASC + ) as dates_and_values, + ARRAY( + SELECT STRUCT(property, value) + FROM TimeSeriesAttribute + WHERE id = ts.id + ORDER BY property ASC, value ASC + ) as attributes + FROM + TimeSeries@{FORCE_INDEX=TimeSeriesByVariableMeasured} ts`, + selectMultiEntityDimension: "ts.id IN (SELECT id FROM TimeSeriesAttribute@{FORCE_INDEX=TimeSeriesAttributePropertyValue} WHERE property = @%s AND value %s)", } diff --git a/internal/server/v2/observation/observation.go b/internal/server/v2/observation/observation.go index c5b2d8e72..ad5e3e435 100644 --- a/internal/server/v2/observation/observation.go +++ b/internal/server/v2/observation/observation.go @@ -41,6 +41,15 @@ func ObservationCore( httpClient *http.Client, in *pbv2.ObservationRequest, ) (*pbv2.ObservationResponse, shared.QueryType, error) { + if len(in.GetObservationDimensions()) > 0 { + return nil, "", status.Error( + codes.Unimplemented, "multi-entity observation requires Spanner dispatcher") + } + if len(in.GetNodeProperties()) > 0 { + return nil, "", status.Error( + codes.InvalidArgument, "node_properties are only supported for multi-entity observation requests") + } + // (TODO): The routing logic here is very rough. This needs more work. var queryDate, queryValue, queryVariable, queryEntity, queryFacet bool for _, item := range in.GetSelect() { diff --git a/proto/v2/observation.proto b/proto/v2/observation.proto index f7f26a895..318f685d3 100644 --- a/proto/v2/observation.proto +++ b/proto/v2/observation.proto @@ -19,6 +19,7 @@ package datacommons.v2; option go_package = "github.com/datacommonsorg/mixer/internal/proto/v2"; import "stat.proto"; +import "v2/node.proto"; // Represent a list of entities passed in as plain list or graph expression. message DcidOrExpression { @@ -43,10 +44,21 @@ message DcidOrExpression { string formula = 3; } +message ObservationDimension { + string property = 1; + string dcid = 2; +} + +message MultiEntityObservation { + repeated ObservationDimension observation_dimensions = 1; + EntityObservation observation = 2; +} + // Holds all observations of a particular variable. message VariableObservation { // Keyed by entity DCID map by_entity = 1; + repeated MultiEntityObservation multi_entity_observations = 2; } // @@ -79,10 +91,18 @@ message FacetFilter { repeated string facet_ids = 3; } +message ObservationDimensionConstraint { + string property = 1; + DcidOrExpression value = 2; +} + // Generic observation request message ObservationRequest { // A list of statistical variable DCIDs, or a formula expression. DcidOrExpression variable = 1; + // Exactly one of entity or observation_dimensions may be set. + // This is enforced in request validation because observation_dimensions is + // repeated and cannot be placed directly in a proto oneof. // A list of entity DCIDs, or an arrow expression that covers a list of entity // DCIDs. DcidOrExpression entity = 2; @@ -98,6 +118,11 @@ message ObservationRequest { FacetFilter filter = 5; // Fields to return, valid values are: "variable", "entity", "date", "value", "facet" repeated string select = 6; + // Multi-entity dimension filters. Each present property is constrained, and + // omitted dimension properties are unconstrained. + repeated ObservationDimensionConstraint observation_dimensions = 7; + // Node properties to include for multi-entity dimension dcids. + repeated string node_properties = 8; } message ObservationResponse { @@ -105,4 +130,6 @@ message ObservationResponse { map by_variable = 1; // Keyed by facet ID map facets = 2; -} \ No newline at end of file + // Keyed by dimension dcid when node_properties is requested. + map nodes = 3; +} From c76a0e5dde7423e2376117c40af1d227e969de54 Mon Sep 17 00:00:00 2001 From: Rohit Kumar Date: Fri, 24 Apr 2026 16:26:03 +0000 Subject: [PATCH 2/2] feat: add support for observation attributes to Spanner queries, protobuf models, and multi-entity facet resolution --- internal/proto/stat.pb.go | 785 ++++++++++-------- internal/server/spanner/dsutil.go | 19 +- internal/server/spanner/model.go | 5 +- internal/server/spanner/model_normalized.go | 5 +- .../spanner/multi_entity_observation.go | 46 +- .../spanner/multi_entity_observation_test.go | 49 +- internal/server/spanner/query_normalized.go | 12 +- .../server/spanner/statements_normalized.go | 51 +- proto/stat.proto | 7 + 9 files changed, 600 insertions(+), 379 deletions(-) diff --git a/internal/proto/stat.pb.go b/internal/proto/stat.pb.go index a4a0b01d0..18cc346c2 100644 --- a/internal/proto/stat.pb.go +++ b/internal/proto/stat.pb.go @@ -331,6 +331,61 @@ func (x *PlaceVariableFacets) GetPlaceVariableFacets() []*PlaceVariableFacet { return nil } +type ObservationAttribute struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Property string `protobuf:"bytes,1,opt,name=property,proto3" json:"property,omitempty"` + Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` +} + +func (x *ObservationAttribute) Reset() { + *x = ObservationAttribute{} + if protoimpl.UnsafeEnabled { + mi := &file_stat_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ObservationAttribute) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ObservationAttribute) ProtoMessage() {} + +func (x *ObservationAttribute) ProtoReflect() protoreflect.Message { + mi := &file_stat_proto_msgTypes[4] + 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 ObservationAttribute.ProtoReflect.Descriptor instead. +func (*ObservationAttribute) Descriptor() ([]byte, []int) { + return file_stat_proto_rawDescGZIP(), []int{4} +} + +func (x *ObservationAttribute) GetProperty() string { + if x != nil { + return x.Property + } + return "" +} + +func (x *ObservationAttribute) GetValue() string { + if x != nil { + return x.Value + } + return "" +} + type PointStat struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -348,12 +403,14 @@ type PointStat struct { MetaHash string `protobuf:"bytes,4,opt,name=meta_hash,json=metaHash,proto3" json:"meta_hash,omitempty"` // Same as meta_hash, used in V1 API. Facet string `protobuf:"bytes,5,opt,name=facet,proto3" json:"facet,omitempty"` + // Attributes scoped to this observation point. + ObservationAttributes []*ObservationAttribute `protobuf:"bytes,6,rep,name=observation_attributes,json=observationAttributes,proto3" json:"observation_attributes,omitempty"` } func (x *PointStat) Reset() { *x = PointStat{} if protoimpl.UnsafeEnabled { - mi := &file_stat_proto_msgTypes[4] + mi := &file_stat_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -366,7 +423,7 @@ func (x *PointStat) String() string { func (*PointStat) ProtoMessage() {} func (x *PointStat) ProtoReflect() protoreflect.Message { - mi := &file_stat_proto_msgTypes[4] + mi := &file_stat_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -379,7 +436,7 @@ func (x *PointStat) ProtoReflect() protoreflect.Message { // Deprecated: Use PointStat.ProtoReflect.Descriptor instead. func (*PointStat) Descriptor() ([]byte, []int) { - return file_stat_proto_rawDescGZIP(), []int{4} + return file_stat_proto_rawDescGZIP(), []int{5} } func (x *PointStat) GetDate() string { @@ -417,6 +474,13 @@ func (x *PointStat) GetFacet() string { return "" } +func (x *PointStat) GetObservationAttributes() []*ObservationAttribute { + if x != nil { + return x.ObservationAttributes + } + return nil +} + type SourceSeries struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -439,7 +503,7 @@ type SourceSeries struct { func (x *SourceSeries) Reset() { *x = SourceSeries{} if protoimpl.UnsafeEnabled { - mi := &file_stat_proto_msgTypes[5] + mi := &file_stat_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -452,7 +516,7 @@ func (x *SourceSeries) String() string { func (*SourceSeries) ProtoMessage() {} func (x *SourceSeries) ProtoReflect() protoreflect.Message { - mi := &file_stat_proto_msgTypes[5] + mi := &file_stat_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -465,7 +529,7 @@ func (x *SourceSeries) ProtoReflect() protoreflect.Message { // Deprecated: Use SourceSeries.ProtoReflect.Descriptor instead. func (*SourceSeries) Descriptor() ([]byte, []int) { - return file_stat_proto_rawDescGZIP(), []int{5} + return file_stat_proto_rawDescGZIP(), []int{6} } func (x *SourceSeries) GetVal() map[string]float64 { @@ -560,7 +624,7 @@ type Series struct { func (x *Series) Reset() { *x = Series{} if protoimpl.UnsafeEnabled { - mi := &file_stat_proto_msgTypes[6] + mi := &file_stat_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -573,7 +637,7 @@ func (x *Series) String() string { func (*Series) ProtoMessage() {} func (x *Series) ProtoReflect() protoreflect.Message { - mi := &file_stat_proto_msgTypes[6] + mi := &file_stat_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -586,7 +650,7 @@ func (x *Series) ProtoReflect() protoreflect.Message { // Deprecated: Use Series.ProtoReflect.Descriptor instead. func (*Series) Descriptor() ([]byte, []int) { - return file_stat_proto_rawDescGZIP(), []int{6} + return file_stat_proto_rawDescGZIP(), []int{7} } func (x *Series) GetVal() map[string]float64 { @@ -620,7 +684,7 @@ type ObsTimeSeries struct { func (x *ObsTimeSeries) Reset() { *x = ObsTimeSeries{} if protoimpl.UnsafeEnabled { - mi := &file_stat_proto_msgTypes[7] + mi := &file_stat_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -633,7 +697,7 @@ func (x *ObsTimeSeries) String() string { func (*ObsTimeSeries) ProtoMessage() {} func (x *ObsTimeSeries) ProtoReflect() protoreflect.Message { - mi := &file_stat_proto_msgTypes[7] + mi := &file_stat_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -646,7 +710,7 @@ func (x *ObsTimeSeries) ProtoReflect() protoreflect.Message { // Deprecated: Use ObsTimeSeries.ProtoReflect.Descriptor instead. func (*ObsTimeSeries) Descriptor() ([]byte, []int) { - return file_stat_proto_rawDescGZIP(), []int{7} + return file_stat_proto_rawDescGZIP(), []int{8} } func (x *ObsTimeSeries) GetPlaceName() string { @@ -694,7 +758,7 @@ type ObsCollection struct { func (x *ObsCollection) Reset() { *x = ObsCollection{} if protoimpl.UnsafeEnabled { - mi := &file_stat_proto_msgTypes[8] + mi := &file_stat_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -707,7 +771,7 @@ func (x *ObsCollection) String() string { func (*ObsCollection) ProtoMessage() {} func (x *ObsCollection) ProtoReflect() protoreflect.Message { - mi := &file_stat_proto_msgTypes[8] + mi := &file_stat_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -720,7 +784,7 @@ func (x *ObsCollection) ProtoReflect() protoreflect.Message { // Deprecated: Use ObsCollection.ProtoReflect.Descriptor instead. func (*ObsCollection) Descriptor() ([]byte, []int) { - return file_stat_proto_rawDescGZIP(), []int{8} + return file_stat_proto_rawDescGZIP(), []int{9} } func (x *ObsCollection) GetSourceCohorts() []*SourceSeries { @@ -753,7 +817,7 @@ type ChartStore struct { func (x *ChartStore) Reset() { *x = ChartStore{} if protoimpl.UnsafeEnabled { - mi := &file_stat_proto_msgTypes[9] + mi := &file_stat_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -766,7 +830,7 @@ func (x *ChartStore) String() string { func (*ChartStore) ProtoMessage() {} func (x *ChartStore) ProtoReflect() protoreflect.Message { - mi := &file_stat_proto_msgTypes[9] + mi := &file_stat_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -779,7 +843,7 @@ func (x *ChartStore) ProtoReflect() protoreflect.Message { // Deprecated: Use ChartStore.ProtoReflect.Descriptor instead. func (*ChartStore) Descriptor() ([]byte, []int) { - return file_stat_proto_rawDescGZIP(), []int{9} + return file_stat_proto_rawDescGZIP(), []int{10} } func (m *ChartStore) GetVal() isChartStore_Val { @@ -833,7 +897,7 @@ type PlaceStat struct { func (x *PlaceStat) Reset() { *x = PlaceStat{} if protoimpl.UnsafeEnabled { - mi := &file_stat_proto_msgTypes[10] + mi := &file_stat_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -846,7 +910,7 @@ func (x *PlaceStat) String() string { func (*PlaceStat) ProtoMessage() {} func (x *PlaceStat) ProtoReflect() protoreflect.Message { - mi := &file_stat_proto_msgTypes[10] + mi := &file_stat_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -859,7 +923,7 @@ func (x *PlaceStat) ProtoReflect() protoreflect.Message { // Deprecated: Use PlaceStat.ProtoReflect.Descriptor instead. func (*PlaceStat) Descriptor() ([]byte, []int) { - return file_stat_proto_rawDescGZIP(), []int{10} + return file_stat_proto_rawDescGZIP(), []int{11} } func (x *PlaceStat) GetStatVarData() map[string]*ObsTimeSeries { @@ -884,7 +948,7 @@ type StatVarSeries struct { func (x *StatVarSeries) Reset() { *x = StatVarSeries{} if protoimpl.UnsafeEnabled { - mi := &file_stat_proto_msgTypes[11] + mi := &file_stat_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -897,7 +961,7 @@ func (x *StatVarSeries) String() string { func (*StatVarSeries) ProtoMessage() {} func (x *StatVarSeries) ProtoReflect() protoreflect.Message { - mi := &file_stat_proto_msgTypes[11] + mi := &file_stat_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -910,7 +974,7 @@ func (x *StatVarSeries) ProtoReflect() protoreflect.Message { // Deprecated: Use StatVarSeries.ProtoReflect.Descriptor instead. func (*StatVarSeries) Descriptor() ([]byte, []int) { - return file_stat_proto_rawDescGZIP(), []int{11} + return file_stat_proto_rawDescGZIP(), []int{12} } func (x *StatVarSeries) GetData() map[string]*Series { @@ -946,7 +1010,7 @@ type GetStatsRequest struct { func (x *GetStatsRequest) Reset() { *x = GetStatsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_stat_proto_msgTypes[12] + mi := &file_stat_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -959,7 +1023,7 @@ func (x *GetStatsRequest) String() string { func (*GetStatsRequest) ProtoMessage() {} func (x *GetStatsRequest) ProtoReflect() protoreflect.Message { - mi := &file_stat_proto_msgTypes[12] + mi := &file_stat_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -972,7 +1036,7 @@ func (x *GetStatsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetStatsRequest.ProtoReflect.Descriptor instead. func (*GetStatsRequest) Descriptor() ([]byte, []int) { - return file_stat_proto_rawDescGZIP(), []int{12} + return file_stat_proto_rawDescGZIP(), []int{13} } func (x *GetStatsRequest) GetPlace() []string { @@ -1030,7 +1094,7 @@ type GetStatsResponse struct { func (x *GetStatsResponse) Reset() { *x = GetStatsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_stat_proto_msgTypes[13] + mi := &file_stat_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1043,7 +1107,7 @@ func (x *GetStatsResponse) String() string { func (*GetStatsResponse) ProtoMessage() {} func (x *GetStatsResponse) ProtoReflect() protoreflect.Message { - mi := &file_stat_proto_msgTypes[13] + mi := &file_stat_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1056,7 +1120,7 @@ func (x *GetStatsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetStatsResponse.ProtoReflect.Descriptor instead. func (*GetStatsResponse) Descriptor() ([]byte, []int) { - return file_stat_proto_rawDescGZIP(), []int{13} + return file_stat_proto_rawDescGZIP(), []int{14} } func (x *GetStatsResponse) GetPayload() string { @@ -1091,7 +1155,7 @@ type GetStatValueRequest struct { func (x *GetStatValueRequest) Reset() { *x = GetStatValueRequest{} if protoimpl.UnsafeEnabled { - mi := &file_stat_proto_msgTypes[14] + mi := &file_stat_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1104,7 +1168,7 @@ func (x *GetStatValueRequest) String() string { func (*GetStatValueRequest) ProtoMessage() {} func (x *GetStatValueRequest) ProtoReflect() protoreflect.Message { - mi := &file_stat_proto_msgTypes[14] + mi := &file_stat_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1117,7 +1181,7 @@ func (x *GetStatValueRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetStatValueRequest.ProtoReflect.Descriptor instead. func (*GetStatValueRequest) Descriptor() ([]byte, []int) { - return file_stat_proto_rawDescGZIP(), []int{14} + return file_stat_proto_rawDescGZIP(), []int{15} } func (x *GetStatValueRequest) GetPlace() string { @@ -1180,7 +1244,7 @@ type GetStatValueResponse struct { func (x *GetStatValueResponse) Reset() { *x = GetStatValueResponse{} if protoimpl.UnsafeEnabled { - mi := &file_stat_proto_msgTypes[15] + mi := &file_stat_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1193,7 +1257,7 @@ func (x *GetStatValueResponse) String() string { func (*GetStatValueResponse) ProtoMessage() {} func (x *GetStatValueResponse) ProtoReflect() protoreflect.Message { - mi := &file_stat_proto_msgTypes[15] + mi := &file_stat_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1206,7 +1270,7 @@ func (x *GetStatValueResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetStatValueResponse.ProtoReflect.Descriptor instead. func (*GetStatValueResponse) Descriptor() ([]byte, []int) { - return file_stat_proto_rawDescGZIP(), []int{15} + return file_stat_proto_rawDescGZIP(), []int{16} } func (x *GetStatValueResponse) GetValue() float64 { @@ -1239,7 +1303,7 @@ type GetStatSeriesRequest struct { func (x *GetStatSeriesRequest) Reset() { *x = GetStatSeriesRequest{} if protoimpl.UnsafeEnabled { - mi := &file_stat_proto_msgTypes[16] + mi := &file_stat_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1252,7 +1316,7 @@ func (x *GetStatSeriesRequest) String() string { func (*GetStatSeriesRequest) ProtoMessage() {} func (x *GetStatSeriesRequest) ProtoReflect() protoreflect.Message { - mi := &file_stat_proto_msgTypes[16] + mi := &file_stat_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1265,7 +1329,7 @@ func (x *GetStatSeriesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetStatSeriesRequest.ProtoReflect.Descriptor instead. func (*GetStatSeriesRequest) Descriptor() ([]byte, []int) { - return file_stat_proto_rawDescGZIP(), []int{16} + return file_stat_proto_rawDescGZIP(), []int{17} } func (x *GetStatSeriesRequest) GetPlace() string { @@ -1323,7 +1387,7 @@ type GetStatSeriesResponse struct { func (x *GetStatSeriesResponse) Reset() { *x = GetStatSeriesResponse{} if protoimpl.UnsafeEnabled { - mi := &file_stat_proto_msgTypes[17] + mi := &file_stat_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1336,7 +1400,7 @@ func (x *GetStatSeriesResponse) String() string { func (*GetStatSeriesResponse) ProtoMessage() {} func (x *GetStatSeriesResponse) ProtoReflect() protoreflect.Message { - mi := &file_stat_proto_msgTypes[17] + mi := &file_stat_proto_msgTypes[18] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1349,7 +1413,7 @@ func (x *GetStatSeriesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetStatSeriesResponse.ProtoReflect.Descriptor instead. func (*GetStatSeriesResponse) Descriptor() ([]byte, []int) { - return file_stat_proto_rawDescGZIP(), []int{17} + return file_stat_proto_rawDescGZIP(), []int{18} } func (x *GetStatSeriesResponse) GetSeries() map[string]float64 { @@ -1374,7 +1438,7 @@ type GetStatAllRequest struct { func (x *GetStatAllRequest) Reset() { *x = GetStatAllRequest{} if protoimpl.UnsafeEnabled { - mi := &file_stat_proto_msgTypes[18] + mi := &file_stat_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1387,7 +1451,7 @@ func (x *GetStatAllRequest) String() string { func (*GetStatAllRequest) ProtoMessage() {} func (x *GetStatAllRequest) ProtoReflect() protoreflect.Message { - mi := &file_stat_proto_msgTypes[18] + mi := &file_stat_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1400,7 +1464,7 @@ func (x *GetStatAllRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetStatAllRequest.ProtoReflect.Descriptor instead. func (*GetStatAllRequest) Descriptor() ([]byte, []int) { - return file_stat_proto_rawDescGZIP(), []int{18} + return file_stat_proto_rawDescGZIP(), []int{19} } func (x *GetStatAllRequest) GetPlaces() []string { @@ -1474,7 +1538,7 @@ type GetStatAllResponse struct { func (x *GetStatAllResponse) Reset() { *x = GetStatAllResponse{} if protoimpl.UnsafeEnabled { - mi := &file_stat_proto_msgTypes[19] + mi := &file_stat_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1487,7 +1551,7 @@ func (x *GetStatAllResponse) String() string { func (*GetStatAllResponse) ProtoMessage() {} func (x *GetStatAllResponse) ProtoReflect() protoreflect.Message { - mi := &file_stat_proto_msgTypes[19] + mi := &file_stat_proto_msgTypes[20] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1500,7 +1564,7 @@ func (x *GetStatAllResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetStatAllResponse.ProtoReflect.Descriptor instead. func (*GetStatAllResponse) Descriptor() ([]byte, []int) { - return file_stat_proto_rawDescGZIP(), []int{19} + return file_stat_proto_rawDescGZIP(), []int{20} } func (x *GetStatAllResponse) GetPlaceData() map[string]*PlaceStat { @@ -1523,7 +1587,7 @@ type StatDate struct { func (x *StatDate) Reset() { *x = StatDate{} if protoimpl.UnsafeEnabled { - mi := &file_stat_proto_msgTypes[20] + mi := &file_stat_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1536,7 +1600,7 @@ func (x *StatDate) String() string { func (*StatDate) ProtoMessage() {} func (x *StatDate) ProtoReflect() protoreflect.Message { - mi := &file_stat_proto_msgTypes[20] + mi := &file_stat_proto_msgTypes[21] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1549,7 +1613,7 @@ func (x *StatDate) ProtoReflect() protoreflect.Message { // Deprecated: Use StatDate.ProtoReflect.Descriptor instead. func (*StatDate) Descriptor() ([]byte, []int) { - return file_stat_proto_rawDescGZIP(), []int{20} + return file_stat_proto_rawDescGZIP(), []int{21} } func (x *StatDate) GetDatePlaceCount() map[string]float64 { @@ -1577,7 +1641,7 @@ type StatDateList struct { func (x *StatDateList) Reset() { *x = StatDateList{} if protoimpl.UnsafeEnabled { - mi := &file_stat_proto_msgTypes[21] + mi := &file_stat_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1590,7 +1654,7 @@ func (x *StatDateList) String() string { func (*StatDateList) ProtoMessage() {} func (x *StatDateList) ProtoReflect() protoreflect.Message { - mi := &file_stat_proto_msgTypes[21] + mi := &file_stat_proto_msgTypes[22] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1603,7 +1667,7 @@ func (x *StatDateList) ProtoReflect() protoreflect.Message { // Deprecated: Use StatDateList.ProtoReflect.Descriptor instead. func (*StatDateList) Descriptor() ([]byte, []int) { - return file_stat_proto_rawDescGZIP(), []int{21} + return file_stat_proto_rawDescGZIP(), []int{22} } func (x *StatDateList) GetStatDate() []*StatDate { @@ -1625,7 +1689,7 @@ type ObsCollection_PlaceTypes struct { func (x *ObsCollection_PlaceTypes) Reset() { *x = ObsCollection_PlaceTypes{} if protoimpl.UnsafeEnabled { - mi := &file_stat_proto_msgTypes[25] + mi := &file_stat_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1638,7 +1702,7 @@ func (x *ObsCollection_PlaceTypes) String() string { func (*ObsCollection_PlaceTypes) ProtoMessage() {} func (x *ObsCollection_PlaceTypes) ProtoReflect() protoreflect.Message { - mi := &file_stat_proto_msgTypes[25] + mi := &file_stat_proto_msgTypes[26] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1651,7 +1715,7 @@ func (x *ObsCollection_PlaceTypes) ProtoReflect() protoreflect.Message { // Deprecated: Use ObsCollection_PlaceTypes.ProtoReflect.Descriptor instead. func (*ObsCollection_PlaceTypes) Descriptor() ([]byte, []int) { - return file_stat_proto_rawDescGZIP(), []int{8, 0} + return file_stat_proto_rawDescGZIP(), []int{9, 0} } func (x *ObsCollection_PlaceTypes) GetTypes() []string { @@ -1707,229 +1771,240 @@ var file_stat_proto_rawDesc = []byte{ 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x61, 0x63, 0x65, 0x74, 0x52, 0x13, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x61, 0x63, 0x65, 0x74, - 0x73, 0x22, 0xa7, 0x01, 0x0a, 0x09, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x12, - 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, - 0x61, 0x74, 0x65, 0x12, 0x19, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x01, 0x48, 0x00, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x88, 0x01, 0x01, 0x12, 0x2e, - 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x12, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x46, - 0x61, 0x63, 0x65, 0x74, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1b, - 0x0a, 0x09, 0x6d, 0x65, 0x74, 0x61, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x48, 0x61, 0x73, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x66, - 0x61, 0x63, 0x65, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x66, 0x61, 0x63, 0x65, - 0x74, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x85, 0x05, 0x0a, 0x0c, - 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x12, 0x34, 0x0a, 0x03, - 0x76, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x64, 0x61, 0x74, 0x61, - 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, + 0x73, 0x22, 0x48, 0x0a, 0x14, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, + 0x70, 0x65, 0x72, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, + 0x70, 0x65, 0x72, 0x74, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x81, 0x02, 0x0a, 0x09, + 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x61, 0x74, 0x65, 0x12, 0x19, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x48, 0x00, 0x52, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x88, 0x01, 0x01, 0x12, 0x2e, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x64, 0x61, 0x74, + 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x46, 0x61, 0x63, 0x65, 0x74, 0x52, 0x08, + 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x65, 0x74, 0x61, + 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x65, 0x74, + 0x61, 0x48, 0x61, 0x73, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x61, 0x63, 0x65, 0x74, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x66, 0x61, 0x63, 0x65, 0x74, 0x12, 0x58, 0x0a, 0x16, 0x6f, + 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, + 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x64, 0x61, + 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x52, 0x15, + 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x74, 0x74, 0x72, 0x69, + 0x62, 0x75, 0x74, 0x65, 0x73, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, + 0x85, 0x05, 0x0a, 0x0c, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, + 0x12, 0x34, 0x0a, 0x03, 0x76, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, + 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x53, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x2e, 0x56, 0x61, 0x6c, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x52, 0x03, 0x76, 0x61, 0x6c, 0x12, 0x2d, 0x0a, 0x12, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, + 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x11, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x4d, + 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x2d, 0x0a, 0x12, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x11, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x65, + 0x72, 0x69, 0x6f, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6d, 0x70, 0x6f, 0x72, + 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x6e, 0x61, + 0x6e, 0x63, 0x65, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x10, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x44, 0x6f, 0x6d, 0x61, + 0x69, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x63, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x61, + 0x63, 0x74, 0x6f, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x63, 0x61, 0x6c, + 0x69, 0x6e, 0x67, 0x46, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x6e, 0x69, + 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x6e, 0x69, 0x74, 0x12, 0x26, 0x0a, + 0x0f, 0x69, 0x73, 0x5f, 0x64, 0x63, 0x5f, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x69, 0x73, 0x44, 0x63, 0x41, 0x67, 0x67, 0x72, + 0x65, 0x67, 0x61, 0x74, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x6e, 0x61, + 0x6e, 0x63, 0x65, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x70, + 0x72, 0x6f, 0x76, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x55, 0x72, 0x6c, 0x12, 0x61, 0x0a, 0x14, + 0x70, 0x6c, 0x61, 0x63, 0x65, 0x5f, 0x74, 0x6f, 0x5f, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x5f, + 0x64, 0x61, 0x74, 0x65, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x64, 0x61, 0x74, + 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, + 0x65, 0x72, 0x69, 0x65, 0x73, 0x2e, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x54, 0x6f, 0x4c, 0x61, 0x74, + 0x65, 0x73, 0x74, 0x44, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x11, 0x70, 0x6c, + 0x61, 0x63, 0x65, 0x54, 0x6f, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x44, 0x61, 0x74, 0x65, 0x12, + 0x22, 0x0a, 0x0d, 0x69, 0x73, 0x5f, 0x64, 0x63, 0x5f, 0x69, 0x6d, 0x70, 0x75, 0x74, 0x65, 0x64, + 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x69, 0x73, 0x44, 0x63, 0x49, 0x6d, 0x70, 0x75, + 0x74, 0x65, 0x64, 0x1a, 0x36, 0x0a, 0x08, 0x56, 0x61, 0x6c, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, + 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x44, 0x0a, 0x16, 0x50, + 0x6c, 0x61, 0x63, 0x65, 0x54, 0x6f, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x44, 0x61, 0x74, 0x65, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, + 0x01, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x22, 0xa0, 0x01, 0x0a, 0x06, 0x53, 0x65, 0x72, 0x69, + 0x65, 0x73, 0x12, 0x2e, 0x0a, 0x03, 0x76, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x1c, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x2e, 0x56, 0x61, 0x6c, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x03, 0x76, - 0x61, 0x6c, 0x12, 0x2d, 0x0a, 0x12, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, - 0x74, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, - 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x68, 0x6f, - 0x64, 0x12, 0x2d, 0x0a, 0x12, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x6f, - 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, - 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4e, 0x61, 0x6d, - 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x5f, - 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x70, 0x72, - 0x6f, 0x76, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x25, - 0x0a, 0x0e, 0x73, 0x63, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x61, 0x63, 0x74, 0x6f, 0x72, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x63, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x46, - 0x61, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x6e, 0x69, 0x74, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x6e, 0x69, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x69, 0x73, 0x5f, - 0x64, 0x63, 0x5f, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x18, 0x09, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x0d, 0x69, 0x73, 0x44, 0x63, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, - 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x5f, - 0x75, 0x72, 0x6c, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x76, 0x65, - 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x55, 0x72, 0x6c, 0x12, 0x61, 0x0a, 0x14, 0x70, 0x6c, 0x61, 0x63, - 0x65, 0x5f, 0x74, 0x6f, 0x5f, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x65, - 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, - 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x72, 0x69, 0x65, - 0x73, 0x2e, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x54, 0x6f, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x44, - 0x61, 0x74, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x11, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x54, - 0x6f, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x44, 0x61, 0x74, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x69, - 0x73, 0x5f, 0x64, 0x63, 0x5f, 0x69, 0x6d, 0x70, 0x75, 0x74, 0x65, 0x64, 0x18, 0x0d, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x0b, 0x69, 0x73, 0x44, 0x63, 0x49, 0x6d, 0x70, 0x75, 0x74, 0x65, 0x64, 0x1a, - 0x36, 0x0a, 0x08, 0x56, 0x61, 0x6c, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, - 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x44, 0x0a, 0x16, 0x50, 0x6c, 0x61, 0x63, 0x65, - 0x54, 0x6f, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x44, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, - 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x4a, 0x04, 0x08, - 0x02, 0x10, 0x03, 0x22, 0xa0, 0x01, 0x0a, 0x06, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x12, 0x2e, - 0x0a, 0x03, 0x76, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x64, 0x61, - 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, - 0x2e, 0x56, 0x61, 0x6c, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x03, 0x76, 0x61, 0x6c, 0x12, 0x2e, - 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x12, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x46, - 0x61, 0x63, 0x65, 0x74, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x36, - 0x0a, 0x08, 0x56, 0x61, 0x6c, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xae, 0x01, 0x0a, 0x0d, 0x4f, 0x62, 0x73, 0x54, 0x69, - 0x6d, 0x65, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x6c, 0x61, 0x63, - 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x6c, - 0x61, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x6c, 0x61, 0x63, 0x65, - 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x6c, - 0x61, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x6c, 0x61, 0x63, - 0x65, 0x5f, 0x64, 0x63, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x6c, - 0x61, 0x63, 0x65, 0x44, 0x63, 0x69, 0x64, 0x12, 0x3e, 0x0a, 0x0d, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x5f, 0x73, 0x65, 0x72, 0x69, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, - 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x53, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, 0x0c, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x22, 0xda, 0x02, 0x0a, 0x0d, 0x4f, 0x62, 0x73, 0x43, - 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x40, 0x0a, 0x0e, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x68, 0x6f, 0x72, 0x74, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x19, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, - 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, 0x0d, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x43, 0x6f, 0x68, 0x6f, 0x72, 0x74, 0x73, 0x12, 0x5f, 0x0a, 0x13, 0x70, - 0x6c, 0x61, 0x63, 0x65, 0x5f, 0x64, 0x63, 0x69, 0x64, 0x5f, 0x74, 0x6f, 0x5f, 0x74, 0x79, 0x70, - 0x65, 0x73, 0x18, 0x11, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, - 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x4f, 0x62, 0x73, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x44, 0x63, 0x69, 0x64, 0x54, 0x6f, - 0x54, 0x79, 0x70, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x10, 0x70, 0x6c, 0x61, 0x63, - 0x65, 0x44, 0x63, 0x69, 0x64, 0x54, 0x6f, 0x54, 0x79, 0x70, 0x65, 0x73, 0x1a, 0x22, 0x0a, 0x0a, - 0x50, 0x6c, 0x61, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x79, - 0x70, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x74, 0x79, 0x70, 0x65, 0x73, - 0x1a, 0x6a, 0x0a, 0x15, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x44, 0x63, 0x69, 0x64, 0x54, 0x6f, 0x54, - 0x79, 0x70, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x3b, 0x0a, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x64, 0x61, 0x74, - 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x4f, 0x62, 0x73, 0x43, 0x6f, 0x6c, 0x6c, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, - 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x4a, 0x04, 0x08, 0x01, - 0x10, 0x02, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x4a, 0x04, - 0x08, 0x04, 0x10, 0x05, 0x22, 0x9e, 0x01, 0x0a, 0x0a, 0x43, 0x68, 0x61, 0x72, 0x74, 0x53, 0x74, - 0x6f, 0x72, 0x65, 0x12, 0x44, 0x0a, 0x0f, 0x6f, 0x62, 0x73, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, - 0x73, 0x65, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x64, - 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x4f, 0x62, 0x73, 0x54, 0x69, - 0x6d, 0x65, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x48, 0x00, 0x52, 0x0d, 0x6f, 0x62, 0x73, 0x54, - 0x69, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x12, 0x43, 0x0a, 0x0e, 0x6f, 0x62, 0x73, - 0x5f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, - 0x4f, 0x62, 0x73, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, - 0x0d, 0x6f, 0x62, 0x73, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x05, - 0x0a, 0x03, 0x76, 0x61, 0x6c, 0x22, 0xb4, 0x01, 0x0a, 0x09, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x53, - 0x74, 0x61, 0x74, 0x12, 0x4b, 0x0a, 0x0d, 0x73, 0x74, 0x61, 0x74, 0x5f, 0x76, 0x61, 0x72, 0x5f, - 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x64, 0x61, 0x74, - 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x53, 0x74, - 0x61, 0x74, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x56, 0x61, 0x72, 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x52, 0x0b, 0x73, 0x74, 0x61, 0x74, 0x56, 0x61, 0x72, 0x44, 0x61, 0x74, 0x61, - 0x1a, 0x5a, 0x0a, 0x10, 0x53, 0x74, 0x61, 0x74, 0x56, 0x61, 0x72, 0x44, 0x61, 0x74, 0x61, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x61, 0x6c, 0x12, 0x2e, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, + 0x6e, 0x73, 0x2e, 0x46, 0x61, 0x63, 0x65, 0x74, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x1a, 0x36, 0x0a, 0x08, 0x56, 0x61, 0x6c, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, + 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, + 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xae, 0x01, 0x0a, 0x0d, 0x4f, + 0x62, 0x73, 0x54, 0x69, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x0a, + 0x70, 0x6c, 0x61, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x70, + 0x6c, 0x61, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x0a, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x0a, + 0x70, 0x6c, 0x61, 0x63, 0x65, 0x5f, 0x64, 0x63, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x44, 0x63, 0x69, 0x64, 0x12, 0x3e, 0x0a, 0x0d, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x72, 0x69, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, + 0x2e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, 0x0c, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x22, 0xda, 0x02, 0x0a, 0x0d, + 0x4f, 0x62, 0x73, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x40, 0x0a, + 0x0e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x68, 0x6f, 0x72, 0x74, 0x73, 0x18, + 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, + 0x6f, 0x6e, 0x73, 0x2e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, + 0x52, 0x0d, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x6f, 0x68, 0x6f, 0x72, 0x74, 0x73, 0x12, + 0x5f, 0x0a, 0x13, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x5f, 0x64, 0x63, 0x69, 0x64, 0x5f, 0x74, 0x6f, + 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x11, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x64, + 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x4f, 0x62, 0x73, 0x43, 0x6f, + 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x44, 0x63, + 0x69, 0x64, 0x54, 0x6f, 0x54, 0x79, 0x70, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x10, + 0x70, 0x6c, 0x61, 0x63, 0x65, 0x44, 0x63, 0x69, 0x64, 0x54, 0x6f, 0x54, 0x79, 0x70, 0x65, 0x73, + 0x1a, 0x22, 0x0a, 0x0a, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x14, + 0x0a, 0x05, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x74, + 0x79, 0x70, 0x65, 0x73, 0x1a, 0x6a, 0x0a, 0x15, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x44, 0x63, 0x69, + 0x64, 0x54, 0x6f, 0x54, 0x79, 0x70, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, + 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, + 0x3b, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, + 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x4f, 0x62, 0x73, + 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x50, 0x6c, 0x61, 0x63, 0x65, + 0x54, 0x79, 0x70, 0x65, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, + 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x4a, 0x04, 0x08, 0x03, + 0x10, 0x04, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x22, 0x9e, 0x01, 0x0a, 0x0a, 0x43, 0x68, 0x61, + 0x72, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x44, 0x0a, 0x0f, 0x6f, 0x62, 0x73, 0x5f, 0x74, + 0x69, 0x6d, 0x65, 0x5f, 0x73, 0x65, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1a, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x4f, + 0x62, 0x73, 0x54, 0x69, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x48, 0x00, 0x52, 0x0d, + 0x6f, 0x62, 0x73, 0x54, 0x69, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x12, 0x43, 0x0a, + 0x0e, 0x6f, 0x62, 0x73, 0x5f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, - 0x6f, 0x6e, 0x73, 0x2e, 0x4f, 0x62, 0x73, 0x54, 0x69, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x69, 0x65, - 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x97, 0x01, 0x0a, - 0x0d, 0x53, 0x74, 0x61, 0x74, 0x56, 0x61, 0x72, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x12, 0x38, - 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x64, - 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x56, - 0x61, 0x72, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, - 0x72, 0x79, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x4c, 0x0a, 0x09, 0x44, 0x61, 0x74, 0x61, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, - 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xdd, 0x01, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x53, 0x74, - 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x6c, - 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x70, 0x6c, 0x61, 0x63, 0x65, - 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x74, 0x61, 0x74, 0x73, 0x5f, 0x76, 0x61, 0x72, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x74, 0x61, 0x74, 0x73, 0x56, 0x61, 0x72, 0x12, 0x2d, 0x0a, - 0x12, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x65, 0x74, - 0x68, 0x6f, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x6d, 0x65, 0x61, 0x73, 0x75, - 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x12, 0x0a, 0x04, - 0x75, 0x6e, 0x69, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x6e, 0x69, 0x74, - 0x12, 0x2d, 0x0a, 0x12, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, - 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x6f, 0x62, - 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x12, - 0x25, 0x0a, 0x0e, 0x73, 0x63, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x61, 0x63, 0x74, 0x6f, - 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x63, 0x61, 0x6c, 0x69, 0x6e, 0x67, - 0x46, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x22, 0x2c, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, - 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, - 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x79, - 0x6c, 0x6f, 0x61, 0x64, 0x22, 0xf3, 0x01, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, - 0x70, 0x6c, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x70, 0x6c, 0x61, - 0x63, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x74, 0x61, 0x74, 0x5f, 0x76, 0x61, 0x72, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x74, 0x61, 0x74, 0x56, 0x61, 0x72, 0x12, 0x12, 0x0a, - 0x04, 0x64, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x61, 0x74, - 0x65, 0x12, 0x2d, 0x0a, 0x12, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x6f, 0x6e, 0x73, 0x2e, 0x4f, 0x62, 0x73, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x48, 0x00, 0x52, 0x0d, 0x6f, 0x62, 0x73, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x42, 0x05, 0x0a, 0x03, 0x76, 0x61, 0x6c, 0x22, 0xb4, 0x01, 0x0a, 0x09, 0x50, 0x6c, + 0x61, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x12, 0x4b, 0x0a, 0x0d, 0x73, 0x74, 0x61, 0x74, 0x5f, + 0x76, 0x61, 0x72, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, + 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x50, 0x6c, 0x61, + 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x56, 0x61, 0x72, 0x44, 0x61, + 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0b, 0x73, 0x74, 0x61, 0x74, 0x56, 0x61, 0x72, + 0x44, 0x61, 0x74, 0x61, 0x1a, 0x5a, 0x0a, 0x10, 0x53, 0x74, 0x61, 0x74, 0x56, 0x61, 0x72, 0x44, + 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x64, 0x61, 0x74, 0x61, + 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x4f, 0x62, 0x73, 0x54, 0x69, 0x6d, 0x65, 0x53, + 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, + 0x22, 0x97, 0x01, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x74, 0x56, 0x61, 0x72, 0x53, 0x65, 0x72, 0x69, + 0x65, 0x73, 0x12, 0x38, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x24, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x53, + 0x74, 0x61, 0x74, 0x56, 0x61, 0x72, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x2e, 0x44, 0x61, 0x74, + 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x4c, 0x0a, 0x09, + 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x64, 0x61, 0x74, + 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xdd, 0x01, 0x0a, 0x0f, 0x47, + 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, + 0x0a, 0x05, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x70, + 0x6c, 0x61, 0x63, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x74, 0x61, 0x74, 0x73, 0x5f, 0x76, 0x61, + 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x74, 0x61, 0x74, 0x73, 0x56, 0x61, + 0x72, 0x12, 0x2d, 0x0a, 0x12, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, - 0x12, 0x2d, 0x0a, 0x12, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, - 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x6f, 0x62, - 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x12, - 0x12, 0x0a, 0x04, 0x75, 0x6e, 0x69, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, - 0x6e, 0x69, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x63, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x66, + 0x12, 0x12, 0x0a, 0x04, 0x75, 0x6e, 0x69, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x75, 0x6e, 0x69, 0x74, 0x12, 0x2d, 0x0a, 0x12, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x11, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x65, 0x72, + 0x69, 0x6f, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x63, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x63, 0x61, - 0x6c, 0x69, 0x6e, 0x67, 0x46, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x22, 0x3b, 0x0a, 0x14, 0x47, 0x65, - 0x74, 0x53, 0x74, 0x61, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x19, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x01, 0x48, 0x00, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x88, 0x01, 0x01, 0x42, 0x08, 0x0a, - 0x06, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0xe0, 0x01, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x53, - 0x74, 0x61, 0x74, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x6c, 0x69, 0x6e, 0x67, 0x46, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x22, 0x2c, 0x0a, 0x10, 0x47, 0x65, + 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, + 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0xf3, 0x01, 0x0a, 0x13, 0x47, 0x65, 0x74, + 0x53, 0x74, 0x61, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x74, 0x61, 0x74, 0x5f, 0x76, 0x61, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x74, 0x61, 0x74, 0x56, 0x61, - 0x72, 0x12, 0x2d, 0x0a, 0x12, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, - 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x6d, - 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, - 0x12, 0x2d, 0x0a, 0x12, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, - 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x6f, 0x62, - 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x12, - 0x12, 0x0a, 0x04, 0x75, 0x6e, 0x69, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, - 0x6e, 0x69, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x63, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x66, - 0x61, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x63, 0x61, - 0x6c, 0x69, 0x6e, 0x67, 0x46, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x22, 0x9a, 0x01, 0x0a, 0x15, 0x47, - 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, - 0x6e, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x73, 0x65, 0x72, 0x69, 0x65, 0x73, 0x1a, 0x39, 0x0a, 0x0b, - 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, - 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x48, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x53, 0x74, - 0x61, 0x74, 0x41, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, - 0x70, 0x6c, 0x61, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6c, - 0x61, 0x63, 0x65, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x74, 0x61, 0x74, 0x5f, 0x76, 0x61, 0x72, - 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x73, 0x74, 0x61, 0x74, 0x56, 0x61, 0x72, - 0x73, 0x22, 0xb9, 0x01, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x41, 0x6c, 0x6c, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x0a, 0x70, 0x6c, 0x61, 0x63, - 0x65, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x64, - 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, - 0x61, 0x74, 0x41, 0x6c, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x50, 0x6c, - 0x61, 0x63, 0x65, 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x09, 0x70, 0x6c, - 0x61, 0x63, 0x65, 0x44, 0x61, 0x74, 0x61, 0x1a, 0x54, 0x0a, 0x0e, 0x50, 0x6c, 0x61, 0x63, 0x65, - 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2c, 0x0a, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x64, 0x61, 0x74, - 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x53, 0x74, - 0x61, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xd2, 0x01, - 0x0a, 0x08, 0x53, 0x74, 0x61, 0x74, 0x44, 0x61, 0x74, 0x65, 0x12, 0x53, 0x0a, 0x10, 0x64, 0x61, - 0x74, 0x65, 0x5f, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, - 0x6e, 0x73, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x44, 0x61, 0x74, 0x65, 0x2e, 0x44, 0x61, 0x74, 0x65, - 0x50, 0x6c, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, - 0x0e, 0x64, 0x61, 0x74, 0x65, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, - 0x2e, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x12, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, - 0x46, 0x61, 0x63, 0x65, 0x74, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, - 0x41, 0x0a, 0x13, 0x44, 0x61, 0x74, 0x65, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, - 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, - 0x38, 0x01, 0x22, 0x42, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x44, 0x61, 0x74, 0x65, 0x4c, 0x69, - 0x73, 0x74, 0x12, 0x32, 0x0a, 0x09, 0x73, 0x74, 0x61, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, - 0x6f, 0x6e, 0x73, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x44, 0x61, 0x74, 0x65, 0x52, 0x08, 0x73, 0x74, - 0x61, 0x74, 0x44, 0x61, 0x74, 0x65, 0x42, 0x30, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, - 0x6f, 0x72, 0x67, 0x2f, 0x6d, 0x69, 0x78, 0x65, 0x72, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, - 0x61, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x72, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x64, 0x61, 0x74, 0x65, 0x12, 0x2d, 0x0a, 0x12, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, + 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x11, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x65, + 0x74, 0x68, 0x6f, 0x64, 0x12, 0x2d, 0x0a, 0x12, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x11, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x65, 0x72, + 0x69, 0x6f, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x6e, 0x69, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x75, 0x6e, 0x69, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x63, 0x61, 0x6c, 0x69, + 0x6e, 0x67, 0x5f, 0x66, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0d, 0x73, 0x63, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x46, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x22, 0x3b, + 0x0a, 0x14, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x19, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x01, 0x48, 0x00, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x88, 0x01, + 0x01, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0xe0, 0x01, 0x0a, 0x14, + 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x74, + 0x61, 0x74, 0x5f, 0x76, 0x61, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x74, + 0x61, 0x74, 0x56, 0x61, 0x72, 0x12, 0x2d, 0x0a, 0x12, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, + 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x11, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x65, + 0x74, 0x68, 0x6f, 0x64, 0x12, 0x2d, 0x0a, 0x12, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x11, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x65, 0x72, + 0x69, 0x6f, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x6e, 0x69, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x75, 0x6e, 0x69, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x63, 0x61, 0x6c, 0x69, + 0x6e, 0x67, 0x5f, 0x66, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0d, 0x73, 0x63, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x46, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x22, 0x9a, + 0x01, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x69, + 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, + 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x53, 0x65, + 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x65, 0x72, + 0x69, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x73, 0x65, 0x72, 0x69, 0x65, 0x73, + 0x1a, 0x39, 0x0a, 0x0b, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, + 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x48, 0x0a, 0x11, 0x47, + 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x41, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x16, 0x0a, 0x06, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x06, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x74, 0x61, 0x74, + 0x5f, 0x76, 0x61, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x73, 0x74, 0x61, + 0x74, 0x56, 0x61, 0x72, 0x73, 0x22, 0xb9, 0x01, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, + 0x74, 0x41, 0x6c, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x0a, + 0x70, 0x6c, 0x61, 0x63, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x2e, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x47, + 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x41, 0x6c, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x2e, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x52, 0x09, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x44, 0x61, 0x74, 0x61, 0x1a, 0x54, 0x0a, 0x0e, 0x50, + 0x6c, 0x61, 0x63, 0x65, 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, + 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, + 0x2c, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, + 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x50, 0x6c, 0x61, + 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, + 0x01, 0x22, 0xd2, 0x01, 0x0a, 0x08, 0x53, 0x74, 0x61, 0x74, 0x44, 0x61, 0x74, 0x65, 0x12, 0x53, + 0x0a, 0x10, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, + 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x44, 0x61, 0x74, 0x65, 0x2e, + 0x44, 0x61, 0x74, 0x65, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x52, 0x0e, 0x64, 0x61, 0x74, 0x65, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x43, 0x6f, + 0x75, 0x6e, 0x74, 0x12, 0x2e, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, 0x6d, + 0x6f, 0x6e, 0x73, 0x2e, 0x46, 0x61, 0x63, 0x65, 0x74, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x1a, 0x41, 0x0a, 0x13, 0x44, 0x61, 0x74, 0x65, 0x50, 0x6c, 0x61, 0x63, 0x65, + 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x42, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x44, 0x61, + 0x74, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x32, 0x0a, 0x09, 0x73, 0x74, 0x61, 0x74, 0x5f, 0x64, + 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x64, 0x61, 0x74, 0x61, + 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x73, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x44, 0x61, 0x74, 0x65, + 0x52, 0x08, 0x73, 0x74, 0x61, 0x74, 0x44, 0x61, 0x74, 0x65, 0x42, 0x30, 0x5a, 0x2e, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x64, 0x61, 0x74, 0x61, 0x63, 0x6f, 0x6d, + 0x6d, 0x6f, 0x6e, 0x73, 0x6f, 0x72, 0x67, 0x2f, 0x6d, 0x69, 0x78, 0x65, 0x72, 0x2f, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1944,71 +2019,73 @@ func file_stat_proto_rawDescGZIP() []byte { return file_stat_proto_rawDescData } -var file_stat_proto_msgTypes = make([]protoimpl.MessageInfo, 32) +var file_stat_proto_msgTypes = make([]protoimpl.MessageInfo, 33) var file_stat_proto_goTypes = []interface{}{ (*Facet)(nil), // 0: datacommons.Facet (*Facets)(nil), // 1: datacommons.Facets (*PlaceVariableFacet)(nil), // 2: datacommons.PlaceVariableFacet (*PlaceVariableFacets)(nil), // 3: datacommons.PlaceVariableFacets - (*PointStat)(nil), // 4: datacommons.PointStat - (*SourceSeries)(nil), // 5: datacommons.SourceSeries - (*Series)(nil), // 6: datacommons.Series - (*ObsTimeSeries)(nil), // 7: datacommons.ObsTimeSeries - (*ObsCollection)(nil), // 8: datacommons.ObsCollection - (*ChartStore)(nil), // 9: datacommons.ChartStore - (*PlaceStat)(nil), // 10: datacommons.PlaceStat - (*StatVarSeries)(nil), // 11: datacommons.StatVarSeries - (*GetStatsRequest)(nil), // 12: datacommons.GetStatsRequest - (*GetStatsResponse)(nil), // 13: datacommons.GetStatsResponse - (*GetStatValueRequest)(nil), // 14: datacommons.GetStatValueRequest - (*GetStatValueResponse)(nil), // 15: datacommons.GetStatValueResponse - (*GetStatSeriesRequest)(nil), // 16: datacommons.GetStatSeriesRequest - (*GetStatSeriesResponse)(nil), // 17: datacommons.GetStatSeriesResponse - (*GetStatAllRequest)(nil), // 18: datacommons.GetStatAllRequest - (*GetStatAllResponse)(nil), // 19: datacommons.GetStatAllResponse - (*StatDate)(nil), // 20: datacommons.StatDate - (*StatDateList)(nil), // 21: datacommons.StatDateList - nil, // 22: datacommons.SourceSeries.ValEntry - nil, // 23: datacommons.SourceSeries.PlaceToLatestDateEntry - nil, // 24: datacommons.Series.ValEntry - (*ObsCollection_PlaceTypes)(nil), // 25: datacommons.ObsCollection.PlaceTypes - nil, // 26: datacommons.ObsCollection.PlaceDcidToTypesEntry - nil, // 27: datacommons.PlaceStat.StatVarDataEntry - nil, // 28: datacommons.StatVarSeries.DataEntry - nil, // 29: datacommons.GetStatSeriesResponse.SeriesEntry - nil, // 30: datacommons.GetStatAllResponse.PlaceDataEntry - nil, // 31: datacommons.StatDate.DatePlaceCountEntry + (*ObservationAttribute)(nil), // 4: datacommons.ObservationAttribute + (*PointStat)(nil), // 5: datacommons.PointStat + (*SourceSeries)(nil), // 6: datacommons.SourceSeries + (*Series)(nil), // 7: datacommons.Series + (*ObsTimeSeries)(nil), // 8: datacommons.ObsTimeSeries + (*ObsCollection)(nil), // 9: datacommons.ObsCollection + (*ChartStore)(nil), // 10: datacommons.ChartStore + (*PlaceStat)(nil), // 11: datacommons.PlaceStat + (*StatVarSeries)(nil), // 12: datacommons.StatVarSeries + (*GetStatsRequest)(nil), // 13: datacommons.GetStatsRequest + (*GetStatsResponse)(nil), // 14: datacommons.GetStatsResponse + (*GetStatValueRequest)(nil), // 15: datacommons.GetStatValueRequest + (*GetStatValueResponse)(nil), // 16: datacommons.GetStatValueResponse + (*GetStatSeriesRequest)(nil), // 17: datacommons.GetStatSeriesRequest + (*GetStatSeriesResponse)(nil), // 18: datacommons.GetStatSeriesResponse + (*GetStatAllRequest)(nil), // 19: datacommons.GetStatAllRequest + (*GetStatAllResponse)(nil), // 20: datacommons.GetStatAllResponse + (*StatDate)(nil), // 21: datacommons.StatDate + (*StatDateList)(nil), // 22: datacommons.StatDateList + nil, // 23: datacommons.SourceSeries.ValEntry + nil, // 24: datacommons.SourceSeries.PlaceToLatestDateEntry + nil, // 25: datacommons.Series.ValEntry + (*ObsCollection_PlaceTypes)(nil), // 26: datacommons.ObsCollection.PlaceTypes + nil, // 27: datacommons.ObsCollection.PlaceDcidToTypesEntry + nil, // 28: datacommons.PlaceStat.StatVarDataEntry + nil, // 29: datacommons.StatVarSeries.DataEntry + nil, // 30: datacommons.GetStatSeriesResponse.SeriesEntry + nil, // 31: datacommons.GetStatAllResponse.PlaceDataEntry + nil, // 32: datacommons.StatDate.DatePlaceCountEntry } var file_stat_proto_depIdxs = []int32{ 0, // 0: datacommons.Facets.facets:type_name -> datacommons.Facet 0, // 1: datacommons.PlaceVariableFacet.facet:type_name -> datacommons.Facet 2, // 2: datacommons.PlaceVariableFacets.place_variable_facets:type_name -> datacommons.PlaceVariableFacet 0, // 3: datacommons.PointStat.metadata:type_name -> datacommons.Facet - 22, // 4: datacommons.SourceSeries.val:type_name -> datacommons.SourceSeries.ValEntry - 23, // 5: datacommons.SourceSeries.place_to_latest_date:type_name -> datacommons.SourceSeries.PlaceToLatestDateEntry - 24, // 6: datacommons.Series.val:type_name -> datacommons.Series.ValEntry - 0, // 7: datacommons.Series.metadata:type_name -> datacommons.Facet - 5, // 8: datacommons.ObsTimeSeries.source_series:type_name -> datacommons.SourceSeries - 5, // 9: datacommons.ObsCollection.source_cohorts:type_name -> datacommons.SourceSeries - 26, // 10: datacommons.ObsCollection.place_dcid_to_types:type_name -> datacommons.ObsCollection.PlaceDcidToTypesEntry - 7, // 11: datacommons.ChartStore.obs_time_series:type_name -> datacommons.ObsTimeSeries - 8, // 12: datacommons.ChartStore.obs_collection:type_name -> datacommons.ObsCollection - 27, // 13: datacommons.PlaceStat.stat_var_data:type_name -> datacommons.PlaceStat.StatVarDataEntry - 28, // 14: datacommons.StatVarSeries.data:type_name -> datacommons.StatVarSeries.DataEntry - 29, // 15: datacommons.GetStatSeriesResponse.series:type_name -> datacommons.GetStatSeriesResponse.SeriesEntry - 30, // 16: datacommons.GetStatAllResponse.place_data:type_name -> datacommons.GetStatAllResponse.PlaceDataEntry - 31, // 17: datacommons.StatDate.date_place_count:type_name -> datacommons.StatDate.DatePlaceCountEntry - 0, // 18: datacommons.StatDate.metadata:type_name -> datacommons.Facet - 20, // 19: datacommons.StatDateList.stat_date:type_name -> datacommons.StatDate - 25, // 20: datacommons.ObsCollection.PlaceDcidToTypesEntry.value:type_name -> datacommons.ObsCollection.PlaceTypes - 7, // 21: datacommons.PlaceStat.StatVarDataEntry.value:type_name -> datacommons.ObsTimeSeries - 6, // 22: datacommons.StatVarSeries.DataEntry.value:type_name -> datacommons.Series - 10, // 23: datacommons.GetStatAllResponse.PlaceDataEntry.value:type_name -> datacommons.PlaceStat - 24, // [24:24] is the sub-list for method output_type - 24, // [24:24] is the sub-list for method input_type - 24, // [24:24] is the sub-list for extension type_name - 24, // [24:24] is the sub-list for extension extendee - 0, // [0:24] is the sub-list for field type_name + 4, // 4: datacommons.PointStat.observation_attributes:type_name -> datacommons.ObservationAttribute + 23, // 5: datacommons.SourceSeries.val:type_name -> datacommons.SourceSeries.ValEntry + 24, // 6: datacommons.SourceSeries.place_to_latest_date:type_name -> datacommons.SourceSeries.PlaceToLatestDateEntry + 25, // 7: datacommons.Series.val:type_name -> datacommons.Series.ValEntry + 0, // 8: datacommons.Series.metadata:type_name -> datacommons.Facet + 6, // 9: datacommons.ObsTimeSeries.source_series:type_name -> datacommons.SourceSeries + 6, // 10: datacommons.ObsCollection.source_cohorts:type_name -> datacommons.SourceSeries + 27, // 11: datacommons.ObsCollection.place_dcid_to_types:type_name -> datacommons.ObsCollection.PlaceDcidToTypesEntry + 8, // 12: datacommons.ChartStore.obs_time_series:type_name -> datacommons.ObsTimeSeries + 9, // 13: datacommons.ChartStore.obs_collection:type_name -> datacommons.ObsCollection + 28, // 14: datacommons.PlaceStat.stat_var_data:type_name -> datacommons.PlaceStat.StatVarDataEntry + 29, // 15: datacommons.StatVarSeries.data:type_name -> datacommons.StatVarSeries.DataEntry + 30, // 16: datacommons.GetStatSeriesResponse.series:type_name -> datacommons.GetStatSeriesResponse.SeriesEntry + 31, // 17: datacommons.GetStatAllResponse.place_data:type_name -> datacommons.GetStatAllResponse.PlaceDataEntry + 32, // 18: datacommons.StatDate.date_place_count:type_name -> datacommons.StatDate.DatePlaceCountEntry + 0, // 19: datacommons.StatDate.metadata:type_name -> datacommons.Facet + 21, // 20: datacommons.StatDateList.stat_date:type_name -> datacommons.StatDate + 26, // 21: datacommons.ObsCollection.PlaceDcidToTypesEntry.value:type_name -> datacommons.ObsCollection.PlaceTypes + 8, // 22: datacommons.PlaceStat.StatVarDataEntry.value:type_name -> datacommons.ObsTimeSeries + 7, // 23: datacommons.StatVarSeries.DataEntry.value:type_name -> datacommons.Series + 11, // 24: datacommons.GetStatAllResponse.PlaceDataEntry.value:type_name -> datacommons.PlaceStat + 25, // [25:25] is the sub-list for method output_type + 25, // [25:25] is the sub-list for method input_type + 25, // [25:25] is the sub-list for extension type_name + 25, // [25:25] is the sub-list for extension extendee + 0, // [0:25] is the sub-list for field type_name } func init() { file_stat_proto_init() } @@ -2066,7 +2143,7 @@ func file_stat_proto_init() { } } file_stat_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PointStat); i { + switch v := v.(*ObservationAttribute); i { case 0: return &v.state case 1: @@ -2078,7 +2155,7 @@ func file_stat_proto_init() { } } file_stat_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SourceSeries); i { + switch v := v.(*PointStat); i { case 0: return &v.state case 1: @@ -2090,7 +2167,7 @@ func file_stat_proto_init() { } } file_stat_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Series); i { + switch v := v.(*SourceSeries); i { case 0: return &v.state case 1: @@ -2102,7 +2179,7 @@ func file_stat_proto_init() { } } file_stat_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ObsTimeSeries); i { + switch v := v.(*Series); i { case 0: return &v.state case 1: @@ -2114,7 +2191,7 @@ func file_stat_proto_init() { } } file_stat_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ObsCollection); i { + switch v := v.(*ObsTimeSeries); i { case 0: return &v.state case 1: @@ -2126,7 +2203,7 @@ func file_stat_proto_init() { } } file_stat_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChartStore); i { + switch v := v.(*ObsCollection); i { case 0: return &v.state case 1: @@ -2138,7 +2215,7 @@ func file_stat_proto_init() { } } file_stat_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PlaceStat); i { + switch v := v.(*ChartStore); i { case 0: return &v.state case 1: @@ -2150,7 +2227,7 @@ func file_stat_proto_init() { } } file_stat_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StatVarSeries); i { + switch v := v.(*PlaceStat); i { case 0: return &v.state case 1: @@ -2162,7 +2239,7 @@ func file_stat_proto_init() { } } file_stat_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetStatsRequest); i { + switch v := v.(*StatVarSeries); i { case 0: return &v.state case 1: @@ -2174,7 +2251,7 @@ func file_stat_proto_init() { } } file_stat_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetStatsResponse); i { + switch v := v.(*GetStatsRequest); i { case 0: return &v.state case 1: @@ -2186,7 +2263,7 @@ func file_stat_proto_init() { } } file_stat_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetStatValueRequest); i { + switch v := v.(*GetStatsResponse); i { case 0: return &v.state case 1: @@ -2198,7 +2275,7 @@ func file_stat_proto_init() { } } file_stat_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetStatValueResponse); i { + switch v := v.(*GetStatValueRequest); i { case 0: return &v.state case 1: @@ -2210,7 +2287,7 @@ func file_stat_proto_init() { } } file_stat_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetStatSeriesRequest); i { + switch v := v.(*GetStatValueResponse); i { case 0: return &v.state case 1: @@ -2222,7 +2299,7 @@ func file_stat_proto_init() { } } file_stat_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetStatSeriesResponse); i { + switch v := v.(*GetStatSeriesRequest); i { case 0: return &v.state case 1: @@ -2234,7 +2311,7 @@ func file_stat_proto_init() { } } file_stat_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetStatAllRequest); i { + switch v := v.(*GetStatSeriesResponse); i { case 0: return &v.state case 1: @@ -2246,7 +2323,7 @@ func file_stat_proto_init() { } } file_stat_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetStatAllResponse); i { + switch v := v.(*GetStatAllRequest); i { case 0: return &v.state case 1: @@ -2258,7 +2335,7 @@ func file_stat_proto_init() { } } file_stat_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StatDate); i { + switch v := v.(*GetStatAllResponse); i { case 0: return &v.state case 1: @@ -2270,6 +2347,18 @@ func file_stat_proto_init() { } } file_stat_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StatDate); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_stat_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*StatDateList); i { case 0: return &v.state @@ -2281,7 +2370,7 @@ func file_stat_proto_init() { return nil } } - file_stat_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { + file_stat_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ObsCollection_PlaceTypes); i { case 0: return &v.state @@ -2294,19 +2383,19 @@ func file_stat_proto_init() { } } } - file_stat_proto_msgTypes[4].OneofWrappers = []interface{}{} - file_stat_proto_msgTypes[9].OneofWrappers = []interface{}{ + file_stat_proto_msgTypes[5].OneofWrappers = []interface{}{} + file_stat_proto_msgTypes[10].OneofWrappers = []interface{}{ (*ChartStore_ObsTimeSeries)(nil), (*ChartStore_ObsCollection)(nil), } - file_stat_proto_msgTypes[15].OneofWrappers = []interface{}{} + file_stat_proto_msgTypes[16].OneofWrappers = []interface{}{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_stat_proto_rawDesc, NumEnums: 0, - NumMessages: 32, + NumMessages: 33, NumExtensions: 0, NumServices: 0, }, diff --git a/internal/server/spanner/dsutil.go b/internal/server/spanner/dsutil.go index 4c071ca84..5d4b39746 100644 --- a/internal/server/spanner/dsutil.go +++ b/internal/server/spanner/dsutil.go @@ -625,11 +625,26 @@ func dateValueToPointStat(dateValue *DateValue) (*pb.PointStat, error) { return nil, fmt.Errorf("failed to decode TimeSeries float value: (%v) for date: (%v)", floatVal, dateValue.Date) } return &pb.PointStat{ - Date: dateValue.Date, - Value: proto.Float64(floatVal), + Date: dateValue.Date, + Value: proto.Float64(floatVal), + ObservationAttributes: observationAttributesToProto(dateValue.Attributes), }, nil } +func observationAttributesToProto(attributes []*spannerAttribute) []*pb.ObservationAttribute { + if len(attributes) == 0 { + return nil + } + result := make([]*pb.ObservationAttribute, 0, len(attributes)) + for _, attr := range attributes { + result = append(result, &pb.ObservationAttribute{ + Property: attr.Property, + Value: attr.Value, + }) + } + return result +} + func searchNodesToNodeSearchResponse(nodes []*SearchNode) *pbv2.NodeSearchResponse { response := &pbv2.NodeSearchResponse{} diff --git a/internal/server/spanner/model.go b/internal/server/spanner/model.go index 8fe5c9e35..89139393b 100644 --- a/internal/server/spanner/model.go +++ b/internal/server/spanner/model.go @@ -66,8 +66,9 @@ type Observation struct { // Single observation in a time series. // Value is a string to allow series with non-numeric types. type DateValue struct { - Date string - Value string + Date string + Value string + Attributes []*spannerAttribute } type TimeSeries []*DateValue diff --git a/internal/server/spanner/model_normalized.go b/internal/server/spanner/model_normalized.go index b49caf841..bab293384 100644 --- a/internal/server/spanner/model_normalized.go +++ b/internal/server/spanner/model_normalized.go @@ -38,8 +38,9 @@ type multiEntityObservation struct { // spannerObservation represents the STRUCT returned in dates_and_values array. type spannerObservation struct { - Date string `spanner:"date"` - Value string `spanner:"value"` + Date string `spanner:"date"` + Value string `spanner:"value"` + Attributes []*spannerAttribute `spanner:"attributes"` } // spannerAttribute represents the STRUCT returned in attributes array. diff --git a/internal/server/spanner/multi_entity_observation.go b/internal/server/spanner/multi_entity_observation.go index 7d1b148f2..c4fddb11c 100644 --- a/internal/server/spanner/multi_entity_observation.go +++ b/internal/server/spanner/multi_entity_observation.go @@ -18,11 +18,13 @@ import ( "context" "fmt" "sort" + "strconv" "strings" pb "github.com/datacommonsorg/mixer/internal/proto" pbv2 "github.com/datacommonsorg/mixer/internal/proto/v2" "github.com/datacommonsorg/mixer/internal/server/datasources" + "github.com/datacommonsorg/mixer/internal/util" ) type multiEntityObservationClient interface { @@ -152,7 +154,8 @@ func multiEntityObservationsToResponse( } for _, observation := range observations { - facetObservation := multiEntityFacetObservation(observation) + facetID, facet := multiEntityFacet(observation) + facetObservation := multiEntityFacetObservation(observation, facetID) if facetObservation == nil { continue } @@ -172,15 +175,13 @@ func multiEntityObservationsToResponse( }, }, ) - if observation.Provenance != "" { - resp.Facets[observation.Provenance] = &pb.Facet{ImportName: observation.Provenance} - } + resp.Facets[facetID] = facet } return resp } -func multiEntityFacetObservation(observation *multiEntityObservation) *pbv2.FacetObservation { +func multiEntityFacetObservation(observation *multiEntityObservation, facetID string) *pbv2.FacetObservation { pointStats := []*pb.PointStat{} for _, dateValue := range observation.Observations { pointStat, err := dateValueToPointStat(dateValue) @@ -196,7 +197,7 @@ func multiEntityFacetObservation(observation *multiEntityObservation) *pbv2.Face return pointStats[i].GetDate() < pointStats[j].GetDate() }) return &pbv2.FacetObservation{ - FacetId: observation.Provenance, + FacetId: facetID, Observations: pointStats, ObsCount: int32(len(pointStats)), EarliestDate: pointStats[0].GetDate(), @@ -204,6 +205,39 @@ func multiEntityFacetObservation(observation *multiEntityObservation) *pbv2.Face } } +func multiEntityFacet(observation *multiEntityObservation) (string, *pb.Facet) { + facetID := "" + facet := &pb.Facet{ + ImportName: observation.Provenance, + } + for _, attr := range observation.Attributes { + switch attr.Property { + case "facetId": + facetID = attr.Value + case "importName": + facet.ImportName = attr.Value + case "provenanceUrl": + facet.ProvenanceUrl = attr.Value + case "observationPeriod": + facet.ObservationPeriod = attr.Value + case "measurementMethod": + facet.MeasurementMethod = attr.Value + case "unit": + facet.Unit = attr.Value + case "scalingFactor": + facet.ScalingFactor = attr.Value + case "isDcAggregate": + if b, err := strconv.ParseBool(attr.Value); err == nil { + facet.IsDcAggregate = b + } + } + } + if facetID == "" { + facetID = util.GetFacetID(facet) + } + return facetID, facet +} + func multiEntityObservationDimensions(attributes []*spannerAttribute) []*pbv2.ObservationDimension { dimensions := []*pbv2.ObservationDimension{} for _, attr := range attributes { diff --git a/internal/server/spanner/multi_entity_observation_test.go b/internal/server/spanner/multi_entity_observation_test.go index bfff14985..65a3c9024 100644 --- a/internal/server/spanner/multi_entity_observation_test.go +++ b/internal/server/spanner/multi_entity_observation_test.go @@ -18,7 +18,9 @@ import ( "strings" "testing" + pb "github.com/datacommonsorg/mixer/internal/proto" pbv2 "github.com/datacommonsorg/mixer/internal/proto/v2" + "github.com/datacommonsorg/mixer/internal/util" ) func TestValidateMultiEntityObservationRequest(t *testing.T) { @@ -121,11 +123,18 @@ func TestMultiEntityObservationsToResponse(t *testing.T) { Attributes: []*spannerAttribute{ {Property: "recipient", Value: "country/GHA"}, {Property: "unit", Value: "dcs:USDollar"}, + {Property: "measurementMethod", Value: "dcid:OECD_UnvalidatedValue"}, {Property: "donor", Value: "country/IRL"}, }, Observations: TimeSeries{ {Date: "1998", Value: "520000"}, - {Date: "1997", Value: "510000"}, + { + Date: "1997", + Value: "510000", + Attributes: []*spannerAttribute{ + {Property: "footnote", Value: "Preliminary data for early Q1"}, + }, + }, }, }, }, @@ -143,14 +152,44 @@ func TestMultiEntityObservationsToResponse(t *testing.T) { t.Fatalf("observation_dimensions = %v, want donor then recipient", obs.GetObservationDimensions()) } + wantFacet := &pb.Facet{ + ImportName: "provenance/OECD", + MeasurementMethod: "dcid:OECD_UnvalidatedValue", + Unit: "dcs:USDollar", + } + wantFacetID := util.GetFacetID(wantFacet) facet := obs.GetObservation().GetOrderedFacets()[0] - if facet.GetFacetId() != "provenance/OECD" { - t.Fatalf("facet_id = %q, want provenance/OECD", facet.GetFacetId()) + if facet.GetFacetId() != wantFacetID { + t.Fatalf("facet_id = %q, want %q", facet.GetFacetId(), wantFacetID) } if facet.GetEarliestDate() != "1997" || facet.GetLatestDate() != "1998" { t.Fatalf("date range = %s-%s, want 1997-1998", facet.GetEarliestDate(), facet.GetLatestDate()) } - if resp.GetFacets()["provenance/OECD"].GetImportName() != "provenance/OECD" { - t.Fatalf("facet import_name = %q, want provenance/OECD", resp.GetFacets()["provenance/OECD"].GetImportName()) + if resp.GetFacets()[wantFacetID].GetUnit() != "dcs:USDollar" { + t.Fatalf("facet unit = %q, want dcs:USDollar", resp.GetFacets()[wantFacetID].GetUnit()) + } + if resp.GetFacets()[wantFacetID].GetMeasurementMethod() != "dcid:OECD_UnvalidatedValue" { + t.Fatalf("facet measurement_method = %q, want dcid:OECD_UnvalidatedValue", resp.GetFacets()[wantFacetID].GetMeasurementMethod()) + } + pointAttrs := facet.GetObservations()[0].GetObservationAttributes() + if len(pointAttrs) != 1 || pointAttrs[0].GetProperty() != "footnote" { + t.Fatalf("point attributes = %v, want footnote", pointAttrs) + } +} + +func TestMultiEntityFacetPrefersStoredFacetID(t *testing.T) { + facetID, facet := multiEntityFacet(&multiEntityObservation{ + Provenance: "provenance/OECD", + Attributes: []*spannerAttribute{ + {Property: "facetId", Value: "storedFacet"}, + {Property: "unit", Value: "dcs:USDollar"}, + }, + }) + + if facetID != "storedFacet" { + t.Fatalf("facetID = %q, want storedFacet", facetID) + } + if facet.GetUnit() != "dcs:USDollar" { + t.Fatalf("facet unit = %q, want dcs:USDollar", facet.GetUnit()) } } diff --git a/internal/server/spanner/query_normalized.go b/internal/server/spanner/query_normalized.go index 4698292fd..ff4fa5aea 100644 --- a/internal/server/spanner/query_normalized.go +++ b/internal/server/spanner/query_normalized.go @@ -85,7 +85,11 @@ func reconstructObservations(rawObs []*rawObservation) []*Observation { } for _, dv := range r.DatesAndValues { - obs.Observations = append(obs.Observations, &DateValue{Date: dv.Date, Value: dv.Value}) + obs.Observations = append(obs.Observations, &DateValue{ + Date: dv.Date, + Value: dv.Value, + Attributes: dv.Attributes, + }) } for _, attr := range r.Attributes { @@ -122,7 +126,11 @@ func reconstructMultiEntityObservations(rawObs []*rawMultiEntityObservation) []* } for _, dv := range r.DatesAndValues { - obs.Observations = append(obs.Observations, &DateValue{Date: dv.Date, Value: dv.Value}) + obs.Observations = append(obs.Observations, &DateValue{ + Date: dv.Date, + Value: dv.Value, + Attributes: dv.Attributes, + }) } result = append(result, obs) } diff --git a/internal/server/spanner/statements_normalized.go b/internal/server/spanner/statements_normalized.go index 5de7edccb..935fc77c0 100644 --- a/internal/server/spanner/statements_normalized.go +++ b/internal/server/spanner/statements_normalized.go @@ -44,10 +44,19 @@ var statementsNormalized = struct { getObs: ` SELECT ts.variable_measured, ARRAY( - SELECT STRUCT(date, value) - FROM StatVarObservation - WHERE id = ts.id - ORDER BY date ASC + SELECT STRUCT( + svo.date AS date, + svo.value AS value, + ARRAY( + SELECT STRUCT(oa.property, oa.value) + FROM ObservationAttribute oa + WHERE oa.id = svo.id AND oa.date = svo.date + ORDER BY oa.property ASC, oa.value ASC + ) AS attributes + ) + FROM StatVarObservation svo + WHERE svo.id = ts.id + ORDER BY svo.date ASC ) as dates_and_values, ARRAY( SELECT STRUCT(property, value) @@ -92,10 +101,19 @@ var statementsNormalized = struct { getObsByContainedInPlace: ` SELECT ts.variable_measured, ARRAY( - SELECT STRUCT(date, value) - FROM StatVarObservation - WHERE id = ts.id - ORDER BY date ASC + SELECT STRUCT( + svo.date AS date, + svo.value AS value, + ARRAY( + SELECT STRUCT(oa.property, oa.value) + FROM ObservationAttribute oa + WHERE oa.id = svo.id AND oa.date = svo.date + ORDER BY oa.property ASC, oa.value ASC + ) AS attributes + ) + FROM StatVarObservation svo + WHERE svo.id = ts.id + ORDER BY svo.date ASC ) as dates_and_values, ARRAY( SELECT STRUCT(property, value) @@ -124,10 +142,19 @@ var statementsNormalized = struct { ts.variable_measured, ts.provenance, ARRAY( - SELECT STRUCT(date, value) - FROM StatVarObservation - WHERE id = ts.id - ORDER BY date ASC + SELECT STRUCT( + svo.date AS date, + svo.value AS value, + ARRAY( + SELECT STRUCT(oa.property, oa.value) + FROM ObservationAttribute oa + WHERE oa.id = svo.id AND oa.date = svo.date + ORDER BY oa.property ASC, oa.value ASC + ) AS attributes + ) + FROM StatVarObservation svo + WHERE svo.id = ts.id + ORDER BY svo.date ASC ) as dates_and_values, ARRAY( SELECT STRUCT(property, value) diff --git a/proto/stat.proto b/proto/stat.proto index ad1a1e897..7f2008862 100644 --- a/proto/stat.proto +++ b/proto/stat.proto @@ -65,6 +65,11 @@ message PlaceVariableFacets { repeated PlaceVariableFacet place_variable_facets = 1; } +message ObservationAttribute { + string property = 1; + string value = 2; +} + message PointStat { // date in IOS-8601 format string date = 1; @@ -78,6 +83,8 @@ message PointStat { string meta_hash = 4; // Same as meta_hash, used in V1 API. string facet = 5; + // Attributes scoped to this observation point. + repeated ObservationAttribute observation_attributes = 6; } message SourceSeries {