Postgres has one fail on this simpletest at the moment, the testInsertDuplicateData() method. My guess, while not 100% certain, is that db_insert() does transactions which are disabled in MySQL. Since the second insert is suppose to fail, then no records would get inserted if the queries were executed in a transaction, however this test asserts that the first record should have been inserted. Does this test need altering to deal with transactions? or does the PostgreSQL driver have an issue here?

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Josh Waihi’s picture

Issue tags: +PostgreSQL Surge, +PostgreSQL
Josh Waihi’s picture

Title: Simpletest: SQL handling tests - test doesn't support transactions » Inconsistant Insert Queries Between Database Drivers

after doing some investigation it turns out that the way MySQL and PostgreSQL deal with multiple inserts are a little different.
for example take a table named 'test' with 2 columns (name, value) with a unique constraint on the 'name' column and then try the following:

INSERT INTO test (name, value) VALUES ('tom', 'boy'), ('dick', 'boy');
INSERT INTO test (name, value) VALUES ('harry', 'boy'), ('dick', 'boy');

MySQL will insert 3 records; tom, dick and harry
PostgreSQL on the other hand will insert 2 records; tom and dick.
Technically the second query is only one query and so should succeed completely or fail completely, seems alot harder to deal with a semi-successful query.

Both MySQL and PostgreSQL drivers use this method to create insert queries however the default driver uses transactions and seperate queries for each record. Its nice to say that Drupal has transaction support but we're not really using it very well yet.

So now I need some feedback on what our approach should be. Do we alter our methods so that the insert queries work the same?, I guess that is what should be happening anyway, shouldn't insert, update and delete queries be wrapped in transactions by default also?

Changing title also as this seems to be more about insert queries for now than simpletests, though the test may still require change depending on the outcome.

Crell’s picture

Wow I totally missed when Postgres started using multi-insert statements. Cool. :-)

The behavior of the default driver would be that if you try to insert multiple records in one shot, and one of them fails, they all roll back (assuming transactions are supported). That's what I presume would happen in SQLite.

Conceptually, we do treat multi-insert query objects as a single operation. They should therefore be atomic if possible.

Therefore, IMO we should alter the MySQL and Postgres drivers both to wrap a transaction around the insert query call itself so that the InsertQuery object executes atomically. (And if transactions aren't supported, oh well, nothing we can do at that point.) I'm not sure if it makes sense to do so for SQLite as well. It probably does, but looking at the driver just now, er, I don't see support for multi-value inserts at all.

Josh, can you roll a quick patch for both drivers accordingly?

Josh Waihi’s picture

Crell, I could be wrong but I think postgres supports it from version 8.3 which would in part be the reason why D7 postgres minimum requirements be 8.3 <.
so let me get this straight:

db_insert('test_table')
    ->fields(array('name', 'value'))
    ->values(array(
        'name' => 'Tom',
        'value' => 'boy',
    ))
    ->values(array(
        'name' => 'Renee',
        'value' => 'boy',
    ))
    ->values(array(
        'name' => 'Harry',
        'value' => 'boy',
    ))
    ->values(array(
        'name' => 'Renee',
        'value' => 'girl',
    ))
    ->execute();

assuming 'name' is a unique field, when transactions are enabled, no records should be inserted but, if they are disabled??.....
It would seem that MySQL inserts every record up untill the constraint error - I don't think the un-inserted rows get returned either.

Postgres has no such behaviour. If transactions are disabled, using the multi-value insert method will still act like a transaction compared to how MySQL handles it. On the other hand if seperate queries are used, if a query fails, Postgres would continue to insert records unless we altered the driver to do differently.

Ideally, multi-value inserts seem nice but make things difficult when transactions get involved, can we set both drivers to reflect what the generic insert object is doing? multi-querys in transactions rather than a single multi-valued insert?

Crell, I can make the patch, I just need feedback on what tac I should take and what the expected behaviour should be

Crell’s picture

can we set both drivers to reflect what the generic insert object is doing? multi-querys in transactions rather than a single multi-valued insert?

Er. That's what I just said to do. :-) The expected behavior of a multi-insert operation should be "all or nothing". Transactions inside the query builder are the easiest way to do that. That will not work properly on MySQL without transaction support, however. For that, the answer is "well, sucks to be using MyISAM" because we really can't do anything else with it and it's an error condition to begin with that developers should be avoiding if they're coding properly. (We should probably therefore fork the mulit-insert test depending on transaction status the way we're doing in the transaction overhaul patch, so that we can still get tests to pass properly.)

Josh Waihi’s picture

The MySQL tests I did before were on a MyISAM table, re-testing on a innodb table returns the same outcome as Postgres, er, so its the test that is the issue. Also http://testing.drupal.org/pifr/file/1/innodb_default.patch <-- is mysql results with innodb which proves the point this test is bogus.

Josh Waihi’s picture

Status: Active » Needs review

@Damien Tournoud also found a bug in SQLite which once fixed also fails this test. Should MySQL be fixed so that MyISAM follows everyone else? Would appreciate Crell and/or David Strauss to comment on this issue

Crell’s picture

If it's possible to make MyISAM tables behave the way everything else does (multi-insert is atomic), we should. If it's impossible, then we need to document that caveat somewhere. Actually I think for SQLite it should work properly, as SQLite supports transactions so we can just wrap whatever the actual queries are in a transaction and be done with it.

I defer to David on whether or not it's possible with MyISAM.

Damien Tournoud’s picture

@Crell: SQLite behavior on duplicate key errors is fully configurable. By default, it doesn't rollback the transaction in that case, but just abort that query. We can easily change this (I consider this as a bug in the driver).

Crell’s picture

OK, let's go ahead and tweak the SQLite driver to roll back in that case, for consistency.

David Strauss’s picture

@crell in #8:

See http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_stri... , specifically the distinction between STRICT_ALL_TABLES and STRICT_TRANS_TABLES for non-transactional tables like MyISAM.

David Strauss’s picture

I should add that the move to use the mode TRADITIONAL implicitly sets STRICT_ALL_TABLES. See http://drupal.org/node/344575 for the patch related to MySQL's mode.

David Strauss’s picture

fiasco also asked for the "real" answer, which is "We cannot guarantee multi-insert atomicity for MyISAM."

Josh Waihi’s picture

STRICT_ALL_TABLES seems the better option but leaves where we started - with myisam sucking heaps, there isn't much we can do without introspecting other than make innodb the default and advise the people using myisam only hosting to use SQLite instead.

In which case is there something still wrong with my patch or can we get it committed?

David Strauss’s picture

Status: Needs review » Reviewed & tested by the community

I haven't actually run the tests, but assuming they pass, this looks good. The updated test basically ensures MySQL is behaving as good or as bad as we expect given Drupal's configuration. That's the most we can ask of these sorts of tests without resorting to introspection.

chx’s picture

Status: Reviewed & tested by the community » Needs work

uh huh. Comments please? I look at the code and go wtf.

chx’s picture

This patch presumes that the multi insert behaviour depends on transaction support and this is not true, it depends on configuration -- and configuration that can be table level ie innodb vs myisam is table level. I vote on removing this test there is no way on earth to figure out what should happen in this case.

Crell’s picture

OK, let me see if I understand all of the various permutations here. When a multi-insert statement dies on a non-first record:

* MySQL-MyISAM: Commit records up to that point. Lack of transactions means we can't roll back.
* MySQL-InnoDB: Commit nothing.
* PostgreSQL: Commit nothing.
* SQLite: Does not apply as native multi-inserts are not supported; we can emulate them using a transaction and roll back the entire set.

So the only case where behavior is different is MySQL-MyISAM. We cannot check for that directly at present; we can check for transaction support, which usually will parallel InnoDB usage but that's not guaranteed.

So then the alternatives are:

1) Remove the test. I heartily dislike this option as it means we have no check to make sure we don't break multi-insert support entirely. It's unlikely that we'd do so at this point, but still, it reduces code coverage.

2) Assume that MySQL-transactions means InnoDB and MySQL-non-transactions means MyISAM. That's what the patch here is doing, although I agree with chx that more documentation is needed if we go that route.

3) Make it possible to detect the table type and adjust the test accordingly, like we do for transactional and non-transactional branches of tests. This would be ideal, but I can't think of a way to do so without rather silly hard-coding of special cases. Perhaps this is something that could go into the DatabaseSchema_mysql class as an extra method, and then we check for $connection->type() and $connection->schema()->tableType('example')?

Thoughts?

David Strauss’s picture

@chx We're doing the same sorts of tests in the transaction layer itself. You could install to MyISAM tables, turn on transaction support, and get failures in the transaction tests. So, we're already assuming elsewhere that, if you enable transaction support, 100% of your tables support transactions. The only alternative is an absurd level of introspection to check that all involved tables are transactional in any given operation. That alternative isn't even that useful because you could be 50% through a transaction and touch a MyISAM table, losing the coherency of your transaction, and we can't predict what tables you might possibly touch. We could try to counter that problem by checking all tables in the database periodically to see that they're transactional, but that also seems excessive.

Lesson: If you want to mix MyISAM and InnoDB tables *and* enable transaction support, you'd better know what you're doing. Fortunately, it's already impossible to use the graphical installer to arrive at such a configuration.

David Strauss’s picture

Oh, and I left the patch CNW for documentation reasons.

chx’s picture

I think multi insert support can (and is) covered by other tests and this one needs to be removed as its unpredictable what happens on a multi insert, really. http://www.sqlite.org/lang_insert.html for example, it depends completely on the driver and someone can have a different sqlite driver than what we have in core. Also, i think the agreement on innodb was that we still need some tables on myisam...

David Strauss’s picture

@Crell Your breakdown of scenarios is correct, though I hesitate to use the word "commit" with MyISAM because it implies transactional-ish operation. Let's go with the rows are "saved" or something similar for clarity when this gets documented.

David Strauss’s picture

@chx I should have said "mix the table engines in a way other than we might do by default." Whatever tables we'd keep on MyISAM would be intelligently determined to avoid issues with transaction support.

Damien Tournoud’s picture

Status: Needs work » Needs review
FileSize
2.39 KB

Here is an implementation of Crell (2) in #18: if the database connection doesn't support transactions, we pass even if the first entry was inserted. Even if the database connection doesn't support transactions, we still pass in the case the db engine exhibit the correct behavior.

Status: Needs review » Needs work

The last submitted patch failed testing.

Josh Waihi’s picture

Status: Needs work » Needs review
FileSize
2.39 KB

Damien's patch fails because regardless of weather the db supports transactions or not, the test will fail if $name = 'Elvis' how ever if the database doesn't support transactions in this condition, then the test is considered a pass.

chx’s picture

looks good but + $this->assertTrue(FALSE, this should be $this->fail

Dries’s picture

Status: Needs review » Needs work
Josh Waihi’s picture

Status: Needs work » Needs review
FileSize
2.37 KB

as per chx request

Josh Waihi’s picture

as per webchicks request

webchick’s picture

Status: Needs review » Fixed

Committed to HEAD. :) Thanks!

Status: Fixed » Closed (fixed)
Issue tags: -PostgreSQL Surge, -PostgreSQL

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