Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic
Versioning](http://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- `CountDistinct` finisher to count the distinct values of a column (`COUNT(DISTINCT column)`).

## [4.19.7] - 2025-12-31

### Fixed
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1356,6 +1356,7 @@ models.Pilots().All(ctx, db)
One() // Retrieve one row as object (same as LIMIT(1))
All() // Retrieve all rows as objects (same as SELECT * FROM)
Count() // Number of rows (same as COUNT(*))
CountDistinct("name") // Number of distinct values in a column (same as COUNT(DISTINCT name))
UpdateAll(models.M{"name": "John", "age": 23}) // Update all rows matching the built query.
DeleteAll() // Delete all rows matching the built query.
Exists() // Returns a bool indicating whether the row(s) for the built query exists.
Expand Down
54 changes: 54 additions & 0 deletions templates/main/03_finishers.go.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,60 @@ func (q {{$alias.DownSingular}}Query) Count({{if .NoContext}}exec boil.Executor{
return count, nil
}

{{if .AddGlobal -}}
// CountDistinctG returns the count of distinct values of the given column in the query using the global executor.
func (q {{$alias.DownSingular}}Query) CountDistinctG({{if not .NoContext}}ctx context.Context, {{end}}column string) (int64, error) {
return q.CountDistinct({{if .NoContext}}boil.GetDB(){{else}}ctx, boil.GetContextDB(){{end}}, column)
}

{{end -}}

{{if and .AddGlobal .AddPanic -}}
// CountDistinctGP returns the count of distinct values of the given column in the query using the global executor, and panics on error.
func (q {{$alias.DownSingular}}Query) CountDistinctGP({{if not .NoContext}}ctx context.Context, {{end}}column string) int64 {
c, err := q.CountDistinct({{if .NoContext}}boil.GetDB(){{else}}ctx, boil.GetContextDB(){{end}}, column)
if err != nil {
panic(boil.WrapErr(err))
}

return c
}

{{end -}}

{{if .AddPanic -}}
// CountDistinctP returns the count of distinct values of the given column in the query, and panics on error.
func (q {{$alias.DownSingular}}Query) CountDistinctP({{if .NoContext}}exec boil.Executor{{else}}ctx context.Context, exec boil.ContextExecutor{{end}}, column string) int64 {
c, err := q.CountDistinct({{if not .NoContext}}ctx, {{end -}} exec, column)
if err != nil {
panic(boil.WrapErr(err))
}

return c
}

{{end -}}

// CountDistinct returns the count of distinct values of the given column in the query.
func (q {{$alias.DownSingular}}Query) CountDistinct({{if .NoContext}}exec boil.Executor{{else}}ctx context.Context, exec boil.ContextExecutor{{end}}, column string) (int64, error) {
var count int64

queries.SetSelect(q.Query, nil)
queries.SetCount(q.Query)
queries.SetDistinct(q.Query, column)

{{if .NoContext -}}
err := q.Query.QueryRow(exec).Scan(&count)
{{else -}}
err := q.Query.QueryRowContext(ctx, exec).Scan(&count)
{{end -}}
if err != nil {
return 0, errors.Wrap(err, "{{.PkgName}}: failed to count distinct {{.Table.Name}} rows")
}

return count, nil
}

{{if .AddGlobal -}}
// ExistsG checks if the row exists in the table using the global executor.
func (q {{$alias.DownSingular}}Query) ExistsG({{if not .NoContext}}ctx context.Context{{end}}) (bool, error) {
Expand Down
35 changes: 35 additions & 0 deletions templates/test/finishers.go.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,38 @@ func test{{$alias.UpPlural}}Count(t *testing.T) {
t.Error("want 2 records, got:", count)
}
}

func test{{$alias.UpPlural}}CountDistinct(t *testing.T) {
t.Parallel()

var err error
seed := randomize.NewSeed()
{{$alias.DownSingular}}One := &{{$alias.UpSingular}}{}
{{$alias.DownSingular}}Two := &{{$alias.UpSingular}}{}
if err = randomize.Struct(seed, {{$alias.DownSingular}}One, {{$alias.DownSingular}}DBTypes, false, {{$alias.DownSingular}}ColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize {{$alias.UpSingular}} struct: %s", err)
}
if err = randomize.Struct(seed, {{$alias.DownSingular}}Two, {{$alias.DownSingular}}DBTypes, false, {{$alias.DownSingular}}ColumnsWithDefault...); err != nil {
t.Errorf("Unable to randomize {{$alias.UpSingular}} struct: %s", err)
}

{{if not .NoContext}}ctx := context.Background(){{end}}
tx := MustTx({{if .NoContext}}boil.Begin(){{else}}boil.BeginTx(ctx, nil){{end}})
defer func() { _ = tx.Rollback() }()
if err = {{$alias.DownSingular}}One.Insert({{if not .NoContext}}ctx, {{end -}} tx, boil.Infer()); err != nil {
t.Error(err)
}
if err = {{$alias.DownSingular}}Two.Insert({{if not .NoContext}}ctx, {{end -}} tx, boil.Infer()); err != nil {
t.Error(err)
}

// Two rows were inserted, so a distinct count of any single column is between 1 and 2.
count, err := {{$alias.UpPlural}}().CountDistinct({{if not .NoContext}}ctx, {{end -}} tx, "{{ (index $.Table.Columns 0).Name | $.Quotes }}")
if err != nil {
t.Error(err)
}

if count < 1 || count > 2 {
t.Error("want a distinct count between 1 and 2, got:", count)
}
}
10 changes: 10 additions & 0 deletions templates/test/singleton/boil_suites_test.go.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,16 @@ func TestCount(t *testing.T) {
{{- end -}}
}

func TestCountDistinct(t *testing.T) {
{{- range .Tables}}
{{- if or .IsJoinTable .IsView -}}
{{- else -}}
{{- $alias := $.Aliases.Table .Name -}}
t.Run("{{$alias.UpPlural}}", test{{$alias.UpPlural}}CountDistinct)
{{end -}}
{{- end -}}
}

{{if not .NoHooks -}}
func TestHooks(t *testing.T) {
{{- range .Tables}}
Expand Down