--- /dev/null
+.. change::
+ :tags: schema, usecase
+ :tickets: 2843
+
+ The create/drop clause constructs :class:`CreateTable`, :class:`DropTable`,
+ :class:`CreateIndex` and :class:`DropIndex` now support the EXISTS
+ operator. If a dialect does not support the EXISTS operator then EXISTS
+ will be ignored. Pull request courtesy Ramon Williams.
# T-SQL's actual default is -9223372036854775808
default_sequence_base = 1
+ supports_exists_table = False
+ supports_exists_index = False
+
supports_native_boolean = False
non_native_boolean_check_constraint = False
supports_unicode_binds = True
if index_prefix:
text += index_prefix + " "
- text += "INDEX %s ON %s " % (name, table)
+ text += "INDEX "
+ if create.if_not_exists and self.dialect.supports_exists_index:
+ text += "IF NOT EXISTS "
+ text += "%s ON %s " % (name, table)
length = index.dialect_options[self.dialect.name]["length"]
if length is not None:
def visit_drop_index(self, drop):
index = drop.element
+ text = "\nDROP INDEX "
+ if drop.if_exists and self.dialect.supports_exists_index:
+ text += "IF EXISTS "
- return "\nDROP INDEX %s ON %s" % (
+ return text + "%s ON %s" % (
self._prepared_index_name(index, include_schema=False),
self.preparer.format_table(index.table),
)
max_identifier_length = 255
max_index_name_length = 64
+ supports_exists_table = True
+ supports_exists_index = False
+
supports_native_enum = True
supports_sequences = False # default for MySQL ...
class OracleDialect(default.DefaultDialect):
name = "oracle"
supports_alter = True
+ supports_exist_table = False
+ supports_exists_index = False
supports_unicode_statements = False
supports_unicode_binds = False
max_identifier_length = 128
if concurrently:
text += "CONCURRENTLY "
+ if create.if_not_exists and self.dialect.supports_exists_index:
+ text += "IF NOT EXISTS "
+
text += "%s ON %s " % (
self._prepared_index_name(index, include_schema=False),
preparer.format_table(index.table),
if concurrently:
text += "CONCURRENTLY "
+ if drop.if_exists and self.dialect.supports_exists_index:
+ text += "IF EXISTS "
+
text += self._prepared_index_name(index, include_schema=True)
return text
max_identifier_length = 63
supports_sane_rowcount = True
+ supports_exists_table = True
+ supports_exists_index = True
+
supports_native_enum = True
supports_native_boolean = True
supports_smallserial = True
text = "CREATE "
if index.unique:
text += "UNIQUE "
- text += "INDEX %s ON %s (%s)" % (
+
+ text += "INDEX "
+
+ if create.if_not_exists and self.dialect.supports_exists_index:
+ text += "IF NOT EXISTS "
+
+ text += "%s ON %s (%s)" % (
self._prepared_index_name(index, include_schema=True),
preparer.format_table(index.table, use_schema=False),
", ".join(
class SQLiteDialect(default.DefaultDialect):
name = "sqlite"
supports_alter = False
+ supports_exists_table = True
+ supports_exists_index = True
supports_unicode_statements = True
supports_unicode_binds = True
supports_default_values = True
supports_alter = True
supports_comments = False
inline_comments = False
+
+ supports_exists_table = False
+ supports_exists_index = False
use_setinputsizes = False
# the first value we'd get for an autoincrement
text = "\nCREATE "
if table._prefixes:
text += " ".join(table._prefixes) + " "
- text += "TABLE " + preparer.format_table(table) + " "
+
+ text += "TABLE "
+ if create.if_not_exists and self.dialect.supports_exists_table:
+ text += "IF NOT EXISTS "
+
+ text += preparer.format_table(table) + " "
create_table_suffix = self.create_table_suffix(table)
if create_table_suffix:
)
def visit_drop_table(self, drop, **kw):
- return "\nDROP TABLE " + self.preparer.format_table(drop.element)
+ text = "\nDROP TABLE "
+ if drop.if_exists and self.dialect.supports_exists_table:
+ text += "IF EXISTS "
+ return text + self.preparer.format_table(drop.element)
def visit_drop_view(self, drop, **kw):
return "\nDROP VIEW " + self.preparer.format_table(drop.element)
raise exc.CompileError(
"CREATE INDEX requires that the index have a name"
)
- text += "INDEX %s ON %s (%s)" % (
+
+ text += "INDEX "
+ if create.if_not_exists and self.dialect.supports_exists_index:
+ text += "IF NOT EXISTS "
+
+ text += "%s ON %s (%s)" % (
self._prepared_index_name(index, include_schema=include_schema),
preparer.format_table(
index.table, use_schema=include_table_schema
raise exc.CompileError(
"DROP INDEX requires that the index have a name"
)
- return "\nDROP INDEX " + self._prepared_index_name(
- index, include_schema=True
- )
+ text = "\nDROP INDEX "
+ if drop.if_exists and self.dialect.supports_exists_index:
+ text += "IF EXISTS "
+
+ return text + self._prepared_index_name(index, include_schema=True)
def _prepared_index_name(self, index, include_schema=False):
if index.table is not None:
return False
+class _CreateBase(_CreateDropBase):
+ """
+ Base class for DDL constructs that represent CREATE with the EXISTS
+ operator.
+
+ .. note:: If a dialect does not support the use of the EXISTS operator with
+ CREATE then it will be ignored when generating the CREATE
+ construct.
+ """
+
+ def __init__(self, element, if_not_exists=False, **kw):
+ self.if_not_exists = if_not_exists
+ super(_CreateBase, self).__init__(element, **kw)
+
+
+class _DropBase(_CreateDropBase):
+ """
+ Base class for DDL constructs that represent DROP with the EXISTS operator.
+
+ .. note:: If a dialect does not support the use of the EXISTS operator with
+ DROP then it will be ignored when generating the DROP construct.
+ """
+
+ def __init__(self, element, if_exists=False, **kw):
+ self.if_exists = if_exists
+ super(_DropBase, self).__init__(element, **kw)
+
+
class CreateSchema(_CreateDropBase):
"""Represent a CREATE SCHEMA statement.
super(DropSchema, self).__init__(name, **kw)
-class CreateTable(_CreateDropBase):
+class CreateTable(_CreateBase):
"""Represent a CREATE TABLE statement."""
__visit_name__ = "create_table"
def __init__(
- self, element, bind=None, include_foreign_key_constraints=None
+ self,
+ element,
+ bind=None,
+ include_foreign_key_constraints=None,
+ if_not_exists=False,
):
"""Create a :class:`.CreateTable` construct.
.. versionadded:: 1.0.0
+ :param if_not_exists: if True, an IF NOT EXISTS operator will be
+ applied to the construct.
"""
- super(CreateTable, self).__init__(element, bind=bind)
+ super(CreateTable, self).__init__(
+ element, bind=bind, if_not_exists=if_not_exists
+ )
self.columns = [CreateColumn(column) for column in element.columns]
self.include_foreign_key_constraints = include_foreign_key_constraints
self.element = element
-class DropTable(_CreateDropBase):
+class DropTable(_DropBase):
"""Represent a DROP TABLE statement."""
__visit_name__ = "drop_table"
+ def __init__(self, element, bind=None, if_exists=False):
+ """Create a :class:`.DropTable` construct.
+
+ :param element: a :class:`_schema.Table` that's the subject
+ of the DROP.
+ :param on: See the description for 'on' in :class:`.DDL`.
+ :param bind: See the description for 'bind' in :class:`.DDL`.
+ :param if_exists: if True, an IF EXISTS operator will be applied to the
+ construct.
+ """
+ super(DropTable, self).__init__(
+ element, bind=bind, if_exists=if_exists
+ )
+
class CreateSequence(_CreateDropBase):
"""Represent a CREATE SEQUENCE statement."""
__visit_name__ = "drop_sequence"
-class CreateIndex(_CreateDropBase):
+class CreateIndex(_CreateBase):
"""Represent a CREATE INDEX statement."""
__visit_name__ = "create_index"
+ def __init__(self, element, bind=None, if_not_exists=False):
+ """Create a :class:`.Createindex` construct.
-class DropIndex(_CreateDropBase):
+ :param element: a :class:`_schema.Index` that's the subject
+ of the CREATE.
+ :param on: See the description for 'on' in :class:`.DDL`.
+ :param bind: See the description for 'bind' in :class:`.DDL`.
+ :param if_not_exists: if True, an IF NOT EXISTS operator will be
+ applied to the construct.
+ """
+ super(CreateIndex, self).__init__(
+ element, bind=bind, if_not_exists=if_not_exists
+ )
+
+
+class DropIndex(_DropBase):
"""Represent a DROP INDEX statement."""
__visit_name__ = "drop_index"
+ def __init__(self, element, bind=None, if_exists=False):
+ """Create a :class:`.DropIndex` construct.
+
+ :param element: a :class:`_schema.Index` that's the subject
+ of the DROP.
+ :param on: See the description for 'on' in :class:`.DDL`.
+ :param bind: See the description for 'bind' in :class:`.DDL`.
+ :param if_exists: if True, an IF EXISTS operator will be applied to the
+ construct.
+ """
+ super(DropIndex, self).__init__(
+ element, bind=bind, if_exists=if_exists
+ )
+
class AddConstraint(_CreateDropBase):
"""Represent an ALTER TABLE ADD CONSTRAINT statement."""
self.assert_compile(
schema.CreateIndex(constraint), "CREATE INDEX name ON tbl (a + 5)"
)
+
+ @testing.skip_if("postgresql < 9.1")
+ def test_create_table_if_not_exists(self):
+ t, t2 = self._constraint_create_fixture()
+
+ # If a dialect doesn't support EXISTS, EXISTS will be ignored silently.
+ if testing.db.dialect.supports_exists_table:
+ if testing.db.dialect.name == "mssql":
+ stmt = (
+ "CREATE TABLE IF NOT EXISTS tbl "
+ "(a INTEGER NULL, b INTEGER NULL)"
+ )
+ else:
+ stmt = "CREATE TABLE IF NOT EXISTS tbl (a INTEGER, b INTEGER)"
+
+ else:
+ if testing.db.dialect.name == "mssql":
+ stmt = "CREATE TABLE tbl (a INTEGER NULL, b INTEGER NULL)"
+ else:
+ stmt = "CREATE TABLE tbl (a INTEGER, b INTEGER)"
+
+ self.assert_compile(
+ schema.CreateTable(t, if_not_exists=True),
+ stmt,
+ dialect=testing.db.dialect,
+ )
+
+ @testing.skip_if("postgresql < 9.5")
+ def test_create_index_if_not_exists(self):
+ t, t2 = self._constraint_create_fixture()
+ idx = Index("idx_name", t.c.a)
+
+ # If a dialect doesn't support EXISTS, EXISTS will be ignored silently.
+ if testing.db.dialect.supports_exists_index:
+ stmt = "CREATE INDEX IF NOT EXISTS idx_name ON tbl (a)"
+ else:
+ stmt = "CREATE INDEX idx_name ON tbl (a)"
+ self.assert_compile(
+ schema.CreateIndex(idx, if_not_exists=True),
+ stmt,
+ dialect=testing.db.dialect,
+ )
+
+ @testing.skip_if("postgresql < 8.2")
+ def test_drop_table_if_exists(self):
+ t, t2 = self._constraint_create_fixture()
+
+ # If a dialect doesn't support EXISTS, EXISTS will be ignored silently.
+ if testing.db.dialect.supports_exists_table:
+ stmt = "DROP TABLE IF EXISTS tbl"
+ else:
+ stmt = "DROP TABLE tbl"
+ self.assert_compile(
+ schema.DropTable(t, if_exists=True),
+ stmt,
+ dialect=testing.db.dialect,
+ )
+
+ @testing.skip_if("postgresql < 8.2")
+ def test_drop_index_if_exists(self):
+ t, t2 = self._constraint_create_fixture()
+ idx = Index("idx_name", t.c.a)
+
+ # If a dialect doesn't support EXISTS, EXISTS will be ignored silently.
+ if testing.db.dialect.supports_exists_index:
+ stmt = "DROP INDEX IF EXISTS idx_name"
+ else:
+ stmt = "DROP INDEX idx_name"
+
+ if testing.db.dialect.name in ("mysql", "mariadb", "mssql"):
+ stmt += " ON tbl"
+ self.assert_compile(
+ schema.DropIndex(idx, if_exists=True),
+ stmt,
+ dialect=testing.db.dialect,
+ )