diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2019-08-06 16:10:09 -0400 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2019-08-06 23:11:54 -0400 |
| commit | d8da7f5ac544f3dd853a221faa5fab4ff788e25b (patch) | |
| tree | 0ec2fa102695a107eb799ed12c4ade60e84a8790 /lib/sqlalchemy | |
| parent | 6a622c636ca5bc55d96b92652fd43b914a77645c (diff) | |
| download | sqlalchemy-d8da7f5ac544f3dd853a221faa5fab4ff788e25b.tar.gz | |
Implement checkfirst for Index.create(), Index.drop()
The :meth:`.Index.create` and :meth:`.Index.drop` methods now have a
parameter :paramref:`.Index.create.checkfirst`, in the same way as that of
:class:`.Table` and :class:`.Sequence`, which when enabled will cause the
operation to detect if the index exists (or not) before performing a create
or drop operation.
Fixes: #527
Change-Id: Idf994bc016359d0ae86cc64ccb20378115cb66d6
Diffstat (limited to 'lib/sqlalchemy')
| -rw-r--r-- | lib/sqlalchemy/engine/default.py | 9 | ||||
| -rw-r--r-- | lib/sqlalchemy/engine/interfaces.py | 18 | ||||
| -rw-r--r-- | lib/sqlalchemy/sql/ddl.py | 33 | ||||
| -rw-r--r-- | lib/sqlalchemy/sql/schema.py | 8 | ||||
| -rw-r--r-- | lib/sqlalchemy/testing/suite/test_reflection.py | 66 |
5 files changed, 126 insertions, 8 deletions
diff --git a/lib/sqlalchemy/engine/default.py b/lib/sqlalchemy/engine/default.py index 9d457b800..fb1728eab 100644 --- a/lib/sqlalchemy/engine/default.py +++ b/lib/sqlalchemy/engine/default.py @@ -426,6 +426,15 @@ class DefaultDialect(interfaces.Dialect): ) } + def has_index(self, connection, table_name, index_name, schema=None): + if not self.has_table(connection, table_name, schema=schema): + return False + for idx in self.get_indexes(connection, table_name, schema=schema): + if idx["name"] == index_name: + return True + else: + return False + def validate_identifier(self, ident): if len(ident) > self.max_identifier_length: raise exc.IdentifierError( diff --git a/lib/sqlalchemy/engine/interfaces.py b/lib/sqlalchemy/engine/interfaces.py index 5bd3b1d3e..fddc1712f 100644 --- a/lib/sqlalchemy/engine/interfaces.py +++ b/lib/sqlalchemy/engine/interfaces.py @@ -439,6 +439,24 @@ class Dialect(object): raise NotImplementedError() + def has_index(self, connection, table_name, index_name, schema=None): + """Check the existence of a particular index name in the database. + + Given a :class:`.Connection` object, a string + `table_name` and stiring index name, return True if an index of the + given name on the given table exists, false otherwise. + + The :class:`.DefaultDialect` implements this in terms of the + :meth:`.Dialect.has_table` and :meth:`.Dialect.get_indexes` methods, + however dialects can implement a more performant version. + + + .. versionadded:: 1.4 + + """ + + raise NotImplementedError() + def has_sequence(self, connection, sequence_name, schema=None, **kw): """Check the existence of a particular sequence in the database. diff --git a/lib/sqlalchemy/sql/ddl.py b/lib/sqlalchemy/sql/ddl.py index ff36a68e4..ef6614b61 100644 --- a/lib/sqlalchemy/sql/ddl.py +++ b/lib/sqlalchemy/sql/ddl.py @@ -732,6 +732,17 @@ class SchemaGenerator(DDLBase): self.connection, table.name, schema=effective_schema ) + def _can_create_index(self, index): + effective_schema = self.connection.schema_for_object(index.table) + if effective_schema: + self.dialect.validate_identifier(effective_schema) + return not self.checkfirst or not self.dialect.has_index( + self.connection, + index.table.name, + index.name, + schema=effective_schema, + ) + def _can_create_sequence(self, sequence): effective_schema = self.connection.schema_for_object(sequence) @@ -831,7 +842,7 @@ class SchemaGenerator(DDLBase): if hasattr(table, "indexes"): for index in table.indexes: - self.traverse_single(index) + self.traverse_single(index, create_ok=True) if self.dialect.supports_comments and not self.dialect.inline_comments: if table.comment is not None: @@ -859,7 +870,9 @@ class SchemaGenerator(DDLBase): return self.connection.execute(CreateSequence(sequence)) - def visit_index(self, index): + def visit_index(self, index, create_ok=False): + if not create_ok and not self._can_create_index(index): + return self.connection.execute(CreateIndex(index)) @@ -973,6 +986,17 @@ class SchemaDropper(DDLBase): self.connection, table.name, schema=effective_schema ) + def _can_drop_index(self, index): + effective_schema = self.connection.schema_for_object(index.table) + if effective_schema: + self.dialect.validate_identifier(effective_schema) + return not self.checkfirst or self.dialect.has_index( + self.connection, + index.table.name, + index.name, + schema=effective_schema, + ) + def _can_drop_sequence(self, sequence): effective_schema = self.connection.schema_for_object(sequence) return self.dialect.supports_sequences and ( @@ -985,7 +1009,10 @@ class SchemaDropper(DDLBase): ) ) - def visit_index(self, index): + def visit_index(self, index, drop_ok=False): + if not drop_ok and not self._can_drop_index(index): + return + self.connection.execute(DropIndex(index)) def visit_table(self, table, drop_ok=False, _is_metadata_operation=False): diff --git a/lib/sqlalchemy/sql/schema.py b/lib/sqlalchemy/sql/schema.py index 091012896..d9667e445 100644 --- a/lib/sqlalchemy/sql/schema.py +++ b/lib/sqlalchemy/sql/schema.py @@ -3670,7 +3670,7 @@ class Index(DialectKWArgs, ColumnCollectionMixin, SchemaItem): return self.table.bind - def create(self, bind=None): + def create(self, bind=None, checkfirst=False): """Issue a ``CREATE`` statement for this :class:`.Index`, using the given :class:`.Connectable` for connectivity. @@ -3682,10 +3682,10 @@ class Index(DialectKWArgs, ColumnCollectionMixin, SchemaItem): """ if bind is None: bind = _bind_or_error(self) - bind._run_ddl_visitor(ddl.SchemaGenerator, self) + bind._run_ddl_visitor(ddl.SchemaGenerator, self, checkfirst=checkfirst) return self - def drop(self, bind=None): + def drop(self, bind=None, checkfirst=False): """Issue a ``DROP`` statement for this :class:`.Index`, using the given :class:`.Connectable` for connectivity. @@ -3697,7 +3697,7 @@ class Index(DialectKWArgs, ColumnCollectionMixin, SchemaItem): """ if bind is None: bind = _bind_or_error(self) - bind._run_ddl_visitor(ddl.SchemaDropper, self) + bind._run_ddl_visitor(ddl.SchemaDropper, self, checkfirst=checkfirst) def __repr__(self): return "Index(%s)" % ( diff --git a/lib/sqlalchemy/testing/suite/test_reflection.py b/lib/sqlalchemy/testing/suite/test_reflection.py index 9ca13ec4e..9b6cec10c 100644 --- a/lib/sqlalchemy/testing/suite/test_reflection.py +++ b/lib/sqlalchemy/testing/suite/test_reflection.py @@ -77,6 +77,65 @@ class HasTableTest(fixtures.TablesTest): ) +class HasIndexTest(fixtures.TablesTest): + __backend__ = True + + @classmethod + def define_tables(cls, metadata): + tt = Table( + "test_table", + metadata, + Column("id", Integer, primary_key=True), + Column("data", String(50)), + ) + Index("my_idx", tt.c.data) + + if testing.requires.schemas.enabled: + tt = Table( + "test_table", + metadata, + Column("id", Integer, primary_key=True), + Column("data", String(50)), + schema=config.test_schema, + ) + Index("my_idx_s", tt.c.data) + + def test_has_index(self): + with config.db.begin() as conn: + assert config.db.dialect.has_index(conn, "test_table", "my_idx") + assert not config.db.dialect.has_index( + conn, "test_table", "my_idx_s" + ) + assert not config.db.dialect.has_index( + conn, "nonexistent_table", "my_idx" + ) + assert not config.db.dialect.has_index( + conn, "test_table", "nonexistent_idx" + ) + + @testing.requires.schemas + def test_has_index_schema(self): + with config.db.begin() as conn: + assert config.db.dialect.has_index( + conn, "test_table", "my_idx_s", schema=config.test_schema + ) + assert not config.db.dialect.has_index( + conn, "test_table", "my_idx", schema=config.test_schema + ) + assert not config.db.dialect.has_index( + conn, + "nonexistent_table", + "my_idx_s", + schema=config.test_schema, + ) + assert not config.db.dialect.has_index( + conn, + "test_table", + "nonexistent_idx_s", + schema=config.test_schema, + ) + + class ComponentReflectionTest(fixtures.TablesTest): run_inserts = run_deletes = None @@ -1129,4 +1188,9 @@ class NormalizedNameTest(fixtures.TablesTest): eq_(tablenames[1].upper(), tablenames[1].lower()) -__all__ = ("ComponentReflectionTest", "HasTableTest", "NormalizedNameTest") +__all__ = ( + "ComponentReflectionTest", + "HasTableTest", + "HasIndexTest", + "NormalizedNameTest", +) |
