go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/resultdb/internal/spanutil/buffer.go (about) 1 // Copyright 2020 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package spanutil 16 17 import ( 18 "fmt" 19 "reflect" 20 21 "cloud.google.com/go/spanner" 22 "github.com/golang/protobuf/proto" 23 "google.golang.org/protobuf/types/known/timestamppb" 24 25 "go.chromium.org/luci/common/errors" 26 "go.chromium.org/luci/resultdb/pbutil" 27 pb "go.chromium.org/luci/resultdb/proto/v1" 28 ) 29 30 // Value can be converted to a Spanner value. 31 // Typically if type T implements Value, then *T implements Ptr. 32 type Value interface { 33 // ToSpanner returns a value of a type supported by Spanner client. 34 ToSpanner() any 35 } 36 37 // Ptr can be used a destination of reading a Spanner cell. 38 // Typically if type *T implements Ptr, then T implements Value. 39 type Ptr interface { 40 // SpannerPtr returns to a pointer of a type supported by Spanner client. 41 // SpannerPtr must use one of typed buffers in b. 42 SpannerPtr(b *Buffer) any 43 // FromSpanner replaces Ptr value with the value in the typed buffer returned 44 // by SpannerPtr. 45 FromSpanner(b *Buffer) error 46 } 47 48 // Buffer can convert a value from a Spanner type to a Go type. 49 // Supported types: 50 // - Value and Ptr 51 // - string 52 // - timestamppb.Timestamp 53 // - pb.BigQueryExport 54 // - pb.ExonerationReason 55 // - pb.InvocationState 56 // - pb.TestStatus 57 // - pb.Variant 58 // - pb.StringPair 59 // - proto.Message 60 // 61 // TODO(nodir): move to buffer.go 62 type Buffer struct { 63 NullString spanner.NullString 64 NullTime spanner.NullTime 65 Int64 int64 66 StringSlice []string 67 ByteSlice []byte 68 ByteSlice2 [][]byte 69 } 70 71 // FromSpanner is a shortcut for (&Buffer{}).FromSpanner. 72 // Appropriate when FromSpanner is called only once, whereas Buffer is reusable 73 // throughout function. 74 func FromSpanner(row *spanner.Row, ptrs ...any) error { 75 return (&Buffer{}).FromSpanner(row, ptrs...) 76 } 77 78 // FromSpanner reads values from row to ptrs, converting types from Spanner 79 // to Go along the way. 80 func (b *Buffer) FromSpanner(row *spanner.Row, ptrs ...any) error { 81 if len(ptrs) != row.Size() { 82 panic("len(ptrs) != row.Size()") 83 } 84 85 for i, goPtr := range ptrs { 86 if err := b.fromSpanner(row, i, goPtr); err != nil { 87 return err 88 } 89 } 90 return nil 91 } 92 93 func (b *Buffer) fromSpanner(row *spanner.Row, col int, goPtr any) error { 94 b.StringSlice = b.StringSlice[:0] 95 b.ByteSlice = b.ByteSlice[:0] 96 b.ByteSlice2 = b.ByteSlice2[:0] 97 98 var spanPtr any 99 switch goPtr := goPtr.(type) { 100 case Ptr: 101 spanPtr = goPtr.SpannerPtr(b) 102 case *string: 103 spanPtr = &b.NullString 104 case **timestamppb.Timestamp: 105 spanPtr = &b.NullTime 106 case *pb.ExonerationReason: 107 spanPtr = &b.Int64 108 case *pb.TestStatus: 109 spanPtr = &b.Int64 110 case *pb.SkipReason: 111 spanPtr = &b.Int64 112 case *pb.Invocation_State: 113 spanPtr = &b.Int64 114 case **pb.Variant: 115 spanPtr = &b.StringSlice 116 case *[]*pb.StringPair: 117 spanPtr = &b.StringSlice 118 case *[]*pb.BigQueryExport: 119 spanPtr = &b.ByteSlice2 120 case proto.Message: 121 spanPtr = &b.ByteSlice 122 default: 123 spanPtr = goPtr 124 } 125 126 if err := row.Column(col, spanPtr); err != nil { 127 return errors.Annotate(err, "failed to read column %q", row.ColumnName(col)).Err() 128 } 129 130 if spanPtr == goPtr { 131 return nil 132 } 133 134 var err error 135 switch goPtr := goPtr.(type) { 136 case Ptr: 137 if err := goPtr.FromSpanner(b); err != nil { 138 return err 139 } 140 141 case *string: 142 *goPtr = "" 143 if b.NullString.Valid { 144 *goPtr = b.NullString.StringVal 145 } 146 147 case **timestamppb.Timestamp: 148 *goPtr = nil 149 if b.NullTime.Valid { 150 *goPtr = pbutil.MustTimestampProto(b.NullTime.Time) 151 } 152 153 case *pb.ExonerationReason: 154 *goPtr = pb.ExonerationReason(b.Int64) 155 156 case *pb.Invocation_State: 157 *goPtr = pb.Invocation_State(b.Int64) 158 159 case *pb.TestStatus: 160 *goPtr = pb.TestStatus(b.Int64) 161 162 case *pb.SkipReason: 163 *goPtr = pb.SkipReason(b.Int64) 164 165 case **pb.Variant: 166 if *goPtr, err = pbutil.VariantFromStrings(b.StringSlice); err != nil { 167 // If it was written to Spanner, it should have been validated. 168 panic(err) 169 } 170 171 case *[]*pb.StringPair: 172 *goPtr = make([]*pb.StringPair, len(b.StringSlice)) 173 for i, p := range b.StringSlice { 174 if (*goPtr)[i], err = pbutil.StringPairFromString(p); err != nil { 175 // If it was written to Spanner, it should have been validated. 176 panic(err) 177 } 178 } 179 180 case *[]*pb.BigQueryExport: 181 *goPtr = make([]*pb.BigQueryExport, len(b.ByteSlice2)) 182 for i, p := range b.ByteSlice2 { 183 (*goPtr)[i] = &pb.BigQueryExport{} 184 if err := proto.Unmarshal(p, (*goPtr)[i]); err != nil { 185 // If it was written to Spanner, it should have been validated. 186 panic(err) 187 } 188 } 189 190 case proto.Message: 191 if reflect.ValueOf(goPtr).IsNil() { 192 return errors.Reason("nil pointer encountered").Err() 193 } 194 if err := proto.Unmarshal(b.ByteSlice, goPtr); err != nil { 195 // If it was written to Spanner, it should have been validated. 196 panic(err) 197 } 198 199 default: 200 panic(fmt.Sprintf("impossible %q", goPtr)) 201 } 202 return nil 203 } 204 205 // ToSpanner converts values from Go types to Spanner types. In addition to 206 // supported types in FromSpanner, also supports []any and 207 // map[string]any. 208 func ToSpanner(v any) any { 209 switch v := v.(type) { 210 case Value: 211 return v.ToSpanner() 212 213 case *timestamppb.Timestamp: 214 if v == nil { 215 return spanner.NullTime{} 216 } 217 ret := spanner.NullTime{Valid: true} 218 ret.Time = v.AsTime() 219 // ptypes.Timestamp always returns a timestamp, even 220 // if the returned err is non-nil, see its documentation. 221 // The error is returned only if the timestamp violates its 222 // own invariants, which will be caught on the attempt to 223 // insert this value into Spanner. 224 // This is consistent with the behavior of spanner.Insert() and 225 // other mutating functions that ignore invalid time.Time 226 // until it is time to apply the mutation. 227 // Not returning an error here significantly simplifies usage 228 // of this function and functions based on this one. 229 return ret 230 231 case pb.ExonerationReason: 232 return int64(v) 233 234 case pb.Invocation_State: 235 return int64(v) 236 237 case pb.TestStatus: 238 return int64(v) 239 240 case pb.SkipReason: 241 return int64(v) 242 243 case *pb.Variant: 244 return pbutil.VariantToStrings(v) 245 246 case []*pb.StringPair: 247 return pbutil.StringPairsToStrings(v...) 248 249 case []*pb.BigQueryExport: 250 var err error 251 bqExportsBytes := make([][]byte, len(v)) 252 for i, bqExport := range v { 253 if bqExportsBytes[i], err = proto.Marshal(bqExport); err != nil { 254 panic(err) 255 } 256 } 257 return bqExportsBytes 258 259 case proto.Message: 260 if isMessageNil(v) { 261 // Do not store empty messages. 262 return []byte(nil) 263 } 264 265 ret, err := proto.Marshal(v) 266 if err != nil { 267 panic(err) 268 } 269 return ret 270 271 case []any: 272 ret := make([]any, len(v)) 273 for i, el := range v { 274 ret[i] = ToSpanner(el) 275 } 276 return ret 277 278 case map[string]any: 279 ret := make(map[string]any, len(v)) 280 for key, el := range v { 281 ret[key] = ToSpanner(el) 282 } 283 return ret 284 285 default: 286 return v 287 } 288 } 289 290 // ToSpannerSlice converts a slice of Go values to a slice of Spanner values. 291 // See also ToSpanner. 292 func ToSpannerSlice(values ...any) []any { 293 return ToSpanner(values).([]any) 294 } 295 296 // ToSpannerMap converts a map of Go values to a map of Spanner values. 297 // See also ToSpanner. 298 func ToSpannerMap(values map[string]any) map[string]any { 299 return ToSpanner(values).(map[string]any) 300 } 301 302 // UpdateMap is a shortcut for spanner.UpdateMap with ToSpannerMap applied to 303 // in. 304 func UpdateMap(table string, in map[string]any) *spanner.Mutation { 305 return spanner.UpdateMap(table, ToSpannerMap(in)) 306 } 307 308 // InsertMap is a shortcut for spanner.InsertMap with ToSpannerMap applied to 309 // in. 310 func InsertMap(table string, in map[string]any) *spanner.Mutation { 311 return spanner.InsertMap(table, ToSpannerMap(in)) 312 } 313 314 // InsertOrUpdateMap is a shortcut for spanner.InsertOrUpdateMap with ToSpannerMap applied to 315 // in. 316 func InsertOrUpdateMap(table string, in map[string]any) *spanner.Mutation { 317 return spanner.InsertOrUpdateMap(table, ToSpannerMap(in)) 318 }