Strict mode and debugging

Table of contents
  1. Strict mode and debugging
    1. Preventing lazy loading
      1. What is always strict
    2. Listening to queries
    3. The query log
    4. Counting queries in tests

Preventing lazy loading

The usual N+1 bug is a loop that touches a relation:

for _, post := range posts {
    author, _ := post.Author.Get(ctx)   // one query per post
}

Turn on strict mode and that line fails instead:

db.ShouldBeStrict(true)   // Eloquent's Model::shouldBeStrict()

_, err := post.Author.Get(ctx)
// migro.CodeOf(err) == migro.LazyLoadingViolation
// "lazy loading Post.Author is disabled: eager load it with With("Author") or orm.Load"

Only the implicit load is refused. A relation loaded with With, orm.Load or Collection.Load reads normally, and post.Author.Query() still builds a query on request. A model that is not persisted yet has nothing to lazy load, so it is exempt.

Enable it in development and tests, and log in production:

db.SetStrict(migro.Strict{
    LazyLoading: true,
    OnLazyLoad: func(model, relation string) { log.Printf("lazy load %s.%s", model, relation) },
})

When OnLazyLoad is set it is called and the load goes ahead, so a violation never breaks a request. Transactions and WriteDB() handles share the setting.

What is always strict

Eloquent also has preventSilentlyDiscardingAttributes and preventAccessingMissingAttributes. migro needs neither switch: Fill fails with MASS_ASSIGNMENT for a column that is guarded or does not exist, and GetAttribute fails with INVALID_SCHEMA for a missing column.

Listening to queries

off := db.Listen(func(e migro.QueryEvent) {
    log.Printf("%s  %d rows  %s", e.SQL, e.Rows, e.Duration)
})
defer off()

QueryEvent carries SQL (with bindings inlined), Rows, Duration and Err. The listener runs on the goroutine that ran the statement, so keep it fast. Your own GORM logger keeps working: migro wraps it.

The query log

db.EnableQueryLog()
// ... run code ...
for _, q := range db.QueryLog() { fmt.Println(q.SQL) }
db.FlushQueryLog()
db.DisableQueryLog()

Counting queries in tests

import "github.com/LouisRiverstone/migro/dbtest"

dbtest.AssertQueryCount(t, db, 2, func() {
    posts, _ := orm.New[Post](db).Query().With("Author").Get(ctx)   // posts + authors
    _ = posts
})

See Testing for the other assertions.