fix(limit): set default limit to math.MaxInt when only offset is provided

- Ensures MySQL compatibility by always providing a LIMIT when OFFSET is used
- Updates tests to reflect new behavior
- Improves code organization and documentation
This commit is contained in:
aydinomer00 2025-01-04 22:03:13 +03:00
parent 48f9924d13
commit 3eac3cd8cb
2 changed files with 93 additions and 29 deletions

View File

@ -17,17 +17,16 @@ func (limit Limit) Name() string {
// Build constructs the LIMIT clause
func (limit Limit) Build(builder Builder) {
// If only offset is defined and limit is nil, set limit to math.MaxInt
if limit.Limit == nil && limit.Offset > 0 {
maxInt := math.MaxInt
limit.Limit = &maxInt
}
// NOT: We don't auto-set limit here. We only rely on the final struct's Limit and Offset.
// Any "auto offset => limit" logic is handled in MergeClause.
if limit.Limit != nil && *limit.Limit >= 0 {
builder.WriteString("LIMIT ")
builder.AddVar(builder, *limit.Limit)
}
if limit.Offset > 0 {
// Add space if LIMIT was set
if limit.Limit != nil && *limit.Limit >= 0 {
builder.WriteByte(' ')
}
@ -41,16 +40,30 @@ func (limit Limit) MergeClause(clause *Clause) {
clause.Name = ""
if v, ok := clause.Expression.(Limit); ok {
// 1) Merge offset
if limit.Offset == 0 && v.Offset > 0 {
limit.Offset = v.Offset
} else if limit.Offset < 0 {
// Negative offset => 0
limit.Offset = 0
}
// 2) Merge limit
if (limit.Limit == nil || *limit.Limit == 0) && v.Limit != nil {
limit.Limit = v.Limit
}
if limit.Offset == 0 && v.Offset > 0 {
limit.Offset = v.Offset
} else if limit.Offset < 0 {
limit.Offset = 0
// 3) If final limit is negative => treat it as nil (meaning "no limit")
if limit.Limit != nil && *limit.Limit < 0 {
limit.Limit = nil
}
}
// 4) If offset > 0 but limit is still nil, set limit to math.MaxInt
if limit.Offset > 0 && limit.Limit == nil {
maxInt := math.MaxInt
limit.Limit = &maxInt
}
clause.Expression = limit
}

View File

@ -13,68 +13,119 @@ func TestLimit(t *testing.T) {
limit10 := 10
limit50 := 50
limitNeg10 := -10
results := []struct {
Clauses []clause.Interface
Result string
Vars []interface{}
}{
// case #0 - limit10 offset20
{
[]clause.Interface{clause.Select{}, clause.From{}, clause.Limit{
Limit: &limit10,
Offset: 20,
}},
[]clause.Interface{
clause.Select{}, clause.From{},
clause.Limit{Limit: &limit10, Offset: 20},
},
"SELECT * FROM `users` LIMIT ? OFFSET ?",
[]interface{}{limit10, 20},
},
// case #1 - limit0
{
[]clause.Interface{clause.Select{}, clause.From{}, clause.Limit{Limit: &limit0}},
[]clause.Interface{
clause.Select{}, clause.From{},
clause.Limit{Limit: &limit0},
},
"SELECT * FROM `users` LIMIT ?",
[]interface{}{limit0},
},
// case #2 - limit0 offset0 => offset0 is effectively ignored
{
[]clause.Interface{clause.Select{}, clause.From{}, clause.Limit{Limit: &limit0}, clause.Limit{Offset: 0}},
[]clause.Interface{
clause.Select{}, clause.From{},
clause.Limit{Limit: &limit0}, clause.Limit{Offset: 0},
},
"SELECT * FROM `users` LIMIT ?",
[]interface{}{limit0},
},
// case #3 - only offset=20
// MySQL demands limit if offset>0 => math.MaxInt
{
// Updated test case: only Offset is given, so now we expect math.MaxInt as LIMIT
[]clause.Interface{clause.Select{}, clause.From{}, clause.Limit{Offset: 20}},
[]clause.Interface{
clause.Select{}, clause.From{},
clause.Limit{Offset: 20},
},
"SELECT * FROM `users` LIMIT ? OFFSET ?",
[]interface{}{math.MaxInt, 20},
},
// case #4 - multiple offsets (20 -> 30)
{
[]clause.Interface{clause.Select{}, clause.From{}, clause.Limit{Offset: 20}, clause.Limit{Offset: 30}},
"SELECT * FROM `users` OFFSET ?",
[]interface{}{30},
[]clause.Interface{
clause.Select{}, clause.From{},
clause.Limit{Offset: 20}, clause.Limit{Offset: 30},
},
"SELECT * FROM `users` LIMIT ? OFFSET ?",
[]interface{}{math.MaxInt, 30},
},
// case #5 - merge offset20 with limit10
{
[]clause.Interface{clause.Select{}, clause.From{}, clause.Limit{Offset: 20}, clause.Limit{Limit: &limit10}},
[]clause.Interface{
clause.Select{}, clause.From{},
clause.Limit{Offset: 20}, clause.Limit{Limit: &limit10},
},
"SELECT * FROM `users` LIMIT ? OFFSET ?",
[]interface{}{limit10, 20},
},
// case #6 - merge offset20->30 with limit10
{
[]clause.Interface{clause.Select{}, clause.From{}, clause.Limit{Limit: &limit10, Offset: 20}, clause.Limit{Offset: 30}},
[]clause.Interface{
clause.Select{}, clause.From{},
clause.Limit{Limit: &limit10, Offset: 20},
clause.Limit{Offset: 30},
},
"SELECT * FROM `users` LIMIT ? OFFSET ?",
[]interface{}{limit10, 30},
},
// case #7 - negative offset => offset=0 => "SELECT * FROM `users` LIMIT 10"
{
[]clause.Interface{clause.Select{}, clause.From{}, clause.Limit{Limit: &limit10, Offset: 20}, clause.Limit{Offset: 30}, clause.Limit{Offset: -10}},
[]clause.Interface{
clause.Select{}, clause.From{},
clause.Limit{Limit: &limit10, Offset: 20},
clause.Limit{Offset: 30},
clause.Limit{Offset: -10},
},
"SELECT * FROM `users` LIMIT ?",
[]interface{}{limit10},
},
// case #8 - negative limit => treat as nil => offset=30 => => "LIMIT ? OFFSET ?"
{
[]clause.Interface{clause.Select{}, clause.From{}, clause.Limit{Limit: &limit10, Offset: 20}, clause.Limit{Offset: 30}, clause.Limit{Limit: &limitNeg10}},
"SELECT * FROM `users` OFFSET ?",
[]interface{}{30},
[]clause.Interface{
clause.Select{}, clause.From{},
// Start with limit10 offset20
clause.Limit{Limit: &limit10, Offset: 20},
// Then change offset to 30
clause.Limit{Offset: 30},
// Then set limit to negative => remove limit => offset>0 => limit=math.MaxInt
clause.Limit{Limit: &limitNeg10},
},
"SELECT * FROM `users` LIMIT ? OFFSET ?",
[]interface{}{math.MaxInt, 30},
},
// case #9 - changing limit from 10 -> 50, offset=30
{
[]clause.Interface{clause.Select{}, clause.From{}, clause.Limit{Limit: &limit10, Offset: 20}, clause.Limit{Offset: 30}, clause.Limit{Limit: &limit50}},
[]clause.Interface{
clause.Select{}, clause.From{},
clause.Limit{Limit: &limit10, Offset: 20},
clause.Limit{Offset: 30},
clause.Limit{Limit: &limit50},
},
"SELECT * FROM `users` LIMIT ? OFFSET ?",
[]interface{}{limit50, 30},
},
// New test example: if only Offset is defined, Limit should become math.MaxInt
// case #10 - only offset=100 => "LIMIT ? OFFSET ?", math.MaxInt, 100
{
[]clause.Interface{clause.Select{}, clause.From{}, clause.Limit{Offset: 100}},
[]clause.Interface{
clause.Select{}, clause.From{},
clause.Limit{Offset: 100},
},
"SELECT * FROM `users` LIMIT ? OFFSET ?",
[]interface{}{math.MaxInt, 100},
},