Search code examples
gogo-gorm

GORM OnConflict Clause Does not take into account optimisticLock Version


When using OnConflict clause, im setting UpdateAll flag to true. My table has a Version field also optmisticLock.Version.

I expect gorm to update all the columns including the version column to a new version in case of a conflict but version is not getting updated automatically.

Example:

type TestEntity struct {
  ID string `gorm:varchar(100);primary_key`
  Name string
  Version optimisticlock.Version
}

Table has one row with entries:

ID: "xyz", Name: "first", Version: 1

I want to update the current row:

testUpdateEntity := &TestEntity{
  ID: "xyz",
  Name: "second",
}

When i run:

dres := u.db.WithContext(context.TODO()).Clauses(clause.OnConflict{
        UpdateAll: true,
    }).Create(testUpdateEntity)

Then it should update version on its own to 2 after detecting the conflict.

  • I do not want to mention each column with DoUpdates because real table has multiple columns.
  • Is it a case of non handling of optimisticlocking behaviour with clauses?

Solution

  • As I well know, GORM doesn't currently support automatically updating the optimistic lock version column when using the OnConflict clause.

    When you use the OnConflict clause with UpdateAll, GORM generates an UPSERT statement that attempts to insert a new row, and if a conflict occurs, it updates all columns of the existing row with the values from the new row. However, GORM doesn't automatically handle the optimistic lock version column in this scenario.

    To handle the optimistic lock version column in this situation, you can explicitly specify the columns to update using the DoUpdates clause.

    Since you mentioned that your table has multiple columns and you don't want to explicitly list all of them, let try below code to dynamically generate the list of columns

    // Define a function to get a list of columns to update excluding primary key and version
    func getColumnsToUpdate(db *gorm.DB, entity interface{}) ([]string, error) {
        var columnsToUpdate []string
    
        // Retrieve table name
        tableName := db.NamingStrategy.TableName("", db.Statement.Schema.Table)
    
        // Retrieve column names
        columnNames, err := db.Migrator().Columns(tableName)
        if err != nil {
            return nil, err
        }
    
        // Exclude primary key and version columns
        for _, columnName := range columnNames {
            if columnName != "ID" && columnName != "Version" {
                columnsToUpdate = append(columnsToUpdate, columnName)
            }
        }
    
            return columnsToUpdate, nil
        
        }
    

    then update your query

    // Define update data
    updateData := make(map[string]interface{})
    for _, column := range columnsToUpdate {
        updateData[column] = "updated_value" // Set your updated value here
    }
    
    // Add version update
    updateData["Version"] = gorm.Expr("Version + 1")
    
    // Apply update with OnConflict clause
    dres := u.db.WithContext(context.TODO()).Clauses(clause.OnConflict{
        UpdateAll: true,
    }).Model(&TestEntity{}).Where("id = ?", testUpdateEntity.ID).Updates(updateData)