package db import ( "crypto/rand" "encoding/hex" "reflect" "strings" ) func randomHex(len int) string { buff := make([]byte, len/2+1) rand.Read(buff) str := hex.EncodeToString(buff) return str[:len] } func fieldInfo(fieldTyp reflect.StructField) (bool, string, reflect.Type, string) { fieldName := fieldTyp.Name columnName, ok := fieldTyp.Tag.Lookup("db") if !ok { columnName = strings.ToLower(fieldName) } if ok && columnName[len(columnName)-1] == '*' { return true, fieldName, fieldTyp.Type, columnName[:len(columnName)-1] } return false, fieldName, fieldTyp.Type, columnName } func fieldPrimaryKey(fieldTyp reflect.StructField) bool { isPK, _, _, _ := fieldInfo(fieldTyp) return isPK } // structDatabaseColumnValues takes a pointer to a struct and returns a pair // of slices, one with the column names and the other with pointers to all // its exported fields. // // The column name can be changed using the "db" tag attribute on that field func structDatabaseColumnValues(s any) ([]string, []any) { v := reflect.ValueOf(s).Elem() numFields := v.NumField() names := []string{} values := []any{} for i := 0; i < numFields; i++ { fieldTyp := v.Type().Field(i) fieldVal := v.Field(i) if fieldTyp.IsExported() { key := strings.ToLower(fieldTyp.Name) if name, ok := fieldTyp.Tag.Lookup("db"); ok { key = name // primary key has a "*" at the end, exclude from column name if key[len(key)-1] == '*' { key = key[:len(key)-1] } } names = append(names, key) values = append(values, fieldVal.Addr().Interface()) } } return names, values } func structPrimaryKeyPtr(s any) (*string, bool) { v := reflect.ValueOf(s).Elem() for i := 0; i < v.NumField(); i++ { fieldTyp := v.Type().Field(i) fieldVal := v.Field(i) if fieldTyp.IsExported() { if fieldPrimaryKey(fieldTyp) { // SAFETY: this is required to cast *Ref[T] to *string, internally // they are the same type so this should be safe ptr := fieldVal.Addr().UnsafePointer() return (*string)(ptr), true } } } return nil, false } // structDatabasePrimaryKeyColumn takes a pointer to a struct and returns the // name of the field with the primary key func structDatabasePrimaryKeyColumn(s any) (field, column string, ok bool) { v := reflect.ValueOf(s).Elem() for i := 0; i < v.NumField(); i++ { fieldTyp := v.Type().Field(i) if fieldTyp.IsExported() { key := fieldTyp.Name if value, ok := fieldTyp.Tag.Lookup("db"); ok { if value[len(value)-1] == '*' { return key, value[:len(value)-1], true } } } } return "", "", false } func repeatSlice[T any](value T, count int) []T { s := make([]T, count) for i := 0; i < count; i++ { s[i] = value } return s }