Update
While update operations can still be performed using gorm’s Save method (see gorm documentation), this is useful only if the model(s) to be updated have already been loaded from the database.
On the contrary, cql’s Update method allows the update of all the models that meet the conditions entered without the need to load the information (via the direct execution of an UPDATE statement).
Update methods
Update operations are divided into two parts: the Update method and the Set method. In the first one, we must define the conditions that will determine which models will be updated. Here, the whole system of compilable queries is valid (for details visit Query). In the second one, we define the updates to be performed.
The object obtained using cql.Update has different methods that will allow you to modify the query:
Modifier methods
Modifier methods are those that modify the query in a certain way, affecting the models updated:
Limit: specifies the number of models to be updated.
Ascending: specifies an ascending order when updating models.
Descending: specifies a descending order when updating models.
Returning: specifies that the updated models must be fetched from the database after being updated (not supported by MySQL). Preload of related data is also possible (not supported by SQLite).
Finishing methods
Finishing methods are those that cause the query to be executed:
Set: defines the updates to be performed.
SetMultiple: (only supported by MySQL) allows updates to be made to different tables at the same time.
Example
type MyModel struct {
model.UUIDModel
Name string
}
updatedCount, err := cql.Update[MyModel](
context.Background(),
db,
conditions.MyModel.Name.Is().Eq(cql.String("a_string")),
).Set(
conditions.MyModel.Name.Set().Eq(cql.String("a_string_2")),
)
As you can see, the syntax for the Set method is similar to the queries system with the difference that the Set method must be used instead of Is.
For attributes that allow null (nullable values, pointers, nullable relations) the .Set().Null() method will also be available.
Joins
It is also possible to perform joins in the first part of the update (Update method):
type MyOtherModel struct {
model.UUIDModel
Name string
}
type MyModel struct {
model.UUIDModel
Name string
Related *MyOtherModel
RelatedID *model.UUID
}
updatedCount, err := cql.Update[MyModel](
context.Background(),
db,
conditions.MyModel.Related(
conditions.MyOtherModel.Name.Is().Eq(cql.String("a_string")),
),
).Set(
conditions.MyModel.Name.Set().Eq(cql.String("a_string_2")),
)
Here the only limitation is that in the Set part, only the values of the initial model can be updated (not of the joined models).
This limitation is imposed by the database engines, with the exception of MySQL, which allows multiple tables to be updated at the same time. To do this, you use the SetMultiple method:
updatedCount, err := cql.Update[MyModel](
context.Background(),
db,
conditions.MyModel.Related(
conditions.MyOtherModel.Name.Is().Eq(cql.String("a_string")),
),
).SetMultiple(
conditions.MyModel.Name.Set().Eq(cql.String("a_string_2")),
conditions.MyOtherModel.Name.Set().Eq(cql.String("a_string_2")),
)
Dynamic updates
Updates can also be dynamic, meaning that the set can be a value from the same entity or another entity. Functions can also be used on the values.
For example:
type MyModel struct {
model.UUIDModel
Value1 int
Value2 int
}
updatedCount, err := cql.Update[MyModel](
context.Background(),
db,
conditions.MyModel.Value1.Is().Eq(cql.Int64(2)),
).Set(
conditions.MyModel.Value1.Set().Eq(conditions.MyModel.Value2.Divided(cql.Int64(2))),
)
Updated at
If your model contains a base model with timestamps (model.UUIDModelWithTimestamps or model.UIntModelWithTimestamps),
cql will automatically add updated_at = now() to entities that are updated.
Type safety
Update uses the same system of compilable conditions as cql.Query, so it shares its features and limitations in terms of type safety at compile time.
For more details, see Query type safety.
Set
In addition, Update also provides the same type safety in Set methods, ensuring that the value to be set is of the same type as the attribute to be modified:
1type MyModel struct {
2 model.UUIDModel
3
4 ValueInt int
5 ValueString string
6}
1updatedCount, err := cql.Update[MyModel](
2 context.Background(),
3 db,
4 conditions.MyModel.ValueInt.Is().Eq(cql.Int64(2)),
5).Set(
6 conditions.MyModel.ValueInt.Set().Eq(conditions.MyModel.ValueInt.Divided(cql.Int64(2))),
7)
1updatedCount, err := cql.Update[MyModel](
2 context.Background(),
3 db,
4 conditions.MyModel.ValueInt.Is().Eq(cql.Int64(2)),
5).Set(
6 conditions.MyModel.ValueInt.Set().Eq(conditions.MyModel.ValueString),
7)
In this case, the compilation error will be:
cannot use conditions.MyModel.ValueString (variable of struct type condition.StringField[MyModel]) as
condition.ValueOfType[float64] value in argument to conditions.MyModel.ValueInt.Set().Eq:
condition.StringField[MyModel] does not implement condition.ValueOfType[float64] (wrong type for method GetValue)
Returning
In cql.Update, the Returning method is also safe at compile time, allowing you to only obtain results in a list of the correct type:
1myModelsUpdated := []MyModel{}
2
3updatedCount, err := cql.Update[MyModel](
4 context.Background(),
5 db,
6 conditions.MyModel.ValueInt.Is().Eq(cql.Int64(2)),
7).Returning(&myModelsUpdated).Set(
8 conditions.MyModel.ValueInt.Set().Eq(cql.Int64(3)),
9)
1myModelsUpdated := []MyOtherModel{}
2
3updatedCount, err := cql.Update[MyModel](
4 context.Background(),
5 db,
6 conditions.MyModel.ValueInt.Is().Eq(cql.Int64(2)),
7).Returning(&myModelsUpdated).Set(
8 conditions.MyModel.ValueInt.Set().Eq(cql.Int64(3)),
9)
In this case, the compilation error will be:
cannot use &myModelsUpdated (value of type *[]MyOtherModel) as *[]MyModel value in argument to
cql.Update[MyModel](context.Background(), db, conditions.MyModel.ValueInt.Is().Eq(cql.Int64(2))).Returning
Null update
For fields that are nullable, such as pointers or null.* types, cql.Update will allow you to safely set their value to null at compile time, i.e., giving a compile-time error if you try to update a non-nullable attribute to null:
1type MyModel struct {
2 model.UUIDModel
3
4 ValueInt int
5 ValueIntPointer *int
6}
1updatedCount, err := cql.Update[MyModel](
2 context.Background(),
3 db,
4 conditions.MyModel.ValueInt.Is().Eq(cql.Int64(2)),
5).Set(
6 conditions.MyModel.ValueIntPointer.Set().Null(),
7)
1updatedCount, err := cql.Update[MyModel](
2 context.Background(),
3 db,
4 conditions.MyModel.ValueInt.Is().Eq(cql.Int64(2)),
5).Set(
6 conditions.MyModel.ValueInt.Set().Null(),
7)
In this case, the compilation error will be:
conditions.MyModel.ValueInt.Set().Null undefined
(type condition.FieldSet[MyModel, int] has no field or method Null)
Type safety limitations and cqllint
Dynamic sets
Once again, similar to cql.Query, Set is not safe at compile time to determine whether the values used in Eq or used in functions in Eq are joined in the query, as in the following examples:
1updatedCount, err := cql.Update[MyModel](
2 context.Background(),
3 db,
4 conditions.MyModel.ValueInt.Is().Eq(cql.Int64(2)),
5).Set(
6 conditions.MyModel.ValueInt.Set().Eq(conditions.MyOtherModel.Value1),
7)
1updatedCount, err := cql.Update[MyModel](
2 context.Background(),
3 db,
4 conditions.MyModel.ValueInt.Is().Eq(cql.Int64(2)),
5).Set(
6 conditions.MyModel.ValueInt.Set().Eq(conditions.MyModel.ValueInt.Plus(conditions.MyOtherModel.Value1)),
7)
Which would generate the following error at runtime:
field's model is not concerned by the query (not joined); not concerned model: models.MyOtherModel
Now, if we run cqllint we will see the following report:
$ cqllint ./...
example.go:6: MyOtherModel is not joined by the query
Repeated sets
While databases allow it, cql detects and generates a runtime error when attempting to set more than one value to the same attribute within an update:
1_, err := cql.Update[models.Brand](
2 context.Background(),
3 db,
4 conditions.Brand.Name.Is().Eq(cql.String("nike")),
5).Set(
6 conditions.Brand.Name.Set().Eq(cql.String("puma")),
7)
1_, err := cql.Update[models.Brand](
2 context.Background(),
3 db,
4 conditions.Brand.Name.Is().Eq(cql.String("nike")),
5).Set(
6 conditions.Brand.Name.Set().Eq(cql.String("adidas")),
7 conditions.Brand.Name.Set().Eq(cql.String("puma")),
8)
If we execute this query we will obtain an error of type cql.ErrFieldIsRepeated with the following message:
field is repeated; field: models.Brand.Name; method: Set
Now, if we run cqllint we will see the following report:
$ cqllint ./...
example.go:5: conditions.Brand.Name is repeated
example.go:6: conditions.Brand.Name is repeated
Set the same value
Although this case does not generate a runtime error, making a Set of exactly the same value is considered misuse and detected by cqllint:
1_, err := cql.Update[models.Brand](
2 context.Background(),
3 db,
4 conditions.Brand.Name.Is().Eq(cql.String("nike")),
5).Set(
6 conditions.Brand.Name.Set().Eq(conditions.Brand.Name),
7)
If we run cqllint we will see the following report:
$ cqllint ./...
example.go:6: conditions.Brand.Name is set to itself
Limit and order
As mentioned above, in MySQL it is possible to limit the number of rows updated using the Limit method. For it to work correctly, it is also necessary to add a call to a sorting method:
1_, err := cql.Update[models.Brand](
2 context.Background(),
3 db,
4 conditions.Brand.Name.Is().Eq(cql.String("nike")),
5).Ascending(
6 conditions.Brand.Name,
7).Limit(1).Set(
8 conditions.Brand.Name.Set().Eq(cql.String("puma")),
9)
1_, err := cql.Update[models.Brand](
2 context.Background(),
3 db,
4 conditions.Brand.Name.Is().Eq(cql.String("nike")),
5).Limit(1).Set(
6 conditions.Brand.Name.Set().Eq(cql.String("puma")),
7)
If we execute this query we will obtain an error of type cql.ErrOrderByMustBeCalled with the following message:
order by must be called before limit in an update statement; method: Limit
This error is not yet supported by cqllint.