fix(FindInBatches): support composite primarykey
This commit is contained in:
parent
b13d1757fa
commit
84e93363bb
@ -173,7 +173,7 @@ func (db *DB) Find(dest interface{}, conds ...interface{}) (tx *DB) {
|
|||||||
// FindInBatches find records in batches
|
// FindInBatches find records in batches
|
||||||
func (db *DB) FindInBatches(dest interface{}, batchSize int, fc func(tx *DB, batch int) error) *DB {
|
func (db *DB) FindInBatches(dest interface{}, batchSize int, fc func(tx *DB, batch int) error) *DB {
|
||||||
var (
|
var (
|
||||||
tx = db.Order(clause.OrderByColumn{
|
tx = db.Session(&Session{}).Order(clause.OrderByColumn{
|
||||||
Column: clause.Column{Table: clause.CurrentTable, Name: clause.PrimaryKey},
|
Column: clause.Column{Table: clause.CurrentTable, Name: clause.PrimaryKey},
|
||||||
}).Session(&Session{})
|
}).Session(&Session{})
|
||||||
queryDB = tx
|
queryDB = tx
|
||||||
@ -183,9 +183,11 @@ func (db *DB) FindInBatches(dest interface{}, batchSize int, fc func(tx *DB, bat
|
|||||||
|
|
||||||
// user specified offset or limit
|
// user specified offset or limit
|
||||||
var totalSize int
|
var totalSize int
|
||||||
|
var totalOffset int
|
||||||
if c, ok := tx.Statement.Clauses["LIMIT"]; ok {
|
if c, ok := tx.Statement.Clauses["LIMIT"]; ok {
|
||||||
if limit, ok := c.Expression.(clause.Limit); ok {
|
if limit, ok := c.Expression.(clause.Limit); ok {
|
||||||
totalSize = limit.Limit
|
totalSize = limit.Limit
|
||||||
|
totalOffset = limit.Offset
|
||||||
|
|
||||||
if totalSize > 0 && batchSize > totalSize {
|
if totalSize > 0 && batchSize > totalSize {
|
||||||
batchSize = totalSize
|
batchSize = totalSize
|
||||||
@ -222,19 +224,59 @@ func (db *DB) FindInBatches(dest interface{}, batchSize int, fc func(tx *DB, bat
|
|||||||
|
|
||||||
// Optimize for-break
|
// Optimize for-break
|
||||||
resultsValue := reflect.Indirect(reflect.ValueOf(dest))
|
resultsValue := reflect.Indirect(reflect.ValueOf(dest))
|
||||||
if result.Statement.Schema.PrioritizedPrimaryField == nil {
|
if result.Statement.Schema.PrioritizedPrimaryField != nil {
|
||||||
|
primaryValue, _ := result.Statement.Schema.PrioritizedPrimaryField.ValueOf(tx.Statement.Context, resultsValue.Index(resultsValue.Len()-1))
|
||||||
|
queryDB = tx.Clauses(clause.Gt{Column: clause.Column{Table: clause.CurrentTable, Name: clause.PrimaryKey}, Value: primaryValue})
|
||||||
|
} else if len(result.Statement.Schema.PrimaryFields) > 0 {
|
||||||
|
offset := totalOffset + int(rowsAffected)
|
||||||
|
queryDB = getCPKBatchesQuery(db.Session(&Session{}), result.Statement.Schema, offset, batchSize)
|
||||||
|
} else {
|
||||||
tx.AddError(ErrPrimaryKeyRequired)
|
tx.AddError(ErrPrimaryKeyRequired)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
primaryValue, _ := result.Statement.Schema.PrioritizedPrimaryField.ValueOf(tx.Statement.Context, resultsValue.Index(resultsValue.Len()-1))
|
|
||||||
queryDB = tx.Clauses(clause.Gt{Column: clause.Column{Table: clause.CurrentTable, Name: clause.PrimaryKey}, Value: primaryValue})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tx.RowsAffected = rowsAffected
|
tx.RowsAffected = rowsAffected
|
||||||
return tx
|
return tx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// use subquery and offset limit to query composite primarykey
|
||||||
|
// so it not support Save in FindInBatches callback func like this case:
|
||||||
|
// https://github.com/go-gorm/gorm/blob/master/tests/query_test.go#L275
|
||||||
|
// it will generate sql like this:
|
||||||
|
// SELECT table.* FROM table INNER JOIN (&{subquery}) sub ON sub.&{primarykey} = table.&{primarykey}
|
||||||
|
func getCPKBatchesQuery(subTx *DB, sch *schema.Schema, offset, limit int) *DB {
|
||||||
|
queryTx := subTx.Session(&Session{NewDB: true})
|
||||||
|
// subquery conditions like DeletedAt
|
||||||
|
for _, c := range sch.QueryClauses {
|
||||||
|
subTx.Statement.AddClause(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
subqueryAlias := "sub"
|
||||||
|
// order conditions
|
||||||
|
subOrderConds := make([]clause.OrderByColumn, len(sch.PrimaryFieldDBNames))
|
||||||
|
// on conditions
|
||||||
|
onConds := make([]clause.Expression, len(sch.PrimaryFieldDBNames))
|
||||||
|
for i, pkname := range sch.PrimaryFieldDBNames {
|
||||||
|
onConds[i] = clause.Eq{
|
||||||
|
Column: clause.Column{Table: sch.Table, Name: pkname},
|
||||||
|
Value: clause.Column{Table: subqueryAlias, Name: pkname},
|
||||||
|
}
|
||||||
|
subOrderConds[i] = clause.OrderByColumn{
|
||||||
|
Column: clause.Column{Table: clause.CurrentTable, Name: pkname},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return queryTx.Table(sch.Table).Select("?.*", clause.Expr{SQL: sch.Table}).Joins("INNER JOIN (?) ? ON ?",
|
||||||
|
// all conditions are judged in subquery
|
||||||
|
subTx.Table(sch.Table).Select(sch.PrimaryFieldDBNames).Offset(offset).Limit(limit).Clauses(clause.OrderBy{
|
||||||
|
Columns: subOrderConds,
|
||||||
|
}),
|
||||||
|
clause.Expr{SQL: subqueryAlias},
|
||||||
|
clause.And(onConds...),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
func (db *DB) assignInterfacesToValue(values ...interface{}) {
|
func (db *DB) assignInterfacesToValue(values ...interface{}) {
|
||||||
for _, value := range values {
|
for _, value := range values {
|
||||||
switch v := value.(type) {
|
switch v := value.(type) {
|
||||||
|
@ -292,6 +292,61 @@ func TestFindInBatches(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFindInBatchesWithCompositePrimarykey(t *testing.T) {
|
||||||
|
type CompositeModel struct {
|
||||||
|
Name string `gorm:"primaryKey"`
|
||||||
|
Gender int `gorm:"primaryKey"`
|
||||||
|
Cond string
|
||||||
|
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||||
|
}
|
||||||
|
DB.Migrator().DropTable(&CompositeModel{})
|
||||||
|
DB.AutoMigrate(&CompositeModel{})
|
||||||
|
|
||||||
|
models := []CompositeModel{
|
||||||
|
{Name: "Composite_0", Gender: 0, Cond: "test"},
|
||||||
|
{Name: "Composite_0", Gender: 1, Cond: "test"},
|
||||||
|
{Name: "Composite_0", Gender: 2, Cond: "test"},
|
||||||
|
{Name: "Composite_1", Gender: 1, Cond: "test"},
|
||||||
|
{Name: "Composite_1", Gender: 2, Cond: "test"},
|
||||||
|
{Name: "Composite_1", Gender: 3, Cond: "test"},
|
||||||
|
{Name: "Composite_2", Gender: 1, Cond: "test"},
|
||||||
|
}
|
||||||
|
|
||||||
|
DB.Create(&models)
|
||||||
|
DB.Delete(&models[0])
|
||||||
|
|
||||||
|
var (
|
||||||
|
sub, results []CompositeModel
|
||||||
|
totalBatch int
|
||||||
|
)
|
||||||
|
|
||||||
|
if result := DB.Where("cond = ?", models[0].Cond).FindInBatches(&sub, 2, func(tx *gorm.DB, batch int) error {
|
||||||
|
totalBatch += batch
|
||||||
|
|
||||||
|
if tx.RowsAffected != 2 {
|
||||||
|
t.Errorf("Incorrect affected rows, expects: 2, got %v", tx.RowsAffected)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(sub) != 2 {
|
||||||
|
t.Errorf("Incorrect users length, expects: 2, got %v", len(sub))
|
||||||
|
}
|
||||||
|
|
||||||
|
results = append(results, sub...)
|
||||||
|
return nil
|
||||||
|
}); result.Error != nil || result.RowsAffected != 6 {
|
||||||
|
t.Errorf("Failed to batch find, got error %v, rows affected: %v", result.Error, result.RowsAffected)
|
||||||
|
}
|
||||||
|
|
||||||
|
if totalBatch != 6 {
|
||||||
|
t.Errorf("incorrect total batch, expects: %v, got %v", 6, totalBatch)
|
||||||
|
}
|
||||||
|
|
||||||
|
targets := models[1:]
|
||||||
|
for i := 0; i < len(targets); i++ {
|
||||||
|
AssertEqual(t, results[i], targets[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestFindInBatchesWithOffsetLimit(t *testing.T) {
|
func TestFindInBatchesWithOffsetLimit(t *testing.T) {
|
||||||
users := []User{
|
users := []User{
|
||||||
*GetUser("find_in_batches_with_offset_limit", Config{}),
|
*GetUser("find_in_batches_with_offset_limit", Config{}),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user