The insert of {actions_aid} don't use useDefaults() in db_insert().

Detail

Original legacy INSERT query is now update as TNGDB syntax (check CVS log message), from:

db_query('INSERT INTO {actions_aid} VALUES (default)');
$aid = db_last_insert_id('actions_aid', 'aid');

To:

$aid = db_insert('actions_aid')->execute();

BTW, according to the query builder of db_insert(), the above command will translate as:

INSERT INTO {actions_aid} () VALUES ()

Which is potentially buggy in SQL syntax.

Bug reproduction

When combine test with #371: resolve ANSI SQL-92/99/2003 reserved words conflict in query statements DB's patch, PDOException is happened with following message:

PDOException: INSERT INTO [{actions_aid}] ([]) VALUES () SQLSTATE[21S01]: Insert value list does not match column list:...

Patch effect

After patch, the final SQL will become:

INSERT INTO [{actions_aid}] ([aid]) VALUES (DEFAULT)

Which is safe for SQL syntax restriction.

Tested platform

Both MySQL and PostgreSQL pass simpletest "System => Actions configuration"; combine test with #371: resolve ANSI SQL-92/99/2003 reserved words conflict in query statements is also passed in both cases.

Comments

dave reid’s picture

Title: [TNGDB]: actions_save() should have useDefaults() for {actions_aid} INSERT » [DBTNG]: actions_save() should have useDefaults() for {actions_aid} INSERT
Status: Needs review » Needs work

That's my bad on the original patch for DBTNG & actions.inc. :) Since this is no longer just a short DB command, we should maybe chain everything down so it looks like:

db_insert('actions_aid')
  ->useDefaults(...)
  ->execute();

Also, I hate to be nit-picky, but please use the already-coined term 'DBTNG' and not 'TNGDB' so that issues searches for 'DBTNG' can find your patches. Thanks!

Other than those two small issues, patch applies cleanly and actions/trigger tests are at 100% pass with 476 passes. Should be RTBC after a quick revision.

hswong3i’s picture

Status: Needs work » Needs review
StatusFileSize
new721 bytes
damien tournoud’s picture

Status: Needs review » Needs work

This was not caught by our tests? ==> we need to extend them

dave reid’s picture

No, as currently it does not fail. This patch depends on another patch.

hswong3i’s picture

Status: Needs work » Needs review

I am wonder about how to catch this bug... Existing code is function but just potentially buggy in final SQL syntax, where MySQL and PostgreSQL still accept it.

Maybe we can add some checking within db_insert() and so warning that there is none of field defined for INSERT? Check it though runtime seems to be more logic. Patch for this is submit to #321100: Empty insert statements should fail gracefully.

dave reid’s picture

After a little more investigating, the actions_aid is a table with one auto-increment field, that's it. What's buggy about not specifying the value for an auto-increment field? It does not need to have ->useDefaults(array('id')). Since there are no other fields so db_insert('actions_aid') should be valid. We don't use ->useDefaults(array('id')) in our database tests:

  /**
   * Test that we can run a query that is "default values for everything".
   */
  function testDefaultInsert() {
    try {
      $query = db_insert('test')->useDefaults(array('job'));
      $id = $query->execute();

      $schema = drupal_get_schema('test');

      $job = db_query("SELECT job FROM {test} WHERE id = :id", array(':id' => $id))->fetchField();
      $this->assertEqual($job, $schema['fields']['job']['default'], t('Default field value is set.'));
    }
    catch (Exception $e) {
      $this->assertTrue(FALSE, $e->getMessage());
    }
  }

  /**
   * Test that we can insert fields with values and defaults in the same query.
   */
  function testDefaultInsertWithFields() {
    try {
      $query = db_insert('test')->fields(array('name' => 'Bob'))->useDefaults(array('job'));
      $id = $query->execute();

      $schema = drupal_get_schema('test');

      $job = db_query("SELECT job FROM {test} WHERE id = :id", array(':id' => $id))->fetchField();
      $this->assertEqual($job, $schema['fields']['job']['default'], t('Default field value is set.'));
    }
    catch (Exception $e) {
      $this->assertTrue(FALSE, $e->getMessage());
    }
  }
dave reid’s picture

What would be a safe SQL way to insert a blank row for a table that has defaults for all fields? INSERT INTO mytable VALUES ()? Maybe the db_insert should check if there are no fields specified, then use that syntax. I'll stop chiming in until I get a more authoritative answer.

dave reid’s picture

Title: [DBTNG]: actions_save() should have useDefaults() for {actions_aid} INSERT » PHP warnings using db_insert('actions_aid')->execute() in actions_save()
StatusFileSize
new2.69 KB

So, summary so far: Steps to reproduce the problem:
- Go to admin/settings/actions
- Create a custom action like 'Redirect to URL' and save it
- Following warnings should show on the result page:
* warning: array_fill() [function.array-fill]: Number of elements must be positive in /home/davereid/Projects/drupal-head/includes/database/mysql/query.inc on line 66.
* warning: implode() [function.implode]: Bad arguments. in /home/davereid/Projects/drupal-head/includes/database/mysql/query.inc on line 67.

We have two alternatives to fix this:
1. Use db_insert('actions_aid')->useDefaults(array('aid'))->execute()
2. Fix the database insert query to work around an insert query with no specific or default fields defined, but on a table that has default values for all it's fields
3. Cause error if no fields are defined as proposed in #321100: Empty insert statements should fail gracefully

Doing solution #2 seems the most reasonable to me since this can help avoid the problem in the future with little to no change to the database API. We shouldn't have to specifically have to use ->useDefaults(...) on an auto-increment field. Ideally, I'd like to finish my work on integrating the aid back into the actions table and remove the one-field actions_aid table, but we should have a fix in case this comes up in the future.

Attached is a patch that does a quick count() check on the InsertQuery->defaultFields in InsertQuery_driver::__toString() exactly like we already do with InsertQuery->insertValues. It also includes a test (thanks hwong3i) for the db_insert('mytable')->execute() statement in the Database Insert, default fields tests.

drewish’s picture

looks like #329223: Notices in ActionsConfigurationTestCase might be a duplicate of this.

hswong3i’s picture

Status: Needs review » Closed (duplicate)

Yes, it is already get fixed in #329223: Notices in ActionsConfigurationTestCase. Mark this issue as duplicated.