Make database changes (queries, migrations, and tests) explicit about shape and semantics: don’t rely on driver defaults, cached plan inference, or “looks right” reads when the DB can mask the truth.
Apply this as a checklist:
1) Prove correctness with real row effects
2) Migrations must use explicit projections and live-schema guards
SELECT * inside migrations where earlier DDL in the same run can change column sets/types.3) Index/DDL changes should be concurrency- and transaction-safe
CONCURRENTLY, create it outside transactional migration blocks (or via a post-startup mechanism) to avoid blocking/invalid transaction constraints.4) Update paths must match the monotonic invariants
< vs <= depending on whether unchanged boundaries must still persist).5) Deterministic batching
LIMIT, add an explicit ORDER BY so each batch removes the intended rows.Example (deterministic batch delete with correct counting):
func (s *ClickHouseLogStore) DeleteLogsBatch(ctx context.Context, cutoff time.Time, batchSize int) (int64, error) {
var ids []string
if err := s.db.WithContext(ctx).
Model(&Log{}).
Select("id").
Where("created_at < ?", cutoff).
Order("created_at ASC").
Limit(batchSize).
Pluck("id", &ids).Error; err != nil {
return 0, err
}
res := s.db.WithContext(ctx).Where("id IN (?)", ids).Delete(&Log{})
_ = res // affected rows may be driver-rewritten; rely on len(ids)
return int64(len(ids)), res.Error
}
Outcome: DB code and migrations become robust to schema drift, driver quirks, and boundary/dedup semantics—so correctness is both provable and repeatable across environments.
Enter the URL of a public GitHub repository