Select

If you only want certain attributes from the models as query results, cql provides cql.Select.

This function allows us to take the results of a query received by parameter and select only certain attributes, both from the main query model and from the joined models.

To perform this selection, the cql.ValueInto function is used, which receives the field to be selected and a function to save that field in the results list.

Example 1: Select only one field from the main model

Model
type MyModel struct {
    model.UUIDModel

    Value1 int64
    Value2 string
}

type Results struct {
    Value1 int64
}
results, err := cql.Select(
    cql.Query[MyModel](
        context.Background(),
        db,
        conditions.MyModel.Value1.Is().Eq(cql.Int64(4)),
    ),
    cql.ValueInto(conditions.MyModel.Value1, func(value float64, result *Results) {
        result.Value1 = int64(value)
    }),
)

Example 2: Select more than one field from the main model

Model
type MyModel struct {
    model.UUIDModel

    Value1 int64
    Value2 String
}

type Results struct {
    Value1 int64
    Value2 String
}
results, err := cql.Select(
    cql.Query[MyModel](
        context.Background(),
        db,
        conditions.MyModel.Value1.Is().Eq(cql.Int64(4)),
    ),
    cql.ValueInto(conditions.MyModel.Value1, func(value float64, result *Results) {
        result.Value1 = int64(value)
    }),
    cql.ValueInto(conditions.MyModel.Value2, func(value string, result *Results) {
        result.Value2 = value
    }),
)

Joins

It is possible to select different attributes from the different entities joined in the queries:

Model
 type MyOtherModel struct {
    model.UUIDModel

    Name string
}

type MyModel struct {
    model.UUIDModel

    Value1 int64

    Related   MyOtherModel
    RelatedID model.UUID
}

type Results struct {
    Value1 int64
    Name String
}
results, err := cql.Select(
    cql.Query[MyModel](
        context.Background(),
        db,
        conditions.MyModel.Value1.Is().Eq(cql.Int64(4)),
        conditions.MyModel.Related(),
    ),
    cql.ValueInto(conditions.MyModel.Value1, func(value float64, result *Results) {
        result.Value1 = int64(value)
    }),
    cql.ValueInto(conditions.MyOtherModel.Name, func(value string, result *Results) {
        result.Name = value
    }),
)

Functions

cql supports applying functions to selected values before retrieving them, either with static values or with other attributes. For more details on the available functions, please consult functions.

Example 1: Function with static value

In this case, we will add 2 to the values obtained.

Model
type MyModel struct {
    model.UUIDModel

    Value1 int64
}

type Results struct {
    Value1 int64
}
results, err := cql.Select(
    cql.Query[MyModel](
        context.Background(),
        db,
        conditions.MyModel.Value1.Is().Eq(cql.Int64(4)),
    ),
    cql.ValueInto(conditions.MyModel.Value1.Plus(cql.Int64(2)), func(value float64, result *Results) {
        result.Value1 = int64(value)
    }),
)

Example 2: Function with other attribute

In this case, we will add two attributes.

Model
type MyModel struct {
    model.UUIDModel

    Value1 int64
    Value2 int64
}

type Results struct {
    Value1PlusValue2 int64
}
results, err := cql.Select(
    cql.Query[MyModel](
        context.Background(),
        db,
        conditions.MyModel.Value1.Is().Eq(cql.Int64(4)),
    ),
    cql.ValueInto(conditions.MyModel.Value1.Plus(conditions.MyModel.Value2), func(value float64, result *Results) {
        result.Value1PlusValue2 = int64(value)
    }),
)

Aggregations

When selecting, it is also possible to perform aggregations on the values. The available aggregations depend on the type of attribute.

The aggregations available for all types are:

  • Count: returns the number of values that are not null.

  • Min: returns the minimum value of all values.

  • Max: returns the maximum value of all values.

For numeric attributes, the following aggregations are also available:

  • Sum: calculates the summation of all values.

  • Average: calculates the average (arithmetic mean) of all values.

  • And: calculates the bitwise AND of all non-null values (null values are ignored). Not available for: sqlite, sqlserver.

  • Or: calculates the bitwise OR of all non-null values (null values are ignored). Not available for: sqlite, sqlserver.

For boolean attributes, the following aggregations are also available:

  • All: returns true if all the values are true.

  • Any: returns true if at least one value is true.

  • None: returns true if all values are false.

Example:

Model
type MyModel struct {
    model.UUIDModel

    Value1 int64
    Value2 int64
}

type Results struct {
    Value1Sum int64
    Value2Max int64
}
results, err := cql.Select(
    cql.Query[MyModel](
        context.Background(),
        db,
        conditions.MyModel.Value1.Is().Eq(cql.Int64(4)),
    ),
    cql.ValueInto(conditions.MyModel.Value1.Aggregate().Sum(), func(value float64, result *Results) {
        result.Value1Sum = int64(value)
    }),
    cql.ValueInto(conditions.MyModel.Value2.Aggregate().Max(), func(value float64, result *Results) {
        result.Value2Max = int64(value)
    }),
)

Warning

Aggregations and non-aggregations cannot be combined within the same select.

Type safety

Select, in addition to inheriting the type safety of Query, adds a new layer of type safety to selections.

Selection type

cql.ValueInto ensures that the type of the selected attribute is stored in a result of the correct type.

Note that selections of all numeric types are of type float64, which must then be cast to the desired type if it is different.

Model
type MyModel struct {
    model.UUIDModel

    Value1 int64
    Value2 string
}

type Results struct {
    Value1 int64
}
Correct
1results, err := cql.Select(
2    cql.Query[MyModel](
3        context.Background(),
4        db,
5    ),
6    cql.ValueInto(conditions.MyModel.Value1, func(value float64, result *Results) {
7        result.Value1 = int64(value)
8    }),
9)
Incorrect 1
1results, err := cql.Select(
2    cql.Query[MyModel](
3        context.Background(),
4        db,
5    ),
6    cql.ValueInto(conditions.MyModel.Value2, func(value float64, result *Results) {
7        result.Value1 = value
8    }),
9)

In this case, the compilation error will be:

in call to cql.ValueInto, type func(value float64, result *Results) of (func(value float64, result *ResultInt) literal)
does not match inferred type func(string, *TResults) for func(TValue, *TResults)
Incorrect 2
1results, err := cql.Select(
2    cql.Query[MyModel](
3        context.Background(),
4        db,
5    ),
6    cql.ValueInto(conditions.MyModel.Value2, func(value string, result *Results) {
7        result.Value1 = value
8    }),
9)

In this case, the compilation error will be:

cannot use value (variable of type string) as int64 value in assignment

ValueInto functions

As in cql.Query, the functions applied to the selected values are type-safe.

Model
type MyModel struct {
    model.UUIDModel

    Value1 int64
    Value2 string
    Value3 int64
}

type Results struct {
    Value1 int64
}
Correct
1results, err := cql.Select(
2    cql.Query[MyModel](
3        context.Background(),
4        db,
5    ),
6    cql.ValueInto(conditions.MyModel.Value1.Plus(conditions.MyModel.Value3), func(value float64, result *Results) {
7        result.Value1 = int64(value)
8    }),
9)
Incorrect
1results, err := cql.Select(
2    cql.Query[MyModel](
3        context.Background(),
4        db,
5    ),
6    cql.ValueInto(conditions.MyModel.Value1.Plus(conditions.MyModel.Value2), func(value float64, result *Results) {
7        result.Value1 = value
8    }),
9)

In this case, the compilation error will be:

cannot use conditions.MyModel.Value2 (variable of struct type condition.StringField[MyModel])
as condition.ValueOfType[float64] value in argument to conditions.MyModel.Value1.Plus: condition.StringField[MyModel]
does not implement condition.ValueOfType[float64] (wrong type for method GetValue)

Type safety limitations and cqllint

Once again, similar to cql.Query, cql.ValueInto is not safe at compile time to determine whether the values selected or used in functions are joined in the query, as in the following examples:

Incorrect
1results, err := cql.Select(
2    cql.Query[MyModel](
3        context.Background(),
4        db,
5    ),
6    cql.ValueInto(conditions.MyOtherModel.Value1, func(value float64, result *Results) {
7        result.Value1 = value
8    }),
9)
Incorrect
1results, err := cql.Select(
2    cql.Query[MyModel](
3        context.Background(),
4        db,
5    ),
6    cql.ValueInto(conditions.MyModel.Value1.Plus(conditions.MyOtherModel.Value2), func(value float64, result *Results) {
7        result.Value1 = value
8    }),
9)
Incorrect
1results, err := cql.Select(
2    cql.Query[MyModel](
3        context.Background(),
4        db,
5    ),
6    cql.ValueInto(conditions.MyOtherModel.Value1.Aggregate().Sum(), func(value float64, result *Results) {
7        result.Value1 = value
8    }),
9)

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: models.MyOtherModel is not joined by the query