Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize Count #395

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
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
15 changes: 8 additions & 7 deletions connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@ var Connections = map[string]*Connection{}

// Connection represents all necessary details to talk with a datastore
type Connection struct {
ID string
Store store
Dialect dialect
Elapsed int64
TX *Tx
eager bool
eagerFields []string
ID string
Store store
Dialect dialect
Elapsed int64
TX *Tx
eager bool
eagerFields []string
OptimizeCount bool
}

func (c *Connection) String() string {
Expand Down
35 changes: 31 additions & 4 deletions finders.go
Original file line number Diff line number Diff line change
Expand Up @@ -348,10 +348,32 @@ func (q Query) CountByField(model interface{}, field string) (int, error) {
tmpQuery.Paginator = nil
tmpQuery.orderClauses = clauses{}
tmpQuery.limitResults = 0
query, args := tmpQuery.ToSQL(&Model{Value: model})
// when query contains custom selected fields / executed using RawQuery,
// sql may already contains limit and offset

var query, countQuery string
var args []interface{}
var isRaw bool

if tmpQuery.RawSQL != nil && tmpQuery.RawSQL.Fragment != "" {
isRaw = true
}

// Count can't be optimized if the query contains raw SQL due to ToSQL internals
if tmpQuery.OptimizeCount && !isRaw {
tmpQuery.addColumns = []string{} // Optimizing Count means giving up selecting any distinct columns.
// This can be changed in the future but will also have to address the issue of
// table aliasing in column names -- AKA reevaluating how Model.ignoreTableName works.
query, args = tmpQuery.ToSQL(&Model{Value: model, ignoreTableName: true},
fmt.Sprintf("COUNT(%s) as row_count", field))
} else {
if tmpQuery.OptimizeCount && isRaw {
log(logging.Warn, "Query contains raw SQL; COUNT cannot be optimized")
}

query, args = tmpQuery.ToSQL(&Model{Value: model})
}

//when query contains custom selected fields / executed using RawQuery,
// sql may already contains limit and offset
if rLimitOffset.MatchString(query) {
foundLimit := rLimitOffset.FindString(query)
query = query[0 : len(query)-len(foundLimit)]
Expand All @@ -360,7 +382,12 @@ func (q Query) CountByField(model interface{}, field string) (int, error) {
query = query[0 : len(query)-len(foundLimit)]
}

countQuery := fmt.Sprintf("SELECT COUNT(%s) AS row_count FROM (%s) a", field, query)
if tmpQuery.OptimizeCount {
countQuery = query
} else {
countQuery = fmt.Sprintf("SELECT COUNT(%s) AS row_count FROM (%s) a", field, query)
}

log(logging.SQL, countQuery, args...)
return q.Connection.Store.Get(res, countQuery, args...)
})
Expand Down
34 changes: 34 additions & 0 deletions finders_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -727,6 +727,40 @@ func Test_Count(t *testing.T) {
})
}

func Test_Count_Optimized(t *testing.T) {
if PDB == nil {
t.Skip("skipping integration tests")
}
transaction(func(tx *Connection) {
r := require.New(t)

tx.OptimizeCount = true

user := User{Name: nulls.NewString("Dylan")}
err := tx.Create(&user)
r.NoError(err)
c, err := tx.Count(&user)
r.NoError(err)
r.Equal(c, 1)

c, err = tx.Where("1=1").CountByField(&user, "distinct id")
r.NoError(err)
r.Equal(c, 1)
// should ignore order in count

c, err = tx.Order("id desc").Count(&user)
r.NoError(err)
r.Equal(c, 1)

var uAQ []UsersAddressQuery
_, err = Q(tx).Select("users_addresses.*").LeftJoin("users", "users.id=users_addresses.user_id").Count(&uAQ)
r.NoError(err)

_, err = Q(tx).Select("users_addresses.*", "users.name", "users.email").LeftJoin("users", "users.id=users_addresses.user_id").Count(&uAQ)
r.NoError(err)
})
}

func Test_Count_Disregards_Pagination(t *testing.T) {
if PDB == nil {
t.Skip("skipping integration tests")
Expand Down
5 changes: 3 additions & 2 deletions model.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ type modelIterable func(*Model) error
// that is passed in to many functions.
type Model struct {
Value
tableName string
As string
tableName string
As string
ignoreTableName bool
}

// ID returns the ID of the Model. All models must have an `ID` field this is
Expand Down
3 changes: 3 additions & 0 deletions query.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type Query struct {
havingClauses havingClauses
Paginator *Paginator
Connection *Connection
OptimizeCount bool
}

// Clone will fill targetQ query with the connection used in q, if
Expand All @@ -42,6 +43,7 @@ func (q *Query) Clone(targetQ *Query) {
targetQ.groupClauses = q.groupClauses
targetQ.havingClauses = q.havingClauses
targetQ.addColumns = q.addColumns
targetQ.OptimizeCount = q.OptimizeCount

if q.Paginator != nil {
paginator := *q.Paginator
Expand Down Expand Up @@ -196,6 +198,7 @@ func Q(c *Connection) *Query {
eager: c.eager,
eagerFields: c.eagerFields,
eagerMode: eagerModeNil,
OptimizeCount: c.OptimizeCount,
}
}

Expand Down
5 changes: 4 additions & 1 deletion sql_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,10 +216,13 @@ var columnCacheMutex = sync.RWMutex{}

func (sq *sqlBuilder) buildColumns() columns.Columns {
tableName := sq.Model.TableName()

asName := sq.Model.As
if asName == "" {
// If asName is not explicitly set and ignoreTableName is set, then don't us an AS name (alias)
if asName == "" && !sq.Model.ignoreTableName {
asName = strings.Replace(tableName, ".", "_", -1)
}

acl := len(sq.AddColumns)
if acl == 0 {
columnCacheMutex.RLock()
Expand Down