Query type safety
The cql.Query method and its query system provide type safety in multiple cases.
Conditions of the model
cql will only allow us to add conditions on the model we are querying, prohibiting the use of conditions from other models in the wrong place:
1_, err := cql.Query[models.City](
2 context.Background(),
3 db,
4 conditions.City.Name.Is().Eq(cql.String("Paris")),
5).Find()
1_, err := cql.Query[models.City](
2 context.Background(),
3 db,
4 conditions.Country.Name.Is().Eq(cql.String("Paris")),
5).Find()
In this case, the compilation error will be:
cannot use conditions.Country.Name.Is().Eq(cql.String("Paris"))
(value of interface type condition.WhereCondition[models.Country]) as condition.Condition[models.City]...
Similarly, conditions are checked when making joins:
1_, err := cql.Query[models.City](
2 context.Background(),
3 db,
4 conditions.City.Country(
5 conditions.Country.Name.Is().Eq(cql.String("France")),
6 ),
7).Find()
1_, err := cql.Query[models.City](
2 context.Background(),
3 db,
4 conditions.City.Country(
5 conditions.City.Name.Is().Eq(cql.String("France")),
6 ),
7).Find()
Name of an attribute or operator
Since the conditions are made using the auto-generated code, the attributes and methods used on it will only allow us to use attributes and operators that exist:
1_, err := cql.Query[models.City](
2 context.Background(),
3 db,
4 conditions.City.Name.Is().Eq(cql.String("Paris")),
5).Find()
1_, err := cql.Query[models.City](
2 context.Background(),
3 db,
4 conditions.City.Namee.Is().Eq(cql.String("Paris")),
5).Find()
In this case, the compilation error will be:
conditions.City.Namee undefined (type conditions.cityConditions has no field or method Namee)
Type of an attribute
cql not only verifies that the attribute used exists but also verifies that the value compared to the attribute is of the correct type:
1_, err := cql.Query[models.City](
2 context.Background(),
3 db,
4 conditions.City.Name.Is().Eq(cql.String("Paris")),
5).Find()
1_, err := cql.Query[models.City](
2 context.Background(),
3 db,
4 conditions.City.Name.Is().Eq(cql.Int64(100)),
5).Find()
In this case, the compilation error will be:
cannot use cql.Int64(100) (value of struct type condition.NumericValue[int64]) as condition.ValueOfType[string]
value in argument to conditions.City.Name.Is().Eq: condition.NumericValue[int64] does not implement
condition.ValueOfType[string] (wrong type for method GetValue)
Type of an attribute (dynamic operator)
cql also checks that the type of the attributes is correct when using dynamic operators. In this case, the type of the two attributes being compared must be the same:
1_, err := cql.Query[models.City](
2 context.Background(),
3 db,
4 conditions.City.Country(
5 conditions.Country.Name.Is().Eq(conditions.City.Name),
6 ),
7).Find()
1_, err := cql.Query[models.City](
2 context.Background(),
3 db,
4 conditions.City.Country(
5 conditions.Country.Name.Is().Eq(conditions.City.Population),
6 ),
7).Find()
In this case, the compilation error will be:
cannot use conditions.City.Population (variable of type condition.UpdatableField[models.City, int]) as
condition.FieldOfType[string] value in argument to conditions.Country.Name.Is().Eq...
Type safety limitations and cqllint
Dynamic operators and functions
cql.Query is not safe at compile time to determine whether the values used in dynamic operators or functions, as in the following examples:
1_, err := cql.Query[models.City](
2 context.Background(),
3 db,
4 conditions.City.Country(
5 conditions.Country.Name.Is().Eq(conditions.City.Name),
6 ),
7).Find()
1_, err := cql.Query[models.City](
2 context.Background(),
3 db,
4 conditions.City.Name.Concat(
5 conditions.Country.Name,
6 ).Is().Eq(cql.String("error")),
7).Find()
1_, err := cql.Query[models.City](
2 context.Background(),
3 db,
4 conditions.City.Name.Is().Eq(
5 conditions.Country.Name,
6 ),
7).Find()
1_, err := cql.Query[models.City](
2 context.Background(),
3 db,
4 conditions.City.Name.Is().Eq(
5 conditions.City.Name.Concat(
6 conditions.Country.Name,
7 ),
8 ),
9).Find()
Which would generate the following error of type cql.ErrFieldModelNotConcerned at runtime:
field's model is not concerned by the query (not joined); not concerned model:
models.Country; operator: Eq; model: models.City, field: Name
Now, if we run cqllint we will see the following report:
$ cqllint ./...
example.go:5: models.Country is not joined by the query
Modifier methods
Similarly, the Descending and Ascending sorting modifier methods are not safe at compile time to terminate if the fields used are part of the query, as in the following examples:
1_, err := cql.Query[models.City](
2 context.Background(),
3 db,
4 conditions.City.Name.Is().Eq(cql.String("Paris")),
5).Descending(
6 conditions.City.Name,
7).Find()
1_, err := cql.Query[models.City](
2 context.Background(),
3 db,
4 conditions.City.Name.Is().Eq(cql.String("Paris")),
5).Descending(
6 conditions.Country.Name,
7).Find()
Which would generate the following error of type cql.ErrFieldModelNotConcerned at runtime:
field's model is not concerned by the query (not joined); not concerned model: models.Seller; method: Descending
Now, if we run cqllint we will see the following report:
$ cqllint ./...
example.go:6: models.Country is not joined by the query
Group by
Similarly, the GroupBy and Having methods are not safe at compile time to terminate if the fields used are part of the query, as in the following examples:
1results, err := cql.Select(
2 cql.Query[MyModel](
3 context.Background(),
4 db,
5 ).GroupBy(
6 conditions.MyModel.Name,
7 ),
8 cql.ValueInto(conditions.MyModel.Name, func(value string, result *Result) {
9 result.Name = value
10 }),
11 cql.ValueInto(conditions.MyModel.Status.Aggregate().Sum(), func(value float64, result *Result) {
12 result.SumStatus = int(value)
13 }),
14)
1results, err := cql.Select(
2 cql.Query[MyModel](
3 context.Background(),
4 db,
5 ).GroupBy(
6 conditions.MyOtherModel.Name,
7 ),
8 cql.ValueInto(conditions.MyModel.Name, func(value string, result *Result) {
9 result.Name = value
10 }),
11 cql.ValueInto(conditions.MyModel.Status.Aggregate().Sum(), func(value float64, result *Result) {
12 result.SumStatus = int(value)
13 }),
14)
Which would generate the following error of type cql.ErrFieldModelNotConcerned at runtime:
field's model is not concerned by the query (not joined); not concerned model: MyOtherModel; method: GroupBy
1results, err := cql.Select(
2 cql.Query[MyModel](
3 context.Background(),
4 db,
5 ).GroupBy(
6 conditions.MyModel.Name,
7 ).Having(
8 conditions.MyOtherModel.Status.Aggregate().Count().Gt(cql.Int(2)),
9 ),
10 cql.ValueInto(conditions.MyModel.Name, func(value string, result *Result) {
11 result.Name = value
12 }),
13 cql.ValueInto(conditions.MyModel.Status.Aggregate().Sum(), func(value float64, result *Result) {
14 result.SumStatus = int(value)
15 }),
16)
Which would generate the following error of type cql.ErrFieldModelNotConcerned at runtime:
field's model is not concerned by the query (not joined); not concerned model: MyOtherModel; method: Having
Now, if we run cqllint we will see the following report:
$ cqllint ./...
example.go:6: MyOtherModel is not joined by the query
Appearance
The selection of the Appearance can generate two runtime errors:
cql.ErrAppearanceMustBeSelected: generated when you try to use a model that appears (is joined) more than once in the query without selecting which one you want to use.
cql.ErrAppearanceOutOfRange: generated when you try select an appearance number (with the Appearance method) greater than the number of appearances of a model.
Both errors can be determined before runtime using cqllint.
ErrAppearanceMustBeSelected
To generate this error we must join the same model more than once and not select the appearance number:
1_, err := cql.Query[models.Child](
2 context.Background(),
3 db,
4 conditions.Child.Parent1(
5 conditions.Parent1.ParentParent(),
6 ),
7 conditions.Child.Parent2(
8 conditions.Parent2.ParentParent(),
9 ),
10 conditions.Child.ID.Is().Eq(conditions.ParentParent.ID),
11).Find()
If we execute this query we will obtain an error of type cql.ErrAppearanceMustBeSelected with the following message:
field's model appears more than once, select which one you want to use with Appearance; model: models.ParentParent; operator: Eq; model: models.Child, field: ID
Now, if we run cqllint we will see the following report:
$ cqllint ./...
example.go:10: models.ParentParent appears more than once, select which one you want to use with Appearance
ErrAppearanceOutOfRange
To generate this error we must use the Appearance method with a value greater than the number of appearances of a model:
1_, err := cql.Query[models.Phone](
2 context.Background(),
3 db,
4 conditions.Phone.Brand(
5 conditions.Brand.Name.Is().Eq(conditions.Phone.Name.Appearance(1)),
6 ),
7).Find()
If we execute this query we will obtain an error of type cql.ErrAppearanceOutOfRange with the following message:
selected appearance is bigger than field's model number of appearances; model: models.Phone; operator: Eq; model: models.Brand, field: Name
Now, if we run cqllint we will see the following report:
$ cqllint ./...
example.go:5: selected appearance is bigger than models.Phone's number of appearances
Unnecessary Appearance selection
This is the case when the Appearance method is used without being necessary, i.e. when the model appears only once:
1_, err := cql.Query[models.Phone](
2 context.Background(),
3 db,
4 conditions.Phone.Brand(
5 conditions.Brand.Name.Is().Eq(conditions.Phone.Name.Appearance(0)),
6 ),
7).Find()
If we run cqllint we will see the following report:
$ cqllint ./...
example.go:5: Appearance call not necessary, models.Phone appears only once
Collections preloads
Another possible runtime error is cql.ErrOnlyPreloadsAllowed, generated when trying to use conditions within a preload of Collections:
type Seller struct {
model.UUIDModel
Name string
Company *Company
CompanyID *model.UUID // Company HasMany Seller (Company 0..1 -> 0..* Seller)
}
type Company struct {
model.UUIDModel
Sellers *[]Seller // Company HasMany Seller (Company 0..1 -> 0..* Seller)
}
1_, err := cql.Query[models.Company](
2 context.Background(),
3 db,
4 conditions.Company.Sellers.Preload(),
5).Find()
1_, err := cql.Query[models.Company](
2 context.Background(),
3 db,
4 conditions.Company.Sellers.Preload(
5 conditions.Seller.ID.Is().Eq(cql.String("Franco")),
6 ),
7).Find()
This error has not yet been determined by cqllint.