go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cv/internal/run/runquery/cl.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 runquery 16 17 import ( 18 "context" 19 "fmt" 20 21 "go.chromium.org/luci/common/errors" 22 "go.chromium.org/luci/common/retry/transient" 23 "go.chromium.org/luci/gae/service/datastore" 24 25 "go.chromium.org/luci/cv/internal/common" 26 "go.chromium.org/luci/cv/internal/run" 27 ) 28 29 // CLQueryBuilder builds datastore.Query for searching Runs of a given CL. 30 type CLQueryBuilder struct { 31 // CLID of the CL being searched for. Required. 32 CLID common.CLID 33 // Optional extra CLs that must be included. 34 AdditionalCLIDs common.CLIDsSet 35 // Project optionally restricts Runs to the given LUCI project. 36 Project string 37 // MaxExcl restricts query to Runs with ID lexicographically smaller. 38 // 39 // This means query will return union of: 40 // * all Runs created after this Run in the same project, 41 // * all Runs in lexicographically smaller projects, 42 // unless .Project is set to the same project (recommended). 43 MaxExcl common.RunID 44 // MinExcl restricts query to Runs with ID lexicographically larger. 45 // 46 // This means query will return union of: 47 // * all Runs created before this Run in the same project, 48 // * all Runs in lexicographically larger projects, 49 // unless .Project is set to the same project (recommended). 50 MinExcl common.RunID 51 52 // Limit limits the number of results if positive. Ignored otherwise. 53 Limit int32 54 } 55 56 // isSatisfied returns whether the given Run satisfies the query. 57 func (b CLQueryBuilder) isSatisfied(r *run.Run) bool { 58 // If there are additional CLs that must be included, 59 // check whether all of these are indeed included. 60 if len(b.AdditionalCLIDs) > 0 { 61 count := 0 62 for _, clid := range r.CLs { 63 if b.AdditionalCLIDs.Has(clid) { 64 count += 1 65 } 66 } 67 if count != len(b.AdditionalCLIDs) { 68 return false 69 } 70 } 71 switch { 72 case r == nil: 73 case b.Project != "" && r.ID.LUCIProject() != b.Project: 74 case b.MinExcl != "" && r.ID <= b.MinExcl: 75 case b.MaxExcl != "" && r.ID >= b.MaxExcl: 76 default: 77 return true 78 } 79 return false 80 } 81 82 // AfterInProject constrains CLQueryBuilder to Runs created after this Run but 83 // belonging to the same LUCI project. 84 // 85 // Panics if CLQueryBuilder is already constrained to a different LUCI Project. 86 func (b CLQueryBuilder) AfterInProject(id common.RunID) CLQueryBuilder { 87 if p := id.LUCIProject(); p != b.Project { 88 if b.Project != "" { 89 panic(fmt.Errorf("invalid CLQueryBuilder.AfterInProject(%q): .Project is already set to %q", id, b.Project)) 90 } 91 b.Project = p 92 } 93 b.MaxExcl = id 94 return b 95 } 96 97 // BeforeInProject constrains CLQueryBuilder to Runs created before this Run but 98 // belonging to the same LUCI project. 99 // 100 // Panics if CLQueryBuilder is already constrained to a different LUCI Project. 101 func (b CLQueryBuilder) BeforeInProject(id common.RunID) CLQueryBuilder { 102 if p := id.LUCIProject(); p != b.Project { 103 if b.Project != "" { 104 panic(fmt.Errorf("invalid CLQueryBuilder.BeforeInProject(%q): .Project is already set to %q", id, b.Project)) 105 } 106 b.Project = p 107 } 108 b.MinExcl = id 109 return b 110 } 111 112 // PageToken constraints CLQueryBuilder to continue searching from the prior 113 // search. 114 func (b CLQueryBuilder) PageToken(pt *PageToken) CLQueryBuilder { 115 if pt != nil { 116 b.MinExcl = common.RunID(pt.GetRun()) 117 } 118 return b 119 } 120 121 // BuildKeysOnly returns keys-only query on RunCL entities. 122 // 123 // It's exposed primarily for debugging reasons. 124 func (b CLQueryBuilder) BuildKeysOnly(ctx context.Context) *datastore.Query { 125 q := datastore.NewQuery(run.RunCLKind).Eq("IndexedID", b.CLID).KeysOnly(true) 126 127 if b.Limit > 0 { 128 q = q.Limit(b.Limit) 129 } 130 131 min := string(b.MinExcl) 132 max := string(b.MaxExcl) 133 if b.Project != "" { 134 prMin, prMax := rangeOfProjectIDs(b.Project) 135 if min == "" || min < prMin { 136 min = prMin 137 } 138 if max == "" || max > prMax { 139 max = prMax 140 } 141 } 142 if min != "" { 143 q = q.Gt("__key__", datastore.MakeKey(ctx, common.RunKind, min, run.RunCLKind, int64(b.CLID))) 144 } 145 if max != "" { 146 q = q.Lt("__key__", datastore.MakeKey(ctx, common.RunKind, max, run.RunCLKind, int64(b.CLID))) 147 } 148 149 return q 150 } 151 152 // GetAllRunKeys runs the query across all matched RunCLs entities and returns 153 // Datastore keys to corresponding Run entities. 154 func (b CLQueryBuilder) GetAllRunKeys(ctx context.Context) ([]*datastore.Key, error) { 155 // Fetch RunCL keys. 156 var keys []*datastore.Key 157 if err := datastore.GetAll(ctx, b.BuildKeysOnly(ctx), &keys); err != nil { 158 return nil, errors.Annotate(err, "failed to fetch RunCLs IDs").Tag(transient.Tag).Err() 159 } 160 161 // Replace each RunCL key with its parent (Run) key. 162 for i := range keys { 163 keys[i] = keys[i].Parent() 164 } 165 return keys, nil 166 } 167 168 // LoadRuns returns matched Runs and the page token to continue search later. 169 func (b CLQueryBuilder) LoadRuns(ctx context.Context, checkers ...run.LoadRunChecker) ([]*run.Run, *PageToken, error) { 170 return loadRunsFromQuery(ctx, b, checkers...) 171 } 172 173 // qLimit implements runKeysQuery interface. 174 func (b CLQueryBuilder) qLimit() int32 { return b.Limit } 175 176 // qPageToken implements runKeysQuery interface. 177 func (b CLQueryBuilder) qPageToken(pt *PageToken) runKeysQuery { return b.PageToken(pt) }