diff --git a/core/src/zeit/connector/interfaces.py b/core/src/zeit/connector/interfaces.py index 48ecc4db28..5eac1e0338 100644 --- a/core/src/zeit/connector/interfaces.py +++ b/core/src/zeit/connector/interfaces.py @@ -217,6 +217,12 @@ def query(): Return query object for properties table """ + def search_sql_count(search_expression): + """Count search results for `search_expression` + + returns integer + """ + class ICachingConnector(IConnector): """A connector that caches.""" diff --git a/core/src/zeit/connector/mock.py b/core/src/zeit/connector/mock.py index 7944916449..70fc3cc962 100644 --- a/core/src/zeit/connector/mock.py +++ b/core/src/zeit/connector/mock.py @@ -331,6 +331,9 @@ def search_sql(self, expression): def query(self): return sqlalchemy.select(self.Content) + def search_sql_count(self, query): + return len(self.search_result) + # internal helpers def _get_cannonical_id(self, id): diff --git a/core/src/zeit/connector/postgresql.py b/core/src/zeit/connector/postgresql.py index 66455c3d68..2db0794016 100644 --- a/core/src/zeit/connector/postgresql.py +++ b/core/src/zeit/connector/postgresql.py @@ -601,6 +601,9 @@ def _search_dav(self, attrlist, expr): def query(self): return select(self.Content) + def search_sql_count(self, query): + return self.session.execute(query.with_only_columns(sqlalchemy.func.count())).scalar() + def search_sql(self, query): result = [] for content in self.session.execute(query).scalars(): diff --git a/core/src/zeit/connector/tests/test_postgresql.py b/core/src/zeit/connector/tests/test_postgresql.py index 56793e5d90..e6e3ea41da 100644 --- a/core/src/zeit/connector/tests/test_postgresql.py +++ b/core/src/zeit/connector/tests/test_postgresql.py @@ -171,6 +171,14 @@ def test_search_by_sql_uses_cache(self): ): self.assertEqual(result[0].data.read(), b'mybody') + def test_search_sql_count_returns_result_count(self): + self.add_resource('one', type='article') + self.add_resource('two', type='centerpage') + self.add_resource('three', type='article') + query = self.connector.query() + query = query.filter_by(type='article') + self.assertEqual(self.connector.search_sql_count(query), 2) + def test_search_returns_uuid(self): res = self.get_resource( 'foo', diff --git a/core/src/zeit/content/cp/tests/test_automatic.py b/core/src/zeit/content/cp/tests/test_automatic.py index 1e6c59e388..cffbd0e023 100644 --- a/core/src/zeit/content/cp/tests/test_automatic.py +++ b/core/src/zeit/content/cp/tests/test_automatic.py @@ -1154,3 +1154,7 @@ def test_offset_query_results_for_pagination(self): self.area.start = 5 IRenderedArea(self.area).values() self.assertEllipsis('...OFFSET 5...', self.connector.search_args[0]) + + def test_get_total_hits(self): + self.connector.search_result = ['http://xml.zeit.de/testcontent'] + self.assertEqual(1, IRenderedArea(self.area)._content_query.total_hits) diff --git a/core/src/zeit/contentquery/query.py b/core/src/zeit/contentquery/query.py index 740f193cbc..0762465ada 100644 --- a/core/src/zeit/contentquery/query.py +++ b/core/src/zeit/contentquery/query.py @@ -56,8 +56,15 @@ class SQLContentQuery(ContentQuery): def connector(self): return zope.component.getUtility(zeit.connector.interfaces.IConnector) + @cachedproperty + def total_hits(self): + return self.connector.search_sql_count(self._build_query()) + def __call__(self): - result = [ICMSContent(x) for x in self.connector.search_sql(self._build_query())] + query = self._build_query() + query = query.order_by(sql(self.context.sql_order)) + query = query.limit(self.rows).offset(self.start) + result = [ICMSContent(x) for x in self.connector.search_sql(query)] return result def _build_query(self): @@ -65,8 +72,6 @@ def _build_query(self): query = query.where(sql(self.context.sql_query)) query = self.add_clauses(query) query = self.hide_dupes_clause(query) - query = query.order_by(sql(self.context.sql_order)) - query = query.limit(self.rows).offset(self.start) return query