Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed safe creation of M2M table for MySQL. #169

Merged
merged 1 commit into from
Aug 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Bugfixes:
- Fixed comment generation for ``PostgreSQL`` to not duplicate comments
- Fixed generation of schema for fields that defined custom ``source_field`` values defined
- Fixed working with Models that have fields with custom ``source_field`` values defined
- Fixed safe creation of M2M tables for MySQL dialect (#168)

Docs/examples:
^^^^^^^^^^^^^^
Expand Down
15 changes: 8 additions & 7 deletions tortoise/backends/base/schema_generator.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import logging
import warnings
from typing import List, Set # noqa

from tortoise import fields
from tortoise.exceptions import ConfigurationError
from tortoise.utils import get_escape_translation_table

logger = logging.getLogger("tortoise")


class BaseSchemaGenerator:
TABLE_CREATE_TEMPLATE = 'CREATE TABLE {exists}"{table_name}" ({fields}){comment};'
Expand Down Expand Up @@ -222,15 +225,13 @@ def _get_table_sql(self, model, safe=True) -> dict:
]
if safe and not self.client.capabilities.safe_indexes:
warnings.warn(
"Skipping creation of field indexes for fields {field_names} "
'on table "{table_name}" : safe index creation is not supported yet for {dialect}. '
"Please find the SQL queries to create the indexes below.".format(
field_names=", ".join(fields_with_index),
table_name=model._meta.table,
dialect=self.client.capabilities.dialect,
"Skipping creation of field indexes: safe index creation is not supported yet for "
"{dialect}. Please find the SQL queries to create the indexes in the logs.".format(
dialect=self.client.capabilities.dialect
)
)
warnings.warn("\n".join(field_indexes_sqls))
for fis in field_indexes_sqls:
logger.warning(fis)
else:
table_create_string = "\n".join([table_create_string, *field_indexes_sqls])

Expand Down
2 changes: 1 addition & 1 deletion tortoise/backends/mysql/schema_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class MySQLSchemaGenerator(BaseSchemaGenerator):
FIELD_TEMPLATE = "`{name}` {type} {nullable} {unique}{comment}"
FK_TEMPLATE = " REFERENCES `{table}` (`{field}`) ON DELETE {on_delete}{comment}"
M2M_TABLE_TEMPLATE = (
"CREATE TABLE `{table_name}` (\n"
"CREATE TABLE {exists}`{table_name}` (\n"
" `{backward_key}` {backward_type} NOT NULL REFERENCES `{backward_table}`"
" (`{backward_field}`) ON DELETE CASCADE,\n"
" `{forward_key}` {forward_type} NOT NULL REFERENCES `{forward_table}`"
Expand Down
172 changes: 172 additions & 0 deletions tortoise/tests/test_generate_schema.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# pylint: disable=C0301
import re
import sys
import warnings

from asynctest.mock import CoroutineMock, patch

Expand Down Expand Up @@ -185,6 +186,53 @@ async def test_schema(self):
"event_id" INT NOT NULL REFERENCES "event" (id) ON DELETE CASCADE,
"team_id" VARCHAR(50) NOT NULL REFERENCES "team" (name) ON DELETE CASCADE
) /* How participants relate */;
""".strip(), # noqa
)

@test.skipIf(sys.version_info < (3, 6), "Dict not sorted in 3.5")
async def test_schema_safe(self):
self.maxDiff = None
await self.init_for("tortoise.tests.models_schema_create")
sql = get_schema_sql(Tortoise.get_connection("default"), safe=True)
self.assertEqual(
sql.strip(),
"""
CREATE TABLE IF NOT EXISTS "sometable" (
"sometable_id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
"some_chars_table" VARCHAR(255) NOT NULL,
"fk_sometable" INT REFERENCES "sometable" (sometable_id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS "sometable_some_ch_115115_idx" ON "sometable" (some_chars_table);
CREATE TABLE IF NOT EXISTS "team" (
"name" VARCHAR(50) NOT NULL PRIMARY KEY /* The TEAM name (and PK) */,
"manager_id" VARCHAR(50) /* The TEAM name (and PK) */ REFERENCES "team" (name) ON DELETE CASCADE
) /* The TEAMS! */;
CREATE TABLE IF NOT EXISTS "tournament" (
"tid" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
"name" TEXT NOT NULL /* Tournament name */,
"created" TIMESTAMP NOT NULL /* Created *\\/'`\\/* datetime */
) /* What Tournaments *\\/'`\\/* we have */;
CREATE INDEX IF NOT EXISTS "tournament_name_116110_idx" ON "tournament" (name);
CREATE TABLE IF NOT EXISTS "event" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL /* Event ID */,
"name" TEXT NOT NULL UNIQUE,
"modified" TIMESTAMP NOT NULL,
"prize" VARCHAR(40),
"token" VARCHAR(100) NOT NULL UNIQUE /* Unique token */,
"tournament_id" INT NOT NULL REFERENCES "tournament" (tid) ON DELETE CASCADE /* FK to tournament */
) /* This table contains a list of all the events */;
CREATE TABLE IF NOT EXISTS "sometable_self" (
"backward_sts" INT NOT NULL REFERENCES "sometable" (sometable_id) ON DELETE CASCADE,
"sts_forward" INT NOT NULL REFERENCES "sometable" (sometable_id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS "team_team" (
"team_rel_id" VARCHAR(50) NOT NULL REFERENCES "team" (name) ON DELETE CASCADE,
"team_id" VARCHAR(50) NOT NULL REFERENCES "team" (name) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS "teamevents" (
"event_id" INT NOT NULL REFERENCES "event" (id) ON DELETE CASCADE,
"team_id" VARCHAR(50) NOT NULL REFERENCES "team" (name) ON DELETE CASCADE
) /* How participants relate */;
""".strip(), # noqa
)

Expand Down Expand Up @@ -288,6 +336,72 @@ async def test_schema(self):
`event_id` INT NOT NULL REFERENCES `event` (`id`) ON DELETE CASCADE,
`team_id` VARCHAR(50) NOT NULL REFERENCES `team` (`name`) ON DELETE CASCADE
) COMMENT='How participants relate';
""".strip(), # noqa
)

@test.skipIf(sys.version_info < (3, 6), "Dict not sorted in 3.5")
async def test_schema_safe(self):
self.maxDiff = None
await self.init_for("tortoise.tests.models_schema_create")
with warnings.catch_warnings(record=True) as w:
with self.assertLogs("tortoise", level="WARNING") as cm:
sql = get_schema_sql(Tortoise.get_connection("default"), safe=True)

self.assertEqual(
cm.output,
[
"WARNING:tortoise:CREATE INDEX `sometable_some_ch_115115_idx` ON `sometable`"
" (some_chars_table);",
"WARNING:tortoise:CREATE INDEX `tournament_name_116110_idx` ON `tournament`"
" (name);",
],
)

self.assertEqual(len(w), 1)
self.assertEqual(w[0].category, UserWarning)
self.assertEqual(
str(w[0].message),
"Skipping creation of field indexes: safe index creation is not supported yet for"
" mysql. Please find the SQL queries to create the indexes in the logs.",
)

self.assertEqual(
sql.strip(),
"""
CREATE TABLE IF NOT EXISTS `sometable` (
`sometable_id` INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
`some_chars_table` VARCHAR(255) NOT NULL,
`fk_sometable` INT REFERENCES `sometable` (`sometable_id`) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS `team` (
`name` VARCHAR(50) NOT NULL COMMENT 'The TEAM name (and PK)',
`manager_id` VARCHAR(50) COMMENT 'The TEAM name (and PK)' REFERENCES `team` (`name`) ON DELETE CASCADE
) COMMENT='The TEAMS!';
CREATE TABLE IF NOT EXISTS `tournament` (
`tid` INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
`name` TEXT NOT NULL COMMENT 'Tournament name',
`created` DATETIME(6) NOT NULL COMMENT 'Created */\\'`/* datetime'
) COMMENT='What Tournaments */\\'`/* we have';
CREATE TABLE IF NOT EXISTS `event` (
`id` INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT 'Event ID',
`name` TEXT NOT NULL UNIQUE,
`modified` DATETIME(6) NOT NULL,
`prize` DECIMAL(10,2),
`token` VARCHAR(100) NOT NULL UNIQUE COMMENT 'Unique token',
`tournament_id` INT NOT NULL REFERENCES `tournament` (`tid`) ON DELETE CASCADE COMMENT 'FK to tournament'
) COMMENT='This table contains a list of all the events';
CREATE TABLE IF NOT EXISTS `sometable_self` (
`backward_sts` INT NOT NULL REFERENCES `sometable` (`sometable_id`) ON DELETE CASCADE,
`sts_forward` INT NOT NULL REFERENCES `sometable` (`sometable_id`) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS `team_team` (
`team_rel_id` VARCHAR(50) NOT NULL REFERENCES `team` (`name`) ON DELETE CASCADE,
`team_id` VARCHAR(50) NOT NULL REFERENCES `team` (`name`) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS `teamevents` (
`event_id` INT NOT NULL REFERENCES `event` (`id`) ON DELETE CASCADE,
`team_id` VARCHAR(50) NOT NULL REFERENCES `team` (`name`) ON DELETE CASCADE
) COMMENT='How participants relate';
""".strip(), # noqa
)

Expand Down Expand Up @@ -389,5 +503,63 @@ async def test_schema(self):
"team_id" VARCHAR(50) NOT NULL REFERENCES "team" (name) ON DELETE CASCADE
);
COMMENT ON TABLE teamevents IS 'How participants relate';
""".strip(),
)

@test.skipIf(sys.version_info < (3, 6), "Dict not sorted in 3.5")
async def test_schema_safe(self):
self.maxDiff = None
await self.init_for("tortoise.tests.models_schema_create")
sql = get_schema_sql(Tortoise.get_connection("default"), safe=True)
self.assertEqual(
sql.strip(),
"""
CREATE TABLE IF NOT EXISTS "sometable" (
"sometable_id" SERIAL NOT NULL PRIMARY KEY,
"some_chars_table" VARCHAR(255) NOT NULL,
"fk_sometable" INT REFERENCES "sometable" (sometable_id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS "sometable_some_ch_115115_idx" ON "sometable" (some_chars_table);
CREATE TABLE IF NOT EXISTS "team" (
"name" VARCHAR(50) NOT NULL PRIMARY KEY,
"manager_id" VARCHAR(50) REFERENCES "team" (name) ON DELETE CASCADE
);
COMMENT ON COLUMN team.name IS 'The TEAM name (and PK)';
COMMENT ON COLUMN team.manager_id IS 'The TEAM name (and PK)';
COMMENT ON TABLE team IS 'The TEAMS!';
CREATE TABLE IF NOT EXISTS "tournament" (
"tid" SERIAL NOT NULL PRIMARY KEY,
"name" TEXT NOT NULL,
"created" TIMESTAMP NOT NULL
);
CREATE INDEX IF NOT EXISTS "tournament_name_116110_idx" ON "tournament" (name);
COMMENT ON COLUMN tournament.name IS 'Tournament name';
COMMENT ON COLUMN tournament.created IS 'Created */''`/* datetime';
COMMENT ON TABLE tournament IS 'What Tournaments */''`/* we have';
CREATE TABLE IF NOT EXISTS "event" (
"id" SERIAL NOT NULL PRIMARY KEY,
"name" TEXT NOT NULL UNIQUE,
"modified" TIMESTAMP NOT NULL,
"prize" DECIMAL(10,2),
"token" VARCHAR(100) NOT NULL UNIQUE,
"tournament_id" INT NOT NULL REFERENCES "tournament" (tid) ON DELETE CASCADE
);
COMMENT ON COLUMN event.id IS 'Event ID';
COMMENT ON COLUMN event.token IS 'Unique token';
COMMENT ON COLUMN event.tournament_id IS 'FK to tournament';
COMMENT ON TABLE event IS 'This table contains a list of all the events';
CREATE TABLE IF NOT EXISTS "sometable_self" (
"backward_sts" INT NOT NULL REFERENCES "sometable" (sometable_id) ON DELETE CASCADE,
"sts_forward" INT NOT NULL REFERENCES "sometable" (sometable_id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS "team_team" (
"team_rel_id" VARCHAR(50) NOT NULL REFERENCES "team" (name) ON DELETE CASCADE,
"team_id" VARCHAR(50) NOT NULL REFERENCES "team" (name) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS "teamevents" (
"event_id" INT NOT NULL REFERENCES "event" (id) ON DELETE CASCADE,
"team_id" VARCHAR(50) NOT NULL REFERENCES "team" (name) ON DELETE CASCADE
);
COMMENT ON TABLE teamevents IS 'How participants relate';
""".strip(),
)