Hi there.

StatementInterface::rowCount() method return the number of rows affected by the last DELETE, INSERT, or UPDATE statement executed.
(according to php.net documentation and api.drupal.org)

But in one core test (name: 'Select tests, complex') this method used to get row count for SELECT statement.
In MySQL it works well, but this behaviour is not guaranteed for all databases.

For example I work to port Oracle Driver for D8 now, and this test fails.
I have created patch that allow skip needed test methods for not MySQL drivers, but maybe a solution will be just to remove them.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

andypost’s picture

Suppose better to get rid of this fragile test at all!
Better to ask @Crell

andypost’s picture

All functions in the file use foreach() to calculate record count, so let's implement a helper method for that and replace this wrong usage

  /**
   * Tests range queries.
   *
   * The SQL clause varies with the database.
   */
  function testRange() {
    $query = db_select('test');
    $query->addField('test', 'name');
    $query->addField('test', 'age', 'age');
    $query->range(0, 2);
    $result = $query->execute();

    $this->assertEqual($result->rowCount(), 2, 'Returned the correct number of rows.');
  }

  /**
   * Tests distinct queries.
   */
  function testDistinct() {
    $query = db_select('test_task');
    $query->addField('test_task', 'task');
    $query->distinct();
    $result = $query->execute();

    $this->assertEqual($result->rowCount(), 6, 'Returned the correct number of rows.');
  }
InternetDevels’s picture

I looked how this done in another tests and implement to this one.

chx’s picture

Status: Needs review » Reviewed & tested by the community

That is correct. It's not what we dreamed up in Drupal, this is PDO: http://php.net/manual/en/pdostatement.rowcount.php

PDOStatement::rowCount() returns the number of rows affected by a DELETE, INSERT, or UPDATE statement.

Crell’s picture

Sigh. I hate SQL. Yes, this looks correct.

andypost’s picture

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs tests

I think we should be throwing an exception if rowCount() is called on a select query so that we can ensure that we never depend on MySQL's ability to do this. If we do, then exception should be tested.

Crell’s picture

Alex: That's going to turn an 4 line patch into a 50 line patch, excluding tests. I'm not sure off hand how the result object can tell what type of query it came from. (It may be easy, but it could also be quite hard; I've not checked.) Are you sure about that? :-/

andypost’s picture

Status: Needs work » Needs review
FileSize
3.38 KB

Added method override, named exception and test

andypost’s picture

Right patch (previous does not have exception)

Not sure it's possible proceed without detection of query type

The last submitted patch, 10: drupal-fix_SelectComplexTest-2146733-10-nopreg.patch, failed testing.

chx’s picture

The statement should not be returned other by select. It makes no sense to fetch the results of an UPDATE. if it gets returned by other, that's a severe bug.

Also, never, ever try parse a SQL query.

chx’s picture

Status: Needs review » Needs work

The last submitted patch, 9: drupal-fix_SelectComplexTest-2146733-8.patch, failed testing.

The last submitted patch, 10: drupal-fix_SelectComplexTest-2146733-10.patch, failed testing.

andypost’s picture

how the result object can tell what type of query it came from

without parsing a query it is not possible.

Suppose there's other ways:
1) add a property to database driver rowCountSupported and set this in Connection::query() for result type Database::RETURN_STATEMENT
2) as we wanna throw exception add property allowRowCount to StatementPrefetch and Statement classes and set as in 1

Both approaches does not save us in case of db_query() without parsing

andypost’s picture

Status: Needs work » Needs review
Issue tags: -Needs tests
FileSize
9.95 KB

Approach 2

chx’s picture

db_query('UPDATE')->execute()->rowCount() throws an exception? Excellent! What else can we do to stop people running such broken code? While db_select is optional, db_update/db_insert/db_delete is not. If you run db_query('UPDATE') you have a bug and this reveals that. Those queries might work on MySQL but good luck on anything else -- check whether your favorite non-MySQL database has an Insert or Update override. If it has, then you can see the problem.

chx’s picture

To make this even more concrete: inserting/updating a blob works from a SQL query on MySQL but if the pgsql classes are any indicator they won't on PostgreSQL.

Status: Needs review » Needs work

The last submitted patch, 17: drupal-fix_SelectComplexTest-2146733-17.patch, failed testing.

andypost’s picture

Status: Needs work » Needs review
FileSize
515 bytes
9.96 KB

Should fix most failures, needs some sleep...

Crell’s picture

#21 seems like a reasonable approach to me. chx?

chx’s picture

FileSize
10.46 KB
3.12 KB

I resolved a couple TODOs and turned the whole ship around: we allow calling rowCount when that will happen. Otherwise, no rowCount for you.

andypost’s picture

I agree with this, just going to add a test for "db_query(insert" case

andypost’s picture

FileSize
1.13 KB
11.59 KB

And now it's looks finished

andypost’s picture

Title: SelectComplexTest: improper use of PDOStatement's methods » Select queries should not use rowCount() to calculate number of rows
FileSize
1.18 KB
12.77 KB

One more fix, there was no tests for result of db_insert()

Status: Needs review » Needs work

The last submitted patch, 26: 2146733-select-rowcount-26.patch, failed testing.

andypost’s picture

Actually inserted 3 records but reported 5 affected... no idea

andypost’s picture

Status: Needs work » Needs review
FileSize
970 bytes
13.19 KB

the proper fix, db_insert() returns last inserted ID or undefined in case of multi-insert

Crell’s picture

+++ b/core/modules/system/lib/Drupal/system/Tests/Database/InsertTest.php
@@ -42,10 +44,10 @@ function testSimpleInsert() {
-    $query = db_insert('test');
+    $query = db_insert('test', array('return' => Database::RETURN_AFFECTED));

Calling code should never have to specify the return mode. That's an internal property callers should never have to think about.

andypost’s picture

FileSize
1.61 KB
11.59 KB

So let's just skip the insert test changes hunk

Crell’s picture

Status: Needs review » Reviewed & tested by the community

OK, let's do. Thanks!

xjm’s picture

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 31: 2146733-select-rowcount-31.patch, failed testing.

alexpott’s picture

Status: Needs work » Needs review
alexpott’s picture

Status: Needs review » Reviewed & tested by the community

Moving back to rtbc since random re-test was the cause of this not being rtbc.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed 588e678 and pushed to 8.x. Thanks!

Yay for Drupal becoming a little bit more db agnostic.

dcrocks’s picture

This patch causes Drupal8 to NOT install on SQLite. See #2167507: Fix rowCount query usage in pgsql and sqlite drivers. Apparently SQLite also has issues with rowCount and Select.

dcrocks’s picture

What is going to tell a database driver writer that they have to be aware of the setting for this flag for those database operations for which rowCount is legitimate, and for the one for which it is not?

andypost’s picture

Status: Fixed » Closed (fixed)

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