I need to use http://api.drupal.org/api/function/db_last_insert_id/6 to get the last inserted ID for adding this one to a reference table. When I save a comment I've got the wid from watchdog table and not the comment cid! This seems to be caused by the implementation of mysql, but I think we need to workaround. The API tells me I need to provide the table and column name, but the auto_increment value I get back is from a different table.

This needs to be changed and I think we can use SHOW TABLE, select the auto_increment column and subtract -1 to get the table specific value for MySQL. PgSQL seems not to have this issue!

If there are better ideas feel free to share.

Comments

damien tournoud’s picture

Status: Active » Closed (won't fix)

There is nothing to fix here. db_last_insert_id() can only return... the last inserted id, even if, for implementation reason, you have to pass it the table and field.

You will have to grab the cid by other means.

hass’s picture

Status: Closed (won't fix) » Active

The API tells me I need to provide a table A and column from table A and I trust core that this function returns me the last inserted ID from table A - not from a random table X. The function does not work as expected and returns a last inserted ID from a different table than I requested from the API function. This API function IS broken by design with MySQL.

We need to fix this for D6. I'm not sure if the same will happen with D7. The code looks different and I was not yet able to test.

I'm going to provide an code example later the day.

damien tournoud’s picture

Priority: Critical » Normal

This is only a documentation issue. db_last_insert_id() only returns the last inserted id. Period.

Drupal 7 is not affected (db_last_insert_id() is deprecated in D7). Feel free to suggest a patch clarifying that fact.

hass’s picture

Interresting... nevertheless the function should do the same on all DB servers supported by core. It was written as a replacement for sequences table and this one have given me the correct value. I don't know why I should expect that I get a different ID from a different table than the one I told the function. And as the very last - this bug can be fixed to provide the same behaviour on both systems!

Does db_insert() returns the inserted ID?

damien tournoud’s picture

Again, the bug is in your code.

Don't use db_last_insert_id() to retrieve anything other than the last insert id of the last executed SQL statement. That behavior is consistent in every version of Drupal (< 7), and I don't believe it is worth changing it. But, still, fell free to suggest a patch to clarify the documentation.

On Drupal 7, db_insert() returns the inserted id of the query, and db_last_insert_id() is deprecated.

hass’s picture

This is not correct! We haven't had a PHP function db_last_insert_id() priior to D6. http://api.drupal.org/api/function/db_next_id/5 gave me the next ID from a sequence I name by table and column like db_last_insert_id(). It is the equivalent of the D6 db_last_insert_id() except the D6 function returns the "last" and in past it was the "next". Except this *minor* difference db_next_id() provided a 100% save value what db_last_insert_id() is not doing on MySQL, but on PgSQL.

You might have not understood where I'm coming from - I do NOT have it in my hands what a comment save API function is doing internally in core and it does a watchdog insert as a last step. D7 doesn't solve this, too. Therefore the db_last_insert_id() provides a wrong value nevertheless I provided the correct table and column.

Please *share* your idea how I'm able to grab the last inserted ID in comments table in a thread save way with multiple saves from different users at the same time.

function linkchecker_form_alter(&$form, &$form_state, $form_id) {
  switch ($form_id) {
    case 'comment_form':
      $form['#submit'][] = 'linkchecker_comment_form_submit';
      break;

    default:
      break;
  }
}


/**
 * Custom submit handler for comment add page.
 */
function linkchecker_comment_form_submit($form, &$form_state) {
  // Comment has been edited.
  if (isset($form_state['values']['cid'])) {
    drupal_set_message(t('Comment @cid has been saved.', array('@cid' => $form_state['values']['cid'])));
  }
  // New comment has been added.
  else {
    // FIXME: Returns watchdog.wid and not comments.cid
    $cid = db_last_insert_id('comments', 'cid');
    drupal_set_message(t('New comment @cid has been created.', array('@cid' => $cid)));
  }
}

I don't think that $cid = db_result(db_query("SELECT MAX(cid) FROM {comments}")); is the right way.

hass’s picture

I found a hook_comment() I could use... but there is no hook_box or hook_block. So potentially same issue with auto increment on boxes.

nullpainter’s picture

Priority: Normal » Critical

Damien,

According to the Drupal API documentation, there are 10 functions in core that use db_last_insert_id() in Drupal 6. Since the identifier returned by the MySQL implementation is the absolute last identifier added to the database and not bound by any transactional context, this call can potentially return false data -- particularly when the site is under load.

FWIW, we have also experienced this issue with the ID of a watchdog row being returned after creation of a row in another table. I understand what the function does and appreciate that it shouldn't be used for our purpose, but I strongly suggest that the implementation of the function is critically flawed and, since it is used in core, this issue should move back to critical priority. If the function shouldn't be used and is not used by any core code, I agree that the priority should be changed to Normal and documentation added to warn against its use and suggest workarounds.

The code in question is:

<?php
db_query('insert into {term_data} (vid, name, description) values (%d, \'%s\', \'\')', $vid, $term);
$tid = db_last_insert_id();
db_query('insert into {term_hierarchy} (tid, parent) values (%d, 0)', $tid);
?>

The tid being returned is the ID of a row being added to the watchdog table and not of the entry being added to the term_data table.

M

nullpainter’s picture

Priority: Critical » Normal

Sorry, we've just tried again and are unable to replicate the issue we were experiencing earlier. We attempted with one user on a breakpoint after the initial db_query() and another user creating content (and thus updating the watchdog table).

Confusing.

damien tournoud’s picture

Status: Active » Closed (won't fix)

Once again there is no bug at all here.

From http://dev.mysql.com/doc/refman/5.0/en/information-functions.html#functi...

The ID that was generated is maintained in the server on a per-connection basis. This means that the value returned by the function to a given client is the first AUTO_INCREMENT value generated for most recent statement affecting an AUTO_INCREMENT column by that client. This value cannot be affected by other clients, even if they generate AUTO_INCREMENT values of their own. This behavior ensures that each client can retrieve its own ID without concern for the activity of other clients, and without the need for locks or transactions.

But of course, if you let another query execute (for example a watchdog query) between the query you are targeting and the call to db_last_insert_id(), for sure the last inserted id will be different. The documentation of that function clearly states:

Returns the last insert id. This function is thread safe.

For the record, you need to pass the $table and $field to that function because PostgreSQL needs that information. But still, you can only get the *last* inserted id (ie. the one potentially inserted by the very last query).

hass’s picture

Status: Closed (won't fix) » Active

Do you have an idea about #7?

There is also an documentation issue about the parameters to the function and what developers expect...

Tom Van Schoor’s picture

I have to agree with hass...

I need an auto_increment id from an insert to a table. If the insert succeeds i can use that id to insert records to another table that references the first.

I get the correct id when the query succeeds, but when it fails i get the id of the watchdog. On top of that, when i look for errors using db_error() i don't get an error, because the insert to watchdog succeeded...? Logic? Maybe it is logical, but I don't see it.

There is a solution to my problem however... At least I hope there is, using drupal_write_record... I am trying it out next:

table testTable:
id int(10) unsigned auto_increment not null primary key
label varchar(150) not null unique key
description mediumtext null

$record = new stdClass();
$record->label = "a label";
$record->description = "a description";
drupal_write_record("testTable", $record);

according to the API it should set the incremented value in $record->id and if the query fails it should not be set... So let's find out...

Tom Van Schoor’s picture

And yes it works! Pitty though because i like to use db_query() but this will be the way around for me.

I am a bit puzzled though... the drupal_write_record function uses db_query and db_last_insert_id to achieve this.

// Execute the SQL.
if (db_query($query, $values)) {
if ($serials) {
// Get last insert ids and fill them in.
foreach ($serials as $field) {
$object->$field = db_last_insert_id($table, $field);
}
}
}
else {
$return = FALSE;
}

And now I feel a little bit like an idiot =D.

A watchdog is written when an error occurs. But of course db_query() returns false if an error occurs... so if an error occurs db_last_insert_id() will return the id of the watchdog added. That is the nature of MySQL...

damien tournoud’s picture

Status: Active » Closed (works as designed)

This is definitely not a bug.

hass’s picture

Status: Closed (works as designed) » Active

Do you have an idea about #7?

There is also an documentation issue about the parameters to the function and what developers expect...

damien tournoud’s picture

@hass: if you have no proper hook and / or information in a submit function, you are screwed. Don't rely on the database layer to do what a hook should do. Open a feature request against those.

hass’s picture

Status: Active » Closed (works as designed)

I have opened case #359546: DX: Implement hook_block_* (insert/update/prepare/delete) functionality for modules in past before I figured out this issue here.

hass’s picture

Status: Closed (works as designed) » Active

Ah, no - this issue still needs to be documented as it doesn't work as the API looks like and I cannot review all API functions first if they work as the expected from API parameters. I also wasn't aware about this difference between mysql and pgsql and I'm not sure what will happen with sqllite.

I do not care about underlying MySQL here - as it's clear from API side - I have said give me BID from BOX table and I get WID from WATCHDOG table!

This is not expected behaviour and not documented that I need to know about the abilities of all possible DB servers Drupal will ever implement. This is why we have a database abstraction layer...

brazorf’s picture

You can call this not a bug by logic, but this practically is and i fully quote hass. An abstraction layer should return the current query last id, not the mysql/pgsql/wtf implementation insert id. That does not abstract us from anything.

Anyway, i did hack the core the same way i did in my own cmf: I added a custom global var on the very next line after the query ran, i have no time to waste in useless turnarounds tipically cascading other issues:

database.mysqli.inc

$result = mysqli_query($active_db, $query);
$GLOBALS['brazorf']['last_insert_id'] = mysqli_insert_id($active_db);

As easy.

Status: Active » Closed (outdated)

Automatically closed because Drupal 6 is no longer supported. If the issue verifiably applies to later versions, please reopen with details and update the version.