Type safety

Compile time safety

One of the most important features of the CQL is

Is compile-time safe:
    queries are validated at compile time to avoid errors
    such as comparing attributes that are of different types,
    trying to use attributes or navigate relationships that do not exist,
    using information from tables that are not included in the query, etc.;
    ensuring that a runtime error will not be raised.

While there are other libraries that provide an API type safety (gorm-gen, jooq (Java), diesel (Rust)), CQL is the only one that allows us to be sure that the generated query is correct, (almost) avoiding runtime errors (to understand why “almost” see Runtime errors)

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:

Correct
1_, err := cql.Query[models.City](
2    db,
3    conditions.City.Name.Is().Eq("Paris"),
4).Find()
Incorrect
1_, err := cql.Query[models.City](
2    db,
3    conditions.Country.Name.Is().Eq("Paris"),
4).Find()

In this case, the compilation error will be:

cannot use conditions.Country.Name.Is().Eq("Paris")
(value of type condition.WhereCondition[models.Country]) as condition.Condition[models.City]...

Similarly, conditions are checked when making joins:

Correct
1_, err := cql.Query[models.City](
2    db,
3    conditions.City.Country(
4        conditions.Country.Name.Is().Eq("France"),
5    ),
6).Find()
Incorrect
1_, err := cql.Query[models.City](
2    db,
3    conditions.City.Country(
4        conditions.City.Name.Is().Eq("France"),
5    ),
6).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:

Correct
1_, err := cql.Query[models.City](
2    db,
3    conditions.City.Name.Is().Eq("Paris"),
4).Find()
Incorrect
1_, err := cql.Query[models.City](
2    db,
3    conditions.City.Namee.Is().Eq("Paris"),
4).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:

Correct
1_, err := cql.Query[models.City](
2    db,
3    conditions.City.Name.Is().Eq("Paris"),
4).Find()
Incorrect
1_, err := cql.Query[models.City](
2    db,
3    conditions.City.Name.Is().Eq(100),
4).Find()

In this case, the compilation error will be:

cannot use 100 (untyped int constant) as string value in argument to conditions.City.Name.Is().Eq

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:

Correct
1_, err := cql.Query[models.City](
2    db,
3    conditions.City.Country(
4        conditions.Country.Name.IsDynamic().Eq(conditions.City.Name.Value()),
5    ),
6).Find()
Incorrect
1_, err := cql.Query[models.City](
2    db,
3    conditions.City.Country(
4        conditions.Country.Name.IsDynamic().Eq(conditions.City.Population.Value()),
5    ),
6).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.IsDynamic().Eq...

Runtime errors

Although all the above checks are at compile-time, there are still some possible cases that generate the following run-time errors:

  • cql.ErrFieldModelNotConcerned (1): generated when trying to use a model that is not related to the rest of the query (not joined).

  • cql.ErrAppearanceMustBeSelected (1): 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 (see Appearance).

  • cql.ErrAppearanceOutOfRange (1): generated when you try select an appearance number (with the Appearance method) greater than the number of appearances of a model. (see Appearance).

  • cql.ErrFieldIsRepeated (1): generated when a field is repeated inside a Set call (see Update).

  • cql.ErrOnlyPreloadsAllowed: generated when trying to use conditions within a preload of collections (see Collections).

  • cql.ErrUnsupportedByDatabase: generated when an attempt is made to use a method or function that is not supported by the database engine used.

  • cql.ErrOrderByMustBeCalled: generated when in MySQL you try to do a delete/update with Limit but without using OrderBy.

Note

(1) errors avoided with cqllint.

However, these errors are discovered by CQL before the query is executed. In addition, CQL will add to the error clear information about the problem so that it is easy to fix, for example:

Query
1_, err := cql.Query[models.Product](
2    ts.db,
3    conditions.Product.Int.Is().Eq(1),
4).Descending(conditions.Seller.ID).Find()
5
6fmt.Println(err)
Result
field's model is not concerned by the query (not joined); not concerned model: models.Seller; method: Descending