Repeated changes of the node type machine name at admin/structure/types leave the {node_type} table in a funny state, and make it impossible to change a node type machine name back to a previous value.

Steps that caused this:

Created a node type via a module (Ubercart). Machine name (type) is "book".

Go to admin/structure/types. Edit the node type. Change the machine name to "fred_book".

Go to admin/structure/types again. Edit the node type again. Change the machine name to "fred_book_test".

Contents of {node_type}:
Entry #1 is:
type (machine name): fred_book
modified: 0
disabled: 1
orig_type: fred_book

Entry #2 is:
type: fred_book_test
disabled: 0
modified: 1
orig_type: book

Entry #1 doesn't show up at admin/structure/types, presumably because it is disabled.

Trying to change the name of the type back to "fred_book" in #2 fails because the "type" field is a primary index, there can't be two entries with the same value, and #1 already has it.

The error message is:

PDOException: SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'fred_book' for key 'PRIMARY': UPDATE {node_type} SET type=:db_update_placeholder_0, name=:db_update_placeholder_1, base=:db_update_placeholder_2, has_title=:db_update_placeholder_3, title_label=:db_update_placeholder_4, description=:db_update_placeholder_5, help=:db_update_placeholder_6, custom=:db_update_placeholder_7, modified=:db_update_placeholder_8, locked=:db_update_placeholder_9, disabled=:db_update_placeholder_10, module=:db_update_placeholder_11 WHERE (type = :db_condition_placeholder_0) ; Array ( [:db_update_placeholder_0] => fred_book [:db_update_placeholder_1] => Fred Book [:db_update_placeholder_2] => uc_product [:db_update_placeholder_3] => 1 [:db_update_placeholder_4] => Title [:db_update_placeholder_5] => Life of Fred math textbook [:db_update_placeholder_6] => [:db_update_placeholder_7] => 0 [:db_update_placeholder_8] => 1 [:db_update_placeholder_9] => 0 [:db_update_placeholder_10] => 0 [:db_update_placeholder_11] => uc_product [:db_condition_placeholder_0] => fred_book_test ) in node_type_save() (line 516 of /var/www/html/drupal_dev/modules/node/node.module).

The offending line from node_type_save() in node.module is:

  if ($is_existing) {
    db_update('node_type')
      ->fields($fields)
      ->condition('type', $existing_type)
      ->execute();

There are two things that need to be fixed here:

#1: Deal with the conflict created by the disabled node type entry. I can't find the code that duplicates and disabled the entry, much less figure out why it would want to keep around disabled old entries and make new ones, instead of just updating the old ones.

#2: Produce a reasonable error message (instead of the PDOException) when there's a conflict on this primary key. Either do a check first or catch the exception somehow.

Comments

Anonymous’s picture

Version: 7.16 » 8.x-dev
Issue tags: +Needs backport to D7

Why the hell would anyone do this crap anyway. IMNSHO, the machine name should remain constant and not allowed to be changed. Not only that, you've changed the machine name of a type created by a module and now the module is out-of-sorts 'cause it won't find its content type.

Anyway, I've seen another issue where the content_type record isn't removed, which is probably for good reason, and that is content depends on it; which is why I think the machine name should not be allowed to be changed, especially if there is content. I'm assigning this issue to D8 for consideration of the developing version. If we allow the content type machine name to be changed modified we must allow for the content that still exists to keep the old name but also allow the user to rename the machine name back to the original name. If no content is presently using the machine name of the change from type then the change from content type record needs to be removed; otherwise the change needs to check for an existing row and update it as active once again.

Dan Z’s picture

Why the hell would anyone do this crap anyway.

Typos and other user errors. It's easy enough to make a mistake, then you want to put it back how it was to fix the mistake. This bug prevents you from fixing it.

you've changed the machine name of a type created by a module and now the module is out-of-sorts 'cause it won't find its content type.

Naw, that's already dealt with. The "base" and "module" fields of {node_type} aren't editable. The module can keep track of its stuff that way.

Also, if the module really doesn't want anyone modifying the machine names for its types, it can set the "locked" field in {node_type} for its node types.

Also, if the module needs to know about node type changes, it can implement hook_node_type_update(). Ubercart does exactly this.

I think the machine name should not be allowed to be changed, especially if there is content.

The content issue is dealt with. Changing the machine name ("type" field) from admin/structure/types also updates the "type" field in the {node} table for all records that match the old type.

If no content is presently using the machine name of the change from type then the change from content type record needs to be removed; otherwise the change needs to check for an existing row and update it as active once again.

See my paragraph above. If you use admin/structure/types to change the type machine name, there will be no content with the old type. All the matching nodes get updated to the new type.

I don't see any reason to keep the old, disabled record around. It might be worth digging around in the code to find if there is a reason, but it might simply be a leftover from some earlier mechanism that never got cleaned up. The UI certainly has nothing that lets you revert to the saved record.

Dan Z’s picture

Ok, I'm digging in the (7.16) code a bit.

The error check for a duplicate machine name should go into node_type_form_validate(). Something like this would get rid of that annoying PDOException:

  if ($old_type != $type->type && array_key_exists($type->type, $types) {
    form_set_error('type', t('The machine-readable name %name is already taken.', array('%name' => $type->type)));
  }

I haven't found the bit that makes the disabled record...still digging....

Dan Z’s picture

After further digging, I couldn't find code that explicitly creates a disabled record. The records are created by node_type_save(), and that explicitly overwrites the existing record (and doesn't create a new one):

    db_update('node_type')
      ->fields($fields)
      ->condition('type', $existing_type)
      ->execute();

In other words, the disabled entry is not supposed to be there.

My current best guess is that this is related to _node_types_build() in combination with some sort of a sync, cache, or module problem. I'll keep checking. Also, see #1832640: Changing node content type machine name fails until manual cache reset. It may be related.

Dan Z’s picture

Ok, after updating just once, it's become slightly clearer.

Created a node type via a module (Ubercart). Machine name (type) is "book".

Go to admin/structure/types. Edit the node type. Change the machine name to "fred_book".

admin/structure/types still shows the type as "book", not "fred_book".

{node_type} contains the following record:
type: fred_book
base: uc_product
module: uc_product
custom: 0
locked: 0
disabled: 1
orig_type: book

{node_type} does NOT contain a record with "type: book". There's just the one record (disabled) record.

The only code I found that could set the disabled field was in _node_types_build() by way of node_types_rebuild(). It appears to mark node type records as disabled if they are defined by a module but no module claims them. So, a possible sequence of events is:

  1. node_type_save() correctly overwrites record.
  2. node_type_save() correctly invokes hook_node_type_update(). The module now knows about the new type and not the old type.
  3. node_type_save() calls node_type_cache_reset(), but this somehow doesn't update the cache.
  4. node_type_form_submit() calls node_types_rebuild() calls _node_types_build()
  5. _node_types_build() gets its data from the (incorrect) cache, so it thinks the updated record in the DB is abandoned, and disables it.

Could that happen? If so, this is probably caused by #1832640: Changing node content type machine name fails until manual cache reset. I won't mark this as a duplicate, because the error check still needs to be added to get rid of the PDOException.

Dan Z’s picture

Title: Repeated changes to node type causes primary key violation » PDOException on duplicate node type machine name
Priority: Normal » Minor

After more testing, I've concluded that #5 is essentially correct, except it's not exactly a caching issue. It appears to be related to a bug in a module providing out-of-date info via hook_node_info(). So, only the error checking of #3 is left. I'll update the title.

Presumably, this needs to be patched to 8.x-dev, then backported to 7.x-dev, right?

Anonymous’s picture

Presumably, this needs to be patched to 8.x-dev, then backported to 7.x-dev, right?

Yes, that is correct. A patch for D8 first, wait until committed, the issue will then be set for D7 and "patch (to be ported)".

Anonymous’s picture

Issue summary: View changes

Typo.

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

dpi’s picture

Status: Active » Closed (outdated)

Node types machine names are now immutable.