Here's a patch to add this into the docs, as hook_update_N_batch(), since it'd probably be too much to cloud up the example update function hook with batch API stuff. Therefore, I put a bold note clarifying that the name is actually the same as hook_update_N(), with a code sample in the documentation body, as well as in the hook example itself.

I also added a note about the ability to do $ret['#finished'] in the original batch doc, and this one.

I added a link in the new doc to the batch operations doc page.

Also, each batch hook @see's the other.

Thanks!

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

yched’s picture

Note that 'tie in to the batch API' doesn't mean just $ret['#finished'] - it's about using the $sandbox variable to store persistent data across multiple passes, and $ret['#finished'] to say 'I'm not done yet, please cal me again'.

'An update function may also tie in to the batch API' : this is quite technical and doesn't explain *why* you'd want to do so. I'd suggest introducing this with something like 'If your update task is potentially time consuming, you'll need to implement a multipass update to avoid PHP timeouts. Multipass updates use the $context parameter provided by the batch API to store information between successive calls, and the $ret['#finished'] return value to provide feedback about completion level.'

webchick’s picture

Status: Needs review » Needs work

Oh, what great documentation to have! :D

In addition to yched's comments, I think it might actually be more confusing to have this documented as a separate hook (hook_update_N_batch) when it is not actually a separate hook. Could we instead do something like:

hook_update_N(&$sandbox = NULL) {
  // For most simple operations, the following is sufficient.
  $ret = array();
  db_add_field($ret, 'mytable1', 'newcol', array('type' => 'int', 'not null' => TRUE));
  $ret[] = update_sql('something');
  return $ret;

  // However, for more complex operations that may take a long time, you may hook into Batch API.
  ## EXAMPLE CODE ##
}

Also, if we're going to commit this to 7.x, the database queries need to be updated to DBTNG.

cha0s’s picture

Status: Needs work » Needs review
FileSize
3 KB

Alright, round 2. =)

Jody Lynn’s picture

Looks great.

"Time-consuming" needs a hyphen.

cha0s’s picture

Alrighty, fixed that one.

webchick’s picture

Version: 7.x-dev » 6.x-dev
Status: Needs review » Patch (to be ported)

Great job!! I committed this to HEAD. Needs to be back-ported and then committed to contributions/docs/developer/hooks/core.php. Anyone with CVS access can do this, so go for it cha0s, and then go ahead and mark this fixed. :)

cha0s’s picture

Thanks, I got this committed to the Drupal 6 docs. Feel free to review/scream if I got it wrong. :)

webchick’s picture

Status: Patch (to be ported) » Fixed

http://cvs.drupal.org/viewvc.py/drupal/contributions/docs/developer/hook...

Looks good here! Marking fixed. Thanks, cha0s! :D

merlinofchaos’s picture

Status: Fixed » Active

The method used in this example has an unfortunate bug. It uses the COUNT of users but it uses user IDs as the step value. Now, for users this is rarely a big problem but if you have user deletions you will have holes n the table and the COUNT will not match the actual max user ID.

Because you're using query range, you should instead use an example more like this:

  // Update 3 users at a time to have an exclamation point after their names.
  // (They're really happy that we can do batch API in this hook!)
  if (!isset($sandbox['progress'])) {
    $sandbox['progress'] = 0;
    // Start at 1 to skip the anonymous user.
    $sandbox['current'] = 1; 
    $sandbox['max'] = db_query('SELECT COUNT(DISTINCT uid) FROM {users}')->fetchField();
  }
  
  $users = db_query_range("SELECT uid, name FROM {users} ORDER BY uid ASC", $sandbox['current'], $sandbox['current'] + 3);
  foreach ($users as $user) {
    $user->name .= '!';
    $ret[] = update_sql("UPDATE {users} SET name = '$user->name' WHERE uid = $user->uid");
    
    $sandbox['progress']++;
    $sandbox['current']++;
  }

  $ret['#finished'] = empty($sandbox['max']) ? 1 : ($sandbox['current'] / $sandbox['max']);

Ok, so the code above has both 'progress' and 'current' which now mean the same thing, but I was afraid to touch progress because I do not know if that is used by batch api to draw the bar. So that needs to be cleaned up, but pure record counting is the way to go here.

merlinofchaos’s picture

also my use of db_query_range is incorrect. The last argument should just be 3, not adding current to it. :P

cha0s’s picture

If that's incorrect, then the batch API docs are wrong too, since they use that technique with nodes. http://drupal.org/node/180528

merlinofchaos’s picture

Upon further review, it is not actually buggy. It is, however, confusing, to have the max and counter not match with no explanation.

cha0s’s picture

Status: Active » Needs review

I'm not sure what you mean by "max and counter [don't] match with no explanation". The $ret['#finished'] variable is how you transmit back how far along the batch is. As you can see when progress == max, the result is '1', which will trigger the batch API to finish up.

Could you elaborate a little more what you mean by that, if I didn't understand correctly?

merlinofchaos’s picture

Sure, it uses a COUNT(*) to determine how many to do, but it works on UIDs and filters on UIDs rather than working like a pager, which looks, at first blush, as though it could be buggy. It's not, because there's the separate 'progress' and 'current' variables but it wasn't clear to me at first.

cha0s’s picture

Ah, I gotcha. Well, as you said, uid's can have holes punched out if a user has been deleted... so I'm not sure there's a more elegant solution than this, really.

Do you have other code you think would make more sense that we could plug in here? We could also apply it over on that batch API tutorial (as well as backporting to Drupal 6)

merlinofchaos’s picture

I suspect simply a few more comments would suffice, to explain why it's being done this way. The only reason to use the UID is that the query is more efficient than using a pager query, though to be honest that efficiency is a very very minor issue. (The pager query being of the type of using LIMIT 700, 3 to pull the 700, 701 and 702 results). On the other hand, using a pager means the data itself is less important so the technique always works.

jhodgdon’s picture

Bump. Does this still need an update?

jhodgdon’s picture

Project: Drupal core » Documentation
Version: 6.x-dev »
Component: documentation » API documentation files

Docs queue... that is where d6 hook docs live...

jhodgdon’s picture

Project: Documentation » Drupal core
Version: » 8.x-dev
Component: API documentation files » documentation
Assigned: cha0s » Unassigned
Status: Needs review » Needs work

Actually, moving this back to d8 in case this still needs to be addressed. Can someone propose a patch to fix this confusion?

jhodgdon’s picture

Reviving this issue... what needs to be changed here?

jhodgdon’s picture

Issue summary: View changes
Status: Needs work » Closed (duplicate)

The hook_update_N() docs were fixed on another issue a while back.