Problem/Motivation

If a pager for a given view display has an offset, this offset is subtracted from the rowcount to calculate the $total_items value. If the rowcount < the offset, this results in a negative value.
Since $total_items is disclosed as token in view display areas as e.g. "@total" this looks like a (minor) bug to me.

Proposed resolution

Simple fix: never let $total_items be negative.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

keesje created an issue. See original summary.

keesje’s picture

dawehner’s picture

Status: Active » Needs work
Issue tags: +Needs tests

It would be nice if we have some form of test coverage here?

keesje’s picture

I will try to provide that, but need some guiding. I would suggest:

run testExecuteCountQueryWithOffset multiple times with 2 sets of data?
like:
3 rows, offset 2 asserts 1
2 rows, offset 3 asserts 0

Or

make a new testcase for this in PagerPluginBaseTest?
like:
testExecuteCountQueryWithOffsetLargerThanResult()

dawehner’s picture

make a new testcase for this in PagerPluginBaseTest?
like:
testExecuteCountQueryWithOffsetLargerThanResult()

This sounds super nice!

keesje’s picture

Manuel Garcia’s picture

Version: 8.3.2 » 8.3.x-dev
Status: Needs work » Needs review
Issue tags: -Needs tests

Looks good to me, let's see what the bot says...

Status: Needs review » Needs work

The last submitted patch, 6: views_pager_total_prevent_negative_unittest-2881030-5.patch, failed testing.

Manuel Garcia’s picture

Version: 8.3.x-dev » 8.4.x-dev
Status: Needs work » Needs review
FileSize
592 bytes
1.87 KB

Test on #6 fails with:

Failed asserting that -1 matches expected 0.

So this tells me that the test is indeed correct.

Here is both the fix and the test combined into one, against 8.4.x

keesje’s picture

Cool, combined patch applied cleanly.
Thanks!

Anonymous’s picture

Status: Needs review » Reviewed & tested by the community

Thanks, looks perfect!


At first I wanted to propose bit refactoring, like:
public function executeCountQuery(&$count_query) {
  $items = $count_query->execute()->fetchField();
  $offset = $this->options['offset'] ?: 0;
  $this->total_items = max($items - $offset, 0);
  return $this->total_items;
}

but it did not get much better and is not related to the problem.



+++ b/core/modules/views/src/Plugin/views/pager/PagerPluginBase.php
@@ -157,6 +157,11 @@ public function executeCountQuery(&$count_query) {
+    // Prevent from being negative.

Do we need this comment?)

larowlan’s picture

  1. +++ b/core/modules/views/src/Plugin/views/pager/PagerPluginBase.php
    @@ -157,6 +157,11 @@ public function executeCountQuery(&$count_query) {
    +    // Prevent from being negative.
    +    if ($this->total_items < 0) {
    +      $this->total_items = 0;
    +    }
    

    We could do this in a one-liner

    $this->total_items = max(0, $this->total_items);
    

    - not sure it matters though

  2. +++ b/core/modules/views/tests/src/Unit/Plugin/pager/PagerPluginBaseTest.php
    @@ -247,6 +247,30 @@ public function testExecuteCountQueryWithOffset() {
    +    $statement = $this->getMock('\Drupal\Tests\views\Unit\Plugin\pager\TestStatementInterface');
    ...
    +    $query = $this->getMockBuilder('\Drupal\Core\Database\Query\Select')
    

    Can we use the ::class constants here. It makes discovery of dependencies more explicit, e.g. IDEs will find, but they're less likely to find these strings.

larowlan’s picture

Status: Reviewed & tested by the community » Needs work

Needs work for item 2 of #12. Not fussed either way on item 1.

keesje’s picture

@lorowlan, thanks for your feedback. I don't see a problem to use ::class constants. but wouldn't that be inconsistent with other tests in the same suite? In my opinion changing that isn't the scope of this issue.

Manuel Garcia’s picture

I tend to agree with @keesje here... a quick grep across core also shows that this is a very common pattern.
Perhaps tackling this problem on a different issue for all core tests would make sense?

Anonymous’s picture

#13: +1 :)

  • at the current time, all new unit tests use class instead of string
  • and this is very common practice among the drupal issues, when we adding good code instead of homogeneous code
  • later (or maybe now), we can be used this as a pattern when refactoring the file
  • if the committers consider this change to be out of scope, we can easily move it to a new issue (or offer it to Novice for improve skills)
Manuel Garcia’s picture

Makes sense @vaplas

if the committers consider this change to be out of scope, we can easily move it to a new issue (or offer it to Novice for improve skills)

It came to mind that @larowlan is now a committer so that settles it then :-D

Anonymous’s picture

Perhaps tackling this problem on a different issue for all core tests would make sense?

Nice idea! This make sense to me regardless of the results here. And I have some experience to do it. But such a global change will be commited around beta-rc phase of 8.4 (i.e. not yet soon).

pk188’s picture

Status: Needs work » Needs review
FileSize
1.81 KB

So, fixed 1 of #12 and not 2.

Anonymous’s picture

#19: thanks for help, @pk188. But we can see on the code:
$this->total_items = max(0, $this->total_items);
like on:

if (x <= 0) {
  x = 0;
}
else {
 x = x;
}

And this is no better than:

if (x < 0) {
  x = 0;
}

right?

I think such a view is main reason why @larowlan added next comment 'Not fussed either way on item 1.', and why #11 also not an ideal :)

And iterdiff between patches is very useful to make sure that no other changes (by chance).

larowlan’s picture

@pk188 can you please provide an interdiff for #19?

Manuel Garcia’s picture

Here it is @larowlan
@pk188 you can have a look at how to create these here https://www.drupal.org/documentation/git/interdiff :)

Manuel Garcia’s picture

Oops, wrong file extension (should be .txt) sorry test bot!

Status: Needs review » Needs work

The last submitted patch, 22: interdiff-9-19.patch, failed testing. View results

Manuel Garcia’s picture

Status: Needs work » Needs review
pk188’s picture

FileSize
560 bytes

Interdiff @larowlan. Sorry for being late.

larowlan’s picture

Status: Needs review » Needs work

@pk188 - no need to apologize, thanks for working on this bug.

Can we get the change to use ::class constants done here too, easier to do it before than later.

+++ b/core/modules/views/src/Plugin/views/pager/PagerPluginBase.php
@@ -157,10 +157,7 @@
-    // Prevent from being negative.

Can we get this comment added back?

This is very close, thanks everyone for accommodating my feedback.

pk188’s picture

Status: Needs work » Needs review
FileSize
1.89 KB

Fixed #27.

larowlan’s picture

@pk188 thanks again - please look after your reviewers by providing an interdiff, so they don't have to review everything from scratch :)

+++ b/core/modules/views/tests/src/Unit/Plugin/pager/PagerPluginBaseTest.php
@@ -247,6 +247,30 @@ public function testExecuteCountQueryWithOffset() {
+    $statement = $this->getMock('\Drupal\Tests\views\Unit\Plugin\pager\TestStatementInterface');
...
+    $query = $this->getMockBuilder('\Drupal\Core\Database\Query\Select')

These still need to use ::class constants

Anonymous’s picture

Anonymous’s picture

Status: Needs review » Needs work

#27

Can we get this comment added back?

What additional information will this comment give?) Is not the self-documenting code? And we have a test coverage to prevent remove this change.

$this->total_items = max(0, $this->total_items);
We can move it closer to a place that has a negative effect. For example, if the offset is moved to another location, we can get rid of unnecessary code. And it will be the closest:
$this->total_items = max(0, $all - $offset);
But in any case, this is not the question that really worries me :)

Manuel Garcia’s picture

Status: Needs work » Needs review
FileSize
1.29 KB
2.11 KB

#15 done #2886352: Replace string 'path/to/class' with ::class constant :)
This can be used here like hint.

Yay!

Switching the test to use ::class constants.

larowlan’s picture

Status: Needs review » Reviewed & tested by the community

Looks good to me

  • catch committed 9fa529d on 8.4.x
    Issue #2881030 by Manuel Garcia, pk188, keesje, vaplas, larowlan: $...
catch’s picture

Status: Reviewed & tested by the community » Fixed

Committed/pushed to 8.4.x, thanks!

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.