cqllint

cqllint is a Go linter that checks that cql queries will not generate run-time errors.

While, in most cases, queries created using cql are checked at compile time, there are still some cases that can generate run-time errors (see Runtime errors).

cqllint analyses the Go code written to detect these cases and fix them without the need to execute the query. It also adds other detections that would not generate runtime errors but are possible misuses of cql.

Note

At the moment, only the errors cql.ErrFieldModelNotConcerned, cql.ErrFieldIsRepeated, cql.ErrAppearanceMustBeSelected and cql.ErrAppearanceOutOfRange are detected.

We recommend integrating cqllint into your CI so that the use of cql ensures 100% that your queries will be executed correctly.

Installation

For simply installing it, use:

go install github.com/FrancoLiberali/cql/cqllint@latest

Warning

The version of cqllint used must be the same as the version of cql. You can install a specific version using go install github.com/FrancoLiberali/cql/cqllint@vX.Y.Z, where X.Y.Z is the version number.

Execution

cqllint can be used independently by running:

cqllint ./...

or using go vet:

go vet -vettool=$(which cqllint) ./...

Errors

ErrFieldModelNotConcerned

The simplest example this error case is trying to make a comparison with an attribute of a model that is not joined by the query:

example.go
1_, err := cql.Query[models.Brand](
2    db,
3    conditions.Brand.Name.IsDynamic().Eq(conditions.City.Name.Value()),
4).Find()

If we execute this query we will obtain an error of type cql.ErrFieldModelNotConcerned with the following message:

field's model is not concerned by the query (not joined); not concerned model: models.City; operator: Eq; model: models.Brand, field: Name

Now, if we run cqllint we will see the following report:

$ cqllint ./...
example.go:3: models.City is not joined by the query

ErrFieldIsRepeated

The simplest example this error case is trying to set the value of an attribute twice:

example.go
1_, err := cql.Update[models.Brand](
2    db,
3    conditions.Brand.Name.Is().Eq("nike"),
4).Set(
5    conditions.Brand.Name.Set().Eq("adidas"),
6    conditions.Brand.Name.Set().Eq("puma"),
7)

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

ErrAppearanceMustBeSelected

To generate this error we must join the same model more than once and not select the appearance number:

example.go
 1_, err := cql.Query[models.Child](
 2    db,
 3    conditions.Child.Parent1(
 4        conditions.Parent1.ParentParent(),
 5    ),
 6    conditions.Child.Parent2(
 7        conditions.Parent2.ParentParent(),
 8    ),
 9    conditions.Child.ID.IsDynamic().Eq(conditions.ParentParent.ID.Value()),
10).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:9: 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:

example.go
1_, err := cql.Query[models.Phone](
2    db,
3    conditions.Phone.Brand(
4        conditions.Brand.Name.IsDynamic().Eq(conditions.Phone.Name.Appearance(1).Value()),
5    ),
6).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:4: selected appearance is bigger than models.Phone's number of appearances

Misuses

Although some cases would not generate runtime errors, cqllint will detect them as they are possible misuses of cql.

Set the same value

This case occurs when making a Set of exactly the same value:

example.go
1_, err := cql.Update[models.Brand](
2    db,
3    conditions.Brand.Name.Is().Eq("nike"),
4).Set(
5    conditions.Brand.Name.Set().Dynamic(conditions.Brand.Name.Value()),
6)

If we run cqllint we will see the following report:

$ cqllint ./...
example.go:5: conditions.Brand.Name is set to itself

Unnecessary Appearance selection

This is the case when the Appearance method is used without being necessary, i.e. when the model appears only once:

example.go
1_, err := cql.Query[models.Phone](
2    db,
3    conditions.Phone.Brand(
4        conditions.Brand.Name.IsDynamic().Eq(conditions.Phone.Name.Appearance(0).Value()),
5    ),
6).Find()

If we run cqllint we will see the following report:

$ cqllint ./...
example.go:4: Appearance call not necessary, models.Phone appears only once