Skip to content

Fix: duplicate attribute slugs#381

Open
ahuininga-orisha wants to merge 8 commits into
developfrom
fix/duplicate-attribute-slugs
Open

Fix: duplicate attribute slugs#381
ahuininga-orisha wants to merge 8 commits into
developfrom
fix/duplicate-attribute-slugs

Conversation

@ahuininga-orisha

Copy link
Copy Markdown
Collaborator

Fixes #337 and #363

Summary

  • Fix DB schema: adds a schema patch (FixSlugUniqueIndexOnTweakwiseAttributeSlugTable) that drops the global unique index on slug (introduced in InstallSchema) and replaces it with a composite unique index on (slug, store_id). This is the root cause of the Unique constraint violation error when saving product attributes in the admin.
  • Fix duplicate inserts: AttributeSlugRepository::save() now loads any existing row by (attribute, store_id) before the collision loop and sets its primary key on the entity, so resource->save() always issues an UPDATE instead of a duplicate INSERT.
  • Fix in-memory cache not updated on new slug: all three write paths in FilterSlugManager (getSlugForFilterItem, saveAttributeSlugForOptionLabel, createFilterSlugByOption) now update $this->lookupTable immediately after a successful save. Previously, a second call for the same attribute within the same request would miss the in-memory table, fall through to save() again, and trigger another unnecessary DB write.
  • Fix stale in-memory table after truncate: FilterSlugManager::truncateSlugTable() now resets $this->lookupTable = null so the next getLookupTable() call reloads from DB rather than serving the pre-truncate in-memory state.
  • Guard uninitialised lookup table: createFilterSlugByOption() now calls getLookupTable() before writing to $this->lookupTable to ensure the property is always initialised before use.

How to test

Scenario 1 — Admin attribute save no longer throws a unique constraint violation

  1. Ensure tweakwise/layered/url_strategy is set to PathSlugStrategy and slugs have already been generated (visit a category page or run tweakwise:regenerate:filter-url-slugs).
  2. In Magento admin go to Catalog > Attributes > Product, open any existing attribute and click Save Attribute.
  3. Save the same attribute a second time.
  4. Verify no Could not save the page: Unique constraint violation found error appears.

Scenario 2 — Slug collision resolution still works across stores

  1. Configure two store views.
  2. Ensure the same attribute label exists in both stores with existing slugs.
  3. Run tweakwise:regenerate:filter-url-slugs.
  4. Verify that each store view gets the correct slug and no DB error is thrown.
  5. Verify the tweakwise_attribute_slug table contains separate rows per (slug, store_id) combination.

Scenario 3 — In-memory cache is updated after a new slug is created

  1. Truncate the tweakwise_attribute_slug table and clear the Magento cache.
  2. Visit a category page that has filters — this triggers getSlugForFilterItem() for each filter.
  3. Enable query logging or add temporary logging to getSlugForFilterItem() and verify that save() is called exactly once per attribute per store, not twice, even when multiple filters are rendered in the same request.

Scenario 4 — Truncating the slug table reloads correctly

  1. Call FilterSlugManager::truncateSlugTable() (via the CLI command or directly in a test).
  2. Immediately request a category page in the same process/request context.
  3. Verify the lookup table is reloaded from DB and no stale in-memory slugs are served.

@@ -289,6 +300,7 @@ protected function loadLookupTable(): array
public function truncateSlugTable(): void
{
$this->attributeSlugRepository->truncateSlugTable();
$this->lookupTable = null;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$this->lookupTable = null; or $this->lookupTable = [];?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getLookupTable() has a if ($this->lookupTable === null) guard (line 267) that triggers a DB reload
Setting it to [] would skip that check and return an empty array on the next call, meaning the in-memory cache would never be reloaded after a truncate

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AttributeSlugRepository cannot save non existing slug

2 participants