diff options
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r-- | lib/sqlalchemy/engine/ddl.py | 102 | ||||
-rw-r--r-- | lib/sqlalchemy/schema.py | 103 |
2 files changed, 169 insertions, 36 deletions
diff --git a/lib/sqlalchemy/engine/ddl.py b/lib/sqlalchemy/engine/ddl.py index 6b5684f64..dca183f9a 100644 --- a/lib/sqlalchemy/engine/ddl.py +++ b/lib/sqlalchemy/engine/ddl.py @@ -22,33 +22,58 @@ class SchemaGenerator(DDLBase): self.preparer = dialect.identifier_preparer self.dialect = dialect - def _can_create(self, table): + def _can_create_table(self, table): self.dialect.validate_identifier(table.name) if table.schema: self.dialect.validate_identifier(table.schema) - return not self.checkfirst or not self.dialect.has_table(self.connection, table.name, schema=table.schema) + return not self.checkfirst or \ + not self.dialect.has_table(self.connection, + table.name, schema=table.schema) + + def _can_create_sequence(self, sequence): + return self.dialect.supports_sequences and \ + ( + (not self.dialect.sequences_optional or + not sequence.optional) and + ( + not self.checkfirst or + not self.dialect.has_sequence( + self.connection, + sequence.name, + schema=sequence.schema) + ) + ) def visit_metadata(self, metadata): if self.tables: tables = self.tables else: tables = metadata.tables.values() - collection = [t for t in sql_util.sort_tables(tables) if self._can_create(t)] + collection = [t for t in sql_util.sort_tables(tables) + if self._can_create_table(t)] + seq_coll = [s for s in metadata._sequences + if s.column is None and self._can_create_sequence(s)] metadata.dispatch.before_create(metadata, self.connection, - tables=collection) + tables=collection, + checkfirst=self.checkfirst) + + for seq in seq_coll: + self.traverse_single(seq, create_ok=True) for table in collection: self.traverse_single(table, create_ok=True) metadata.dispatch.after_create(metadata, self.connection, - tables=collection) + tables=collection, + checkfirst=self.checkfirst) def visit_table(self, table, create_ok=False): - if not create_ok and not self._can_create(table): + if not create_ok and not self._can_create_table(table): return - table.dispatch.before_create(table, self.connection) + table.dispatch.before_create(table, self.connection, + checkfirst=self.checkfirst) for column in table.columns: if column.default is not None: @@ -60,15 +85,13 @@ class SchemaGenerator(DDLBase): for index in table.indexes: self.traverse_single(index) - table.dispatch.after_create(table, self.connection) + table.dispatch.after_create(table, self.connection, + checkfirst=self.checkfirst) - def visit_sequence(self, sequence): - if self.dialect.supports_sequences: - if ((not self.dialect.sequences_optional or - not sequence.optional) and - (not self.checkfirst or - not self.dialect.has_sequence(self.connection, sequence.name, schema=sequence.schema))): - self.connection.execute(schema.CreateSequence(sequence)) + def visit_sequence(self, sequence, create_ok=False): + if not create_ok and not self._can_create_sequence(sequence): + return + self.connection.execute(schema.CreateSequence(sequence)) def visit_index(self, index): self.connection.execute(schema.CreateIndex(index)) @@ -87,31 +110,52 @@ class SchemaDropper(DDLBase): tables = self.tables else: tables = metadata.tables.values() - collection = [t for t in reversed(sql_util.sort_tables(tables)) if self._can_drop(t)] + collection = [t for t in reversed(sql_util.sort_tables(tables)) + if self._can_drop_table(t)] + seq_coll = [s for s in metadata._sequences + if s.column is None and self._can_drop_sequence(s)] metadata.dispatch.before_drop(metadata, self.connection, - tables=collection) + tables=collection, + checkfirst=self.checkfirst) for table in collection: self.traverse_single(table, drop_ok=True) + for seq in seq_coll: + self.traverse_single(seq, drop_ok=True) + metadata.dispatch.after_drop(metadata, self.connection, - tables=collection) + tables=collection, + checkfirst=self.checkfirst) - def _can_drop(self, table): + def _can_drop_table(self, table): self.dialect.validate_identifier(table.name) if table.schema: self.dialect.validate_identifier(table.schema) - return not self.checkfirst or self.dialect.has_table(self.connection, table.name, schema=table.schema) + return not self.checkfirst or self.dialect.has_table(self.connection, + table.name, schema=table.schema) + + def _can_drop_sequence(self, sequence): + return self.dialect.supports_sequences and \ + ((not self.dialect.sequences_optional or + not sequence.optional) and + (not self.checkfirst or + self.dialect.has_sequence( + self.connection, + sequence.name, + schema=sequence.schema)) + ) def visit_index(self, index): self.connection.execute(schema.DropIndex(index)) def visit_table(self, table, drop_ok=False): - if not drop_ok and not self._can_drop(table): + if not drop_ok and not self._can_drop_table(table): return - table.dispatch.before_drop(table, self.connection) + table.dispatch.before_drop(table, self.connection, + checkfirst=self.checkfirst) for column in table.columns: if column.default is not None: @@ -119,12 +163,10 @@ class SchemaDropper(DDLBase): self.connection.execute(schema.DropTable(table)) - table.dispatch.after_drop(table, self.connection) + table.dispatch.after_drop(table, self.connection, + checkfirst=self.checkfirst) - def visit_sequence(self, sequence): - if self.dialect.supports_sequences: - if ((not self.dialect.sequences_optional or - not sequence.optional) and - (not self.checkfirst or - self.dialect.has_sequence(self.connection, sequence.name, schema=sequence.schema))): - self.connection.execute(schema.DropSequence(sequence)) + def visit_sequence(self, sequence, drop_ok=False): + if not drop_ok and not self._can_drop_sequence(sequence): + return + self.connection.execute(schema.DropSequence(sequence)) diff --git a/lib/sqlalchemy/schema.py b/lib/sqlalchemy/schema.py index 3a4bd90ce..40d7de945 100644 --- a/lib/sqlalchemy/schema.py +++ b/lib/sqlalchemy/schema.py @@ -1219,6 +1219,7 @@ class DefaultGenerator(SchemaItem): is_sequence = False is_server_default = False + column = None def __init__(self, for_update=False): self.for_update = for_update @@ -1336,14 +1337,84 @@ class ColumnDefault(DefaultGenerator): return "ColumnDefault(%r)" % self.arg class Sequence(DefaultGenerator): - """Represents a named database sequence.""" + """Represents a named database sequence. + + The :class:`.Sequence` object represents the name and configurational + parameters of a database sequence. It also represents + a construct that can be "executed" by a SQLAlchemy :class:`.Engine` + or :class:`.Connection`, rendering the appropriate "next value" function + for the target database and returning a result. + + The :class:`.Sequence` is typically associated with a primary key column:: + + some_table = Table('some_table', metadata, + Column('id', Integer, Sequence('some_table_seq'), primary_key=True) + ) + + When CREATE TABLE is emitted for the above :class:`.Table`, if the + target platform supports sequences, a CREATE SEQUENCE statement will + be emitted as well. For platforms that don't support sequences, + the :class:`.Sequence` construct is ignored. + + See also: :class:`.CreateSequence` :class:`.DropSequence` + + """ __visit_name__ = 'sequence' is_sequence = True def __init__(self, name, start=None, increment=None, schema=None, - optional=False, quote=None, metadata=None, for_update=False): + optional=False, quote=None, metadata=None, + for_update=False): + """Construct a :class:`.Sequence` object. + + :param name: The name of the sequence. + :param start: the starting index of the sequence. This value is + used when the CREATE SEQUENCE command is emitted to the database + as the value of the "START WITH" clause. If ``None``, the + clause is omitted, which on most platforms indicates a starting + value of 1. + :param increment: the increment value of the sequence. This + value is used when the CREATE SEQUENCE command is emitted to + the database as the value of the "INCREMENT BY" clause. If ``None``, + the clause is omitted, which on most platforms indicates an + increment of 1. + :param schema: Optional schema name for the sequence, if located + in a schema other than the default. + :param optional: boolean value, when ``True``, indicates that this + :class:`.Sequence` object only needs to be explicitly generated + on backends that don't provide another way to generate primary + key identifiers. Currently, it essentially means, "don't create + this sequence on the Postgresql backend, where the SERIAL keyword + creates a sequence for us automatically". + :param quote: boolean value, when ``True`` or ``False``, explicitly + forces quoting of the schema name on or off. When left at its + default of ``None``, normal quoting rules based on casing and reserved + words take place. + :param metadata: optional :class:`.MetaData` object which will be + associated with this :class:`.Sequence`. A :class:`.Sequence` + that is associated with a :class:`.MetaData` gains access to the + ``bind`` of that :class:`.MetaData`, meaning the :meth:`.Sequence.create` + and :meth:`.Sequence.drop` methods will make usage of that engine + automatically. Additionally, the appropriate CREATE SEQUENCE/ + DROP SEQUENCE DDL commands will be emitted corresponding to this + :class:`.Sequence` when :meth:`.MetaData.create_all` and + :meth:`.MetaData.drop_all` are invoked (new in 0.7). + + Note that when a :class:`.Sequence` is applied to a :class:`.Column`, + the :class:`.Sequence` is automatically associated with the + :class:`.MetaData` object of that column's parent :class:`.Table`, + when that association is made. The :class:`.Sequence` will then + be subject to automatic CREATE SEQUENCE/DROP SEQUENCE corresponding + to when the :class:`.Table` object itself is created or dropped, + rather than that of the :class:`.MetaData` object overall. + :param for_update: Indicates this :class:`.Sequence`, when associated + with a :class:`.Column`, should be invoked for UPDATE statements + on that column's table, rather than for INSERT statements, when + no value is otherwise present for that column in the statement. + + """ super(Sequence, self).__init__(for_update=for_update) self.name = name self.start = start @@ -1352,6 +1423,8 @@ class Sequence(DefaultGenerator): self.quote = quote self.schema = schema self.metadata = metadata + if metadata: + self._set_metadata(metadata) @util.memoized_property def is_callable(self): @@ -1372,7 +1445,11 @@ class Sequence(DefaultGenerator): column._on_table_attach(self._set_table) def _set_table(self, column, table): - self.metadata = table.metadata + self._set_metadata(table.metadata) + + def _set_metadata(self, metadata): + self.metadata = metadata + self.metadata._sequences.add(self) @property def bind(self): @@ -1939,6 +2016,7 @@ class MetaData(SchemaItem): """ self.tables = util.immutabledict() self._schemas = set() + self._sequences = set() self.bind = bind self.metadata = self if reflect: @@ -2335,7 +2413,7 @@ class DDLElement(expression.Executable, expression.ClauseElement): self.target = target @expression._generative - def execute_if(self, dialect=None, callable_=None): + def execute_if(self, dialect=None, callable_=None, state=None): """Return a callable that will execute this DDLElement conditionally. @@ -2375,10 +2453,22 @@ class DDLElement(expression.Executable, expression.ClauseElement): Optional keyword argument - a list of Table objects which are to be created/ dropped within a MetaData.create_all() or drop_all() method call. - + + :state: + Optional keyword argument - will be the ``state`` argument + passed to this function. + + :checkfirst: + Keyword argument, will be True if the 'checkfirst' flag was + set during the call to ``create()``, ``create_all()``, + ``drop()``, ``drop_all()``. + If the callable returns a true value, the DDL statement will be executed. + :param state: any value which will be passed to the callable_ + as the ``state`` keyword argument. + See also: :class:`.DDLEvents` @@ -2388,6 +2478,7 @@ class DDLElement(expression.Executable, expression.ClauseElement): """ self.dialect = dialect self.callable_ = callable_ + self.state = state def _should_execute(self, target, bind, **kw): if self.on is not None and \ @@ -2401,7 +2492,7 @@ class DDLElement(expression.Executable, expression.ClauseElement): if bind.engine.name not in self.dialect: return False if self.callable_ is not None and \ - not self.callable_(self, target, bind, **kw): + not self.callable_(self, target, bind, state=self.state, **kw): return False return True |