We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Support measurement metrics in new metrics/traces sample endpoint


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/sentry/sentry_metrics/querying/samples_list.py`
Content:
```
1 from abc import ABC, abstractmethod
2 from datetime import datetime
3 from typing import Any
4 
5 from snuba_sdk import And, Condition, Op, Or
6 
7 from sentry import options
8 from sentry.search.events.builder import QueryBuilder, SpansIndexedQueryBuilder
9 from sentry.search.events.types import QueryBuilderConfig, SnubaParams
10 from sentry.snuba.dataset import Dataset
11 from sentry.snuba.metrics.naming_layer.mri import SpanMRI, TransactionMRI
12 from sentry.snuba.referrer import Referrer
13 
14 
15 class SamplesListExecutor(ABC):
16     def __init__(
17         self,
18         mri: str,
19         params: dict[str, Any],
20         snuba_params: SnubaParams,
21         fields: list[str],
22         query: str | None,
23         rollup: int,
24         referrer: Referrer,
25     ):
26         self.mri = mri
27         self.params = params
28         self.snuba_params = snuba_params
29         self.fields = fields
30         self.query = query
31         self.rollup = rollup
32         self.referrer = referrer
33 
34     @classmethod
35     @abstractmethod
36     def supports(cls, metric_mri: str) -> bool:
37         raise NotImplementedError
38 
39     @abstractmethod
40     def execute(self, offset, limit):
41         raise NotImplementedError
42 
43     def get_spans_by_key(self, span_ids: list[tuple[str, str, str]]):
44         if not span_ids:
45             return {"data": []}
46 
47         builder = SpansIndexedQueryBuilder(
48             Dataset.SpansIndexed,
49             self.params,
50             snuba_params=self.snuba_params,
51             selected_columns=self.fields,
52             limit=len(span_ids),
53             offset=0,
54         )
55 
56         # Using `IN` sometimes does not use the bloomfilter index
57         # on the table. So we're explicitly writing the condition
58         # using `OR`s.
59         #
60         # May not be necessary because it's also filtering on the
61         # `span.group` as well which allows Clickhouse to filter
62         # via the primary key but this is a precaution.
63         conditions = [
64             And(
65                 [
66                     Condition(builder.column("span.group"), Op.EQ, group),
67                     Condition(
68                         builder.column("timestamp"), Op.EQ, datetime.fromisoformat(timestamp)
69                     ),
70                     Condition(builder.column("id"), Op.EQ, span_id),
71                 ]
72             )
73             for (group, timestamp, span_id) in span_ids
74         ]
75 
76         if len(conditions) == 1:
77             span_condition = conditions[0]
78         else:
79             span_condition = Or(conditions)
80 
81         builder.add_conditions([span_condition])
82 
83         query_results = builder.run_query(self.referrer.value)
84         return builder.process_results(query_results)
85 
86 
87 class SegmentsSamplesListExecutor(SamplesListExecutor):
88     @classmethod
89     def mri_to_column(cls, mri) -> str | None:
90         if mri == TransactionMRI.DURATION.value:
91             return "duration"
92         return None
93 
94     @classmethod
95     def supports(cls, mri: str) -> bool:
96         return cls.mri_to_column(mri) is not None
97 
98     def execute(self, offset, limit):
99         span_keys = self.get_span_keys(offset, limit)
100         return self.get_spans_by_key(span_keys)
101 
102     def get_span_keys(self, offset: int, limit: int) -> list[tuple[str, str, str]]:
103         rounded_timestamp = f"rounded_timestamp({self.rollup})"
104 
105         builder = QueryBuilder(
106             Dataset.Transactions,
107             self.params,
108             snuba_params=self.snuba_params,
109             query=self.query,
110             selected_columns=[rounded_timestamp, "example()"],
111             limit=limit,
112             offset=offset,
113             sample_rate=options.get("metrics.sample-list.sample-rate"),
114             config=QueryBuilderConfig(functions_acl=["rounded_timestamp", "example"]),
115         )
116 
117         query_results = builder.run_query(self.referrer.value)
118         result = builder.process_results(query_results)
119 
120         return [
121             (
122                 "00",  # all segments have a group of `00` currently
123                 row["example"][0],  # timestamp
124                 row["example"][1],  # span_id
125             )
126             for row in result["data"]
127         ]
128 
129 
130 class SpansSamplesListExecutor(SamplesListExecutor):
131     MRI_MAPPING = {
132         SpanMRI.DURATION.value: "span.duration",
133         SpanMRI.SELF_TIME.value: "span.self_time",
134     }
135 
136     @classmethod
137     def mri_to_column(cls, mri) -> str | None:
138         return cls.MRI_MAPPING.get(mri)
139 
140     @classmethod
141     def supports(cls, mri: str) -> bool:
142         return cls.mri_to_column(mri) is not None
143 
144     def execute(self, offset, limit):
145         span_keys = self.get_span_keys(offset, limit)
146         return self.get_spans_by_key(span_keys)
147 
148     def get_span_keys(self, offset: int, limit: int) -> list[tuple[str, str, str]]:
149         rounded_timestamp = f"rounded_timestamp({self.rollup})"
150 
151         builder = SpansIndexedQueryBuilder(
152             Dataset.SpansIndexed,
153             self.params,
154             snuba_params=self.snuba_params,
155             query=self.query,
156             selected_columns=[rounded_timestamp, "example()"],
157             limit=limit,
158             offset=offset,
159             sample_rate=options.get("metrics.sample-list.sample-rate"),
160             config=QueryBuilderConfig(functions_acl=["rounded_timestamp", "example"]),
161         )
162 
163         builder.add_conditions(
164             [
165                 # The `00` group is used for spans not used within the
166                 # new starfish experience. It's effectively the group
167                 # for other. It is a massive group, so we've chosen
168                 # to exclude it here.
169                 #
170                 # In the future, we will want to look into exposing them
171                 Condition(builder.column("span.group"), Op.NEQ, "00")
172             ]
173         )
174 
175         query_results = builder.run_query(self.referrer.value)
176         result = builder.process_results(query_results)
177 
178         return [
179             (
180                 row["example"][0],  # group
181                 row["example"][1],  # timestamp
182                 row["example"][2],  # span_id
183             )
184             for row in result["data"]
185         ]
186 
187 
188 SAMPLE_LIST_EXECUTORS = [
189     SpansSamplesListExecutor,
190     SegmentsSamplesListExecutor,
191 ]
192 
193 
194 def get_sample_list_executor_cls(mri) -> type[SamplesListExecutor] | None:
195     for executor_cls in SAMPLE_LIST_EXECUTORS:
196         if executor_cls.supports(mri):
197             return executor_cls
198     return None
199
```
--- 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/src/sentry/sentry_metrics/querying/samples_list.py b/src/sentry/sentry_metrics/querying/samples_list.py
--- a/src/sentry/sentry_metrics/querying/samples_list.py
+++ b/src/sentry/sentry_metrics/querying/samples_list.py
@@ -2,13 +2,13 @@
 from datetime import datetime
 from typing import Any
 
-from snuba_sdk import And, Condition, Op, Or
+from snuba_sdk import And, Column, Condition, Function, Op, Or
 
 from sentry import options
 from sentry.search.events.builder import QueryBuilder, SpansIndexedQueryBuilder
 from sentry.search.events.types import QueryBuilderConfig, SnubaParams
 from sentry.snuba.dataset import Dataset
-from sentry.snuba.metrics.naming_layer.mri import SpanMRI, TransactionMRI
+from sentry.snuba.metrics.naming_layer.mri import SpanMRI, TransactionMRI, is_measurement, parse_mri
 from sentry.snuba.referrer import Referrer
 
 
@@ -86,10 +86,9 @@
 
 class SegmentsSamplesListExecutor(SamplesListExecutor):
     @classmethod
+    @abstractmethod
     def mri_to_column(cls, mri) -> str | None:
-        if mri == TransactionMRI.DURATION.value:
-            return "duration"
-        return None
+        raise NotImplementedError
 
     @classmethod
     def supports(cls, mri: str) -> bool:
@@ -102,6 +101,16 @@
     def get_span_keys(self, offset: int, limit: int) -> list[tuple[str, str, str]]:
         rounded_timestamp = f"rounded_timestamp({self.rollup})"
 
+        """
+        When getting examples for a segment, it's actually much faster to read it
+        from the transactions dataset compared to the spans dataset as it's a much
+        smaller dataset.
+
+        One consideration here is that there is an one to one mapping between a
+        transaction to a segment today. If this relationship changes, we'll have to
+        rethink how to fetch segment samples a little as the transactions dataset
+        may not contain all the necessary data.
+        """
         builder = QueryBuilder(
             Dataset.Transactions,
             self.params,
@@ -114,6 +123,8 @@
             config=QueryBuilderConfig(functions_acl=["rounded_timestamp", "example"]),
         )
 
+        builder.add_conditions(self.get_additional_conditions())
+
         query_results = builder.run_query(self.referrer.value)
         result = builder.process_results(query_results)
 
@@ -126,6 +137,42 @@
             for row in result["data"]
         ]
 
+    @abstractmethod
+    def get_additional_conditions(self) -> list[Condition]:
+        raise NotImplementedError
+
+
+class TransactionDurationSamplesListExecutor(SegmentsSamplesListExecutor):
+    @classmethod
+    def mri_to_column(cls, mri) -> str | None:
+        if mri == TransactionMRI.DURATION.value:
+            return "duration"
+        return None
+
+    def get_additional_conditions(self) -> list[Condition]:
+        return []
+
+
+class MeasurementsSamplesListExecutor(SegmentsSamplesListExecutor):
+    @classmethod
+    def mri_to_column(cls, mri) -> str | None:
+        name = cls.measurement_name(mri)
+        if name is not None:
+            return f"measurements[{name}]"
+
+        return None
+
+    @classmethod
+    def measurement_name(cls, mri) -> str | None:
+        parsed_mri = parse_mri(mri)
+        if parsed_mri is not None and is_measurement(parsed_mri):
+            return parsed_mri.name[len("measurements:") :]
+        return None
+
+    def get_additional_conditions(self) -> list[Condition]:
+        name = self.measurement_name(self.mri)
+        return [Condition(Function("has", [Column("measurements.key"), name]), Op.EQ, 1)]
+
 
 class SpansSamplesListExecutor(SamplesListExecutor):
     MRI_MAPPING = {
@@ -187,7 +234,8 @@
 
 SAMPLE_LIST_EXECUTORS = [
     SpansSamplesListExecutor,
-    SegmentsSamplesListExecutor,
+    TransactionDurationSamplesListExecutor,
+    MeasurementsSamplesListExecutor,
 ]

{"golden_diff": "diff --git a/src/sentry/sentry_metrics/querying/samples_list.py b/src/sentry/sentry_metrics/querying/samples_list.py\n--- a/src/sentry/sentry_metrics/querying/samples_list.py\n+++ b/src/sentry/sentry_metrics/querying/samples_list.py\n@@ -2,13 +2,13 @@\n from datetime import datetime\n from typing import Any\n \n-from snuba_sdk import And, Condition, Op, Or\n+from snuba_sdk import And, Column, Condition, Function, Op, Or\n \n from sentry import options\n from sentry.search.events.builder import QueryBuilder, SpansIndexedQueryBuilder\n from sentry.search.events.types import QueryBuilderConfig, SnubaParams\n from sentry.snuba.dataset import Dataset\n-from sentry.snuba.metrics.naming_layer.mri import SpanMRI, TransactionMRI\n+from sentry.snuba.metrics.naming_layer.mri import SpanMRI, TransactionMRI, is_measurement, parse_mri\n from sentry.snuba.referrer import Referrer\n \n \n@@ -86,10 +86,9 @@\n \n class SegmentsSamplesListExecutor(SamplesListExecutor):\n     @classmethod\n+    @abstractmethod\n     def mri_to_column(cls, mri) -> str | None:\n-        if mri == TransactionMRI.DURATION.value:\n-            return \"duration\"\n-        return None\n+        raise NotImplementedError\n \n     @classmethod\n     def supports(cls, mri: str) -> bool:\n@@ -102,6 +101,16 @@\n     def get_span_keys(self, offset: int, limit: int) -> list[tuple[str, str, str]]:\n         rounded_timestamp = f\"rounded_timestamp({self.rollup})\"\n \n+        \"\"\"\n+        When getting examples for a segment, it's actually much faster to read it\n+        from the transactions dataset compared to the spans dataset as it's a much\n+        smaller dataset.\n+\n+        One consideration here is that there is an one to one mapping between a\n+        transaction to a segment today. If this relationship changes, we'll have to\n+        rethink how to fetch segment samples a little as the transactions dataset\n+        may not contain all the necessary data.\n+        \"\"\"\n         builder = QueryBuilder(\n             Dataset.Transactions,\n             self.params,\n@@ -114,6 +123,8 @@\n             config=QueryBuilderConfig(functions_acl=[\"rounded_timestamp\", \"example\"]),\n         )\n \n+        builder.add_conditions(self.get_additional_conditions())\n+\n         query_results = builder.run_query(self.referrer.value)\n         result = builder.process_results(query_results)\n \n@@ -126,6 +137,42 @@\n             for row in result[\"data\"]\n         ]\n \n+    @abstractmethod\n+    def get_additional_conditions(self) -> list[Condition]:\n+        raise NotImplementedError\n+\n+\n+class TransactionDurationSamplesListExecutor(SegmentsSamplesListExecutor):\n+    @classmethod\n+    def mri_to_column(cls, mri) -> str | None:\n+        if mri == TransactionMRI.DURATION.value:\n+            return \"duration\"\n+        return None\n+\n+    def get_additional_conditions(self) -> list[Condition]:\n+        return []\n+\n+\n+class MeasurementsSamplesListExecutor(SegmentsSamplesListExecutor):\n+    @classmethod\n+    def mri_to_column(cls, mri) -> str | None:\n+        name = cls.measurement_name(mri)\n+        if name is not None:\n+            return f\"measurements[{name}]\"\n+\n+        return None\n+\n+    @classmethod\n+    def measurement_name(cls, mri) -> str | None:\n+        parsed_mri = parse_mri(mri)\n+        if parsed_mri is not None and is_measurement(parsed_mri):\n+            return parsed_mri.name[len(\"measurements:\") :]\n+        return None\n+\n+    def get_additional_conditions(self) -> list[Condition]:\n+        name = self.measurement_name(self.mri)\n+        return [Condition(Function(\"has\", [Column(\"measurements.key\"), name]), Op.EQ, 1)]\n+\n \n class SpansSamplesListExecutor(SamplesListExecutor):\n     MRI_MAPPING = {\n@@ -187,7 +234,8 @@\n \n SAMPLE_LIST_EXECUTORS = [\n     SpansSamplesListExecutor,\n-    SegmentsSamplesListExecutor,\n+    TransactionDurationSamplesListExecutor,\n+    MeasurementsSamplesListExecutor,\n ]\n", "issue": "Support measurement metrics in new metrics/traces sample endpoint\n\n", "before_files": [{"content": "from abc import ABC, abstractmethod\nfrom datetime import datetime\nfrom typing import Any\n\nfrom snuba_sdk import And, Condition, Op, Or\n\nfrom sentry import options\nfrom sentry.search.events.builder import QueryBuilder, SpansIndexedQueryBuilder\nfrom sentry.search.events.types import QueryBuilderConfig, SnubaParams\nfrom sentry.snuba.dataset import Dataset\nfrom sentry.snuba.metrics.naming_layer.mri import SpanMRI, TransactionMRI\nfrom sentry.snuba.referrer import Referrer\n\n\nclass SamplesListExecutor(ABC):\n    def __init__(\n        self,\n        mri: str,\n        params: dict[str, Any],\n        snuba_params: SnubaParams,\n        fields: list[str],\n        query: str | None,\n        rollup: int,\n        referrer: Referrer,\n    ):\n        self.mri = mri\n        self.params = params\n        self.snuba_params = snuba_params\n        self.fields = fields\n        self.query = query\n        self.rollup = rollup\n        self.referrer = referrer\n\n    @classmethod\n    @abstractmethod\n    def supports(cls, metric_mri: str) -> bool:\n        raise NotImplementedError\n\n    @abstractmethod\n    def execute(self, offset, limit):\n        raise NotImplementedError\n\n    def get_spans_by_key(self, span_ids: list[tuple[str, str, str]]):\n        if not span_ids:\n            return {\"data\": []}\n\n        builder = SpansIndexedQueryBuilder(\n            Dataset.SpansIndexed,\n            self.params,\n            snuba_params=self.snuba_params,\n            selected_columns=self.fields,\n            limit=len(span_ids),\n            offset=0,\n        )\n\n        # Using `IN` sometimes does not use the bloomfilter index\n        # on the table. So we're explicitly writing the condition\n        # using `OR`s.\n        #\n        # May not be necessary because it's also filtering on the\n        # `span.group` as well which allows Clickhouse to filter\n        # via the primary key but this is a precaution.\n        conditions = [\n            And(\n                [\n                    Condition(builder.column(\"span.group\"), Op.EQ, group),\n                    Condition(\n                        builder.column(\"timestamp\"), Op.EQ, datetime.fromisoformat(timestamp)\n                    ),\n                    Condition(builder.column(\"id\"), Op.EQ, span_id),\n                ]\n            )\n            for (group, timestamp, span_id) in span_ids\n        ]\n\n        if len(conditions) == 1:\n            span_condition = conditions[0]\n        else:\n            span_condition = Or(conditions)\n\n        builder.add_conditions([span_condition])\n\n        query_results = builder.run_query(self.referrer.value)\n        return builder.process_results(query_results)\n\n\nclass SegmentsSamplesListExecutor(SamplesListExecutor):\n    @classmethod\n    def mri_to_column(cls, mri) -> str | None:\n        if mri == TransactionMRI.DURATION.value:\n            return \"duration\"\n        return None\n\n    @classmethod\n    def supports(cls, mri: str) -> bool:\n        return cls.mri_to_column(mri) is not None\n\n    def execute(self, offset, limit):\n        span_keys = self.get_span_keys(offset, limit)\n        return self.get_spans_by_key(span_keys)\n\n    def get_span_keys(self, offset: int, limit: int) -> list[tuple[str, str, str]]:\n        rounded_timestamp = f\"rounded_timestamp({self.rollup})\"\n\n        builder = QueryBuilder(\n            Dataset.Transactions,\n            self.params,\n            snuba_params=self.snuba_params,\n            query=self.query,\n            selected_columns=[rounded_timestamp, \"example()\"],\n            limit=limit,\n            offset=offset,\n            sample_rate=options.get(\"metrics.sample-list.sample-rate\"),\n            config=QueryBuilderConfig(functions_acl=[\"rounded_timestamp\", \"example\"]),\n        )\n\n        query_results = builder.run_query(self.referrer.value)\n        result = builder.process_results(query_results)\n\n        return [\n            (\n                \"00\",  # all segments have a group of `00` currently\n                row[\"example\"][0],  # timestamp\n                row[\"example\"][1],  # span_id\n            )\n            for row in result[\"data\"]\n        ]\n\n\nclass SpansSamplesListExecutor(SamplesListExecutor):\n    MRI_MAPPING = {\n        SpanMRI.DURATION.value: \"span.duration\",\n        SpanMRI.SELF_TIME.value: \"span.self_time\",\n    }\n\n    @classmethod\n    def mri_to_column(cls, mri) -> str | None:\n        return cls.MRI_MAPPING.get(mri)\n\n    @classmethod\n    def supports(cls, mri: str) -> bool:\n        return cls.mri_to_column(mri) is not None\n\n    def execute(self, offset, limit):\n        span_keys = self.get_span_keys(offset, limit)\n        return self.get_spans_by_key(span_keys)\n\n    def get_span_keys(self, offset: int, limit: int) -> list[tuple[str, str, str]]:\n        rounded_timestamp = f\"rounded_timestamp({self.rollup})\"\n\n        builder = SpansIndexedQueryBuilder(\n            Dataset.SpansIndexed,\n            self.params,\n            snuba_params=self.snuba_params,\n            query=self.query,\n            selected_columns=[rounded_timestamp, \"example()\"],\n            limit=limit,\n            offset=offset,\n            sample_rate=options.get(\"metrics.sample-list.sample-rate\"),\n            config=QueryBuilderConfig(functions_acl=[\"rounded_timestamp\", \"example\"]),\n        )\n\n        builder.add_conditions(\n            [\n                # The `00` group is used for spans not used within the\n                # new starfish experience. It's effectively the group\n                # for other. It is a massive group, so we've chosen\n                # to exclude it here.\n                #\n                # In the future, we will want to look into exposing them\n                Condition(builder.column(\"span.group\"), Op.NEQ, \"00\")\n            ]\n        )\n\n        query_results = builder.run_query(self.referrer.value)\n        result = builder.process_results(query_results)\n\n        return [\n            (\n                row[\"example\"][0],  # group\n                row[\"example\"][1],  # timestamp\n                row[\"example\"][2],  # span_id\n            )\n            for row in result[\"data\"]\n        ]\n\n\nSAMPLE_LIST_EXECUTORS = [\n    SpansSamplesListExecutor,\n    SegmentsSamplesListExecutor,\n]\n\n\ndef get_sample_list_executor_cls(mri) -> type[SamplesListExecutor] | None:\n    for executor_cls in SAMPLE_LIST_EXECUTORS:\n        if executor_cls.supports(mri):\n            return executor_cls\n    return None\n", "path": "src/sentry/sentry_metrics/querying/samples_list.py"}], "after_files": [{"content": "from abc import ABC, abstractmethod\nfrom datetime import datetime\nfrom typing import Any\n\nfrom snuba_sdk import And, Column, Condition, Function, Op, Or\n\nfrom sentry import options\nfrom sentry.search.events.builder import QueryBuilder, SpansIndexedQueryBuilder\nfrom sentry.search.events.types import QueryBuilderConfig, SnubaParams\nfrom sentry.snuba.dataset import Dataset\nfrom sentry.snuba.metrics.naming_layer.mri import SpanMRI, TransactionMRI, is_measurement, parse_mri\nfrom sentry.snuba.referrer import Referrer\n\n\nclass SamplesListExecutor(ABC):\n    def __init__(\n        self,\n        mri: str,\n        params: dict[str, Any],\n        snuba_params: SnubaParams,\n        fields: list[str],\n        query: str | None,\n        rollup: int,\n        referrer: Referrer,\n    ):\n        self.mri = mri\n        self.params = params\n        self.snuba_params = snuba_params\n        self.fields = fields\n        self.query = query\n        self.rollup = rollup\n        self.referrer = referrer\n\n    @classmethod\n    @abstractmethod\n    def supports(cls, metric_mri: str) -> bool:\n        raise NotImplementedError\n\n    @abstractmethod\n    def execute(self, offset, limit):\n        raise NotImplementedError\n\n    def get_spans_by_key(self, span_ids: list[tuple[str, str, str]]):\n        if not span_ids:\n            return {\"data\": []}\n\n        builder = SpansIndexedQueryBuilder(\n            Dataset.SpansIndexed,\n            self.params,\n            snuba_params=self.snuba_params,\n            selected_columns=self.fields,\n            limit=len(span_ids),\n            offset=0,\n        )\n\n        # Using `IN` sometimes does not use the bloomfilter index\n        # on the table. So we're explicitly writing the condition\n        # using `OR`s.\n        #\n        # May not be necessary because it's also filtering on the\n        # `span.group` as well which allows Clickhouse to filter\n        # via the primary key but this is a precaution.\n        conditions = [\n            And(\n                [\n                    Condition(builder.column(\"span.group\"), Op.EQ, group),\n                    Condition(\n                        builder.column(\"timestamp\"), Op.EQ, datetime.fromisoformat(timestamp)\n                    ),\n                    Condition(builder.column(\"id\"), Op.EQ, span_id),\n                ]\n            )\n            for (group, timestamp, span_id) in span_ids\n        ]\n\n        if len(conditions) == 1:\n            span_condition = conditions[0]\n        else:\n            span_condition = Or(conditions)\n\n        builder.add_conditions([span_condition])\n\n        query_results = builder.run_query(self.referrer.value)\n        return builder.process_results(query_results)\n\n\nclass SegmentsSamplesListExecutor(SamplesListExecutor):\n    @classmethod\n    @abstractmethod\n    def mri_to_column(cls, mri) -> str | None:\n        raise NotImplementedError\n\n    @classmethod\n    def supports(cls, mri: str) -> bool:\n        return cls.mri_to_column(mri) is not None\n\n    def execute(self, offset, limit):\n        span_keys = self.get_span_keys(offset, limit)\n        return self.get_spans_by_key(span_keys)\n\n    def get_span_keys(self, offset: int, limit: int) -> list[tuple[str, str, str]]:\n        rounded_timestamp = f\"rounded_timestamp({self.rollup})\"\n\n        \"\"\"\n        When getting examples for a segment, it's actually much faster to read it\n        from the transactions dataset compared to the spans dataset as it's a much\n        smaller dataset.\n\n        One consideration here is that there is an one to one mapping between a\n        transaction to a segment today. If this relationship changes, we'll have to\n        rethink how to fetch segment samples a little as the transactions dataset\n        may not contain all the necessary data.\n        \"\"\"\n        builder = QueryBuilder(\n            Dataset.Transactions,\n            self.params,\n            snuba_params=self.snuba_params,\n            query=self.query,\n            selected_columns=[rounded_timestamp, \"example()\"],\n            limit=limit,\n            offset=offset,\n            sample_rate=options.get(\"metrics.sample-list.sample-rate\"),\n            config=QueryBuilderConfig(functions_acl=[\"rounded_timestamp\", \"example\"]),\n        )\n\n        builder.add_conditions(self.get_additional_conditions())\n\n        query_results = builder.run_query(self.referrer.value)\n        result = builder.process_results(query_results)\n\n        return [\n            (\n                \"00\",  # all segments have a group of `00` currently\n                row[\"example\"][0],  # timestamp\n                row[\"example\"][1],  # span_id\n            )\n            for row in result[\"data\"]\n        ]\n\n    @abstractmethod\n    def get_additional_conditions(self) -> list[Condition]:\n        raise NotImplementedError\n\n\nclass TransactionDurationSamplesListExecutor(SegmentsSamplesListExecutor):\n    @classmethod\n    def mri_to_column(cls, mri) -> str | None:\n        if mri == TransactionMRI.DURATION.value:\n            return \"duration\"\n        return None\n\n    def get_additional_conditions(self) -> list[Condition]:\n        return []\n\n\nclass MeasurementsSamplesListExecutor(SegmentsSamplesListExecutor):\n    @classmethod\n    def mri_to_column(cls, mri) -> str | None:\n        name = cls.measurement_name(mri)\n        if name is not None:\n            return f\"measurements[{name}]\"\n\n        return None\n\n    @classmethod\n    def measurement_name(cls, mri) -> str | None:\n        parsed_mri = parse_mri(mri)\n        if parsed_mri is not None and is_measurement(parsed_mri):\n            return parsed_mri.name[len(\"measurements:\") :]\n        return None\n\n    def get_additional_conditions(self) -> list[Condition]:\n        name = self.measurement_name(self.mri)\n        return [Condition(Function(\"has\", [Column(\"measurements.key\"), name]), Op.EQ, 1)]\n\n\nclass SpansSamplesListExecutor(SamplesListExecutor):\n    MRI_MAPPING = {\n        SpanMRI.DURATION.value: \"span.duration\",\n        SpanMRI.SELF_TIME.value: \"span.self_time\",\n    }\n\n    @classmethod\n    def mri_to_column(cls, mri) -> str | None:\n        return cls.MRI_MAPPING.get(mri)\n\n    @classmethod\n    def supports(cls, mri: str) -> bool:\n        return cls.mri_to_column(mri) is not None\n\n    def execute(self, offset, limit):\n        span_keys = self.get_span_keys(offset, limit)\n        return self.get_spans_by_key(span_keys)\n\n    def get_span_keys(self, offset: int, limit: int) -> list[tuple[str, str, str]]:\n        rounded_timestamp = f\"rounded_timestamp({self.rollup})\"\n\n        builder = SpansIndexedQueryBuilder(\n            Dataset.SpansIndexed,\n            self.params,\n            snuba_params=self.snuba_params,\n            query=self.query,\n            selected_columns=[rounded_timestamp, \"example()\"],\n            limit=limit,\n            offset=offset,\n            sample_rate=options.get(\"metrics.sample-list.sample-rate\"),\n            config=QueryBuilderConfig(functions_acl=[\"rounded_timestamp\", \"example\"]),\n        )\n\n        builder.add_conditions(\n            [\n                # The `00` group is used for spans not used within the\n                # new starfish experience. It's effectively the group\n                # for other. It is a massive group, so we've chosen\n                # to exclude it here.\n                #\n                # In the future, we will want to look into exposing them\n                Condition(builder.column(\"span.group\"), Op.NEQ, \"00\")\n            ]\n        )\n\n        query_results = builder.run_query(self.referrer.value)\n        result = builder.process_results(query_results)\n\n        return [\n            (\n                row[\"example\"][0],  # group\n                row[\"example\"][1],  # timestamp\n                row[\"example\"][2],  # span_id\n            )\n            for row in result[\"data\"]\n        ]\n\n\nSAMPLE_LIST_EXECUTORS = [\n    SpansSamplesListExecutor,\n    TransactionDurationSamplesListExecutor,\n    MeasurementsSamplesListExecutor,\n]\n\n\ndef get_sample_list_executor_cls(mri) -> type[SamplesListExecutor] | None:\n    for executor_cls in SAMPLE_LIST_EXECUTORS:\n        if executor_cls.supports(mri):\n            return executor_cls\n    return None\n", "path": "src/sentry/sentry_metrics/querying/samples_list.py"}]}