From d96a8f149ecb4d3fd8a8e5226774b7060c96ec95 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Sat, 3 Aug 2019 21:15:16 +0300 Subject: [PATCH] Use existing m2m table if one exists --- sqlite_utils/db.py | 17 ++++++++++++++++- tests/test_m2m.py | 33 +++++++++++++++++++++++++++++---- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/sqlite_utils/db.py b/sqlite_utils/db.py index f52b2e8..6695580 100644 --- a/sqlite_utils/db.py +++ b/sqlite_utils/db.py @@ -1069,7 +1069,22 @@ class Table: assert record_or_list is not None, "Provide lookup= or record, not both" tables = list(sorted([self.name, other_table])) columns = ["{}_id".format(t) for t in tables] - m2m_table_name = m2m_table or "{}_{}".format(*tables) + if m2m_table is not None: + m2m_table_name = m2m_table + else: + # Detect if there is a single, unambiguous option + candidates = self.db.m2m_table_candidates(self.name, other_table) + if len(candidates) == 1: + m2m_table_name = candidates[0] + elif len(candidates) > 1: + raise NoObviousTable( + "No single obvious m2m table for {}, {} - use m2m_table= parameter".format( + self.name, other_table + ) + ) + else: + # If not, create a new table + m2m_table_name = m2m_table or "{}_{}".format(*tables) m2m_table = self.db.table(m2m_table_name, pk=columns, foreign_keys=columns) if lookup is None: records = ( diff --git a/tests/test_m2m.py b/tests/test_m2m.py index 5cfe29f..cdd7d8c 100644 --- a/tests/test_m2m.py +++ b/tests/test_m2m.py @@ -1,4 +1,4 @@ -from sqlite_utils.db import ForeignKey +from sqlite_utils.db import ForeignKey, NoObviousTable import pytest @@ -107,12 +107,37 @@ def test_m2m_table_candidates(fresh_db): def test_uses_existing_m2m_table_if_exists(fresh_db): - # Code should look for an existing toble with fks to both tables + # Code should look for an existing table with fks to both tables # and use that if it exists. - assert False + people = fresh_db.create_table("people", {"id": int, "name": str}, pk="id") + fresh_db["tags"].lookup({"tag": "Coworker"}) + fresh_db.create_table( + "tagged", + {"people_id": int, "tags_id": int}, + foreign_keys=["people_id", "tags_id"], + ) + people.insert({"name": "Wahyu"}).m2m("tags", lookup={"tag": "Coworker"}) + assert fresh_db["tags"].exists + assert fresh_db["tagged"].exists + assert not fresh_db["people_tags"].exists + assert not fresh_db["tags_people"].exists + assert [{"people_id": 1, "tags_id": 1}] == list(fresh_db["tagged"].rows) def test_requires_explicit_m2m_table_if_multiple_options(fresh_db): # If the code scans for m2m tables and finds more than one candidate # it should require that the m2m_table=x argument is used - assert False + people = fresh_db.create_table("people", {"id": int, "name": str}, pk="id") + fresh_db["tags"].lookup({"tag": "Coworker"}) + fresh_db.create_table( + "tagged", + {"people_id": int, "tags_id": int}, + foreign_keys=["people_id", "tags_id"], + ) + fresh_db.create_table( + "tagged2", + {"people_id": int, "tags_id": int}, + foreign_keys=["people_id", "tags_id"], + ) + with pytest.raises(NoObviousTable): + people.insert({"name": "Wahyu"}).m2m("tags", lookup={"tag": "Coworker"})