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 }