We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Rename `subject_links` to `topic_links` in our API
This is an element of the broader `subject` -> `topic` migration (see #1192) that should be straightforward to change, because I believe the mobile apps don't access `subject_links` yet, so there's no compatibility work required.  (What the data is used for in the webapp is the little in-topic-field links we show when there is a link or linkifier matching the topic line of the message).

@gnprice to confirm I'm reading the mobile codebase correctly that it's indeed not accessed.

Noticed in #13587; tagging as a priority since this sort of API migration gets more complex when delayed.  We should be sure to look again at updating the docs as discussed in #13587 once this is complete.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `zerver/lib/topic.py`
Content:
```
1 import datetime
2 
3 from django.db import connection
4 from django.db.models.query import QuerySet, Q
5 from django.utils.timezone import now as timezone_now
6 
7 from sqlalchemy.sql import (
8     column,
9     literal,
10     func,
11 )
12 
13 from zerver.lib.request import REQ
14 from zerver.models import (
15     Message,
16     Recipient,
17     UserMessage,
18     UserProfile,
19 )
20 
21 from typing import Any, Dict, List, Optional, Tuple
22 
23 # Only use these constants for events.
24 ORIG_TOPIC = "orig_subject"
25 TOPIC_NAME = "subject"
26 TOPIC_LINKS = "subject_links"
27 MATCH_TOPIC = "match_subject"
28 
29 # This constant is actually embedded into
30 # the JSON data for message edit history,
31 # so we'll always need to handle legacy data
32 # unless we do a pretty tricky migration.
33 LEGACY_PREV_TOPIC = "prev_subject"
34 
35 # This constant is pretty closely coupled to the
36 # database, but it's the JSON field.
37 EXPORT_TOPIC_NAME = "subject"
38 
39 '''
40 The following functions are for user-facing APIs
41 where we'll want to support "subject" for a while.
42 '''
43 
44 def get_topic_from_message_info(message_info: Dict[str, Any]) -> str:
45     '''
46     Use this where you are getting dicts that are based off of messages
47     that may come from the outside world, especially from third party
48     APIs and bots.
49 
50     We prefer 'topic' to 'subject' here.  We expect at least one field
51     to be present (or the caller must know how to handle KeyError).
52     '''
53     if 'topic' in message_info:
54         return message_info['topic']
55 
56     return message_info['subject']
57 
58 def REQ_topic() -> Optional[str]:
59     # REQ handlers really return a REQ, but we
60     # lie to make the rest of the type matching work.
61     return REQ(
62         whence='topic',
63         aliases=['subject'],
64         converter=lambda x: x.strip(),
65         default=None,
66     )
67 
68 '''
69 TRY TO KEEP THIS DIVIDING LINE.
70 
71 Below this line we want to make it so that functions are only
72 using "subject" in the DB sense, and nothing customer facing.
73 
74 '''
75 
76 # This is used in low-level message functions in
77 # zerver/lib/message.py, and it's not user facing.
78 DB_TOPIC_NAME = "subject"
79 MESSAGE__TOPIC = 'message__subject'
80 
81 def topic_match_sa(topic_name: str) -> Any:
82     # _sa is short for Sql Alchemy, which we use mostly for
83     # queries that search messages
84     topic_cond = func.upper(column("subject")) == func.upper(literal(topic_name))
85     return topic_cond
86 
87 def topic_column_sa() -> Any:
88     return column("subject")
89 
90 def filter_by_exact_message_topic(query: QuerySet, message: Message) -> QuerySet:
91     topic_name = message.topic_name()
92     return query.filter(subject=topic_name)
93 
94 def filter_by_topic_name_via_message(query: QuerySet, topic_name: str) -> QuerySet:
95     return query.filter(message__subject__iexact=topic_name)
96 
97 def messages_for_topic(stream_id: int, topic_name: str) -> QuerySet:
98     return Message.objects.filter(
99         recipient__type_id=stream_id,
100         subject__iexact=topic_name,
101     )
102 
103 def save_message_for_edit_use_case(message: Message) -> None:
104     message.save(update_fields=[TOPIC_NAME, "content", "rendered_content",
105                                 "rendered_content_version", "last_edit_time",
106                                 "edit_history", "has_attachment", "has_image",
107                                 "has_link"])
108 
109 
110 def user_message_exists_for_topic(user_profile: UserProfile,
111                                   recipient: Recipient,
112                                   topic_name: str) -> bool:
113     return UserMessage.objects.filter(
114         user_profile=user_profile,
115         message__recipient=recipient,
116         message__subject__iexact=topic_name,
117     ).exists()
118 
119 def update_messages_for_topic_edit(message: Message,
120                                    propagate_mode: str,
121                                    orig_topic_name: str,
122                                    topic_name: str) -> List[Message]:
123     propagate_query = Q(recipient = message.recipient, subject = orig_topic_name)
124     # We only change messages up to 7 days in the past, to avoid hammering our
125     # DB by changing an unbounded amount of messages
126     if propagate_mode == 'change_all':
127         before_bound = timezone_now() - datetime.timedelta(days=7)
128 
129         propagate_query = (propagate_query & ~Q(id = message.id) &
130                            Q(date_sent__range=(before_bound, timezone_now())))
131     if propagate_mode == 'change_later':
132         propagate_query = propagate_query & Q(id__gt = message.id)
133 
134     messages = Message.objects.filter(propagate_query).select_related()
135 
136     # Evaluate the query before running the update
137     messages_list = list(messages)
138     messages.update(subject=topic_name)
139 
140     for m in messages_list:
141         # The cached ORM object is not changed by messages.update()
142         # and the remote cache update requires the new value
143         m.set_topic_name(topic_name)
144 
145     return messages_list
146 
147 def generate_topic_history_from_db_rows(rows: List[Tuple[str, int]]) -> List[Dict[str, Any]]:
148     canonical_topic_names = {}  # type: Dict[str, Tuple[int, str]]
149 
150     # Sort rows by max_message_id so that if a topic
151     # has many different casings, we use the most
152     # recent row.
153     rows = sorted(rows, key=lambda tup: tup[1])
154 
155     for (topic_name, max_message_id) in rows:
156         canonical_name = topic_name.lower()
157         canonical_topic_names[canonical_name] = (max_message_id, topic_name)
158 
159     history = []
160     for canonical_topic, (max_message_id, topic_name) in canonical_topic_names.items():
161         history.append(dict(
162             name=topic_name,
163             max_id=max_message_id)
164         )
165     return sorted(history, key=lambda x: -x['max_id'])
166 
167 def get_topic_history_for_stream(user_profile: UserProfile,
168                                  recipient: Recipient,
169                                  public_history: bool) -> List[Dict[str, Any]]:
170     cursor = connection.cursor()
171     if public_history:
172         query = '''
173         SELECT
174             "zerver_message"."subject" as topic,
175             max("zerver_message".id) as max_message_id
176         FROM "zerver_message"
177         WHERE (
178             "zerver_message"."recipient_id" = %s
179         )
180         GROUP BY (
181             "zerver_message"."subject"
182         )
183         ORDER BY max("zerver_message".id) DESC
184         '''
185         cursor.execute(query, [recipient.id])
186     else:
187         query = '''
188         SELECT
189             "zerver_message"."subject" as topic,
190             max("zerver_message".id) as max_message_id
191         FROM "zerver_message"
192         INNER JOIN "zerver_usermessage" ON (
193             "zerver_usermessage"."message_id" = "zerver_message"."id"
194         )
195         WHERE (
196             "zerver_usermessage"."user_profile_id" = %s AND
197             "zerver_message"."recipient_id" = %s
198         )
199         GROUP BY (
200             "zerver_message"."subject"
201         )
202         ORDER BY max("zerver_message".id) DESC
203         '''
204         cursor.execute(query, [user_profile.id, recipient.id])
205     rows = cursor.fetchall()
206     cursor.close()
207 
208     return generate_topic_history_from_db_rows(rows)
209 
210 def get_topic_history_for_web_public_stream(recipient: Recipient) -> List[Dict[str, Any]]:
211     cursor = connection.cursor()
212     query = '''
213     SELECT
214         "zerver_message"."subject" as topic,
215         max("zerver_message".id) as max_message_id
216     FROM "zerver_message"
217     WHERE (
218         "zerver_message"."recipient_id" = %s
219     )
220     GROUP BY (
221         "zerver_message"."subject"
222     )
223     ORDER BY max("zerver_message".id) DESC
224     '''
225     cursor.execute(query, [recipient.id])
226     rows = cursor.fetchall()
227     cursor.close()
228 
229     return generate_topic_history_from_db_rows(rows)
230
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/zerver/lib/topic.py b/zerver/lib/topic.py
--- a/zerver/lib/topic.py
+++ b/zerver/lib/topic.py
@@ -23,7 +23,7 @@
 # Only use these constants for events.
 ORIG_TOPIC = "orig_subject"
 TOPIC_NAME = "subject"
-TOPIC_LINKS = "subject_links"
+TOPIC_LINKS = "topic_links"
 MATCH_TOPIC = "match_subject"
 
 # This constant is actually embedded into

{"golden_diff": "diff --git a/zerver/lib/topic.py b/zerver/lib/topic.py\n--- a/zerver/lib/topic.py\n+++ b/zerver/lib/topic.py\n@@ -23,7 +23,7 @@\n # Only use these constants for events.\n ORIG_TOPIC = \"orig_subject\"\n TOPIC_NAME = \"subject\"\n-TOPIC_LINKS = \"subject_links\"\n+TOPIC_LINKS = \"topic_links\"\n MATCH_TOPIC = \"match_subject\"\n \n # This constant is actually embedded into\n", "issue": "Rename `subject_links` to `topic_links` in our API\nThis is an element of the broader `subject` -> `topic` migration (see #1192) that should be straightforward to change, because I believe the mobile apps don't access `subject_links` yet, so there's no compatibility work required.  (What the data is used for in the webapp is the little in-topic-field links we show when there is a link or linkifier matching the topic line of the message).\r\n\r\n@gnprice to confirm I'm reading the mobile codebase correctly that it's indeed not accessed.\r\n\r\nNoticed in #13587; tagging as a priority since this sort of API migration gets more complex when delayed.  We should be sure to look again at updating the docs as discussed in #13587 once this is complete.\n", "before_files": [{"content": "import datetime\n\nfrom django.db import connection\nfrom django.db.models.query import QuerySet, Q\nfrom django.utils.timezone import now as timezone_now\n\nfrom sqlalchemy.sql import (\n    column,\n    literal,\n    func,\n)\n\nfrom zerver.lib.request import REQ\nfrom zerver.models import (\n    Message,\n    Recipient,\n    UserMessage,\n    UserProfile,\n)\n\nfrom typing import Any, Dict, List, Optional, Tuple\n\n# Only use these constants for events.\nORIG_TOPIC = \"orig_subject\"\nTOPIC_NAME = \"subject\"\nTOPIC_LINKS = \"subject_links\"\nMATCH_TOPIC = \"match_subject\"\n\n# This constant is actually embedded into\n# the JSON data for message edit history,\n# so we'll always need to handle legacy data\n# unless we do a pretty tricky migration.\nLEGACY_PREV_TOPIC = \"prev_subject\"\n\n# This constant is pretty closely coupled to the\n# database, but it's the JSON field.\nEXPORT_TOPIC_NAME = \"subject\"\n\n'''\nThe following functions are for user-facing APIs\nwhere we'll want to support \"subject\" for a while.\n'''\n\ndef get_topic_from_message_info(message_info: Dict[str, Any]) -> str:\n    '''\n    Use this where you are getting dicts that are based off of messages\n    that may come from the outside world, especially from third party\n    APIs and bots.\n\n    We prefer 'topic' to 'subject' here.  We expect at least one field\n    to be present (or the caller must know how to handle KeyError).\n    '''\n    if 'topic' in message_info:\n        return message_info['topic']\n\n    return message_info['subject']\n\ndef REQ_topic() -> Optional[str]:\n    # REQ handlers really return a REQ, but we\n    # lie to make the rest of the type matching work.\n    return REQ(\n        whence='topic',\n        aliases=['subject'],\n        converter=lambda x: x.strip(),\n        default=None,\n    )\n\n'''\nTRY TO KEEP THIS DIVIDING LINE.\n\nBelow this line we want to make it so that functions are only\nusing \"subject\" in the DB sense, and nothing customer facing.\n\n'''\n\n# This is used in low-level message functions in\n# zerver/lib/message.py, and it's not user facing.\nDB_TOPIC_NAME = \"subject\"\nMESSAGE__TOPIC = 'message__subject'\n\ndef topic_match_sa(topic_name: str) -> Any:\n    # _sa is short for Sql Alchemy, which we use mostly for\n    # queries that search messages\n    topic_cond = func.upper(column(\"subject\")) == func.upper(literal(topic_name))\n    return topic_cond\n\ndef topic_column_sa() -> Any:\n    return column(\"subject\")\n\ndef filter_by_exact_message_topic(query: QuerySet, message: Message) -> QuerySet:\n    topic_name = message.topic_name()\n    return query.filter(subject=topic_name)\n\ndef filter_by_topic_name_via_message(query: QuerySet, topic_name: str) -> QuerySet:\n    return query.filter(message__subject__iexact=topic_name)\n\ndef messages_for_topic(stream_id: int, topic_name: str) -> QuerySet:\n    return Message.objects.filter(\n        recipient__type_id=stream_id,\n        subject__iexact=topic_name,\n    )\n\ndef save_message_for_edit_use_case(message: Message) -> None:\n    message.save(update_fields=[TOPIC_NAME, \"content\", \"rendered_content\",\n                                \"rendered_content_version\", \"last_edit_time\",\n                                \"edit_history\", \"has_attachment\", \"has_image\",\n                                \"has_link\"])\n\n\ndef user_message_exists_for_topic(user_profile: UserProfile,\n                                  recipient: Recipient,\n                                  topic_name: str) -> bool:\n    return UserMessage.objects.filter(\n        user_profile=user_profile,\n        message__recipient=recipient,\n        message__subject__iexact=topic_name,\n    ).exists()\n\ndef update_messages_for_topic_edit(message: Message,\n                                   propagate_mode: str,\n                                   orig_topic_name: str,\n                                   topic_name: str) -> List[Message]:\n    propagate_query = Q(recipient = message.recipient, subject = orig_topic_name)\n    # We only change messages up to 7 days in the past, to avoid hammering our\n    # DB by changing an unbounded amount of messages\n    if propagate_mode == 'change_all':\n        before_bound = timezone_now() - datetime.timedelta(days=7)\n\n        propagate_query = (propagate_query & ~Q(id = message.id) &\n                           Q(date_sent__range=(before_bound, timezone_now())))\n    if propagate_mode == 'change_later':\n        propagate_query = propagate_query & Q(id__gt = message.id)\n\n    messages = Message.objects.filter(propagate_query).select_related()\n\n    # Evaluate the query before running the update\n    messages_list = list(messages)\n    messages.update(subject=topic_name)\n\n    for m in messages_list:\n        # The cached ORM object is not changed by messages.update()\n        # and the remote cache update requires the new value\n        m.set_topic_name(topic_name)\n\n    return messages_list\n\ndef generate_topic_history_from_db_rows(rows: List[Tuple[str, int]]) -> List[Dict[str, Any]]:\n    canonical_topic_names = {}  # type: Dict[str, Tuple[int, str]]\n\n    # Sort rows by max_message_id so that if a topic\n    # has many different casings, we use the most\n    # recent row.\n    rows = sorted(rows, key=lambda tup: tup[1])\n\n    for (topic_name, max_message_id) in rows:\n        canonical_name = topic_name.lower()\n        canonical_topic_names[canonical_name] = (max_message_id, topic_name)\n\n    history = []\n    for canonical_topic, (max_message_id, topic_name) in canonical_topic_names.items():\n        history.append(dict(\n            name=topic_name,\n            max_id=max_message_id)\n        )\n    return sorted(history, key=lambda x: -x['max_id'])\n\ndef get_topic_history_for_stream(user_profile: UserProfile,\n                                 recipient: Recipient,\n                                 public_history: bool) -> List[Dict[str, Any]]:\n    cursor = connection.cursor()\n    if public_history:\n        query = '''\n        SELECT\n            \"zerver_message\".\"subject\" as topic,\n            max(\"zerver_message\".id) as max_message_id\n        FROM \"zerver_message\"\n        WHERE (\n            \"zerver_message\".\"recipient_id\" = %s\n        )\n        GROUP BY (\n            \"zerver_message\".\"subject\"\n        )\n        ORDER BY max(\"zerver_message\".id) DESC\n        '''\n        cursor.execute(query, [recipient.id])\n    else:\n        query = '''\n        SELECT\n            \"zerver_message\".\"subject\" as topic,\n            max(\"zerver_message\".id) as max_message_id\n        FROM \"zerver_message\"\n        INNER JOIN \"zerver_usermessage\" ON (\n            \"zerver_usermessage\".\"message_id\" = \"zerver_message\".\"id\"\n        )\n        WHERE (\n            \"zerver_usermessage\".\"user_profile_id\" = %s AND\n            \"zerver_message\".\"recipient_id\" = %s\n        )\n        GROUP BY (\n            \"zerver_message\".\"subject\"\n        )\n        ORDER BY max(\"zerver_message\".id) DESC\n        '''\n        cursor.execute(query, [user_profile.id, recipient.id])\n    rows = cursor.fetchall()\n    cursor.close()\n\n    return generate_topic_history_from_db_rows(rows)\n\ndef get_topic_history_for_web_public_stream(recipient: Recipient) -> List[Dict[str, Any]]:\n    cursor = connection.cursor()\n    query = '''\n    SELECT\n        \"zerver_message\".\"subject\" as topic,\n        max(\"zerver_message\".id) as max_message_id\n    FROM \"zerver_message\"\n    WHERE (\n        \"zerver_message\".\"recipient_id\" = %s\n    )\n    GROUP BY (\n        \"zerver_message\".\"subject\"\n    )\n    ORDER BY max(\"zerver_message\".id) DESC\n    '''\n    cursor.execute(query, [recipient.id])\n    rows = cursor.fetchall()\n    cursor.close()\n\n    return generate_topic_history_from_db_rows(rows)\n", "path": "zerver/lib/topic.py"}], "after_files": [{"content": "import datetime\n\nfrom django.db import connection\nfrom django.db.models.query import QuerySet, Q\nfrom django.utils.timezone import now as timezone_now\n\nfrom sqlalchemy.sql import (\n    column,\n    literal,\n    func,\n)\n\nfrom zerver.lib.request import REQ\nfrom zerver.models import (\n    Message,\n    Recipient,\n    UserMessage,\n    UserProfile,\n)\n\nfrom typing import Any, Dict, List, Optional, Tuple\n\n# Only use these constants for events.\nORIG_TOPIC = \"orig_subject\"\nTOPIC_NAME = \"subject\"\nTOPIC_LINKS = \"topic_links\"\nMATCH_TOPIC = \"match_subject\"\n\n# This constant is actually embedded into\n# the JSON data for message edit history,\n# so we'll always need to handle legacy data\n# unless we do a pretty tricky migration.\nLEGACY_PREV_TOPIC = \"prev_subject\"\n\n# This constant is pretty closely coupled to the\n# database, but it's the JSON field.\nEXPORT_TOPIC_NAME = \"subject\"\n\n'''\nThe following functions are for user-facing APIs\nwhere we'll want to support \"subject\" for a while.\n'''\n\ndef get_topic_from_message_info(message_info: Dict[str, Any]) -> str:\n    '''\n    Use this where you are getting dicts that are based off of messages\n    that may come from the outside world, especially from third party\n    APIs and bots.\n\n    We prefer 'topic' to 'subject' here.  We expect at least one field\n    to be present (or the caller must know how to handle KeyError).\n    '''\n    if 'topic' in message_info:\n        return message_info['topic']\n\n    return message_info['subject']\n\ndef REQ_topic() -> Optional[str]:\n    # REQ handlers really return a REQ, but we\n    # lie to make the rest of the type matching work.\n    return REQ(\n        whence='topic',\n        aliases=['subject'],\n        converter=lambda x: x.strip(),\n        default=None,\n    )\n\n'''\nTRY TO KEEP THIS DIVIDING LINE.\n\nBelow this line we want to make it so that functions are only\nusing \"subject\" in the DB sense, and nothing customer facing.\n\n'''\n\n# This is used in low-level message functions in\n# zerver/lib/message.py, and it's not user facing.\nDB_TOPIC_NAME = \"subject\"\nMESSAGE__TOPIC = 'message__subject'\n\ndef topic_match_sa(topic_name: str) -> Any:\n    # _sa is short for Sql Alchemy, which we use mostly for\n    # queries that search messages\n    topic_cond = func.upper(column(\"subject\")) == func.upper(literal(topic_name))\n    return topic_cond\n\ndef topic_column_sa() -> Any:\n    return column(\"subject\")\n\ndef filter_by_exact_message_topic(query: QuerySet, message: Message) -> QuerySet:\n    topic_name = message.topic_name()\n    return query.filter(subject=topic_name)\n\ndef filter_by_topic_name_via_message(query: QuerySet, topic_name: str) -> QuerySet:\n    return query.filter(message__subject__iexact=topic_name)\n\ndef messages_for_topic(stream_id: int, topic_name: str) -> QuerySet:\n    return Message.objects.filter(\n        recipient__type_id=stream_id,\n        subject__iexact=topic_name,\n    )\n\ndef save_message_for_edit_use_case(message: Message) -> None:\n    message.save(update_fields=[TOPIC_NAME, \"content\", \"rendered_content\",\n                                \"rendered_content_version\", \"last_edit_time\",\n                                \"edit_history\", \"has_attachment\", \"has_image\",\n                                \"has_link\"])\n\n\ndef user_message_exists_for_topic(user_profile: UserProfile,\n                                  recipient: Recipient,\n                                  topic_name: str) -> bool:\n    return UserMessage.objects.filter(\n        user_profile=user_profile,\n        message__recipient=recipient,\n        message__subject__iexact=topic_name,\n    ).exists()\n\ndef update_messages_for_topic_edit(message: Message,\n                                   propagate_mode: str,\n                                   orig_topic_name: str,\n                                   topic_name: str) -> List[Message]:\n    propagate_query = Q(recipient = message.recipient, subject = orig_topic_name)\n    # We only change messages up to 7 days in the past, to avoid hammering our\n    # DB by changing an unbounded amount of messages\n    if propagate_mode == 'change_all':\n        before_bound = timezone_now() - datetime.timedelta(days=7)\n\n        propagate_query = (propagate_query & ~Q(id = message.id) &\n                           Q(date_sent__range=(before_bound, timezone_now())))\n    if propagate_mode == 'change_later':\n        propagate_query = propagate_query & Q(id__gt = message.id)\n\n    messages = Message.objects.filter(propagate_query).select_related()\n\n    # Evaluate the query before running the update\n    messages_list = list(messages)\n    messages.update(subject=topic_name)\n\n    for m in messages_list:\n        # The cached ORM object is not changed by messages.update()\n        # and the remote cache update requires the new value\n        m.set_topic_name(topic_name)\n\n    return messages_list\n\ndef generate_topic_history_from_db_rows(rows: List[Tuple[str, int]]) -> List[Dict[str, Any]]:\n    canonical_topic_names = {}  # type: Dict[str, Tuple[int, str]]\n\n    # Sort rows by max_message_id so that if a topic\n    # has many different casings, we use the most\n    # recent row.\n    rows = sorted(rows, key=lambda tup: tup[1])\n\n    for (topic_name, max_message_id) in rows:\n        canonical_name = topic_name.lower()\n        canonical_topic_names[canonical_name] = (max_message_id, topic_name)\n\n    history = []\n    for canonical_topic, (max_message_id, topic_name) in canonical_topic_names.items():\n        history.append(dict(\n            name=topic_name,\n            max_id=max_message_id)\n        )\n    return sorted(history, key=lambda x: -x['max_id'])\n\ndef get_topic_history_for_stream(user_profile: UserProfile,\n                                 recipient: Recipient,\n                                 public_history: bool) -> List[Dict[str, Any]]:\n    cursor = connection.cursor()\n    if public_history:\n        query = '''\n        SELECT\n            \"zerver_message\".\"subject\" as topic,\n            max(\"zerver_message\".id) as max_message_id\n        FROM \"zerver_message\"\n        WHERE (\n            \"zerver_message\".\"recipient_id\" = %s\n        )\n        GROUP BY (\n            \"zerver_message\".\"subject\"\n        )\n        ORDER BY max(\"zerver_message\".id) DESC\n        '''\n        cursor.execute(query, [recipient.id])\n    else:\n        query = '''\n        SELECT\n            \"zerver_message\".\"subject\" as topic,\n            max(\"zerver_message\".id) as max_message_id\n        FROM \"zerver_message\"\n        INNER JOIN \"zerver_usermessage\" ON (\n            \"zerver_usermessage\".\"message_id\" = \"zerver_message\".\"id\"\n        )\n        WHERE (\n            \"zerver_usermessage\".\"user_profile_id\" = %s AND\n            \"zerver_message\".\"recipient_id\" = %s\n        )\n        GROUP BY (\n            \"zerver_message\".\"subject\"\n        )\n        ORDER BY max(\"zerver_message\".id) DESC\n        '''\n        cursor.execute(query, [user_profile.id, recipient.id])\n    rows = cursor.fetchall()\n    cursor.close()\n\n    return generate_topic_history_from_db_rows(rows)\n\ndef get_topic_history_for_web_public_stream(recipient: Recipient) -> List[Dict[str, Any]]:\n    cursor = connection.cursor()\n    query = '''\n    SELECT\n        \"zerver_message\".\"subject\" as topic,\n        max(\"zerver_message\".id) as max_message_id\n    FROM \"zerver_message\"\n    WHERE (\n        \"zerver_message\".\"recipient_id\" = %s\n    )\n    GROUP BY (\n        \"zerver_message\".\"subject\"\n    )\n    ORDER BY max(\"zerver_message\".id) DESC\n    '''\n    cursor.execute(query, [recipient.id])\n    rows = cursor.fetchall()\n    cursor.close()\n\n    return generate_topic_history_from_db_rows(rows)\n", "path": "zerver/lib/topic.py"}]}