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  }