go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/analysis/internal/aip/datamodel.go (about)

     1  // Copyright 2022 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 aip contains utilities used to comply with API Improvement
    16  // Proposals (AIPs) from https://google.aip.dev/. This includes
    17  // an AIP-160 filter parser and SQL generator and AIP-132 order by
    18  // clause parser and SQL generator.
    19  package aip
    20  
    21  import (
    22  	"fmt"
    23  	"strings"
    24  )
    25  
    26  const (
    27  	// ColumnTypeString is a column of type string.
    28  	ColumnTypeString ColumnType = iota
    29  	// ColumnTypeBool is a column of type boolean.  NULL values are mapped to FALSE.
    30  	ColumnTypeBool = iota
    31  )
    32  
    33  // ColumnType is an enum for the type of a column.  Valid values are in the const block above.
    34  type ColumnType int32
    35  
    36  func (t ColumnType) String() string {
    37  	switch t {
    38  	case ColumnTypeString:
    39  		return "STRING"
    40  	case ColumnTypeBool:
    41  		return "BOOL"
    42  	default:
    43  		return "UNKNOWN"
    44  	}
    45  }
    46  
    47  // Column represents the schema of a Database column.
    48  type Column struct {
    49  	// The externally-visible field path this column maps to.
    50  	// This path may be referenced in AIP-160 filters and AIP-132 order by clauses.
    51  	fieldPath FieldPath
    52  
    53  	// The database name of the column.
    54  	// Important: Only assign assign safe constants to this field.
    55  	// User input MUST NOT flow to this field, as it will be used directly
    56  	// in SQL statements and would allow the user to perform SQL injection
    57  	// attacks.
    58  	databaseName string
    59  
    60  	// Whether this column can be sorted on.
    61  	sortable bool
    62  
    63  	// Whether this column can be filtered on.
    64  	filterable bool
    65  
    66  	// ImplicitFilter controls whether this field is searched implicitly
    67  	// in AIP-160 filter expressions.
    68  	implicitFilter bool
    69  
    70  	// Whether this column is an array of structs with two string members: key and value.
    71  	keyValue bool
    72  
    73  	// The type of the column, defaults to ColumnType_STRING.
    74  	columnType ColumnType
    75  
    76  	// The function which is applied to the filter arguments.
    77  	argSubstitute func(sub string) string
    78  }
    79  
    80  // Table represents the schema of a Database table, view or query.
    81  type Table struct {
    82  	// The columns in the database table.
    83  	columns []*Column
    84  
    85  	// A mapping from externally-visible field path to the column
    86  	// definition. The column name used as a key is in lowercase.
    87  	columnByFieldPath map[string]*Column
    88  }
    89  
    90  // FilterableColumnByFieldPath returns the database name of the filterable column
    91  // with the given field path.
    92  func (t *Table) FilterableColumnByFieldPath(path FieldPath) (*Column, error) {
    93  	col := t.columnByFieldPath[path.String()]
    94  	if col != nil && col.filterable {
    95  		return col, nil
    96  	}
    97  
    98  	columnNames := []string{}
    99  	for _, column := range t.columns {
   100  		if column.filterable {
   101  			columnNames = append(columnNames, column.fieldPath.String())
   102  		}
   103  	}
   104  	return nil, fmt.Errorf("no filterable field %q, valid fields are %s", path.String(), strings.Join(columnNames, ", "))
   105  }
   106  
   107  // SortableColumnByFieldPath returns the sortable database column
   108  // with the given externally-visible field path.
   109  func (t *Table) SortableColumnByFieldPath(path FieldPath) (*Column, error) {
   110  	col := t.columnByFieldPath[path.String()]
   111  	if col != nil && col.sortable {
   112  		return col, nil
   113  	}
   114  
   115  	columnNames := []string{}
   116  	for _, column := range t.columns {
   117  		if column.sortable {
   118  			columnNames = append(columnNames, column.fieldPath.String())
   119  		}
   120  	}
   121  	return nil, fmt.Errorf("no sortable field named %q, valid fields are %s", path.String(), strings.Join(columnNames, ", "))
   122  }