golang.org/x/build@v0.0.0-20240506185731-218518f32b70/perfdata/db/query.go (about) 1 // Copyright 2017 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package db 6 7 import ( 8 "fmt" 9 "io" 10 "strings" 11 "unicode" 12 ) 13 14 // operation is the enum for possible query operations. 15 type operation rune 16 17 // Query operations. 18 // Only equals, lt, and gt can be specified in a query. 19 // The order of these operations is used by merge. 20 const ( 21 equals operation = iota 22 ltgt 23 lt 24 gt 25 ) 26 27 // A part is a single query part with a key, operator, and value. 28 type part struct { 29 key string 30 operator operation 31 // value and value2 hold the values to compare against. 32 value, value2 string 33 } 34 35 // sepToOperation maps runes to operation values. 36 var sepToOperation = map[byte]operation{ 37 ':': equals, 38 '<': lt, 39 '>': gt, 40 } 41 42 // parseWord parse a single query part (as returned by SplitWords with quoting and escaping already removed) into a part struct. 43 func parseWord(word string) (part, error) { 44 sepIndex := strings.IndexFunc(word, func(r rune) bool { 45 return r == ':' || r == '>' || r == '<' || unicode.IsSpace(r) || unicode.IsUpper(r) 46 }) 47 if sepIndex < 0 { 48 return part{}, fmt.Errorf("query part %q is missing operator", word) 49 } 50 key, sep, value := word[:sepIndex], word[sepIndex], word[sepIndex+1:] 51 if oper, ok := sepToOperation[sep]; ok { 52 return part{key, oper, value, ""}, nil 53 } 54 return part{}, fmt.Errorf("query part %q has invalid key", word) 55 } 56 57 // merge merges two query parts together into a single query part. 58 // The keys of the two parts must be equal. 59 // If the result is a query part that can never match, io.EOF is returned as the error. 60 func (p part) merge(p2 part) (part, error) { 61 if p2.operator < p.operator { 62 // Sort the parts so we only need half the table below. 63 p, p2 = p2, p 64 } 65 switch p.operator { 66 case equals: 67 switch p2.operator { 68 case equals: 69 if p.value == p2.value { 70 return p, nil 71 } 72 return part{}, io.EOF 73 case lt: 74 if p.value < p2.value { 75 return p, nil 76 } 77 return part{}, io.EOF 78 case gt: 79 if p.value > p2.value { 80 return p, nil 81 } 82 return part{}, io.EOF 83 case ltgt: 84 if p.value < p2.value && p.value > p2.value2 { 85 return p, nil 86 } 87 return part{}, io.EOF 88 } 89 case ltgt: 90 switch p2.operator { 91 case ltgt: 92 if p2.value < p.value { 93 p.value = p2.value 94 } 95 if p2.value2 > p.value2 { 96 p.value2 = p2.value2 97 } 98 case lt: 99 if p2.value < p.value { 100 p.value = p2.value 101 } 102 case gt: 103 if p2.value > p.value2 { 104 p.value2 = p2.value 105 } 106 } 107 case lt: 108 switch p2.operator { 109 case lt: 110 if p2.value < p.value { 111 return p2, nil 112 } 113 return p, nil 114 case gt: 115 p = part{p.key, ltgt, p.value, p2.value} 116 } 117 case gt: 118 // p2.operator == gt 119 if p2.value > p.value { 120 return p2, nil 121 } 122 return p, nil 123 } 124 // p.operator == ltgt 125 if p.value <= p.value2 || p.value == "" { 126 return part{}, io.EOF 127 } 128 if p.value2 == "" { 129 return part{p.key, lt, p.value, ""}, nil 130 } 131 return p, nil 132 } 133 134 // sql returns a SQL expression and a list of arguments for finding records matching p. 135 func (p part) sql() (sql string, args []interface{}, err error) { 136 if p.key == "upload" { 137 switch p.operator { 138 case equals: 139 return "SELECT UploadID, RecordID FROM Records WHERE UploadID = ?", []interface{}{p.value}, nil 140 case lt: 141 return "SELECT UploadID, RecordID FROM Records WHERE UploadID < ?", []interface{}{p.value}, nil 142 case gt: 143 return "SELECT UploadID, RecordID FROM Records WHERE UploadID > ?", []interface{}{p.value}, nil 144 case ltgt: 145 return "SELECT UploadID, RecordID FROM Records WHERE UploadID < ? AND UploadID > ?", []interface{}{p.value, p.value2}, nil 146 } 147 } 148 switch p.operator { 149 case equals: 150 if p.value == "" { 151 // TODO(quentin): Implement support for searching for missing labels. 152 return "", nil, fmt.Errorf("missing value for key %q", p.key) 153 } 154 return "SELECT UploadID, RecordID FROM RecordLabels WHERE Name = ? AND Value = ?", []interface{}{p.key, p.value}, nil 155 case lt: 156 return "SELECT UploadID, RecordID FROM RecordLabels WHERE Name = ? AND Value < ?", []interface{}{p.key, p.value}, nil 157 case gt: 158 if p.value == "" { 159 // Simplify queries for any value. 160 return "SELECT UploadID, RecordID FROM RecordLabels WHERE Name = ?", []interface{}{p.key}, nil 161 } 162 return "SELECT UploadID, RecordID FROM RecordLabels WHERE Name = ? AND Value > ?", []interface{}{p.key, p.value}, nil 163 case ltgt: 164 return "SELECT UploadID, RecordID FROM RecordLabels WHERE Name = ? AND Value < ? AND Value > ?", []interface{}{p.key, p.value, p.value2}, nil 165 default: 166 panic("unknown operator " + string(p.operator)) 167 } 168 }