fix: add deterministic tiebreaker to all paginated chat queries (#22387)
Add Chat.id as a secondary sort key to all paginated chat queries that use offset/limit pagination. When multiple chats share the same updated_at timestamp, the database does not guarantee a stable order across page boundaries, causing chats to appear on multiple pages. This produces duplicate keys in the Svelte sidebar each-block (each_key_duplicate error). Adding Chat.id as a tiebreaker ensures fully deterministic ordering. Extends the fix from #22383 (which addressed get_chat_ids_by_model_id) to all remaining paginated chat queries.
This commit is contained in:
@@ -734,13 +734,13 @@ class ChatTable:
|
|||||||
raise ValueError("Invalid order_by field")
|
raise ValueError("Invalid order_by field")
|
||||||
|
|
||||||
if direction.lower() == "asc":
|
if direction.lower() == "asc":
|
||||||
query = query.order_by(getattr(Chat, order_by).asc())
|
query = query.order_by(getattr(Chat, order_by).asc(), Chat.id)
|
||||||
elif direction.lower() == "desc":
|
elif direction.lower() == "desc":
|
||||||
query = query.order_by(getattr(Chat, order_by).desc())
|
query = query.order_by(getattr(Chat, order_by).desc(), Chat.id)
|
||||||
else:
|
else:
|
||||||
raise ValueError("Invalid direction for ordering")
|
raise ValueError("Invalid direction for ordering")
|
||||||
else:
|
else:
|
||||||
query = query.order_by(Chat.updated_at.desc())
|
query = query.order_by(Chat.updated_at.desc(), Chat.id)
|
||||||
|
|
||||||
query = query.with_entities(
|
query = query.with_entities(
|
||||||
Chat.id, Chat.title, Chat.updated_at, Chat.created_at
|
Chat.id, Chat.title, Chat.updated_at, Chat.created_at
|
||||||
@@ -793,13 +793,13 @@ class ChatTable:
|
|||||||
raise ValueError("Invalid order_by field")
|
raise ValueError("Invalid order_by field")
|
||||||
|
|
||||||
if direction.lower() == "asc":
|
if direction.lower() == "asc":
|
||||||
query = query.order_by(getattr(Chat, order_by).asc())
|
query = query.order_by(getattr(Chat, order_by).asc(), Chat.id)
|
||||||
elif direction.lower() == "desc":
|
elif direction.lower() == "desc":
|
||||||
query = query.order_by(getattr(Chat, order_by).desc())
|
query = query.order_by(getattr(Chat, order_by).desc(), Chat.id)
|
||||||
else:
|
else:
|
||||||
raise ValueError("Invalid direction for ordering")
|
raise ValueError("Invalid direction for ordering")
|
||||||
else:
|
else:
|
||||||
query = query.order_by(Chat.updated_at.desc())
|
query = query.order_by(Chat.updated_at.desc(), Chat.id)
|
||||||
|
|
||||||
# Select only the columns needed for SharedChatResponse
|
# Select only the columns needed for SharedChatResponse
|
||||||
# to avoid loading the heavy chat JSON blob
|
# to avoid loading the heavy chat JSON blob
|
||||||
@@ -854,13 +854,13 @@ class ChatTable:
|
|||||||
|
|
||||||
if order_by and direction and getattr(Chat, order_by):
|
if order_by and direction and getattr(Chat, order_by):
|
||||||
if direction.lower() == "asc":
|
if direction.lower() == "asc":
|
||||||
query = query.order_by(getattr(Chat, order_by).asc())
|
query = query.order_by(getattr(Chat, order_by).asc(), Chat.id)
|
||||||
elif direction.lower() == "desc":
|
elif direction.lower() == "desc":
|
||||||
query = query.order_by(getattr(Chat, order_by).desc())
|
query = query.order_by(getattr(Chat, order_by).desc(), Chat.id)
|
||||||
else:
|
else:
|
||||||
raise ValueError("Invalid direction for ordering")
|
raise ValueError("Invalid direction for ordering")
|
||||||
else:
|
else:
|
||||||
query = query.order_by(Chat.updated_at.desc())
|
query = query.order_by(Chat.updated_at.desc(), Chat.id)
|
||||||
|
|
||||||
if skip:
|
if skip:
|
||||||
query = query.offset(skip)
|
query = query.offset(skip)
|
||||||
@@ -892,7 +892,7 @@ class ChatTable:
|
|||||||
if not include_archived:
|
if not include_archived:
|
||||||
query = query.filter_by(archived=False)
|
query = query.filter_by(archived=False)
|
||||||
|
|
||||||
query = query.order_by(Chat.updated_at.desc()).with_entities(
|
query = query.order_by(Chat.updated_at.desc(), Chat.id).with_entities(
|
||||||
Chat.id, Chat.title, Chat.updated_at, Chat.created_at
|
Chat.id, Chat.title, Chat.updated_at, Chat.created_at
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1039,14 +1039,14 @@ class ChatTable:
|
|||||||
if order_by and direction:
|
if order_by and direction:
|
||||||
if hasattr(Chat, order_by):
|
if hasattr(Chat, order_by):
|
||||||
if direction.lower() == "asc":
|
if direction.lower() == "asc":
|
||||||
query = query.order_by(getattr(Chat, order_by).asc())
|
query = query.order_by(getattr(Chat, order_by).asc(), Chat.id)
|
||||||
elif direction.lower() == "desc":
|
elif direction.lower() == "desc":
|
||||||
query = query.order_by(getattr(Chat, order_by).desc())
|
query = query.order_by(getattr(Chat, order_by).desc(), Chat.id)
|
||||||
else:
|
else:
|
||||||
query = query.order_by(Chat.updated_at.desc())
|
query = query.order_by(Chat.updated_at.desc(), Chat.id)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
query = query.order_by(Chat.updated_at.desc())
|
query = query.order_by(Chat.updated_at.desc(), Chat.id)
|
||||||
|
|
||||||
total = query.count()
|
total = query.count()
|
||||||
|
|
||||||
@@ -1188,7 +1188,7 @@ class ChatTable:
|
|||||||
if folder_ids:
|
if folder_ids:
|
||||||
query = query.filter(Chat.folder_id.in_(folder_ids))
|
query = query.filter(Chat.folder_id.in_(folder_ids))
|
||||||
|
|
||||||
query = query.order_by(Chat.updated_at.desc())
|
query = query.order_by(Chat.updated_at.desc(), Chat.id)
|
||||||
|
|
||||||
# Check if the database dialect is either 'sqlite' or 'postgresql'
|
# Check if the database dialect is either 'sqlite' or 'postgresql'
|
||||||
dialect_name = db.bind.dialect.name
|
dialect_name = db.bind.dialect.name
|
||||||
@@ -1309,7 +1309,7 @@ class ChatTable:
|
|||||||
query = query.filter(or_(Chat.pinned == False, Chat.pinned == None))
|
query = query.filter(or_(Chat.pinned == False, Chat.pinned == None))
|
||||||
query = query.filter_by(archived=False)
|
query = query.filter_by(archived=False)
|
||||||
|
|
||||||
query = query.order_by(Chat.updated_at.desc())
|
query = query.order_by(Chat.updated_at.desc(), Chat.id)
|
||||||
|
|
||||||
if skip:
|
if skip:
|
||||||
query = query.offset(skip)
|
query = query.offset(skip)
|
||||||
|
|||||||
Reference in New Issue
Block a user