Series Overview & ToC | Previous Article | Next Article


In the last article, we wrapped up migrating configuration to match the content model we specified in our upgrade plan. Ready to start migrating content? Hang in there — it’s coming up next. But first, we are addressing one of the hardest issues to resolve when they show up: entity ID conflicts in content migrations.

We introduced this topic in article 4 and expanded on it in article 5. Today, we’ll detail one way to prevent these problems. We’ll first tackle the issue using a custom approach, then show how the AUTO_INCREMENT Alter module can help.

Revisiting entity ID conflicts

Imagine you are close to wrapping up a large migration project. Editors have been testing the new content authoring experience, but they have not had the opportunity to try the full editorial workflow. A Drupal 10 staging environment is provisioned for them to do so. Having content and users in the staging environment would speed up the testing process so a full migration is executed. In addition to checking migrated content, editors also create new test content: nodes, users, files, etc. All this time, the Drupal 7 site continues to be available and content is created there as well.

Two weeks later, an incremental migration is run to test other features and that’s when things start to break. Drupal assigns entity IDs automatically when new content is created. The Drupal 10 staging environment and the Drupal 7 production environment ended up with conflicting entity IDs. The editorial team is informed that to overcome problems with the incremental migration, the site needs to be re-installed and a full migration executed again. After this, all the test content that editors created in the staging environment is lost.

Editors are not happy, but they take the loss with grace.

A month later, and after a lot of testing and updates, the upgraded Drupal 10 site is almost ready to launch. During this time, editors go above and beyond. They not only finish testing the editorial workflow but also create complex landing pages to get familiar with the new components-based page building system. To avoid having to recreate this content again after the new site is launched, editors ask the developer team if it is possible to somehow preserve the landing pages created in the staging environment. Developers give it a shot, but it ends up being too hard to reconcile the staging database with the freshly migrated database that will be used to launch the new site. Editors are informed that they will have to recreate the landing pages manually after the Drupal 10 site launches.

This time the pill is harder to swallow.

This fictitious case exposes two critical issues:

  1. No precautions were taken to account for entity ID conflicts before running content migrations.
  2. Miscommunications between the development and editorial teams led to losses in time, effort, and trust.

Both issues affect the project's budget and create friction among the team. We discussed the technical side of the entity ID conflicts previously in article 5. This time, we are highlighting non-technical challenges. Careful planning, or lack thereof, have big consequences in migration projects. So, take advantage of guides like ours and plan your Drupal 7 migration diligently. Measure twice, cut once.

Preventing entity ID conflicts

From a technical standpoint, there are two primary ways to prevent entity ID conflicts:

  1. Do not reuse the entity IDs from Drupal 7 in Drupal 10. This requires extra customizations of generated migrations because the migrate API assumes entity IDs are kept the same.
  2. Artificially inflate the auto-increment value for all content entities in Drupal 10. Arguably easier to do, but you need to pay very close attention to how entities are created in the new site.

Our recommendation? No matter if you decide to reuse entity IDs from Drupal 7 or not, always manipulate the auto-increment value of content entities in Drupal 10. Doing this will allow for incremental migrations to run without conflicting with manually created content before the Drupal 10 site is launched.

Most real life migration projects include changes to the content model. In that case, it is not possible to preserve the IDs for some entities from Drupal 7. So, altering the auto-increment value of content entities will prevent issues between incremental migration runs.

Content entity IDs auto-increment values

Before explaining how to alter auto-increment values, let's talk about entity IDs. As explained in article 7, Drupal entities have unique identifiers. Configuration entities usually leverage machine names stored as string values. Content entities favor numeric identifiers stored as integer values. It is possible to have other formats (like UUIDs) or data types (like floats) to uniquely identify entities as long as their values are unique. That being said, strings for configuration entities and integers for content entities is the common practice.

Technical note: Both content and configuration entities use entity_keys to determine the name of their identifiers. Take the Node content entity and NodeType configuration entities as examples. For the Node content entity, the ContentEntityBase::baseFieldDefinitions method reads the id entity key and creates an unsigned integer identifier name nid. For the NodeType configuration entity the node.schema.yml schema reads the id entity key and validates an identifier named type exists. In this case, type is a machine name, an extension of strings with additional constraints.

Integer identifiers, as those used by content entities, have the following behavior:

  • Start with 1, unless otherwise instructed. This is what we will alter in a bit.
  • Increment by 1 each time a new entity is created. It is possible to change this to a different value.
  • If an entity is deleted, its identifier cannot be reused. It is disposed of altogether.

Consider nodes as an example. The first node created on the site will have a node id (nid) value of 1, the second of 2, third of 3, and so on. If the node with nid 2 is deleted and a new one is created, the nid of the new node will not be 2 but the value of the highest node id at the time plus one.

It is important to note that each content entity type has a separate counter for their identifiers. That is, it is fine to have a node with nid 1, a user with uid 1, a taxonomy term with tid 1, etc.

Also, bundles within the same entity type share the same counter for identifiers. For example, it is not possible to have a node of type article with nid 1 and at the same time a node of type of basic page with nid 1. No matter the content type, all nodes follow the same counter.

Altering auto-increment values for content entity tables

To prevent entity ID conflicts, we need to alter the auto-increment value of tables holding content entity data. This is a three step process:

  1. Identify which Drupal 10 tables to alter.
  2. Find out the new auto-increment value for each table.
  3. Connect to the database to perform the ALTER operation on each table.

With regards to the first step, the tables to alter are those defined in the base_table and, if used, the revision_table keys of the annotation that defines the configuration entity. The code below is part of the ContentEntityType annotation for the node entity:

/**
* Defines the node entity class.
*
* @ContentEntityType(
*   id = "node",
*   label = @Translation("Content"),
*   base_table = "node",
*   data_table = "node_field_data",
*   revision_table = "node_revision",
*   revision_data_table = "node_field_revision",
*   entity_keys = {
*     "id" = "nid",
*     "revision" = "vid",
*     "bundle" = "type",
*     "label" = "title",
*     "langcode" = "langcode",
*     "uuid" = "uuid",
*     "status" = "status",
*     "published" = "status",
*     "uid" = "uid",
*     "owner" = "uid",
*   }
* )
*/
class Node extends EditorialContentEntityBase implements NodeInterface {
   ...
}

Note: There are ongoing efforts to convert doctrine annotations to native PHP attributes. A lot of progress was made in the 10.3.x release cycle. At the time of writing, an issue to convert ContentEntityType and ConfigEntityType from annotations to PHP attributes is very close to landing in Drupal core. It is very likely that this change will be introduced in Drupal 11.1.0.

For the node entity, node and node_revision are the tables to alter as specified by the base_table and revision_table annotation keys respectively. The user and file entities do not have revision_table key in their corresponding classes: User and File respectively. For these two entities, the data tables to alter are users and file_managed.

It would be impractical to list the base and revision tables for all possible content entities. That varies per project and depends on the set of enabled modules. Instead, open an interactive PHP shell with Drush using ddev drush php:cli in the Drupal 10 folder. Then, run the following code:

// Get all entities Drupal 10 entities in the current installation.
$entities = \Drupal::entityTypeManager()->getDefinitions();
// Filter entity list to only include content entities.
$content_entities = array_filter($entities, function ($entity_definition) {
 return $entity_definition->getGroup() === 'content';
});
// Find the class that defines each content entity.
$entity_classes = array_map(function ($entity_definition) {
   return $entity_definition->getClass();
}, $content_entities);
// Find the base tables of content entities.
$base_tables = array_map(function ($entity_definition) {
 $table = $entity_definition->getBaseTable();
 $label = $entity_definition->getLabel();
return $table
  ? "The $label content entity uses '$table' as its data table."
  : "The $label content entity does NOT use a base table.";
}, $content_entities);
// Find the revision tables of content entities.
$revision_tables = array_map(function ($entity_definition) {
 $table = $entity_definition->getRevisionTable();
 $label = $entity_definition->getLabel();
return $table
  ? "The $label content entity uses '$table' as its revision table."
  : "The $label content entity DOES NOT use a revision table.";
}, $content_entities);
// Print the class that defines each content entity.
$entity_classes
// Print the base tables.
$base_tables
// Print the revision tables.
$revision_tables

In our example Drupal 10 project, you will get results similar to the following inside the interactive PHP shell:



> $entity_classes
= [
   "block_content" => "Drupal\block_content\Entity\BlockContent",
   "contact_message" => "Drupal\contact\Entity\Message",
   "file" => "Drupal\file\Entity\File",
   "media" => "Drupal\media\Entity\Media",
   "menu_link_content" => "Drupal\menu_link_content\Entity\MenuLinkContent",
   "node" => "Drupal\node\Entity\Node",
   "path_alias" => "Drupal\path_alias\Entity\PathAlias",
   "shortcut" => "Drupal\shortcut\Entity\Shortcut",
   "taxonomy_term" => "Drupal\taxonomy\Entity\Term",
   "user" => "Drupal\user\Entity\User",
   "paragraph" => "Drupal\paragraphs\Entity\Paragraph",
 ]
> $base_tables
= [
   "block_content" => "The Content block content entity uses 'block_content' as its data table.",
   "contact_message" => "The Contact message content entity DOES NOT use a base table.",
   "file" => "The File content entity uses 'file_managed' as its data table.",
   "media" => "The Media content entity uses 'media' as its data table.",
   "menu_link_content" => "The Custom menu link content entity uses 'menu_link_content' as its data table.",
   "node" => "The Content content entity uses 'node' as its data table.",
   "path_alias" => "The URL alias content entity uses 'path_alias' as its data table.",
   "shortcut" => "The Shortcut link content entity uses 'shortcut' as its data table.",
   "taxonomy_term" => "The Taxonomy term content entity uses 'taxonomy_term_data' as its data table.",
   "user" => "The User content entity uses 'users' as its data table.",
   "paragraph" => "The Paragraph content entity uses 'paragraphs_item' as its data table.",
 ]
> $revision_tables
= [
   "block_content" => "The Content block content entity uses 'block_content_revision' as its revision table.",
   "contact_message" => "The Contact message content entity DOES NOT use a revision table.",
   "file" => "The File content entity DOES NOT use a revision table.",
   "media" => "The Media content entity uses 'media_revision' as its revision table.",
   "menu_link_content" => "The Custom menu link content entity uses 'menu_link_content_revision' as its revision table.",
   "node" => "The Content content entity uses 'node_revision' as its revision table.",
   "path_alias" => "The URL alias content entity uses 'path_alias_revision' as its revision table.",
   "shortcut" => "The Shortcut link content entity DOES NOT use a revision table.",
   "taxonomy_term" => "The Taxonomy term content entity uses 'taxonomy_term_revision' as its revision table.",
   "user" => "The User content entity DOES NOT use a revision table.",
   "paragraph" => "The Paragraph content entity uses 'paragraphs_item_revision' as its revision table.",
 ]
> exit

Use exit to leave the interactive PHP shell. For all three variables, the array keys are the machine names of the content entities we are gathering information about. The array values for $entity_classes, $base_tables, and $revision_tables correspond to the class that defines the entity, its base table, and its revision table respectively.

Finding out the new auto-increment value for each content entity

Now that we know which table to alter, how do we find out the new auto-increment value for each? We need to check the Drupal 7 database. Unfortunately, the entity API in Drupal 7 is not as mature as that in Drupal 10. Moreover, many Drupal 10 content entities were not entities at all in Drupal 7. This makes the process a lot more complicated.

That being said, let's try to extract as much useful information as possible using Drupal 7 APIs. Open an interactive PHP shell with Drush using ddev drush php:cli in the Drupal 7 folder. Then, run the following code:

// Get Drupal 7 entity defintions.
$entities = entity_get_info();
// Get entity IDs (machine names).
$entities_ids = array_keys($entities);
// Find the class that defines each entity.
$entity_classes = array_map(function ($entity_definition) { return $entity_definition['controller class']; }, $entities);
// Find the base tables of entities.
$base_tables = array_map(function ($entity_definition) { return $entity_definition['base table']; }, $entities);
// Find the revision tables of content entities.
$id_columns = array_map(function ($entity_definition) { return $entity_definition['base table'] . '.' . $entity_definition['entity keys']['id']; }, $entities);
// Find the revision tables of content entities.
$revision_columns = array_map(function ($entity_definition) { return $entity_definition['entity keys']['revision'] ? $entity_definition['base table'] . "." . $entity_definition['entity keys']['revision'] : 'NO revision column'; }, $entities);
// Print the class that defines each entity.
$entity_classes
// Print the base tables.
$base_tables
// Print the id columns from the base tables.
$id_columns
// Print the revision columns from the base tables.
$revision_columns

In our example Drupal 7 project, you will get results similar to the following inside the interactive PHP shell:


>>> $entity_classes
=> [
    "field_collection_item" => "EntityAPIController",
    "node" => "NodeController",
    "file" => "DrupalDefaultEntityController",
    "taxonomy_term" => "TaxonomyTermController",
    "taxonomy_vocabulary" => "TaxonomyVocabularyController",
    "user" => "UserController",
  ]
>>> $base_tables
=> [
    "field_collection_item" => "field_collection_item",
    "node" => "node",
    "file" => "file_managed",
    "taxonomy_term" => "taxonomy_term_data",
    "taxonomy_vocabulary" => "taxonomy_vocabulary",
    "user" => "users",
  ]
>>> $id_columns
=> [
    "field_collection_item" => "field_collection_item.item_id",
    "node" => "node.nid",
    "file" => "file_managed.fid",
    "taxonomy_term" => "taxonomy_term_data.tid",
    "taxonomy_vocabulary" => "taxonomy_vocabulary.vid",
    "user" => "users.uid",
  ]
>>> $revision_columns
=> [
    "field_collection_item" => "field_collection_item.revision_id",
    "node" => "node.vid",
    "file" => "NO revision column",
    "taxonomy_term" => "NO revision column",
    "taxonomy_vocabulary" => "NO revision column",
    "user" => "NO revision column",
  ]
>>> exit

Use exit to leave the interactive PHP shell. The first thing to note is that the number of entities in Drupal 7 is much lower compared to Drupal 10. Also, the list makes no distinction between content and configuration entities. For all four variables, the array keys are the machine names of the entities we are gathering information about. The array values for $entity_classes and $base_tables correspond to the class that defines the entity and its base table. The array values of $id_columns and $revision_columns correspond to the id and revision columns of the entity’s base table..

With the information above, we can reach the following conclusion: nodes and field collections were revisionable in Drupal 7, while taxonomy terms, users, and files were not.

We can also build the following SQL queries to run against the Drupal 7 database.

-- Get higest node id value.
SELECT nid FROM node ORDER BY nid DESC LIMIT 1;
-- Get higest node revision id value.
SELECT vid FROM node ORDER BY vid DESC LIMIT 1;
-- Get higest taxonomy term id value.
SELECT tid FROM taxonomy_term_data ORDER BY tid DESC LIMIT 1;
-- Get higest user id value.
SELECT uid FROM users ORDER BY uid DESC LIMIT 1;
-- Get higest file id value.
SELECT fid FROM file_managed ORDER BY fid DESC LIMIT 1;
-- Get higest field collection id value.
SELECT item_id FROM field_collection_item ORDER BY item_id DESC LIMIT 1;
-- Get higest field collection revision id value.
SELECT revision_id FROM field_collection_item ORDER BY revision_id DESC LIMIT 1;

You can run these queries in multiple ways. One of them is opening a SQL command-line interface with Drush: ddev drush sql:cli from the Drupal 7 folder. Save the results of executing these queries.

But this may not be enough. What about URL aliases, blocks, menu items, or redirects? The list of entities you need to consider will vary per project, and you need to account for any content model changes. For example, our example project converts field collections to paragraphs and nodes to type Speaker to user entities. We need to further review the Drupal 7 database to collect the new auto-increment values for more content entities.

We’ll start with getting a list of all tables. Depending on the database server used to run the Drupal 7 website, you will have different CLI clients. DDEV bundles the following:

  • ddev mysql for interacting with MySQL/MariaDB. Use SHOW TABLES; in the CLI to list all tables.
  • ddev psql for interacting with PostgreSQL. Use \dt in the CLI to list all tables.
  • ddev . sqlite3 for interacting with SQLite. Use .tables in the CLI to list all tables.

If you are not comfortable with a CLI database client, you can use one of the many GUI database clients available in DDEV. Of course, you are free to use any other database client you are familiar with. Many IDEs provide SQL clients out of the box or via plugins.

We will proceed using the command-line interface for MySQL. Execute ddev mysql from the Drupal 7 folder and run the following commands to get relevant information for our example project:

-- Get a list of all tables.
SHOW TABLES;
-- Get information about the url_alias table and get the higest id value.
DESCRIBE url_alias;
SELECT pid FROM url_alias ORDER BY pid DESC LIMIT 1;
-- Get information about the block_custom table and get the higest id value.
DESCRIBE block_custom;
SELECT bid FROM block_custom ORDER BY bid DESC LIMIT 1;
-- Get information about the menu_links table and get the higest id value.
DESCRIBE menu_links;
SELECT mlid FROM menu_links ORDER BY mlid DESC LIMIT 1;

After getting a list of all Drupal 7 tables, we look for those that are likely to store data that would be migrated as content entities in Drupal 10. In our example, for blocks the table to query is block_custom, not block. And for menu links, the right table is menu_links, not menu_custom nor menu_router. Identifying the correct tables requires experience and a bit of trial and error.

Technical note: In Drupal 7, hook_schema was used to declare tables that needed to be created when modules were installed for them to work properly. Look for implementations of this hook. If the Devel module is available in Drupal 7, you can use drush hook schema to get a list of implementations by currently enabled modules.

After the highest ID value has been gathered from all relevant Drupal 7 tables, you need to come up with the new auto-increment value for Drupal 10. When doing so, keep the following considerations in mind:

  • Choose a high enough value that would make it unlikely for Drupal 7 entities to have the same ID as those created in Drupal 10 before the new site launches. For example, if over the lifespan of the Drupal 7 site 52,123 nodes were created, you can double and round the value to 100,000.
  • The value for the Drupal 10 base and revisions tables will likely be different. For example, in Drupal 7 there might be 52,123 nodes created but content updates produced 231,051 revisions. So, you can set the node base table in Drupal 10 to start at 100,000 while the revision table starts at 500,000.
  • The value will likely be different for each entity type. For example, in Drupal 7 you can have 52,123 nodes and 3,701 users. So, you can set the node base table in Drupal 10 to start at 100,000 while the user base table starts at 7,500.
  • Think how content model changes might affect values to use. For example, we are going to convert Speaker nodes to user entities. If in Drupal 7 you have 52,123 nodes and 3,701 users, for Drupal 10 your reference value should be the higher of the two. One of the Speaker nodes can have node id 3, or 512, or 5,250, or 7,503, or 12,001 ...you get the point. If you decide to map the Drupal 7 node id to a Drupal 10 user id, make sure that current and potential future node ids values are considered. When one Drupal 10 entity can be populated from two or more Drupal 7 entities, use the highest value among them as the base for the calculations. In this example, we can set the base tables for both the node and user entities to 100,000.
  • Be familiar with all entities that are created during an import operation. For example, it is possible, and sometimes desired, to automatically create URL aliases when nodes are created. This can be done explicitly as part of the migration or implicitly by modules like the Pathauto module implementing entity insert hooks. So, consider using the value of the Drupal 7 url_alias table to set a new auto-increment for the base table of the path_alias content entity in Drupal 10.

Technical note: Drupal 10 stores content entity IDs using the serial data type. This will be mapped to a native type provided by the database engine used to power the site. The MySQL/MariaDB driver converts serial to an unsigned INT (4 bytes) that accepts a maximum value of 4,294,967,295. The PostgreSQL driver transforms it to a serial (4 bytes) that accepts a maximum value of 2,147,483,647. The SQLite driver maps it to a signed INTEGER (64 bytes) that accepts a maximum value of 9,223,372,036,854,775,807. Take these numbers with a grain of salt. Above all, use common sense when deciding the new auto-increment values.

Performing the ALTER operation on Drupal 10 tables

We have had very lengthy and highly technical discussions. Let's take a shortcut and see how we can perform the ALTER operation in our example, which uses a MySQL/MariaDB engine. Remember that other database engines might require a different approach.


/**
* Alter AUTO_INCREMENT value for base and revision tables of content entities.
*
* This helps prevent entity ID conflicts during D7 to D10 migrations.
*/
function tag1_migration_table_auto_increment_alter() {
 // Associative array whose keys represent the table name and values indicate
 // the new auto-increment value for the table to alter.
 $auto_increment = [
   // Drupal 10 class: \Drupal\node\Entity\Node
   // Drupal 7 tables: node and node_revision
   'node' => 450,
   'node_revision' => 1000,
   // Drupal 10 class: \Drupal\user\Entity\User
   // Drupal 7 tables: users
   'users' => 350,
   // Drupal 10 class: \Drupal\taxonomy\Entity\Term
   // Drupal 7 tables: taxonomy_term_data
   'taxonomy_term_data' => 200,
   'taxonomy_term_revision' => 200,
   // Drupal 10 class: \Drupal\path_alias\Entity\PathAlias
   // Drupal 7 tables: url_alias, node, node_revision, users, and taxonomy_term_data
   'path_alias' => 1000,
   'path_alias_revision' => 1000,
   // Drupal 10 class: \Drupal\file\Entity\File
   // Drupal 7 tables: file_managed
   'file_managed' => 350,
   // Drupal 10 class: \Drupal\media\Entity\Media
   // Drupal 7 tables: file_managed, node, and node_revision
   'media' => 1000,
   'media_revision' => 1000,
   // Drupal 10 class: \Drupal\paragraphs\Entity\Paragraph
   // Drupal 7 tables: paragraphs_item and paragraphs_item_revision
   'paragraphs_item' => 750,
   'paragraphs_item_revision' => 1500,
   // Drupal 10 class: \Drupal\menu_link_content\Entity\MenuLinkContent
   // Drupal 7 tables: menu_links
   'menu_link_content' => 500,
   'menu_link_content_revision' => 500,
 ];
 foreach ($auto_increment as $table => $new_value) {
   \Drupal::database()->query(\sprintf('ALTER TABLE {%s} AUTO_INCREMENT = %s;', $table, $new_value));
 }
}

Add the above code to the tag1_migration.module file, make sure the module is enabled, and call the function responsible for making the alterations:

ddev drush pm:enable tag1_migration
ddev drush php:eval "tag1_migration_table_auto_increment_alter();"

Wait, not too fast! Let's review what we did here:

  1. Create the tag1_migration_table_auto_increment_alter PHP function in our custom tag1_migration module that uses Drupal 10's database API to execute the table ALTER statements.
  2. Store in an associative array named $auto_increment the name of the Drupal 10 tables to alter with their new auto-increment value.
  3. Iterate over the array to dynamically produce SQL statements similar to ALTER TABLE node AUTO_INCREMENT = 450;. Note: You can manually create and execute these queries directly in the database server if desired.
  4. Use Drupal 10's database API to execute the ALTER statements.
  5. Enable our custom tag1_migration so our new function is available.
  6. Use Drush to execute our new tag1_migration_table_auto_increment_alter function.
  7. Hope for the best... I mean, profit!

To make sure our operation works, you can verify the current auto-increment value of tables in MySQL/MariaDB as follows:

-- In all statements, replace 'db' with the name of your database and
-- replace 'node' with the table to check.
USE db;
SHOW CREATE TABLE node\G
SHOW TABLE STATUS FROM db WHERE name = 'node'\G
SELECT `AUTO_INCREMENT` FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'db' AND TABLE_NAME = 'node'\G

Before wrapping up this section, we want to highlight two very, very important points. In MySQL/MariaDB:

  1. It is not possible to reset the counter to a value less than or equal to the value that is currently in use. If the current auto-increment value for the node table is 453 and you try to set it to 450, it will remain at 453.
  2. Alterations to auto-increment values need to be executed in each database server instance. In our case, we should call the tag1_migration_table_auto_increment_alter function in our local development environment and in any remote (staging) environment. A SQL dump can capture the AUTO_INCREMENT value, but it is generally a terrible idea to upload a local database to a remote environment..

The AUTO_INCREMENT Alter module

This article has been packed with in-depth SQL content, but we aren’t done yet. We have one more thing... introducing the AUTO_INCREMENT Alter module. Our custom function can be replaced with the following setting:


$settings['auto_increment_alter_content_entities'] = [
 'node' => [450, 1000], // Alter the tables for the node content entity.
 'user' => [350], // Alter the tables for the user content entity.
 'taxonomy_term' => [200], // Alter the tables for the taxonomy term content entity.
 'path_alias' => [1000], // Alter the tables for the URL alias content entity.
 'file' => [350], // Alter the tables for the file content entity.
 'paragraph' => [750, 1500], // Alter the tables for the paragraph content entity.
 'media' => [1000], // Alter the tables for the media content entity.
 'menu_link_content' => [500], // Alter the tables for the custom menu link content entity.
];

Add the above to settings.migrate.php. Then, use the following snippet in the Drupal 10 folder to trigger the ALTER operations:

ddev drush php:eval "\Drupal::service('auto_increment_alter')->alterContentEntities();"

The benefit of using this approach is that there is no need to know which tables to alter. The module leverages Drupal 10 APIs to introspect entity definitions and determine which are the base and revisions tables to alter. You do need to know the machine name of the content entity and the new auto-increment values for tables. Refer to the project README.me file provided by the module to learn more about its features.

You can go a long way in a migration project writing very little SQL. But the reality is that being familiar with the database structure and knowing how to query data is very handy.

And don’t worry! Future articles will not be as technical as this one. When necessary, we will address database questions in the context of the content migration at hand. In the next article, we start with migrating content. Ready, set, go!

Drupal 7 goes end-of life in January 2025
You don't have to do this alone. We have support plans that fit your budget and needs.

Image by Raul lucus from Pixabay