fix: database unique constraints (#2594)

* fixed missing migration name

* added unique constraints to all m2m tables

* fixed bug trying to create duplicate tags

* added more unique constraints

* fixed duplicate seeder data

* updated tests

* fixed seed rollback error
This commit is contained in:
Michael Genson
2023-10-07 14:23:13 -05:00
committed by GitHub
parent 247a4de283
commit a98e863bca
13 changed files with 222 additions and 15 deletions

View File

@@ -1,6 +1,6 @@
from typing import TYPE_CHECKING, Optional
from sqlalchemy import Boolean, Float, ForeignKey, Integer, String, orm
from sqlalchemy import Boolean, Float, ForeignKey, Integer, String, UniqueConstraint, orm
from sqlalchemy.ext.orderinglist import ordering_list
from sqlalchemy.orm import Mapped, mapped_column
@@ -25,7 +25,7 @@ class ShoppingListItemRecipeReference(BaseMixins, SqlAlchemyBase):
recipe_id: Mapped[GUID | None] = mapped_column(GUID, ForeignKey("recipes.id"), index=True)
recipe: Mapped[Optional["RecipeModel"]] = orm.relationship("RecipeModel", back_populates="shopping_list_item_refs")
recipe_quantity: Mapped[float] = mapped_column(Float, nullable=False)
recipe_scale: Mapped[float | None] = mapped_column(Float, default=1)
recipe_scale: Mapped[float] = mapped_column(Float, default=1)
recipe_note: Mapped[str | None] = mapped_column(String)
@auto_init()
@@ -102,6 +102,7 @@ class ShoppingListRecipeReference(BaseMixins, SqlAlchemyBase):
class ShoppingListMultiPurposeLabel(SqlAlchemyBase, BaseMixins):
__tablename__ = "shopping_lists_multi_purpose_labels"
__table_args__ = (UniqueConstraint("shopping_list_id", "label_id", name="shopping_list_id_label_id_key"),)
id: Mapped[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
shopping_list_id: Mapped[GUID] = mapped_column(GUID, ForeignKey("shopping_lists.id"), primary_key=True)

View File

@@ -1,6 +1,6 @@
from typing import TYPE_CHECKING
from sqlalchemy import ForeignKey, String, orm
from sqlalchemy import ForeignKey, String, UniqueConstraint, orm
from sqlalchemy.orm import Mapped, mapped_column
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
@@ -16,6 +16,8 @@ if TYPE_CHECKING:
class MultiPurposeLabel(SqlAlchemyBase, BaseMixins):
__tablename__ = "multi_purpose_labels"
__table_args__ = (UniqueConstraint("name", "group_id", name="multi_purpose_labels_name_group_id_key"),)
id: Mapped[GUID] = mapped_column(GUID, default=GUID.generate, primary_key=True)
name: Mapped[str] = mapped_column(String(255), nullable=False)
color: Mapped[str] = mapped_column(String(10), nullable=False, default="")

View File

@@ -21,6 +21,7 @@ group_to_categories = sa.Table(
SqlAlchemyBase.metadata,
sa.Column("group_id", GUID, sa.ForeignKey("groups.id"), index=True),
sa.Column("category_id", GUID, sa.ForeignKey("categories.id"), index=True),
sa.UniqueConstraint("group_id", "category_id", name="group_id_category_id_key"),
)
plan_rules_to_categories = sa.Table(
@@ -28,6 +29,7 @@ plan_rules_to_categories = sa.Table(
SqlAlchemyBase.metadata,
sa.Column("group_plan_rule_id", GUID, sa.ForeignKey("group_meal_plan_rules.id"), index=True),
sa.Column("category_id", GUID, sa.ForeignKey("categories.id"), index=True),
sa.UniqueConstraint("group_plan_rule_id", "category_id", name="group_plan_rule_id_category_id_key"),
)
recipes_to_categories = sa.Table(
@@ -35,6 +37,7 @@ recipes_to_categories = sa.Table(
SqlAlchemyBase.metadata,
sa.Column("recipe_id", GUID, sa.ForeignKey("recipes.id"), index=True),
sa.Column("category_id", GUID, sa.ForeignKey("categories.id"), index=True),
sa.UniqueConstraint("recipe_id", "category_id", name="recipe_id_category_id_key"),
)
cookbooks_to_categories = sa.Table(
@@ -42,6 +45,7 @@ cookbooks_to_categories = sa.Table(
SqlAlchemyBase.metadata,
sa.Column("cookbook_id", GUID, sa.ForeignKey("cookbooks.id"), index=True),
sa.Column("category_id", GUID, sa.ForeignKey("categories.id"), index=True),
sa.UniqueConstraint("cookbook_id", "category_id", name="cookbook_id_category_id_key"),
)

View File

@@ -46,6 +46,7 @@ class IngredientUnitModel(SqlAlchemyBase, BaseMixins):
self.abbreviation = self.normalize(abbreviation)
tableargs = [
sa.UniqueConstraint("name", "group_id", name="ingredient_units_name_group_id_key"),
sa.Index(
"ix_ingredient_units_name_normalized",
"name_normalized",
@@ -113,6 +114,7 @@ class IngredientFoodModel(SqlAlchemyBase, BaseMixins):
self.name_normalized = self.normalize(name)
tableargs = [
sa.UniqueConstraint("name", "group_id", name="ingredient_foods_name_group_id_key"),
sa.Index(
"ix_ingredient_foods_name_normalized",
"name_normalized",

View File

@@ -21,6 +21,7 @@ recipes_to_tags = sa.Table(
SqlAlchemyBase.metadata,
sa.Column("recipe_id", guid.GUID, sa.ForeignKey("recipes.id"), index=True),
sa.Column("tag_id", guid.GUID, sa.ForeignKey("tags.id"), index=True),
sa.UniqueConstraint("recipe_id", "tag_id", name="recipe_id_tag_id_key"),
)
plan_rules_to_tags = sa.Table(
@@ -28,6 +29,7 @@ plan_rules_to_tags = sa.Table(
SqlAlchemyBase.metadata,
sa.Column("plan_rule_id", guid.GUID, sa.ForeignKey("group_meal_plan_rules.id"), index=True),
sa.Column("tag_id", guid.GUID, sa.ForeignKey("tags.id"), index=True),
sa.UniqueConstraint("plan_rule_id", "tag_id", name="plan_rule_id_tag_id_key"),
)
cookbooks_to_tags = sa.Table(
@@ -35,6 +37,7 @@ cookbooks_to_tags = sa.Table(
SqlAlchemyBase.metadata,
sa.Column("cookbook_id", guid.GUID, sa.ForeignKey("cookbooks.id"), index=True),
sa.Column("tag_id", guid.GUID, sa.ForeignKey("tags.id"), index=True),
sa.UniqueConstraint("cookbook_id", "tag_id", name="cookbook_id_tag_id_key"),
)

View File

@@ -17,6 +17,7 @@ recipes_to_tools = Table(
SqlAlchemyBase.metadata,
Column("recipe_id", GUID, ForeignKey("recipes.id"), index=True),
Column("tool_id", GUID, ForeignKey("tools.id"), index=True),
UniqueConstraint("recipe_id", "tool_id", name="recipe_id_tool_id_key"),
)
cookbooks_to_tools = Table(
@@ -24,6 +25,7 @@ cookbooks_to_tools = Table(
SqlAlchemyBase.metadata,
Column("cookbook_id", GUID, ForeignKey("cookbooks.id"), index=True),
Column("tool_id", GUID, ForeignKey("tools.id"), index=True),
UniqueConstraint("cookbook_id", "tool_id", name="cookbook_id_tool_id_key"),
)

View File

@@ -1,4 +1,4 @@
from sqlalchemy import Column, ForeignKey, Table
from sqlalchemy import Column, ForeignKey, Table, UniqueConstraint
from .._model_base import SqlAlchemyBase
from .._model_utils import GUID
@@ -8,4 +8,5 @@ users_to_favorites = Table(
SqlAlchemyBase.metadata,
Column("user_id", GUID, ForeignKey("users.id"), index=True),
Column("recipe_id", GUID, ForeignKey("recipes.id"), index=True),
UniqueConstraint("user_id", "recipe_id", name="user_id_recipe_id_key"),
)