import files, add README & LICENSE
This commit is contained in:
+314
@@ -0,0 +1,314 @@
|
||||
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) >")
|
||||
}
|
||||
Reference in New Issue
Block a user