315 lines
7.9 KiB
Go
315 lines
7.9 KiB
Go
package cursor
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/go-jet/jet/v2/mysql"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestTimestampValue(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
RegisterColumn(Meeting.StartTime)
|
|
|
|
when := time.Date(2026, 3, 15, 14, 30, 0, 0, time.UTC)
|
|
v := NewTimestampValue(when, Meeting.StartTime)
|
|
assert.Equal(when, v.Val)
|
|
assert.Equal(Meeting.StartTime, v.Col())
|
|
assert.False(v.IsEmpty())
|
|
assert.True(NewTimestampValue(time.Time{}, Meeting.StartTime).IsEmpty())
|
|
}
|
|
|
|
func TestMeetingCompositeCursor(t *testing.T) {
|
|
assert := assert.New(t)
|
|
registerMeetingCursorColumns(t)
|
|
|
|
when := time.Date(2026, 6, 1, 9, 0, 0, 0, time.UTC)
|
|
base := NewCursor(
|
|
NewStringValue("", Meeting.ID),
|
|
Meeting.StartTime,
|
|
OrderDescending,
|
|
)
|
|
cursor := base.CopyWithVals(
|
|
NewStringValue("meeting-uuid-1", Meeting.ID),
|
|
NewTimestampValue(when, Meeting.StartTime),
|
|
)
|
|
|
|
assert.Equal(NewColumnKey(Meeting.ID), cursor.Index.ColumnKey())
|
|
assert.Equal(NewColumnKey(Meeting.StartTime), cursor.OrderColumnKey)
|
|
assert.True(cursor.IsComposite())
|
|
assert.False(cursor.IsEmpty())
|
|
|
|
encoded, err := cursor.Encode()
|
|
require.NoError(t, err)
|
|
assert.Contains(encoded, `"order_val"`)
|
|
assert.Contains(encoded, `"start_time"`)
|
|
|
|
decoded := NewCursor(NewStringValue("", Meeting.ID), Meeting.StartTime, OrderDescending)
|
|
require.NoError(t, decoded.Decode(encoded))
|
|
assert.True(decoded.IsComposite())
|
|
assert.Equal("meeting-uuid-1", decoded.Index.(*StringValue).Val)
|
|
assert.Equal(when, decoded.GenericOrderValue().(*TimestampValue).Val)
|
|
}
|
|
|
|
func TestMeetingCursorWithoutOrderValueIsNotComposite(t *testing.T) {
|
|
assert := assert.New(t)
|
|
registerMeetingCursorColumns(t)
|
|
|
|
cursor := NewCursor(
|
|
NewStringValue("", Meeting.ID),
|
|
Meeting.StartTime,
|
|
OrderDescending,
|
|
)
|
|
|
|
assert.False(cursor.IsComposite())
|
|
}
|
|
|
|
func TestMeetingCursorUsesTupleOrdering(t *testing.T) {
|
|
assert := assert.New(t)
|
|
registerMeetingCursorColumns(t)
|
|
|
|
cursor := NewCursor(
|
|
NewStringValue("", Meeting.ID),
|
|
Meeting.StartTime,
|
|
OrderDescending,
|
|
)
|
|
|
|
assert.True(cursor.UsesTupleOrdering())
|
|
assert.False(cursor.IsComposite())
|
|
}
|
|
|
|
func TestPaginateCompositeDESC(t *testing.T) {
|
|
assert := assert.New(t)
|
|
registerMeetingCursorColumns(t)
|
|
|
|
when := time.Date(2026, 6, 1, 9, 0, 0, 0, time.UTC)
|
|
cursor := NewCursor(
|
|
NewStringValue("", Meeting.ID),
|
|
Meeting.StartTime,
|
|
OrderDescending,
|
|
).CopyWithVals(
|
|
NewStringValue("meeting-uuid-1", Meeting.ID),
|
|
NewTimestampValue(when, Meeting.StartTime),
|
|
)
|
|
|
|
stmt := mysqlSELECTWhere(PaginateConds(cursor))
|
|
sql := stmt.DebugSql()
|
|
|
|
assert.Contains(sql, "(meeting.start_time, meeting.id) < ('")
|
|
}
|
|
|
|
func TestPaginateCompositeASC(t *testing.T) {
|
|
assert := assert.New(t)
|
|
registerMeetingCursorColumns(t)
|
|
|
|
when := time.Date(2026, 6, 1, 9, 0, 0, 0, time.UTC)
|
|
cursor := NewCursor(
|
|
NewStringValue("", Meeting.ID),
|
|
Meeting.StartTime,
|
|
OrderAscending,
|
|
).CopyWithVals(
|
|
NewStringValue("meeting-uuid-1", Meeting.ID),
|
|
NewTimestampValue(when, Meeting.StartTime),
|
|
)
|
|
|
|
stmt := mysqlSELECTWhere(PaginateConds(cursor))
|
|
sql := stmt.DebugSql()
|
|
|
|
assert.Contains(sql, "(meeting.start_time, meeting.id) > ('")
|
|
}
|
|
|
|
func TestOrderByClausesComposite(t *testing.T) {
|
|
assert := assert.New(t)
|
|
registerMeetingCursorColumns(t)
|
|
|
|
when := time.Date(2026, 6, 1, 9, 0, 0, 0, time.UTC)
|
|
cursor := NewCursor(
|
|
NewStringValue("", Meeting.ID),
|
|
Meeting.StartTime,
|
|
OrderDescending,
|
|
).CopyWithVals(
|
|
NewStringValue("meeting-uuid-1", Meeting.ID),
|
|
NewTimestampValue(when, Meeting.StartTime),
|
|
)
|
|
|
|
clauses := OrderByClauses(cursor)
|
|
require.Len(t, clauses, 2)
|
|
|
|
stmt := Meeting.SELECT(Meeting.AllColumns).ORDER_BY(clauses...)
|
|
sql := stmt.DebugSql()
|
|
assert.True(
|
|
strings.Contains(sql, "start_time DESC") && strings.Contains(sql, "id DESC"),
|
|
"expected composite order by, got: %s", sql,
|
|
)
|
|
}
|
|
|
|
func TestOrderByClausesTupleOrderingDefaultCursor(t *testing.T) {
|
|
assert := assert.New(t)
|
|
registerMeetingCursorColumns(t)
|
|
|
|
cursor := NewCursor(
|
|
NewStringValue("", Meeting.ID),
|
|
Meeting.StartTime,
|
|
OrderDescending,
|
|
)
|
|
|
|
assert.True(cursor.UsesTupleOrdering())
|
|
assert.False(cursor.IsComposite())
|
|
|
|
clauses := OrderByClauses(cursor)
|
|
require.Len(t, clauses, 2)
|
|
|
|
stmt := Meeting.SELECT(Meeting.AllColumns).ORDER_BY(clauses...)
|
|
sql := stmt.DebugSql()
|
|
assert.True(
|
|
strings.Contains(sql, "start_time DESC") && strings.Contains(sql, "id DESC"),
|
|
"expected tuple order by on default cursor, got: %s", sql,
|
|
)
|
|
}
|
|
|
|
func TestOrderByClausesNil(t *testing.T) {
|
|
assert.Nil(t, OrderByClauses(nil))
|
|
}
|
|
|
|
func TestOrderByClausesSingleColumnASC(t *testing.T) {
|
|
RegisterColumn(User.ID)
|
|
|
|
cursor := NewCursor(NewInt64Value(1, User.ID), User.ID, OrderAscending)
|
|
clauses := OrderByClauses(cursor)
|
|
require.Len(t, clauses, 1)
|
|
|
|
stmt := User.SELECT(User.AllColumns).ORDER_BY(clauses...)
|
|
sql := stmt.DebugSql()
|
|
assert.Contains(t, sql, "id ASC")
|
|
}
|
|
|
|
func TestPaginateValueTypes(t *testing.T) {
|
|
RegisterColumn(User.ID, Meeting.StartTime)
|
|
|
|
tests := []struct {
|
|
name string
|
|
cond mysql.BoolExpression
|
|
wantSubstr string
|
|
}{
|
|
{
|
|
name: "int64 desc",
|
|
cond: PaginateConds(NewCursor(
|
|
NewInt64Value(10, User.ID),
|
|
User.ID,
|
|
OrderDescending,
|
|
)),
|
|
wantSubstr: "user.id < 10",
|
|
},
|
|
{
|
|
name: "int64 asc",
|
|
cond: PaginateConds(NewCursor(
|
|
NewInt64Value(10, User.ID),
|
|
User.ID,
|
|
OrderAscending,
|
|
)),
|
|
wantSubstr: "user.id > 10",
|
|
},
|
|
{
|
|
name: "uint64 desc",
|
|
cond: PaginateConds(NewCursor(
|
|
NewUint64Value(10, User.ID),
|
|
User.ID,
|
|
OrderDescending,
|
|
)),
|
|
wantSubstr: "user.id < 10",
|
|
},
|
|
{
|
|
name: "timestamp desc",
|
|
cond: PaginateConds(NewCursor(
|
|
NewTimestampValue(time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC), Meeting.StartTime),
|
|
Meeting.StartTime,
|
|
OrderDescending,
|
|
)),
|
|
wantSubstr: "meeting.start_time <",
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
sql := User.SELECT(User.AllColumns).WHERE(tc.cond).DebugSql()
|
|
assert.Contains(t, sql, tc.wantSubstr)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestQueryCountCompositeSQL(t *testing.T) {
|
|
assert := assert.New(t)
|
|
registerMeetingCursorColumns(t)
|
|
|
|
when := time.Date(2026, 6, 1, 9, 0, 0, 0, time.UTC)
|
|
start := NewCursor(
|
|
NewStringValue("", Meeting.ID),
|
|
Meeting.StartTime,
|
|
OrderDescending,
|
|
).CopyWithVals(
|
|
NewStringValue("meeting-uuid-1", Meeting.ID),
|
|
NewTimestampValue(when, Meeting.StartTime),
|
|
)
|
|
end := start.CopyWithVals(
|
|
NewStringValue("meeting-uuid-2", Meeting.ID),
|
|
NewTimestampValue(when, Meeting.StartTime),
|
|
)
|
|
|
|
stmt := Meeting.SELECT(
|
|
mysql.COUNT(countBoundExpr(start, ">")).AS("before"),
|
|
mysql.COUNT(countBoundExpr(end, "<")).AS("after"),
|
|
).WHERE(mysql.Bool(true))
|
|
sql := stmt.DebugSql()
|
|
|
|
assert.Contains(sql, "IF((meeting.start_time, meeting.id) > ('")
|
|
assert.Contains(sql, "IF((meeting.start_time, meeting.id) < ('")
|
|
}
|
|
|
|
func TestCountBoundExprSimple(t *testing.T) {
|
|
RegisterColumn(MeetingRoom.ID)
|
|
|
|
cursor := NewCursor(
|
|
NewStringValue("room-1", MeetingRoom.ID),
|
|
MeetingRoom.ID,
|
|
OrderDescending,
|
|
)
|
|
|
|
stmt := MeetingRoom.SELECT(
|
|
mysql.COUNT(countBoundExpr(cursor, "<")).AS("after"),
|
|
).WHERE(mysql.Bool(true))
|
|
sql := stmt.DebugSql()
|
|
|
|
assert.Contains(t, sql, "IF(meeting_room.id < 'room-1', 1, NULL)")
|
|
}
|
|
|
|
func TestBuildQueryCountFn(t *testing.T) {
|
|
registerMeetingCursorColumns(t)
|
|
|
|
fn := BuildQueryCountFn(Meeting.ID, Meeting, mysql.Bool(true))
|
|
require.NotNil(t, fn)
|
|
|
|
when := time.Date(2026, 6, 1, 9, 0, 0, 0, time.UTC)
|
|
start := NewCursor(
|
|
NewStringValue("", Meeting.ID),
|
|
Meeting.StartTime,
|
|
OrderDescending,
|
|
).CopyWithVals(
|
|
NewStringValue("meeting-uuid-1", Meeting.ID),
|
|
NewTimestampValue(when, Meeting.StartTime),
|
|
)
|
|
|
|
// Verify the returned fn builds the same SQL shape as QueryCount would.
|
|
stmt := Meeting.SELECT(
|
|
mysql.COUNT(Meeting.ID).AS("QueryCountResult.Total"),
|
|
mysql.COUNT(countBoundExpr(start, ">")).AS("QueryCountResult.Before"),
|
|
mysql.COUNT(countBoundExpr(start, "<")).AS("QueryCountResult.After"),
|
|
).WHERE(mysql.Bool(true))
|
|
sql := stmt.DebugSql()
|
|
assert.Contains(t, sql, "COUNT(meeting.id)")
|
|
assert.Contains(t, sql, "IF((meeting.start_time, meeting.id) >")
|
|
}
|