go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/resultdb/internal/exonerations/query.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 exonerations 16 17 import ( 18 "context" 19 20 "cloud.google.com/go/spanner" 21 22 "go.chromium.org/luci/common/proto/mask" 23 24 "go.chromium.org/luci/resultdb/internal/invocations" 25 "go.chromium.org/luci/resultdb/internal/pagination" 26 "go.chromium.org/luci/resultdb/internal/spanutil" 27 "go.chromium.org/luci/resultdb/pbutil" 28 pb "go.chromium.org/luci/resultdb/proto/v1" 29 ) 30 31 // LimitedFields is a field mask for TestExoneration to use when the caller 32 // only has the listLimited permission for test exonerations. 33 var limitedFields = mask.MustFromReadMask(&pb.TestExoneration{}, 34 "name", 35 "test_id", 36 "exoneration_id", 37 "variant_hash", 38 "explanation_html", 39 "reason", 40 ) 41 42 // Query specifies test exonerations to fetch. 43 type Query struct { 44 InvocationIDs invocations.IDSet 45 Predicate *pb.TestExonerationPredicate 46 PageSize int // must be positive 47 PageToken string 48 } 49 50 // Fetch returns a page test of exonerations matching the query. 51 // Returned test exonerations are ordered by invocation ID, test ID and 52 // exoneration ID. 53 func (q *Query) Fetch(ctx context.Context) (tes []*pb.TestExoneration, nextPageToken string, err error) { 54 if q.PageSize <= 0 { 55 panic("PageSize <= 0") 56 } 57 58 st := spanner.NewStatement(` 59 SELECT InvocationId, TestId, ExonerationId, Variant, VariantHash, ExplanationHtml, Reason 60 FROM TestExonerations 61 WHERE InvocationId IN UNNEST(@invIDs) 62 # Skip test exonerations after the one specified in the page token. 63 AND ( 64 (InvocationId > @afterInvocationId) OR 65 (InvocationId = @afterInvocationId AND TestId > @afterTestId) OR 66 (InvocationId = @afterInvocationId AND TestId = @afterTestId AND ExonerationID > @afterExonerationID) 67 ) 68 ORDER BY InvocationId, TestId, ExonerationId 69 LIMIT @limit 70 `) 71 st.Params["invIDs"] = q.InvocationIDs 72 st.Params["limit"] = q.PageSize 73 err = invocations.TokenToMap(q.PageToken, st.Params, "afterInvocationId", "afterTestId", "afterExonerationID") 74 if err != nil { 75 return 76 } 77 78 // TODO(nodir): add support for q.Predicate.TestId. 79 // TODO(nodir): add support for q.Predicate.Variant. 80 81 tes = make([]*pb.TestExoneration, 0, q.PageSize) 82 var b spanutil.Buffer 83 var explanationHTML spanutil.Compressed 84 err = spanutil.Query(ctx, st, func(row *spanner.Row) error { 85 var invID invocations.ID 86 ex := &pb.TestExoneration{} 87 err := b.FromSpanner(row, &invID, &ex.TestId, &ex.ExonerationId, &ex.Variant, &ex.VariantHash, &explanationHTML, &ex.Reason) 88 if err != nil { 89 return err 90 } 91 ex.Name = pbutil.TestExonerationName(string(invID), ex.TestId, ex.ExonerationId) 92 ex.ExplanationHtml = string(explanationHTML) 93 tes = append(tes, ex) 94 return nil 95 }) 96 if err != nil { 97 tes = nil 98 return 99 } 100 101 // If we got pageSize results, then we haven't exhausted the collection and 102 // need to return the next page token. 103 if len(tes) == q.PageSize { 104 last := tes[q.PageSize-1] 105 invID, testID, exID := MustParseName(last.Name) 106 nextPageToken = pagination.Token(string(invID), testID, exID) 107 } 108 return 109 } 110 111 // ToLimitedData limits the given TestExoneration to the fields allowed when 112 // the caller only has the listLimited permission for test exonerations. 113 func ToLimitedData(ctx context.Context, te *pb.TestExoneration) error { 114 if err := limitedFields.Trim(te); err != nil { 115 return err 116 } 117 118 te.IsMasked = true 119 120 return nil 121 }