| 1 | package sessioncatalog |
| 2 | |
| 3 | // TopicSortKeyAfterCursor reports whether a topic key belongs after an |
| 4 | // exclusive ListTopics cursor under the catalog's canonical ordering. |
| 5 | func TopicSortKeyAfterCursor(encoded string, pinned bool, activity int64, topicID string) (bool, error) { |
| 6 | return TopicSortKeyAfterCursorBound(encoded, "", pinned, activity, topicID) |
| 7 | } |
| 8 | |
| 9 | // TopicSortKeyAfterCursorBound also rejects a cursor produced for a different |
| 10 | // filtered list. Metadata fallback uses this to share the catalog cursor |
| 11 | // contract without exposing cursor internals outside this package. |
| 12 | func TopicSortKeyAfterCursorBound(encoded, expectedBinding string, pinned bool, activity int64, topicID string) (bool, error) { |
| 13 | cursor, err := decodeCursor(encoded) |
| 14 | if err != nil { |
| 15 | return false, err |
| 16 | } |
| 17 | if cursor == nil { |
| 18 | return true, nil |
| 19 | } |
| 20 | if cursor.ManualOrder { |
| 21 | return false, errCursorSortModeChanged |
| 22 | } |
| 23 | if cursor.Binding != expectedBinding { |
| 24 | return false, errCursorSortModeChanged |
| 25 | } |
| 26 | pinnedValue := 0 |
| 27 | if pinned { |
| 28 | pinnedValue = 1 |
| 29 | } |
| 30 | return pinnedValue < cursor.Pinned || |
| 31 | pinnedValue == cursor.Pinned && activity < cursor.Activity || |
| 32 | pinnedValue == cursor.Pinned && activity == cursor.Activity && topicID > cursor.TopicID, nil |
| 33 | } |
| 34 | |
| 35 | // TopicSortKeyAfterOrderedCursor is the manual-order counterpart used by the |
| 36 | // metadata continuity path. Its comparison exactly matches ListTopics SQL. |
| 37 | func TopicSortKeyAfterOrderedCursor(encoded string, pinned bool, sortOrder int, activity int64, topicID string) (bool, error) { |
| 38 | return TopicSortKeyAfterOrderedCursorBound(encoded, "", pinned, sortOrder, activity, topicID) |
| 39 | } |
| 40 | |
| 41 | // TopicSortKeyAfterOrderedCursorBound is the bound manual-order counterpart. |
| 42 | func TopicSortKeyAfterOrderedCursorBound(encoded, expectedBinding string, pinned bool, sortOrder int, activity int64, topicID string) (bool, error) { |
| 43 | cursor, err := decodeCursor(encoded) |
| 44 | if err != nil { |
| 45 | return false, err |
| 46 | } |
| 47 | if cursor == nil { |
| 48 | return true, nil |
| 49 | } |
| 50 | if !cursor.ManualOrder { |
| 51 | return false, errCursorSortModeChanged |
| 52 | } |
| 53 | if cursor.Binding != expectedBinding { |
| 54 | return false, errCursorSortModeChanged |
| 55 | } |
| 56 | pinnedValue := 0 |
| 57 | if pinned { |
| 58 | pinnedValue = 1 |
| 59 | } |
| 60 | manualSortOrder := int64(sortOrder) |
| 61 | if sortOrder < 0 { |
| 62 | manualSortOrder = unrankedTopicSortOrder |
| 63 | } |
| 64 | return pinnedValue < cursor.Pinned || |
| 65 | pinnedValue == cursor.Pinned && manualSortOrder > cursor.SortOrder || |
| 66 | pinnedValue == cursor.Pinned && manualSortOrder == cursor.SortOrder && activity < cursor.Activity || |
| 67 | pinnedValue == cursor.Pinned && manualSortOrder == cursor.SortOrder && activity == cursor.Activity && topicID > cursor.TopicID, nil |
| 68 | } |
| 69 |