go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/analysis/internal/aip/datamodel_builder.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 16 17 type ColumnBuilder struct { 18 column Column 19 } 20 21 // NewColumn starts building a new column. 22 func NewColumn() *ColumnBuilder { 23 return &ColumnBuilder{Column{columnType: ColumnTypeString}} 24 } 25 26 // WithFieldPath specifies the field path the column maps to 27 // in the returned resource. Field paths are described in AIP-161. 28 // 29 // For convenience, the field path is described here as a set of 30 // segments where each segment is joined by the traversal operator (.). 31 // E.g. the field path "metrics.`some-metric`.value" would be specified 32 // as ["metrics", "some-metric", "value"]. 33 func (c *ColumnBuilder) WithFieldPath(segments ...string) *ColumnBuilder { 34 c.column.fieldPath = NewFieldPath(segments...) 35 return c 36 } 37 38 // WithDatabaseName specifies the database name of the column. 39 // Important: Only pass safe values (e.g. compile-time constants) to this 40 // field. 41 // User input MUST NOT flow to this field, as it will be used directly 42 // in SQL statements and would allow the user to perform SQL injection 43 // attacks. 44 func (c *ColumnBuilder) WithDatabaseName(name string) *ColumnBuilder { 45 c.column.databaseName = name 46 return c 47 } 48 49 // KeyValue specifies this column is an array of structs with two string members: key and value. 50 // The key is exposed as a field on the column name, the value can be queried with :, = and != 51 // Example query: tag.key=value 52 func (c *ColumnBuilder) KeyValue() *ColumnBuilder { 53 c.column.keyValue = true 54 return c 55 } 56 57 // Bool specifies this column has bool type in the database. 58 func (c *ColumnBuilder) Bool() *ColumnBuilder { 59 c.column.columnType = ColumnTypeBool 60 return c 61 } 62 63 // Sortable specifies this column can be sorted on. 64 func (c *ColumnBuilder) Sortable() *ColumnBuilder { 65 c.column.sortable = true 66 return c 67 } 68 69 // Filterable specifies this column can be filtered on. 70 func (c *ColumnBuilder) Filterable() *ColumnBuilder { 71 c.column.filterable = true 72 return c 73 } 74 75 // FilterableImplicitly specifies this column can be filtered on implicitly. 76 // This means that AIP-160 filter expressions not referencing any 77 // particular field will try to search in this column. 78 func (c *ColumnBuilder) FilterableImplicitly() *ColumnBuilder { 79 c.column.filterable = true 80 c.column.implicitFilter = true 81 return c 82 } 83 84 // WithArgumentSubstitutor specifies a substitution that should happen to the user-specified 85 // filter argument before it is matched against the database value. If this option is enabled, 86 // the filter operators permitted will be limited to = (equals) and != (not equals). 87 func (c *ColumnBuilder) WithArgumentSubstitutor(f func(sub string) string) *ColumnBuilder { 88 c.column.argSubstitute = f 89 return c 90 } 91 92 // Build returns the built column. 93 func (c *ColumnBuilder) Build() *Column { 94 result := &Column{} 95 *result = c.column 96 return result 97 } 98 99 type TableBuilder struct { 100 columns []*Column 101 } 102 103 // NewTable starts building a new table. 104 func NewTable() *TableBuilder { 105 return &TableBuilder{} 106 } 107 108 // WithColumns specifies the columns in the table. 109 func (t *TableBuilder) WithColumns(columns ...*Column) *TableBuilder { 110 t.columns = columns 111 return t 112 } 113 114 // Build returns the built table. 115 func (t *TableBuilder) Build() *Table { 116 columnByFieldPath := make(map[string]*Column) 117 for _, c := range t.columns { 118 if _, ok := columnByFieldPath[c.fieldPath.String()]; ok { 119 panic("multiple columns with the same field path: " + c.fieldPath.String()) 120 } 121 columnByFieldPath[c.fieldPath.String()] = c 122 } 123 124 return &Table{ 125 columns: t.columns, 126 columnByFieldPath: columnByFieldPath, 127 } 128 }