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  }