Пример #1
0
    def test_no_index_for_foreignkey(self):
        """
        MySQL on InnoDB already creates indexes automatically for foreign keys.
        (#14180). An index should be created if db_constraint=False (#26171).
        """
        storage = connection.introspection.get_storage_engine(
            connection.cursor(), ArticleTranslation._meta.db_table
        )
        if storage != "InnoDB":
            self.skip("This test only applies to the InnoDB storage engine")
        index_sql = [str(statement) for statement in connection.schema_editor()._model_indexes_sql(ArticleTranslation)]
        self.assertEqual(index_sql, [
            'CREATE INDEX `indexes_articletranslation_article_no_constraint_id_d6c0806b` '
            'ON `indexes_articletranslation` (`article_no_constraint_id`)'
        ])

        # The index also shouldn't be created if the ForeignKey is added after
        # the model was created.
        field_created = False
        try:
            with connection.schema_editor() as editor:
                new_field = ForeignKey(Article, CASCADE)
                new_field.set_attributes_from_name('new_foreign_key')
                editor.add_field(ArticleTranslation, new_field)
                field_created = True
                self.assertEqual([str(statement) for statement in editor.deferred_sql], [
                    'ALTER TABLE `indexes_articletranslation` '
                    'ADD CONSTRAINT `indexes_articletrans_new_foreign_key_id_d27a9146_fk_indexes_a` '
                    'FOREIGN KEY (`new_foreign_key_id`) REFERENCES `indexes_article` (`id`)'
                ])
        finally:
            if field_created:
                with connection.schema_editor() as editor:
                    editor.remove_field(ArticleTranslation, new_field)
Пример #2
0
 def test_spgist_parameters(self):
     index_name = 'integer_array_spgist_fillfactor'
     index = SpGistIndex(fields=['field'], name=index_name, fillfactor=80)
     with connection.schema_editor() as editor:
         editor.add_index(CharFieldModel, index)
     constraints = self.get_constraints(CharFieldModel._meta.db_table)
     self.assertEqual(constraints[index_name]['type'], SpGistIndex.suffix)
     self.assertEqual(constraints[index_name]['options'], ['fillfactor=80'])
     with connection.schema_editor() as editor:
         editor.remove_index(CharFieldModel, index)
     self.assertNotIn(index_name,
                      self.get_constraints(CharFieldModel._meta.db_table))
Пример #3
0
    def test_detect_soft_applied_add_field_manytomanyfield(self):
        """
        executor.detect_soft_applied() detects ManyToManyField tables from an
        AddField operation. This checks the case of AddField in a migration
        with other operations (0001) and the case of AddField in its own
        migration (0002).
        """
        tables = [
            # from 0001
            "migrations_project",
            "migrations_task",
            "migrations_project_tasks",
            # from 0002
            "migrations_task_projects",
        ]
        executor = MigrationExecutor(connection)
        # Create the tables for 0001 but make it look like the migration hasn't
        # been applied.
        executor.migrate([("migrations", "0001_initial")])
        executor.migrate([("migrations", None)], fake=True)
        for table in tables[:3]:
            self.assertTableExists(table)
        # Table detection sees 0001 is applied but not 0002.
        migration = executor.loader.get_migration("migrations", "0001_initial")
        self.assertIs(executor.detect_soft_applied(None, migration)[0], True)
        migration = executor.loader.get_migration("migrations", "0002_initial")
        self.assertIs(executor.detect_soft_applied(None, migration)[0], False)

        # Create the tables for both migrations but make it look like neither
        # has been applied.
        executor.loader.build_graph()
        executor.migrate([("migrations", "0001_initial")], fake=True)
        executor.migrate([("migrations", "0002_initial")])
        executor.loader.build_graph()
        executor.migrate([("migrations", None)], fake=True)
        # Table detection sees 0002 is applied.
        migration = executor.loader.get_migration("migrations", "0002_initial")
        self.assertIs(executor.detect_soft_applied(None, migration)[0], True)

        # Leave the tables for 0001 except the many-to-many table. That missing
        # table should cause detect_soft_applied() to return False.
        with connection.schema_editor() as editor:
            for table in tables[2:]:
                editor.execute(editor.sql_delete_table % {"table": table})
        migration = executor.loader.get_migration("migrations", "0001_initial")
        self.assertIs(executor.detect_soft_applied(None, migration)[0], False)

        # Cleanup by removing the remaining tables.
        with connection.schema_editor() as editor:
            for table in tables[:2]:
                editor.execute(editor.sql_delete_table % {"table": table})
        for table in tables:
            self.assertTableNotExists(table)
Пример #4
0
 def test_brin_index(self):
     index_name = 'char_field_model_field_brin'
     index = BrinIndex(fields=['field'], name=index_name, pages_per_range=4)
     with connection.schema_editor() as editor:
         editor.add_index(CharFieldModel, index)
     constraints = self.get_constraints(CharFieldModel._meta.db_table)
     self.assertEqual(constraints[index_name]['type'], BrinIndex.suffix)
     self.assertEqual(constraints[index_name]['options'],
                      ['pages_per_range=4'])
     with connection.schema_editor() as editor:
         editor.remove_index(CharFieldModel, index)
     self.assertNotIn(index_name,
                      self.get_constraints(CharFieldModel._meta.db_table))
Пример #5
0
 def test_gin_fastupdate(self):
     index_name = 'integer_array_gin_fastupdate'
     index = GinIndex(fields=['field'], name=index_name, fastupdate=False)
     with connection.schema_editor() as editor:
         editor.add_index(IntegerArrayModel, index)
     constraints = self.get_constraints(IntegerArrayModel._meta.db_table)
     self.assertEqual(constraints[index_name]['type'], 'gin')
     self.assertEqual(constraints[index_name]['options'],
                      ['fastupdate=off'])
     with connection.schema_editor() as editor:
         editor.remove_index(IntegerArrayModel, index)
     self.assertNotIn(
         index_name, self.get_constraints(IntegerArrayModel._meta.db_table))
Пример #6
0
 def test_brin_parameters(self):
     index_name = 'char_field_brin_params'
     index = BrinIndex(fields=['field'],
                       name=index_name,
                       autosummarize=True)
     with connection.schema_editor() as editor:
         editor.add_index(CharFieldModel, index)
     constraints = self.get_constraints(CharFieldModel._meta.db_table)
     self.assertEqual(constraints[index_name]['type'], BrinIndex.suffix)
     self.assertEqual(constraints[index_name]['options'],
                      ['autosummarize=on'])
     with connection.schema_editor() as editor:
         editor.remove_index(CharFieldModel, index)
     self.assertNotIn(index_name,
                      self.get_constraints(CharFieldModel._meta.db_table))
Пример #7
0
    def test_alter_id_type_with_fk(self):
        try:
            executor = MigrationExecutor(connection)
            self.assertTableNotExists("author_app_author")
            self.assertTableNotExists("book_app_book")
            # Apply initial migrations
            executor.migrate([
                ("author_app", "0001_initial"),
                ("book_app", "0001_initial"),
            ])
            self.assertTableExists("author_app_author")
            self.assertTableExists("book_app_book")
            # Rebuild the graph to reflect the new DB state
            executor.loader.build_graph()

            # Apply PK type alteration
            executor.migrate([("author_app", "0002_alter_id")])

            # Rebuild the graph to reflect the new DB state
            executor.loader.build_graph()
        finally:
            # We can't simply unapply the migrations here because there is no
            # implicit cast from VARCHAR to INT on the database level.
            with connection.schema_editor() as editor:
                editor.execute(editor.sql_delete_table %
                               {"table": "book_app_book"})
                editor.execute(editor.sql_delete_table %
                               {"table": "author_app_author"})
            self.assertTableNotExists("author_app_author")
            self.assertTableNotExists("book_app_book")
            executor.migrate([("author_app", None)], fake=True)
Пример #8
0
 def test_gin_parameters(self):
     index_name = 'integer_array_gin_params'
     index = GinIndex(fields=['field'],
                      name=index_name,
                      fastupdate=True,
                      gin_pending_list_limit=64)
     with connection.schema_editor() as editor:
         editor.add_index(IntegerArrayModel, index)
     constraints = self.get_constraints(IntegerArrayModel._meta.db_table)
     self.assertEqual(constraints[index_name]['type'], 'gin')
     self.assertEqual(constraints[index_name]['options'],
                      ['gin_pending_list_limit=64', 'fastupdate=on'])
     with connection.schema_editor() as editor:
         editor.remove_index(IntegerArrayModel, index)
     self.assertNotIn(
         index_name, self.get_constraints(IntegerArrayModel._meta.db_table))
Пример #9
0
 def test_gin_index(self):
     # Ensure the table is there and doesn't have an index.
     self.assertNotIn(
         'field', self.get_constraints(IntegerArrayModel._meta.db_table))
     # Add the index
     index_name = 'integer_array_model_field_gin'
     index = GinIndex(fields=['field'], name=index_name)
     with connection.schema_editor() as editor:
         editor.add_index(IntegerArrayModel, index)
     constraints = self.get_constraints(IntegerArrayModel._meta.db_table)
     # Check gin index was added
     self.assertEqual(constraints[index_name]['type'], GinIndex.suffix)
     # Drop the index
     with connection.schema_editor() as editor:
         editor.remove_index(IntegerArrayModel, index)
     self.assertNotIn(
         index_name, self.get_constraints(IntegerArrayModel._meta.db_table))
Пример #10
0
 def test_spgist_index(self):
     # Ensure the table is there and doesn't have an index.
     self.assertNotIn('field',
                      self.get_constraints(CharFieldModel._meta.db_table))
     # Add the index.
     index_name = 'char_field_model_field_spgist'
     index = SpGistIndex(fields=['field'], name=index_name)
     with connection.schema_editor() as editor:
         editor.add_index(CharFieldModel, index)
     constraints = self.get_constraints(CharFieldModel._meta.db_table)
     # The index was added.
     self.assertEqual(constraints[index_name]['type'], SpGistIndex.suffix)
     # Drop the index.
     with connection.schema_editor() as editor:
         editor.remove_index(CharFieldModel, index)
     self.assertNotIn(index_name,
                      self.get_constraints(CharFieldModel._meta.db_table))
Пример #11
0
 def _test_procedure(self, procedure_sql, params, param_types, kparams=None):
     with connection.cursor() as cursor:
         cursor.execute(procedure_sql)
     # Use a new cursor because in MySQL a procedure can't be used in the
     # same cursor in which it was created.
     with connection.cursor() as cursor:
         cursor.callproc('test_procedure', params, kparams)
     with connection.schema_editor() as editor:
         editor.remove_procedure('test_procedure', param_types)
Пример #12
0
 def test_create_index_ignores_opclasses(self):
     index = Index(
         name='test_ops_class',
         fields=['headline'],
         opclasses=['varchar_pattern_ops'],
     )
     with connection.schema_editor() as editor:
         # This would error if opclasses weren't ignored.
         editor.add_index(IndexedArticle2, index)
Пример #13
0
 def test_text_indexes(self):
     """Test creation of PostgreSQL-specific text indexes (#12234)"""
     from .models import IndexedArticle
     index_sql = [str(statement) for statement in connection.schema_editor()._model_indexes_sql(IndexedArticle)]
     self.assertEqual(len(index_sql), 5)
     self.assertIn('("headline" varchar_pattern_ops)', index_sql[1])
     self.assertIn('("body" text_pattern_ops)', index_sql[3])
     # unique=True and db_index=True should only create the varchar-specific
     # index (#19441).
     self.assertIn('("slug" varchar_pattern_ops)', index_sql[4])
Пример #14
0
 def test_quote_value(self):
     editor = connection.schema_editor()
     tested_values = [
         ('string', "'string'"),
         (42, '42'),
         (1.754, '1.754'),
         (False, '0'),
     ]
     for value, expected in tested_values:
         with self.subTest(value=value):
             self.assertEqual(editor.quote_value(value), expected)
Пример #15
0
 def test_index_name_hash(self):
     """
     Index names should be deterministic.
     """
     with connection.schema_editor() as editor:
         index_name = editor._create_index_name(
             table_name=Article._meta.db_table,
             column_names=("c1",),
             suffix="123",
         )
     self.assertEqual(index_name, "indexes_article_c1_a52bd80b123")
Пример #16
0
 def test_index_together(self):
     editor = connection.schema_editor()
     index_sql = [str(statement) for statement in editor._model_indexes_sql(Article)]
     self.assertEqual(len(index_sql), 1)
     # Ensure the index name is properly quoted
     self.assertIn(
         connection.ops.quote_name(
             editor._create_index_name(Article._meta.db_table, ['headline', 'pub_date'], suffix='_idx')
         ),
         index_sql[0]
     )
Пример #17
0
 def test_ops_class(self):
     index = Index(
         name='test_ops_class',
         fields=['headline'],
         opclasses=['varchar_pattern_ops'],
     )
     with connection.schema_editor() as editor:
         editor.add_index(IndexedArticle2, index)
     with editor.connection.cursor() as cursor:
         cursor.execute(self.get_opclass_query % 'test_ops_class')
         self.assertEqual(cursor.fetchall(), [('varchar_pattern_ops', 'test_ops_class')])
Пример #18
0
 def test_brin_index_not_supported(self):
     index_name = 'brin_index_exception'
     index = BrinIndex(fields=['field'], name=index_name)
     with self.assertRaisesMessage(NotSupportedError,
                                   'BRIN indexes require PostgreSQL 9.5+.'):
         with mock.patch(
                 'djmodels.db.connection.features.has_brin_index_support',
                 False):
             with connection.schema_editor() as editor:
                 editor.add_index(CharFieldModel, index)
     self.assertNotIn(index_name,
                      self.get_constraints(CharFieldModel._meta.db_table))
Пример #19
0
 def test_extra_args(self):
     editor = connection.schema_editor(collect_sql=True)
     sql = 'SELECT * FROM foo WHERE id in (%s, %s)'
     params = [42, 1337]
     with self.assertLogs('djmodels.db.backends.schema', 'DEBUG') as cm:
         editor.execute(sql, params)
     self.assertEqual(cm.records[0].sql, sql)
     self.assertEqual(cm.records[0].params, params)
     self.assertEqual(
         cm.records[0].getMessage(),
         'SELECT * FROM foo WHERE id in (%s, %s); (params [42, 1337])',
     )
Пример #20
0
 def test_get_sequences_manually_created_index(self):
     with connection.cursor() as cursor:
         with connection.schema_editor() as editor:
             editor._drop_identity(Person._meta.db_table, 'id')
             seqs = connection.introspection.get_sequences(
                 cursor, Person._meta.db_table, Person._meta.local_fields)
             self.assertEqual(seqs, [{
                 'table': Person._meta.db_table,
                 'column': 'id'
             }])
             # Recreate model, because adding identity is impossible.
             editor.delete_model(Person)
             editor.create_model(Person)
Пример #21
0
    def test_get_relations(self):
        with connection.cursor() as cursor:
            relations = connection.introspection.get_relations(
                cursor, Article._meta.db_table)

        # That's {field_name: (field_name_other_table, other_table)}
        expected_relations = {
            'reporter_id': ('id', Reporter._meta.db_table),
            'response_to_id': ('id', Article._meta.db_table),
        }
        self.assertEqual(relations, expected_relations)

        # Removing a field shouldn't disturb get_relations (#17785)
        body = Article._meta.get_field('body')
        with connection.schema_editor() as editor:
            editor.remove_field(Article, body)
        with connection.cursor() as cursor:
            relations = connection.introspection.get_relations(
                cursor, Article._meta.db_table)
        with connection.schema_editor() as editor:
            editor.add_field(Article, body)
        self.assertEqual(relations, expected_relations)
Пример #22
0
 def test_table_rename_inside_atomic_block(self):
     """
     NotImplementedError is raised when a table rename is attempted inside
     an atomic block.
     """
     msg = (
         "Renaming the 'backends_author' table while in a transaction is "
         "not supported on SQLite because it would break referential "
         "integrity. Try adding `atomic = False` to the Migration class."
     )
     with self.assertRaisesMessage(NotSupportedError, msg):
         with connection.schema_editor(atomic=True) as editor:
             editor.alter_db_table(Author, "backends_author", "renamed_table")
Пример #23
0
 def test_gin_parameters_exception(self):
     index_name = 'gin_options_exception'
     index = GinIndex(fields=['field'],
                      name=index_name,
                      gin_pending_list_limit=64)
     msg = 'GIN option gin_pending_list_limit requires PostgreSQL 9.5+.'
     with self.assertRaisesMessage(NotSupportedError, msg):
         with mock.patch(
                 'djmodels.db.connection.features.has_gin_pending_list_limit',
                 False):
             with connection.schema_editor() as editor:
                 editor.add_index(IntegerArrayModel, index)
     self.assertNotIn(
         index_name, self.get_constraints(IntegerArrayModel._meta.db_table))
Пример #24
0
 def test_ops_class_multiple_columns(self):
     index = Index(
         name='test_ops_class_multiple',
         fields=['headline', 'body'],
         opclasses=['varchar_pattern_ops', 'text_pattern_ops'],
     )
     with connection.schema_editor() as editor:
         editor.add_index(IndexedArticle2, index)
     with editor.connection.cursor() as cursor:
         cursor.execute(self.get_opclass_query % 'test_ops_class_multiple')
         expected_ops_classes = (
             ('varchar_pattern_ops', 'test_ops_class_multiple'),
             ('text_pattern_ops', 'test_ops_class_multiple'),
         )
         self.assertCountEqual(cursor.fetchall(), expected_ops_classes)
Пример #25
0
 def test_autoincrement(self):
     """
     auto_increment fields are created with the AUTOINCREMENT keyword
     in order to be monotonically increasing (#10164).
     """
     with connection.schema_editor(collect_sql=True) as editor:
         editor.create_model(Square)
         statements = editor.collected_sql
     match = re.search('"id" ([^,]+),', statements[0])
     self.assertIsNotNone(match)
     self.assertEqual(
         'integer NOT NULL PRIMARY KEY AUTOINCREMENT',
         match.group(1),
         'Wrong SQL used to create an auto-increment column on SQLite'
     )
Пример #26
0
 def _test_create_model(self, app_label, should_run):
     """
     CreateModel honors multi-db settings.
     """
     operation = migrations.CreateModel(
         "Pony",
         [("id", models.AutoField(primary_key=True))],
     )
     # Test the state alteration
     project_state = ProjectState()
     new_state = project_state.clone()
     operation.state_forwards(app_label, new_state)
     # Test the database alteration
     self.assertTableNotExists("%s_pony" % app_label)
     with connection.schema_editor() as editor:
         operation.database_forwards(app_label, editor, project_state, new_state)
     if should_run:
         self.assertTableExists("%s_pony" % app_label)
     else:
         self.assertTableNotExists("%s_pony" % app_label)
     # And test reversal
     with connection.schema_editor() as editor:
         operation.database_backwards(app_label, editor, new_state, project_state)
     self.assertTableNotExists("%s_pony" % app_label)
Пример #27
0
 def test_field_rename_inside_atomic_block(self):
     """
     NotImplementedError is raised when a model field rename is attempted
     inside an atomic block.
     """
     new_field = CharField(max_length=255, unique=True)
     new_field.set_attributes_from_name('renamed')
     msg = (
         "Renaming the 'backends_author'.'name' column while in a "
         "transaction is not supported on SQLite because it would break "
         "referential integrity. Try adding `atomic = False` to the "
         "Migration class."
     )
     with self.assertRaisesMessage(NotSupportedError, msg):
         with connection.schema_editor(atomic=True) as editor:
             editor.alter_field(Author, Author._meta.get_field('name'), new_field)
Пример #28
0
 def alter_gis_model(self,
                     migration_class,
                     model_name,
                     field_name,
                     blank=False,
                     field_class=None,
                     field_class_kwargs=None):
     args = [model_name, field_name]
     if field_class:
         field_class_kwargs = field_class_kwargs or {
             'srid': 4326,
             'blank': blank
         }
         args.append(field_class(**field_class_kwargs))
     operation = migration_class(*args)
     old_state = self.current_state.clone()
     operation.state_forwards('gis', self.current_state)
     with connection.schema_editor() as editor:
         operation.database_forwards('gis', editor, old_state,
                                     self.current_state)
Пример #29
0
 def test_db_tablespace(self):
     with connection.schema_editor() as editor:
         # Index with db_tablespace attribute.
         for fields in [
                 # Field with db_tablespace specified on model.
             ['shortcut'],
                 # Field without db_tablespace specified on model.
             ['author'],
                 # Multi-column with db_tablespaces specified on model.
             ['shortcut', 'isbn'],
                 # Multi-column without db_tablespace specified on model.
             ['title', 'author'],
         ]:
             with self.subTest(fields=fields):
                 index = models.Index(fields=fields,
                                      db_tablespace='idx_tbls2')
                 self.assertIn('"idx_tbls2"',
                               str(index.create_sql(Book, editor)).lower())
         # Indexes without db_tablespace attribute.
         for fields in [['author'], ['shortcut', 'isbn'],
                        ['title', 'author']]:
             with self.subTest(fields=fields):
                 index = models.Index(fields=fields)
                 # The DEFAULT_INDEX_TABLESPACE setting can't be tested
                 # because it's evaluated when the model class is defined.
                 # As a consequence, @override_settings doesn't work.
                 if settings.DEFAULT_INDEX_TABLESPACE:
                     self.assertIn(
                         '"%s"' % settings.DEFAULT_INDEX_TABLESPACE,
                         str(index.create_sql(Book, editor)).lower())
                 else:
                     self.assertNotIn('TABLESPACE',
                                      str(index.create_sql(Book, editor)))
         # Field with db_tablespace specified on the model and an index
         # without db_tablespace.
         index = models.Index(fields=['shortcut'])
         self.assertIn('"idx_tbls"',
                       str(index.create_sql(Book, editor)).lower())
Пример #30
0
    def _test_run_sql(self, app_label, should_run, hints=None):
        with override_settings(DATABASE_ROUTERS=[MigrateEverythingRouter()]):
            project_state = self.set_up_test_model(app_label)

        sql = """
        INSERT INTO {0}_pony (pink, weight) VALUES (1, 3.55);
        INSERT INTO {0}_pony (pink, weight) VALUES (3, 5.0);
        """.format(app_label)

        operation = migrations.RunSQL(sql, hints=hints or {})
        # Test the state alteration does nothing
        new_state = project_state.clone()
        operation.state_forwards(app_label, new_state)
        self.assertEqual(new_state, project_state)
        # Test the database alteration
        self.assertEqual(project_state.apps.get_model(app_label, "Pony").objects.count(), 0)
        with connection.schema_editor() as editor:
            operation.database_forwards(app_label, editor, project_state, new_state)
        Pony = project_state.apps.get_model(app_label, "Pony")
        if should_run:
            self.assertEqual(Pony.objects.count(), 2)
        else:
            self.assertEqual(Pony.objects.count(), 0)