The url_alias table needs a UNIQUE constraint to ensure that there are no duplicate entries. Currently, there is a test to ensure that only the most recent duplicate alias is returned. It would be far better to ensure that there are no such entries.

Original report

This report is identical to #818214: Improper table structure for url_alias. In that issue a patch was applied and then rolled back. I believe it should be applied again.

This is the original issue description:

The table structure for the url_alias table is improper right now. There is a UNIQUE index set on the dst_language_pid grouping. The pid column should be removed from that index as it's now possible to have duplicate destinations for the same language because pid is ALWAYS a unique value.

This should be changed to UNIQUE KEY (`dst`, `language`) to correct this structural issue.

The point is: It doesn't make sense to have the same alias in a given language pointing to more than one source path.

The current database schema was originally introduced (to both D6 and D7) in #358315: drupal_lookup_path() not respects alias' order. The argument for changing the key from (alias, language) to (alias, language, pid) was to remove a filesort from this query:

SELECT source FROM {url_alias} WHERE alias = :alias AND language IN (:language, :language_none) ORDER BY language DESC, pid DESC

However, simply removing "pid DESC" from the ORDER BY should give the same result, because (alias, language) is already unique so there is no need for the "pid DESC".

Comments

andypost’s picture

subscribe

EvanDonovan’s picture

Version: 8.x-dev » 7.x-dev

If this is actually a bug, and not a feature request, it should be against 7.x, since 8.x is not open for development yet.

stefan freudenberg’s picture

Well. After reading the original thread I vaguely understand that the unique key has been changed because there are means in the UI to prevent duplicate aliases. Well there are other methods to create content in Drupal that don't use the UI. Since march we have accumulated a bunch of duplicates and one case caused a lot of trouble for us today. I am evaluating if something has been coded improperly on our end...

But the unique key is a constraint which has a functional meaning besides improving performance. Also... introducing those kinds of changes in updates that come along with security fixes should be reviewed in general.

stefan freudenberg’s picture

Priority: Normal » Major

The mechanism guarding against duplicate aliases as of now will not work if the aliases are created at the same time. Removing the unique key constraint has opened the opportunity for duplicates to occur. This is definitely undesired behaviour (a bug) and should be fixed by either reverting to the old schema or using a lock.

catch’s picture

Component: path.module » system.module

If anything, we should add a new unique index on alias, language. For existing sites that have this problem it'll be necessary to have an update that finds and deletes duplicate url aliases as well.

catch’s picture

Version: 7.x-dev » 8.x-dev
Issue tags: +Needs backport to D7
sun’s picture

Title: Improper table structure for url_alias II » Improper table structure for {url_alias} #2
Category: bug » task

The "bug" is hypothetical at this point, so this is a task, which might be backported to D7.

stefan freudenberg’s picture

Well, it's definitely not hypothetical. I can assure you it happened on my site.

chx’s picture

Category: task » bug

I am working on a site that has thousands of same src-dst pairs, some of those more than 70 times -- and no language in sight.

tim.plunkett’s picture

Status: Active » Postponed (maintainer needs more info)
Issue tags: +Needs issue summary update

The dst_language_pid index in the OP doesn't exist anymore, is this still valid?

stefan freudenberg’s picture

This is valid as long as the UNIQUE KEY constraint is missing, unless someone prefers a different solution for the problem.

liam morland’s picture

Status: Postponed (maintainer needs more info) » Needs review
StatusFileSize
new536 bytes

I spent most of the day trying to figure out why I had duplicate entries in the url_alias table. Turns out it was because of #1737048: 2 URL aliases created when saving new node as published. If there has been a unique constraint in places, this would have been caught long ago in development, before it became a problem.

The attached patch adds the unique key. It would be best to have an update hook clear away duplicates and add the key to existing databases. This fixes it for new installations.

Status: Needs review » Needs work

The last submitted patch, core_url_alias_unique_925474.patch, failed testing.

tim.plunkett’s picture

Priority: Major » Normal
Issue tags: +Needs tests
liam morland’s picture

In AliasTest.php, the test that is causing the failure is 'Newer alias record is returned when comparing two LANGUAGE_NOT_SPECIFIED paths with the same alias.' So, this test deliberately violates the unique key that my patch adds.

I think I should change this test so that it tests that the unique constraint is working. Can I use a try()catch() block for this? Am I on the right track?

liam morland’s picture

Status: Needs work » Needs review
StatusFileSize
new536 bytes

I tried putting a try()catch() block around the database insertion which ought to fail if the constraint is working, but the exception gets caught elsewhere, so the test still doesn't work.

This revised patch just removes the obsolete test.

liam morland’s picture

StatusFileSize
new1.49 KB

Sorry, this is the correct patch.

liam morland’s picture

Status: Needs review » Needs work
Issue tags: +Needs tests, +Needs issue summary update, +Needs backport to D7

The last submitted patch, core_url_alias_unique_925474.patch, failed testing.

liam morland’s picture

Status: Needs work » Needs review
StatusFileSize
new1.49 KB

Reroll.

liam morland’s picture

Issue summary has been updated.

This removes an unneeded test. The UNIQUE constraint cannot be tested with the Drupal testing system.

liam morland’s picture

Reroll.

liam morland’s picture

liam morland’s picture

Issue summary: View changes

Create short explanation of current state of issue.

liam morland’s picture

Issue summary: View changes
StatusFileSize
new1.5 KB

Reroll.

andypost’s picture

Maybe better to use db_merge() because there's no support for handling unique key exceptions

liam morland’s picture

@andypost, I don't understand. Can you elaborate?

mgifford’s picture

mgifford’s picture

I don't understand @andypost's comment either, but must be instead of:

+ 'unique keys' => array('alias_langcode' => array('alias', 'langcode')),

This is a small patch after all.

liam morland’s picture

Re-reading it, I understand: My patch would cause an attempt to insert a duplicate alias to fail, which preserves data integrity.

If in addition to my patch, the alias creation code used db_merge() instead of db_insert(), then such an attempt would update the existing alias instead of failing, which might be a preferred fail condition.

Either way, my patch can go in.

Status: Needs review » Needs work

The last submitted patch, 28: core_url_alias_unique_925474.patch, failed testing.

The last submitted patch, 28: core_url_alias_unique_925474.patch, failed testing.

liam morland’s picture

Status: Needs work » Needs review
StatusFileSize
new1.59 KB

Reroll.

Status: Needs review » Needs work

The last submitted patch, 36: core_url_alias_unique_925474.patch, failed testing.

liam morland’s picture

StatusFileSize
new1.21 KB

Reroll. This version adds the UNIQUE constraint and changes the code for adding aliases so that if you try to add an alias on the same language and path as an existing one, it instead updates the existing alias. The resulting behavior is identical to before, so no changes are required to any of the tests.

liam morland’s picture

Status: Needs work » Needs review
liam morland’s picture

Title: Improper table structure for {url_alias} #2 » Improve table structure for {url_alias} #2
liam morland’s picture

StatusFileSize
new1.24 KB

Reroll.

Status: Needs review » Needs work

The last submitted patch, 41: core-url_alias_unique-925474-41.patch, failed testing.

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 41: core-url_alias_unique-925474-41.patch, failed testing.

liam morland’s picture

Status: Needs work » Needs review
StatusFileSize
new1.99 KB

Status: Needs review » Needs work

The last submitted patch, 45: core-url_alias_unique-925474-45.patch, failed testing.

liam morland’s picture

Status: Needs work » Needs review
StatusFileSize
new1.88 KB
liam morland’s picture

liam morland’s picture

StatusFileSize
new1.88 KB
jhedstrom’s picture

Patch in #49 still applies.

+++ b/core/modules/migrate_drupal/src/Tests/d6/MigrateUrlAliasTest.php
@@ -69,7 +69,7 @@ public function testUrlAlias() {
-    $path = \Drupal::service('path.alias_storage')->load(array('pid' => $path['pid']));
+    $path = \Drupal::service('path.alias_storage')->load(array('source' => $conditions['source']));

Why is this change necessary?

liam morland’s picture

I don't remember. Does it work without it? My D8 install is not working right now.

liam morland’s picture

I think it is a better test: Do the test starting with the source and ensure that the right alias comes out.

mgifford’s picture

Issue tags: +multilingual, +i18n

This makes sense to me. It still applies nicely. Drupal 8 is going to be the most multilingual version of Drupal ever, seems kinda silly to not include this.

Tagging for i18n.

Would there be any performance issues with the merge?

Status: Needs review » Needs work

The last submitted patch, 49: core-url_alias_unique-925474-49.patch, failed testing.

andypost’s picture

Issue tags: +D8MI, +language-base

suppose proper tags

liam morland’s picture

I don't think the merge should cause a performance issue. It may be a little bit slower than insert, but it only runs once, not in a loop.

liam morland’s picture

Does anyone understand why there are two test failures of the same item? Why does this test run twice?

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 49: core-url_alias_unique-925474-49.patch, failed testing.

liam morland’s picture

StatusFileSize
new1.18 KB

Reroll without change to test.

liam morland’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 61: core-url_alias_unique-925474-61.patch, failed testing.

liam morland’s picture

Status: Needs work » Needs review
StatusFileSize
new536 bytes

The code already has a check for whether it should insert or update, so all that is needed is the unique constraint. This passes when I run it on my local.

Status: Needs review » Needs work

The last submitted patch, 64: core-url_alias_unique-925474-64.patch, failed testing.

liam morland’s picture

OK, so the merge is needed. The only problem is that with the merge, the D6 migration stuff doesn't work. I don't understand what is going wrong there.

mgifford’s picture

Thanks for plugging away at this. Would be good if we could bring in more i18n folks to review it.

liam morland’s picture

Status: Needs work » Needs review
StatusFileSize
new1.88 KB

This is a reroll of the last version of this patch which passes the testbot.

Status: Needs review » Needs work

The last submitted patch, 68: core-url_alias_unique-925474-68.patch, failed testing.

Status: Needs work » Needs review

The last submitted patch, 49: core-url_alias_unique-925474-49.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 68: core-url_alias_unique-925474-68.patch, failed testing.

The last submitted patch, 64: core-url_alias_unique-925474-64.patch, failed testing.

The last submitted patch, 61: core-url_alias_unique-925474-61.patch, failed testing.

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 68: core-url_alias_unique-925474-68.patch, failed testing.

siva_epari’s picture

Status: Needs work » Needs review
Issue tags: -Needs reroll
StatusFileSize
new1.88 KB

Patch rerolled.

Status: Needs review » Needs work

The last submitted patch, 81: core-url_alias_unique-925474-81.patch, failed testing.

liam morland’s picture

Status: Needs work » Needs review
StatusFileSize
new1.18 KB

Reroll. I'm getting a new error when I try this locally. I want to see if the testbot gets the same thing.

dawehner’s picture

this would need an update function

Status: Needs review » Needs work

The last submitted patch, 83: core-url_alias_unique-925474-83.patch, failed testing.

liam morland’s picture

@#84: Yes, I wanted to get it working first, then worry about the update hook.

I am getting the MySQL error "1071 Specified key was too long". We didn't used to get this error for that line of code. I don't know what is different. I can get around it by making the key only include the first part of the alias, but that could prevent the insertion of valid aliases.

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.

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

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.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.3.x-dev » 8.4.x-dev

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.5.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.4.x-dev » 8.5.x-dev

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.5.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.6.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.5.x-dev » 8.6.x-dev

Drupal 8.5.6 was released on August 1, 2018 and is the final bugfix release for the Drupal 8.5.x series. Drupal 8.5.x will not receive any further development aside from security fixes. Sites should prepare to update to 8.6.0 on September 5, 2018. (Drupal 8.6.0-rc1 is available for testing.)

Bug reports should be targeted against the 8.6.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.7.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.6.x-dev » 8.8.x-dev

Drupal 8.6.x will not receive any further development aside from security fixes. Bug reports should be targeted against the 8.8.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.9.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.7 was released on June 3, 2020 and is the final full bugfix release for the Drupal 8.8.x series. Drupal 8.8.x will not receive any further development aside from security fixes. Sites should prepare to update to Drupal 8.9.0 or Drupal 9.0.0 for ongoing support.

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

quietone’s picture

Status: Needs work » Closed (outdated)
Issue tags: +Bug Smash Initiative

aliases were converted to entities in #2336597: Convert path aliases to full featured entities and included adding a constraint for uniqueness, UniquePathAlias. So, I reckon this is now outdated.

Therefore, closing as outdated. If this is incorrect reopen the issue, by setting the status to 'Active', and add a comment explaining what still needs to be done.

Thanks!