GORM Playground Link
https://github.com/go-gorm/playground/pull/812
Description
Since upgrading to Gorm v1.30.0, Preload
with a custom scope that includes an OR
condition generates a malformed SQL WHERE
clause. The conditions from the scope are duplicated and incorrectly grouped with the foreign key condition, leading to unexpected query results.
This issue was not present in previous versions (e.g., v1.26.1), where the same code produced the correct SQL.
To Reproduce
The following self-contained Go code demonstrates the issue. It defines two models with a one-to-many relationship and attempts to preload the "many" side using a scope with an OR
condition.
package main
import (
"fmt"
"log"
"os"
"path/filepath"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/plugin/soft_delete"
)
// --- Models ---
type Dataset struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"comment:Name;size:30;index;not null;"`
Columns []*DatasetColumn
}
type DatasetColumn struct {
ID uint `gorm:"primaryKey"`
DatasetID uint
Dataset Dataset
DeletedAt soft_delete.DeletedAt `gorm:"comment:删除时间;uniqueIndex:idx_dc_ddn;not null;"`
Name string `gorm:"comment:名称;size:30;uniqueIndex:idx_dc_ddn;not null;"`
IsSystem bool `gorm:"comment:是否系统列;not null;default:false;"`
IsHidden bool `gorm:"comment:是否隐藏;not null;"`
}
func Preload(query string, args ...interface{}) func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
return db.Preload(query, args...)
}
}
func QueryColumnsFilter() func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
return db.Where(db.Where("is_system = ?", true).Or("is_hidden = ?", false))
}
}
func main() {
db, err := gorm.Open(sqlite.Open(filepath.Join(os.TempDir(), "gorm.db")), &gorm.Config{})
if err != nil {
log.Fatalf("failed to connect to database: %v", err)
}
db.Logger = db.Logger.LogMode(logger.Info)
// --- Setup ---
db.Migrator().DropTable(&Dataset{}, &DatasetColumn{})
db.AutoMigrate(&Dataset{}, &DatasetColumn{})
// Create sample data
dataset := Dataset{
Name: "My Dataset",
Columns: []*DatasetColumn{
{Name: "System Column 1", IsSystem: true, IsHidden: false}, // Should be loaded
{Name: "Visible Column 2", IsSystem: false, IsHidden: false}, // Should be loaded
{Name: "Hidden System Column 3", IsSystem: true, IsHidden: true}, // Should be loaded
{Name: "Hidden Normal Column 4", IsSystem: false, IsHidden: true}, // Should NOT be loaded
},
}
db.Create(&dataset)
dataset2 := Dataset{
Name: "My Dataset2",
Columns: []*DatasetColumn{
{Name: "New Colmn", IsSystem: true, IsHidden: false},
},
}
db.Create(&dataset2)
fmt.Println("--- Reproducing the Bug ---")
var ds *Dataset
// Perform the Preload operation that fails
err = db.Model(&Dataset{}).Where(&Dataset{ID: dataset.ID}).Scopes(Preload("Columns", QueryColumnsFilter())).First(&ds).Error
if err != nil {
log.Fatalf("Error during preload: %v", err)
}
fmt.Printf("\nLoaded %d columns.\n", len(ds.Columns))
}
Expected Behavior
The Preload
query for the Columns
association should correctly combine the custom scope's WHERE
condition with the foreign key and soft-delete conditions.
The expected SQL (as generated by Gorm v1.26.1) should be:
SELECT * FROM `dataset_columns` WHERE (is_system = true OR is_hidden = false) AND `dataset_columns`.`dataset_id` = 1 AND `dataset_columns`.`deleted_at` = 0
This correctly fetches 3 records based on the sample data.
Actual Behavior
In v1.30.0, the generated SQL for the Preload
is incorrect. The WHERE
clause is duplicated and improperly nested.
The actual SQL generated is:
SELECT * FROM `dataset_columns` WHERE (`dataset_columns`.`dataset_id` = 1 AND is_system = true OR is_hidden = false AND (`dataset_columns`.`dataset_id` = 1 AND is_system = true OR is_hidden = false)) AND `dataset_columns`.`deleted_at` = 0
This query breaks the intended logic of (A or B) AND C
and instead produces a much more complex and incorrect condition.
Possible Cause / Investigation
I believe this bug was introduced in PR #7424 (https://github.com/go-gorm/gorm/pull/7424), specifically in the changes made to callbacks/preload.go
. The logic for merging the preloader's conditions with custom scope conditions seems to have been changed in a way that doesn't correctly handle scopes containing OR
clauses.
Environment
- GORM version:
v1.30.0
- GORM driver:
sqlite
- Go version:
go1.23.0
(or any recent version) - Database:
sqlite