Skip to content
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
4 changes: 3 additions & 1 deletion django/core/management/commands/migrate.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from django.core.management.base import BaseCommand, CommandError, no_translations
from django.core.management.sql import emit_post_migrate_signal, emit_pre_migrate_signal
from django.db import DEFAULT_DB_ALIAS, connections, router
from django.db.backends.utils import truncate_name
from django.db.migrations.autodetector import MigrationAutodetector
from django.db.migrations.executor import MigrationExecutor
from django.db.migrations.loader import AmbiguityError
Expand Down Expand Up @@ -447,8 +448,9 @@ def sync_apps(self, connection, app_labels):
def model_installed(model):
opts = model._meta
converter = connection.introspection.identifier_converter
max_name_length = connection.ops.max_name_length()
return not (
(converter(opts.db_table) in tables)
(converter(truncate_name(opts.db_table, max_name_length)) in tables)
or (
opts.auto_created
and converter(opts.auto_created._meta.db_table) in tables
Expand Down
4 changes: 4 additions & 0 deletions django/db/backends/base/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,10 @@ class BaseDatabaseFeatures:
# injection?
prohibits_dollar_signs_in_column_aliases = False

# Should PatternLookup.process_rhs() use self.param_pattern? It's unneeded
# on databases that don't use LIKE for pattern matching.
pattern_lookup_needs_param_pattern = True

# A set of dotted paths to tests in Django's test suite that are expected
# to fail on this database.
django_test_expected_failures = set()
Expand Down
8 changes: 4 additions & 4 deletions django/db/models/lookups.py
Original file line number Diff line number Diff line change
Expand Up @@ -598,10 +598,10 @@ def get_rhs_op(self, connection, rhs):
def process_rhs(self, qn, connection):
rhs, params = super().process_rhs(qn, connection)
if self.rhs_is_direct_value() and params and not self.bilateral_transforms:
params = (
self.param_pattern % connection.ops.prep_for_like_query(params[0]),
*params[1:],
)
param = connection.ops.prep_for_like_query(params[0])
if connection.features.pattern_lookup_needs_param_pattern:
param = self.param_pattern % param
params = (param, *params[1:])
return rhs, params


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,8 @@ class Classroom(models.Model):

class Lesson(models.Model):
classroom = models.ForeignKey(Classroom, on_delete=models.CASCADE)


class VeryLongNameModel(models.Model):
class Meta:
db_table = "long_db_table_that_should_be_truncated_before_checking"
53 changes: 50 additions & 3 deletions tests/migrations/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
connections,
models,
)
from django.db.backends.base.introspection import BaseDatabaseIntrospection
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
from django.db.backends.utils import truncate_name
from django.db.migrations.autodetector import MigrationAutodetector
Expand Down Expand Up @@ -1222,10 +1223,10 @@ def test_migrate_syncdb_deferred_sql_executed_with_schemaeditor(self):
create_table_count = len(
[call for call in execute.mock_calls if "CREATE TABLE" in str(call)]
)
self.assertEqual(create_table_count, 2)
self.assertEqual(create_table_count, 3)
# There's at least one deferred SQL for creating the foreign key
# index.
self.assertGreater(len(execute.mock_calls), 2)
self.assertGreater(len(execute.mock_calls), 3)
stdout = stdout.getvalue()
self.assertIn("Synchronize unmigrated apps: unmigrated_app_syncdb", stdout)
self.assertIn("Creating tables...", stdout)
Expand Down Expand Up @@ -1259,8 +1260,54 @@ def test_migrate_syncdb_app_label(self):
create_table_count = len(
[call for call in execute.mock_calls if "CREATE TABLE" in str(call)]
)
self.assertEqual(create_table_count, 2)
self.assertEqual(create_table_count, 3)
self.assertGreater(len(execute.mock_calls), 3)
self.assertIn(
"Synchronize unmigrated app: unmigrated_app_syncdb", stdout.getvalue()
)

@override_settings(
INSTALLED_APPS=[
"migrations.migrations_test_apps.unmigrated_app_syncdb",
"migrations.migrations_test_apps.unmigrated_app_simple",
]
)
def test_migrate_syncdb_installed_truncated_db_model(self):
"""
Running migrate --run-syncdb doesn't try to create models with long
truncated name if already exist.
"""
with connection.cursor() as cursor:
mock_existing_tables = connection.introspection.table_names(cursor)
# Add truncated name for the VeryLongNameModel to the list of
# existing table names.
table_name = truncate_name(
"long_db_table_that_should_be_truncated_before_checking",
connection.ops.max_name_length(),
)
mock_existing_tables.append(table_name)
stdout = io.StringIO()
with (
mock.patch.object(BaseDatabaseSchemaEditor, "execute") as execute,
mock.patch.object(
BaseDatabaseIntrospection,
"table_names",
return_value=mock_existing_tables,
),
):
call_command(
"migrate", "unmigrated_app_syncdb", run_syncdb=True, stdout=stdout
)
create_table_calls = [
str(call).upper()
for call in execute.mock_calls
if "CREATE TABLE" in str(call)
]
self.assertEqual(len(create_table_calls), 2)
self.assertGreater(len(execute.mock_calls), 2)
self.assertFalse(
any([table_name.upper() in call for call in create_table_calls])
)
self.assertIn(
"Synchronize unmigrated app: unmigrated_app_syncdb", stdout.getvalue()
)
Expand Down
Loading