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) }