github.com/CycloneDX/sbom-utility@v0.16.0/common/query_types.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 /* 3 * Licensed to the Apache Software Foundation (ASF) under one or more 4 * contributor license agreements. See the NOTICE file distributed with 5 * this work for additional information regarding copyright ownership. 6 * The ASF licenses this file to You under the Apache License, Version 2.0 7 * (the "License"); you may not use this file except in compliance with 8 * the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 package common 20 21 import ( 22 "fmt" 23 "regexp" 24 "strings" 25 26 "github.com/CycloneDX/sbom-utility/utils" 27 ) 28 29 // Named tokens 30 const ( 31 QUERY_TOKEN_WILDCARD = "*" 32 QUERY_FROM_CLAUSE_SEP = "." 33 QUERY_SELECT_CLAUSE_SEP = "," 34 QUERY_WHERE_EXPRESSION_SEP = "," 35 QUERY_WHERE_OPERAND_EQUALS = "=" 36 ) 37 38 // ================================================================== 39 // QueryRequest 40 // ================================================================== 41 42 // query JSON map and return selected subset using SQL-like syntax: 43 // SELECT: <key.1>, <key.2>, ... // "firstname, lastname, email" || * (default) 44 // FROM: <key path> // "product.customers" 45 // WHERE: <key.X> == <value> // "country='Germany'" 46 // ORDERBY: <key.N> // "lastname" 47 // e.g.,SELECT * FROM product.customers WHERE country="Germany"; 48 type QueryRequest struct { 49 selectKeysRaw string 50 selectKeys []string 51 fromPathsRaw string 52 fromPathSelectors []string 53 wherePredicatesRaw string 54 wherePredicates []string 55 whereFilters []WhereFilter 56 orderByKeysRaw string 57 //orderByKeys []string // TODO 58 } 59 60 // Implement the Stringer interface for QueryRequest 61 func (qr *QueryRequest) String() string { 62 buffer, _ := utils.EncodeAnyToDefaultIndentedJSONStr(qr) 63 return buffer.String() 64 } 65 66 func (qr *QueryRequest) StringAsParameters() string { 67 sb := new(strings.Builder) 68 sb.WriteString(fmt.Sprintf("--select: %s\n", qr.selectKeysRaw)) 69 sb.WriteString(fmt.Sprintf("--from: %s\n", qr.fromPathsRaw)) 70 sb.WriteString(fmt.Sprintf("--where: %s\n", qr.wherePredicatesRaw)) 71 sb.WriteString(fmt.Sprintf("--orderby: %s\n", qr.orderByKeysRaw)) 72 return sb.String() 73 } 74 75 func NewQueryRequest() (qr *QueryRequest) { 76 qr = new(QueryRequest) 77 return 78 } 79 80 func NewQueryRequestSelectFromWhere(rawSelect string, rawFrom string, rawWhere string) (qr *QueryRequest, err error) { 81 qr = new(QueryRequest) 82 qr.selectKeysRaw = rawSelect 83 qr.fromPathsRaw = rawFrom 84 qr.wherePredicatesRaw = rawWhere 85 err = qr.parseQueryClauses() 86 return 87 } 88 89 func NewQueryRequestSelectFrom(rawSelect string, rawFrom string) (qr *QueryRequest, err error) { 90 return NewQueryRequestSelectFromWhere(rawSelect, rawFrom, "") 91 } 92 93 func NewQueryRequestSelectWildcardFrom(rawFrom string) (qr *QueryRequest, err error) { 94 return NewQueryRequestSelectFromWhere(QUERY_TOKEN_WILDCARD, rawFrom, "") 95 } 96 97 func NewQueryRequestSelectWildcardFromWhere(rawFrom string, rawWhere string) (qr *QueryRequest, err error) { 98 return NewQueryRequestSelectFromWhere(QUERY_TOKEN_WILDCARD, rawFrom, rawWhere) 99 } 100 101 // ------------ 102 // SELECT 103 // ------------ 104 105 // parse out field (keys) from raw '--select' flag's value 106 func ParseSelectKeys(rawSelectKeys string) (selectKeys []string) { 107 if rawSelectKeys != "" { 108 selectKeys = strings.Split(rawSelectKeys, QUERY_SELECT_CLAUSE_SEP) 109 } 110 //getLogger().Tracef("SELECT keys: %v\n", selectKeys) 111 return 112 } 113 114 func (qr *QueryRequest) SetRawSelectKeys(rawSelectKeys string) []string { 115 qr.selectKeysRaw = rawSelectKeys 116 // Note: it is an intentional side-effect to update the parsed, slice version 117 qr.selectKeys = ParseSelectKeys(rawSelectKeys) 118 return qr.selectKeys 119 } 120 121 func (qr *QueryRequest) GetSelectKeys() []string { 122 return qr.selectKeys 123 } 124 125 // ------------ 126 // FROM 127 // ------------ 128 129 // parse out field (keys) from raw '--select' flag's value 130 func ParseFromPaths(rawFromPaths string) (fromPaths []string) { 131 if rawFromPaths != "" { 132 fromPaths = strings.Split(rawFromPaths, QUERY_FROM_CLAUSE_SEP) 133 } 134 //getLogger().Tracef("FROM paths: %v\n", fromPaths) 135 return 136 } 137 138 func (qr *QueryRequest) SetRawFromPaths(rawFromPaths string) []string { 139 qr.fromPathsRaw = rawFromPaths 140 // Note: it is an intentional side-effect to update the parsed, slice version 141 qr.fromPathSelectors = ParseFromPaths(rawFromPaths) 142 return qr.fromPathSelectors 143 } 144 145 func (qr *QueryRequest) GetFromKeys() []string { 146 return qr.fromPathSelectors 147 } 148 149 // ------------ 150 // WHERE 151 // ------------ 152 153 // parse out `key=<regex>` predicates from the raw `--where` flag's value 154 func ParseWherePredicates(rawWherePredicates string) (wherePredicates []string) { 155 if rawWherePredicates != "" { 156 wherePredicates = strings.Split(rawWherePredicates, QUERY_WHERE_EXPRESSION_SEP) 157 } 158 //getLogger().Tracef("WHERE predicates: %v\n", wherePredicates) 159 return 160 } 161 162 func ParseWhereFilters(wherePredicates []string) (whereFilters []WhereFilter, err error) { 163 if len(wherePredicates) == 0 { 164 return 165 } 166 167 var filter *WhereFilter 168 for _, predicate := range wherePredicates { 169 170 filter = ParseWhereFilter(predicate) 171 172 if filter == nil { 173 err = NewQueryWhereClauseError(nil, predicate) 174 return 175 } 176 177 whereFilters = append(whereFilters, *filter) 178 } 179 180 return 181 } 182 183 // TODO: generate more specific error messages on why parsing failed 184 func ParseWhereFilter(rawExpression string) (pWhereSelector *WhereFilter) { 185 186 if rawExpression == "" { 187 return // nil 188 } 189 190 tokens := strings.Split(rawExpression, QUERY_WHERE_OPERAND_EQUALS) 191 192 if len(tokens) != 2 { 193 return // nil 194 } 195 196 var whereFilter = WhereFilter{} 197 whereFilter.Operand = QUERY_WHERE_OPERAND_EQUALS 198 whereFilter.Key = tokens[0] 199 whereFilter.Value = tokens[1] 200 201 if whereFilter.Value == "" { 202 return // nil 203 } 204 205 var errCompile error 206 whereFilter.ValueRegEx, errCompile = utils.CompileRegex(whereFilter.Value) 207 //getLogger().Debugf(">> Regular expression: `%v`...", whereFilter.ValueRegEx) 208 209 if errCompile != nil { 210 return // nil 211 } 212 213 return &whereFilter 214 } 215 216 func (qr *QueryRequest) GetWhereFilters() ([]WhereFilter, error) { 217 if len(qr.wherePredicates) == 0 && qr.wherePredicatesRaw != "" { 218 // TODO: consider if we really need error handling 219 err := qr.parseWhereFilterClauses() 220 if err != nil { 221 return nil, err 222 } 223 } 224 return qr.whereFilters, nil 225 } 226 227 func (qr *QueryRequest) SetRawWherePredicates(rawWherePredicates string) []WhereFilter { 228 qr.wherePredicatesRaw = rawWherePredicates 229 // Note: it is an intentional side-effect to update the parsed, slice versions 230 // of the predicates as well as the subsequent filters. 231 qr.wherePredicates = ParseWherePredicates(qr.wherePredicatesRaw) 232 // TODO: implement a getLogger() and log (and perhaps return) the parsing error 233 qr.whereFilters, _ = ParseWhereFilters(qr.wherePredicates) 234 return qr.whereFilters 235 } 236 237 // -------------- 238 // Other helpers 239 // -------------- 240 241 // Parse command-line flag values including: 242 // --select <clause> --from <clause> and --where <clause> 243 func (qr *QueryRequest) parseQueryClauses() (err error) { 244 qr.selectKeys = ParseSelectKeys(qr.selectKeysRaw) 245 qr.fromPathSelectors = ParseFromPaths(qr.fromPathsRaw) 246 qr.wherePredicates = ParseWherePredicates(qr.wherePredicatesRaw) 247 qr.whereFilters, err = ParseWhereFilters(qr.wherePredicates) 248 return 249 } 250 251 // Parse/validate each key=<regex> expression found on WHERE clause 252 func (qr *QueryRequest) parseWhereFilterClauses() (err error) { 253 if len(qr.wherePredicates) == 0 { 254 return NewQueryWhereClauseError(qr, qr.wherePredicatesRaw) 255 } 256 257 var filter *WhereFilter 258 for _, predicate := range qr.wherePredicates { 259 260 filter = ParseWhereFilter(predicate) 261 262 if filter == nil { 263 err = NewQueryWhereClauseError(qr, predicate) 264 return 265 } 266 267 qr.whereFilters = append(qr.whereFilters, *filter) 268 } 269 270 return 271 } 272 273 // ================================================================== 274 // QueryResponse 275 // ================================================================== 276 type QueryResponse struct { 277 resultMap map[string]interface{} 278 } 279 280 // Implement the Stringer interface for QueryRequest 281 func (qr *QueryResponse) String() string { 282 buffer, _ := utils.EncodeAnyToDefaultIndentedJSONStr(qr) 283 return buffer.String() 284 } 285 286 func NewQueryResponse() *QueryResponse { 287 qr := new(QueryResponse) 288 qr.resultMap = make(map[string]interface{}) 289 return qr 290 } 291 292 // ================================================================== 293 // WhereFilter 294 // ================================================================== 295 type WhereFilter struct { 296 Key string 297 Operand string 298 Value string 299 ValueRegEx *regexp.Regexp 300 } 301 302 // Implement the Stringer interface for QueryRequest 303 func (filter *WhereFilter) String() string { 304 buffer, _ := utils.EncodeAnyToDefaultIndentedJSONStr(filter) 305 return buffer.String() 306 } 307 308 // Note: Used to normalize key lookups in maps accounting for changes in 309 // key names on CDX structures created from annotations during JSON unmarshal 310 // TODO: unused as of now, as we opted to use CycloneDX keys as they appear 311 // in schema (for now) 312 func (filter *WhereFilter) GetNormalizedMapKey() (normalizedKey string) { 313 normalizedKey = strings.ToLower(filter.Key) 314 // Note: accounts for changes in JSON annotations (e.g., "bom-ref", etc.) 315 normalizedKey = strings.Replace(normalizedKey, "-", "", -1) 316 return 317 }