go.charczuk.com@v0.0.0-20240327042549-bc490516bd1a/sdk/db/type_meta.go (about) 1 /* 2 3 Copyright (c) 2023 - Present. Will Charczuk. All rights reserved. 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file at the root of the repository. 5 6 */ 7 8 package db 9 10 import ( 11 "reflect" 12 "strings" 13 ) 14 15 // TypeMetaFor returns the TypeMeta for an object. 16 func TypeMetaFor(object any) *TypeMeta { 17 t := reflect.TypeOf(object) 18 return NewTypeMetaFromColumns(generateColumnsForType(nil, t)...) 19 } 20 21 // NewTypeMetaFromColumns returns a new TypeMeta instance from a given list of columns. 22 func NewTypeMetaFromColumns(columns ...Column) *TypeMeta { 23 cc := TypeMeta{ 24 columns: columns, 25 } 26 lookup := make(map[string]*Column) 27 for i := 0; i < len(columns); i++ { 28 col := &columns[i] 29 lookup[col.ColumnName] = col 30 } 31 cc.lookup = lookup 32 return &cc 33 } 34 35 // NewTypeMetaFromColumnsWithPrefix makes a new TypeMeta instance from a given list 36 // of columns with a column prefix. 37 func NewTypeMetaFromColumnsWithPrefix(columnPrefix string, columns ...Column) *TypeMeta { 38 cc := TypeMeta{ 39 columns: columns, 40 } 41 lookup := make(map[string]*Column) 42 for i := 0; i < len(columns); i++ { 43 col := &columns[i] 44 lookup[col.ColumnName] = col 45 } 46 cc.lookup = lookup 47 cc.columnPrefix = columnPrefix 48 return &cc 49 } 50 51 // TypeMeta represents the column metadata for a given struct. 52 type TypeMeta struct { 53 columns []Column 54 lookup map[string]*Column 55 columnPrefix string 56 57 autos []*Column 58 notAutos []*Column 59 readOnly []*Column 60 notReadOnly []*Column 61 primaryKeys []*Column 62 notPrimaryKeys []*Column 63 uniqueKeys []*Column 64 notUniqueKeys []*Column 65 insertColumns []*Column 66 updateColumns []*Column 67 } 68 69 // Len returns the number of columns. 70 func (cc *TypeMeta) Len() int { 71 if cc == nil { 72 return 0 73 } 74 return len(cc.columns) 75 } 76 77 // Add adds a column. 78 func (cc *TypeMeta) Add(c Column) { 79 cc.columns = append(cc.columns, c) 80 cc.lookup[c.ColumnName] = &c 81 } 82 83 // Remove removes a column (by column name) from the collection. 84 func (cc *TypeMeta) Remove(columnName string) { 85 var newColumns []Column 86 for _, c := range cc.columns { 87 if c.ColumnName != columnName { 88 newColumns = append(newColumns, c) 89 } 90 } 91 cc.columns = newColumns 92 delete(cc.lookup, columnName) 93 } 94 95 // Column returns a column by name is present in the collection. 96 func (cc *TypeMeta) Column(columnName string) (c *Column) { 97 c = cc.lookup[columnName] 98 return 99 } 100 101 // HasColumn returns if a column name is present in the collection. 102 func (cc *TypeMeta) HasColumn(columnName string) bool { 103 _, hasColumn := cc.lookup[columnName] 104 return hasColumn 105 } 106 107 // Copy creates a new TypeMeta instance and carries over an existing column prefix. 108 func (cc *TypeMeta) Copy() *TypeMeta { 109 return NewTypeMetaFromColumnsWithPrefix(cc.columnPrefix, cc.columns...) 110 } 111 112 // CopyWithColumnPrefix applies a column prefix to column names and returns a new column collection. 113 func (cc *TypeMeta) CopyWithColumnPrefix(prefix string) *TypeMeta { 114 return NewTypeMetaFromColumnsWithPrefix(prefix, cc.columns...) 115 } 116 117 // InsertColumns are non-auto, non-readonly columns. 118 func (cc *TypeMeta) InsertColumns() []*Column { 119 if cc.insertColumns != nil { 120 return cc.insertColumns 121 } 122 for index, col := range cc.columns { 123 if !col.IsReadOnly && !col.IsAuto { 124 cc.insertColumns = append(cc.insertColumns, &cc.columns[index]) 125 } 126 } 127 return cc.insertColumns 128 } 129 130 // UpdateColumns are non-primary key, non-readonly columns. 131 func (cc *TypeMeta) UpdateColumns() []*Column { 132 if cc.updateColumns != nil { 133 return cc.updateColumns 134 } 135 for index, col := range cc.columns { 136 if !col.IsReadOnly && !col.IsPrimaryKey { 137 cc.updateColumns = append(cc.updateColumns, &cc.columns[index]) 138 } 139 } 140 return cc.updateColumns 141 } 142 143 // PrimaryKeys are columns we use as where predicates and can't update. 144 func (cc *TypeMeta) PrimaryKeys() []*Column { 145 if cc.primaryKeys != nil { 146 return cc.primaryKeys 147 } 148 for index, col := range cc.columns { 149 if col.IsPrimaryKey { 150 cc.primaryKeys = append(cc.primaryKeys, &cc.columns[index]) 151 } 152 } 153 return cc.primaryKeys 154 } 155 156 // NotPrimaryKeys are columns we can update. 157 func (cc *TypeMeta) NotPrimaryKeys() []*Column { 158 if cc.notPrimaryKeys != nil { 159 return cc.notPrimaryKeys 160 } 161 162 for index, col := range cc.columns { 163 if !col.IsPrimaryKey { 164 cc.notPrimaryKeys = append(cc.notPrimaryKeys, &cc.columns[index]) 165 } 166 } 167 return cc.notPrimaryKeys 168 } 169 170 // UniqueKeys are columns we use as where predicates and can't update. 171 func (cc *TypeMeta) UniqueKeys() []*Column { 172 if cc.uniqueKeys != nil { 173 return cc.uniqueKeys 174 } 175 for index, col := range cc.columns { 176 if col.IsUniqueKey { 177 cc.uniqueKeys = append(cc.uniqueKeys, &cc.columns[index]) 178 } 179 } 180 return cc.uniqueKeys 181 } 182 183 // NotUniqueKeys are columns we can update. 184 func (cc *TypeMeta) NotUniqueKeys() []*Column { 185 if cc.notUniqueKeys != nil { 186 return cc.notUniqueKeys 187 } 188 189 for index, col := range cc.columns { 190 if !col.IsUniqueKey { 191 cc.notUniqueKeys = append(cc.notUniqueKeys, &cc.columns[index]) 192 } 193 } 194 return cc.notUniqueKeys 195 } 196 197 // Autos are columns we have to return the id of. 198 func (cc *TypeMeta) Autos() []*Column { 199 if cc.autos != nil { 200 return cc.autos 201 } 202 203 for index, col := range cc.columns { 204 if col.IsAuto { 205 cc.autos = append(cc.autos, &cc.columns[index]) 206 } 207 } 208 return cc.autos 209 } 210 211 // NotAutos are columns we don't have to return the id of. 212 func (cc *TypeMeta) NotAutos() []*Column { 213 if cc.notAutos != nil { 214 return cc.notAutos 215 } 216 217 for index, col := range cc.columns { 218 if !col.IsAuto { 219 cc.notAutos = append(cc.notAutos, &cc.columns[index]) 220 } 221 } 222 return cc.notAutos 223 } 224 225 // ReadOnly are columns that we don't have to insert upon Create(). 226 func (cc *TypeMeta) ReadOnly() []*Column { 227 if cc.readOnly != nil { 228 return cc.readOnly 229 } 230 231 for index, col := range cc.columns { 232 if col.IsReadOnly { 233 cc.readOnly = append(cc.readOnly, &cc.columns[index]) 234 } 235 } 236 return cc.readOnly 237 } 238 239 // NotReadOnly are columns that we have to insert upon Create(). 240 func (cc *TypeMeta) NotReadOnly() []*Column { 241 if cc.notReadOnly != nil { 242 return cc.notReadOnly 243 } 244 245 for index, col := range cc.columns { 246 if !col.IsReadOnly { 247 cc.notReadOnly = append(cc.notReadOnly, &cc.columns[index]) 248 } 249 } 250 return cc.notReadOnly 251 } 252 253 // Columns returns the colummns 254 func (cc *TypeMeta) Columns() (output []*Column) { 255 output = make([]*Column, len(cc.columns)) 256 for index := range cc.columns { 257 output[index] = &cc.columns[index] 258 } 259 return 260 } 261 262 // Lookup gets the column name lookup. 263 func (cc *TypeMeta) Lookup() map[string]*Column { 264 if len(cc.columnPrefix) != 0 { 265 lookup := map[string]*Column{} 266 for key, value := range cc.lookup { 267 lookup[cc.columnPrefix+key] = value 268 } 269 return lookup 270 } 271 return cc.lookup 272 } 273 274 // 275 // helpers 276 // 277 278 // ColumnsZero returns unset fields on an instance that correspond to fields in the column collection. 279 func ColumnsZero(cols []*Column, instance any) (output []*Column) { 280 objValue := reflectValue(instance) 281 var fieldValue reflect.Value 282 for index, c := range cols { 283 fieldValue = objValue.Field(c.Index) 284 if fieldValue.IsZero() { 285 output = append(output, cols[index]) 286 } 287 } 288 return 289 } 290 291 // ColumnsNotZero returns set fields on an instance that correspond to fields in the column collection. 292 func ColumnsNotZero(cols []*Column, instance any) (output []*Column) { 293 objValue := reflectValue(instance) 294 var fieldValue reflect.Value 295 for index, c := range cols { 296 fieldValue = objValue.Field(c.Index) 297 if !fieldValue.IsZero() { 298 output = append(output, cols[index]) 299 } 300 } 301 return 302 } 303 304 // ColumnNames returns the string names for all the columns in the collection. 305 func ColumnNames(cols []*Column) []string { 306 names := make([]string, len(cols)) 307 for x := 0; x < len(cols); x++ { 308 c := cols[x] 309 names[x] = c.ColumnName 310 } 311 return names 312 } 313 314 // ColumnNamesCSV returns a csv of column names. 315 func ColumnNamesCSV(cols []*Column) string { 316 return strings.Join(ColumnNames(cols), ", ") 317 } 318 319 // ColumnNamesWithPrefix returns the string names for all the columns in the 320 // collection with a given disambiguation prefix. 321 func ColumnNamesWithPrefix(cols []*Column, columnPrefix string) []string { 322 names := make([]string, len(cols)) 323 for x := 0; x < len(cols); x++ { 324 c := cols[x] 325 if len(columnPrefix) != 0 { 326 names[x] = columnPrefix + c.ColumnName 327 } else { 328 names[x] = c.ColumnName 329 } 330 } 331 return names 332 } 333 334 // ColumnNamesWithPrefixCSV returns a csv of column names with a given disambiguation prefix. 335 func ColumnNamesWithPrefixCSV(cols []*Column, columnPrefix string) string { 336 return strings.Join(ColumnNamesWithPrefix(cols, columnPrefix), ", ") 337 } 338 339 // ColumnNamesFromAlias returns the string names for all the columns in the collection. 340 func ColumnNamesFromAlias(cols []*Column, tableAlias string) []string { 341 names := make([]string, len(cols)) 342 for x := 0; x < len(cols); x++ { 343 c := cols[x] 344 names[x] = tableAlias + "." + c.ColumnName 345 } 346 return names 347 } 348 349 // ColumnNamesCSV returns a csv of column names. 350 func ColumnNamesFromAliasCSV(cols []*Column, tableAlias string) string { 351 return strings.Join(ColumnNamesFromAlias(cols, tableAlias), ", ") 352 } 353 354 // ColumnNamesWithPrefixFromAlias returns the string names for all the columns in the collection. 355 func ColumnNamesWithPrefixFromAlias(cols []*Column, columnPrefix, tableAlias string) []string { 356 names := make([]string, len(cols)) 357 for x := 0; x < len(cols); x++ { 358 c := cols[x] 359 if columnPrefix != "" { 360 names[x] = tableAlias + "." + c.ColumnName + " as " + columnPrefix + c.ColumnName 361 } else { 362 names[x] = tableAlias + "." + c.ColumnName 363 } 364 } 365 return names 366 } 367 368 // ColumnNamesCSV returns a csv of column names. 369 func ColumnNamesWithPrefixFromAliasCSV(cols []*Column, columnPrefix, tableAlias string) string { 370 return strings.Join(ColumnNamesWithPrefixFromAlias(cols, columnPrefix, tableAlias), ", ") 371 } 372 373 // ColumnValues returns the reflected value for all the columns on a given instance. 374 func ColumnValues(cols []*Column, instance any) (output []any) { 375 value := reflectValue(instance) 376 output = make([]any, len(cols)) 377 for x := 0; x < len(cols); x++ { 378 c := cols[x] 379 valueField := value.FieldByName(c.FieldName) 380 if c.IsJSON { 381 output[x] = JSON(valueField.Interface()) 382 } else { 383 output[x] = valueField.Interface() 384 } 385 } 386 return 387 } 388 389 // HasColumn returns if a column with a given name exists in the list. 390 func HasColumn(cols []*Column, name string) bool { 391 for _, c := range cols { 392 if c.ColumnName == name { 393 return true 394 } 395 } 396 return false 397 } 398 399 // filter a slice with a function. 400 func filter[T any](values []T, fn func(T) bool) (out []T) { 401 out = make([]T, 0, len(values)) 402 for _, v := range values { 403 if fn(v) { 404 out = append(out, v) 405 } 406 } 407 return 408 } 409 410 // newColumnCacheKey creates a cache key for a type. 411 func newColumnCacheKey(objectType reflect.Type) string { 412 typeName := objectType.String() 413 instance := reflect.New(objectType).Interface() 414 if typed, ok := instance.(ColumnMetaCacheKeyProvider); ok { 415 return typeName + "_" + typed.ColumnMetaCacheKey() 416 } 417 if typed, ok := instance.(TableNameProvider); ok { 418 return typeName + "_" + typed.TableName() 419 } 420 return typeName 421 } 422 423 // generateColumnsForType generates a column list for a given type. 424 func generateColumnsForType(parent *Column, t reflect.Type) []Column { 425 for t.Kind() == reflect.Ptr { 426 t = t.Elem() 427 } 428 429 var tableName string 430 if parent != nil { 431 tableName = parent.TableName 432 } else { 433 tableName = TableNameByType(t) 434 } 435 436 numFields := t.NumField() 437 438 var cols []Column 439 for index := 0; index < numFields; index++ { 440 field := t.Field(index) 441 col := NewColumnFromFieldTag(field) 442 if col != nil { 443 col.Parent = parent 444 col.Index = index 445 col.TableName = tableName 446 if col.Inline && field.Anonymous { // if it's not anonymous, whatchu doin 447 cols = append(cols, generateColumnsForType(col, col.FieldType)...) 448 } else if !field.Anonymous { 449 cols = append(cols, *col) 450 } 451 } 452 } 453 454 return cols 455 }