diff --git a/python/lsst/daf/butler/_butler.py b/python/lsst/daf/butler/_butler.py index 13bce01e8e..a2e26a5caa 100644 --- a/python/lsst/daf/butler/_butler.py +++ b/python/lsst/daf/butler/_butler.py @@ -1743,8 +1743,13 @@ def query_dimension_records( Names of the columns/dimensions to use for ordering returned data IDs. Column name can be prefixed with minus (``-``) to use descending ordering. - limit : `int`, optional - Upper limit on the number of returned records. + limit : `int` or `None`, optional + Upper limit on the number of returned records. `None` can be used + if no limit is wanted. A limit of ``0`` means that the query will + be executed and validated but no results will be returned. In this + case there will be no exception even if ``explain`` is `True`. + If a negative value is given a warning will be issued if the number + of results is capped by that limit. explain : `bool`, optional If `True` (default) then `EmptyQueryResultError` exception is raised when resulting list is empty. The exception contains @@ -1781,15 +1786,27 @@ def query_dimension_records( data_id = DataCoordinate.make_empty(self.dimensions) if order_by is None: order_by = [] + query_limit = limit + warn_limit = False + if limit is not None and limit < 0: + query_limit = abs(limit) + 1 + warn_limit = True with self.query() as query: result = ( query.where(data_id, where, bind=bind, **kwargs) .dimension_records(element) .order_by(*ensure_iterable(order_by)) - .limit(limit) + .limit(query_limit) ) dimension_records = list(result) - if explain and not dimension_records: + if warn_limit and len(dimension_records) == query_limit: + # We asked for one too many so must remove that from the list. + dimension_records.pop(-1) + assert limit is not None # For mypy. + _LOG.warning( + "More dimension records are available than the requested limit of %d.", abs(limit) + ) + if explain and (limit is None or limit != 0) and not dimension_records: raise EmptyQueryResultError(list(result.explain_no_results())) return dimension_records diff --git a/python/lsst/daf/butler/tests/butler_queries.py b/python/lsst/daf/butler/tests/butler_queries.py index a25d9e53bd..7e3b1b2188 100644 --- a/python/lsst/daf/butler/tests/butler_queries.py +++ b/python/lsst/daf/butler/tests/butler_queries.py @@ -182,6 +182,7 @@ def test_simple_record_query(self) -> None: results = query.dimension_records("detector") self.check_detector_records(results) self.check_detector_records_returned(butler.query_dimension_records("detector")) + self.assertEqual(len(butler.query_dimension_records("detector", limit=0)), 0) self.check_detector_records(results.order_by("detector"), ordered=True) self.check_detector_records_returned( butler.query_dimension_records("detector", order_by="detector"), ordered=True @@ -200,6 +201,13 @@ def test_simple_record_query(self) -> None: ids=[1, 2], ordered=True, ) + with self.assertLogs("lsst.daf.butler", level="WARNING") as wcm: + self.check_detector_records_returned( + butler.query_dimension_records("detector", limit=-2, order_by="-detector"), + ids=[4, 3], + ordered=True, + ) + self.assertIn("More dimension records are available", wcm.output[0]) self.check_detector_records(results.where(_x.detector.raft == "B", instrument="Cam1"), [3, 4]) self.check_detector_records_returned( butler.query_dimension_records(