Add support for excluding a field from an index in the model struct.
This commit is contained in:
parent
adf9b80fb7
commit
8cf98c0598
@ -27,7 +27,7 @@ func (s *sqlite3) DataTypeOf(field *StructField) string {
|
||||
if sqlType == "" {
|
||||
switch dataValue.Kind() {
|
||||
case reflect.Bool:
|
||||
sqlType = "bool"
|
||||
sqlType = "numeric"
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uintptr:
|
||||
if field.IsPrimaryKey {
|
||||
field.TagSettings["AUTO_INCREMENT"] = "AUTO_INCREMENT"
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
@ -436,3 +437,50 @@ func TestMultipleIndexes(t *testing.T) {
|
||||
t.Error("MultipleIndexes unique index failed")
|
||||
}
|
||||
}
|
||||
|
||||
type ExcludedColumnIndex struct {
|
||||
ID int64
|
||||
Email string `sql:"unique_index:uix_excluded_column_index_email"`
|
||||
Deleted bool `sql:"unique_index:!uix_excluded_column_index_email"`
|
||||
}
|
||||
|
||||
func TestConditionalIndexExcludesColumn(t *testing.T) {
|
||||
// This behavior is only supported for DBMSes that support partial indices
|
||||
// (i.e., not MySQL).
|
||||
dialect := os.Getenv("GORM_DIALECT")
|
||||
switch dialect {
|
||||
case "", "sqlite", "postgres", "mssql":
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
if err := DB.DropTableIfExists(&ExcludedColumnIndex{}).Error; err != nil {
|
||||
fmt.Printf("Got error when try to delete table excluded_column_index, %+v\n", err)
|
||||
}
|
||||
|
||||
if err := DB.AutoMigrate(&ExcludedColumnIndex{}).Error; err != nil {
|
||||
t.Errorf("Failed to migrate: %+v", err)
|
||||
}
|
||||
|
||||
if err := DB.Save(&ExcludedColumnIndex{Email: "impl@example.com"}).Error; err != nil {
|
||||
t.Errorf("Unexpected error saving first entry: %v", err)
|
||||
}
|
||||
|
||||
if err := DB.Save(&ExcludedColumnIndex{Email: "impl@example.com"}).Error; err == nil {
|
||||
t.Error("Unique index was not created")
|
||||
}
|
||||
|
||||
var u ExcludedColumnIndex
|
||||
if err := DB.First(&u).Error; err != nil {
|
||||
t.Errorf("Enexpected error retrieving first entry: %v", err)
|
||||
}
|
||||
|
||||
u.Deleted = true
|
||||
if err := DB.Save(&u).Error; err != nil {
|
||||
t.Errorf("Unexpected error saving first entry: %v", err)
|
||||
}
|
||||
|
||||
if err := DB.Save(&ExcludedColumnIndex{Email: "impl@example.com"}).Error; err != nil {
|
||||
t.Errorf("Conditional index failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
95
scope.go
95
scope.go
@ -1185,40 +1185,81 @@ func (scope *Scope) autoMigrate() *Scope {
|
||||
return scope
|
||||
}
|
||||
|
||||
type derivedIndex struct {
|
||||
columns []string
|
||||
q *DB
|
||||
}
|
||||
|
||||
func (scope *Scope) autoIndex() *Scope {
|
||||
var indexes = map[string][]string{}
|
||||
var uniqueIndexes = map[string][]string{}
|
||||
indexes := map[string]map[string]*derivedIndex{
|
||||
"INDEX": make(map[string]*derivedIndex),
|
||||
"UNIQUE_INDEX": make(map[string]*derivedIndex),
|
||||
}
|
||||
|
||||
derive := func(field *StructField, tag, prefix string) {
|
||||
if name, ok := field.TagSettings[tag]; ok {
|
||||
names := strings.Split(name, ",")
|
||||
|
||||
for _, name := range names {
|
||||
exclude := false
|
||||
|
||||
if name == tag || name == "" {
|
||||
name = fmt.Sprintf("%s_%v_%v", prefix, scope.TableName(), field.DBName)
|
||||
} else if name[0] == '!' {
|
||||
exclude = true
|
||||
name = name[1:]
|
||||
}
|
||||
|
||||
idx, ok := indexes[tag][name]
|
||||
if !ok {
|
||||
idx = &derivedIndex{q: scope.NewDB().Model(scope.Value)}
|
||||
indexes[tag][name] = idx
|
||||
}
|
||||
|
||||
if exclude {
|
||||
// We can't just bind this; most (all?) DBMSes don't seem
|
||||
// to support parameterizing partial indices.
|
||||
f, _ := scope.FieldByName(field.Name)
|
||||
v, _ := driver.DefaultParameterConverter.ConvertValue(reflect.Zero(f.Field.Type()).Interface())
|
||||
|
||||
// Possibilities are limited:
|
||||
// https://golang.org/pkg/database/sql/driver/#Value
|
||||
cond := "IS NULL"
|
||||
switch value := v.(type) {
|
||||
case int64, float64:
|
||||
cond = "= 0"
|
||||
case bool:
|
||||
switch scope.Dialect().DataTypeOf(field) {
|
||||
case "bool", "boolean":
|
||||
cond = "= false"
|
||||
default:
|
||||
cond = "= 0"
|
||||
}
|
||||
case []byte, string:
|
||||
cond = "= ''"
|
||||
case time.Time:
|
||||
cond = fmt.Sprintf("= '%s'", value.Format(time.RFC3339))
|
||||
}
|
||||
|
||||
idx.q = idx.q.Where(fmt.Sprintf("%s %s", scope.Quote(field.DBName), cond))
|
||||
} else {
|
||||
idx.columns = append(idx.columns, field.DBName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, field := range scope.GetStructFields() {
|
||||
if name, ok := field.TagSettings["INDEX"]; ok {
|
||||
names := strings.Split(name, ",")
|
||||
|
||||
for _, name := range names {
|
||||
if name == "INDEX" || name == "" {
|
||||
name = fmt.Sprintf("idx_%v_%v", scope.TableName(), field.DBName)
|
||||
}
|
||||
indexes[name] = append(indexes[name], field.DBName)
|
||||
}
|
||||
}
|
||||
|
||||
if name, ok := field.TagSettings["UNIQUE_INDEX"]; ok {
|
||||
names := strings.Split(name, ",")
|
||||
|
||||
for _, name := range names {
|
||||
if name == "UNIQUE_INDEX" || name == "" {
|
||||
name = fmt.Sprintf("uix_%v_%v", scope.TableName(), field.DBName)
|
||||
}
|
||||
uniqueIndexes[name] = append(uniqueIndexes[name], field.DBName)
|
||||
}
|
||||
}
|
||||
derive(field, "INDEX", "idx")
|
||||
derive(field, "UNIQUE_INDEX", "uix")
|
||||
}
|
||||
|
||||
for name, columns := range indexes {
|
||||
scope.NewDB().Model(scope.Value).AddIndex(name, columns...)
|
||||
for name, idx := range indexes["INDEX"] {
|
||||
idx.q.AddIndex(name, idx.columns...)
|
||||
}
|
||||
|
||||
for name, columns := range uniqueIndexes {
|
||||
scope.NewDB().Model(scope.Value).AddUniqueIndex(name, columns...)
|
||||
for name, idx := range indexes["UNIQUE_INDEX"] {
|
||||
idx.q.AddUniqueIndex(name, idx.columns...)
|
||||
}
|
||||
|
||||
return scope
|
||||
|
Loading…
x
Reference in New Issue
Block a user