From: Mike Bayer Date: Wed, 19 Jul 2006 22:13:29 +0000 (+0000) Subject: added 'checkfirst' argument to table.create()/table.drop() X-Git-Tag: rel_0_2_6~5 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=8b50115acc7d249129aa8046839eefa2c611c453;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git added 'checkfirst' argument to table.create()/table.drop() some 0.2.6 prep --- diff --git a/CHANGES b/CHANGES index 86a65d7284..564eef21fd 100644 --- a/CHANGES +++ b/CHANGES @@ -44,6 +44,7 @@ aware of their "inherited" status if so. relationships to an inheriting mapper (which is also self-referential) - reduced bind param size in query._get to appease the picky oracle [ticket:244] +- added 'checkfirst' argument to table.create()/table.drop() 0.2.5 - fixed endless loop bug in select_by(), if the traversal hit diff --git a/doc/build/content/document_base.myt b/doc/build/content/document_base.myt index ceb596b1cf..f1cb3241f7 100644 --- a/doc/build/content/document_base.myt +++ b/doc/build/content/document_base.myt @@ -24,7 +24,7 @@ onepage='documentation' index='index' title='SQLAlchemy 0.2 Documentation' - version = '0.2.5' + version = '0.2.6' <%method title> diff --git a/doc/build/content/metadata.txt b/doc/build/content/metadata.txt index 48941347a9..0bce1f5e81 100644 --- a/doc/build/content/metadata.txt +++ b/doc/build/content/metadata.txt @@ -31,6 +31,27 @@ Then to construct a Table, use the `Table` class: The specific datatypes for each Column, such as Integer, String, etc. are described in [types](rel:types), and exist within the module `sqlalchemy.types` as well as the global `sqlalchemy` namespace. +Foreign keys are most easily specified by the `ForeignKey` object within a `Column` object. For a composite foreign key, i.e. a foreign key that contains multiple columns referencing multiple columns to a composite primary key, an explicit syntax is provided which allows the correct table CREATE statements to be generated: + + {python} + # a table with a composite primary key + invoices = Table('invoices', metadata, + Column('invoice_id', Integer, primary_key=True), + Column('ref_num', Integer, primary_key=True), + Column('description', String(60), nullable=False) + ) + + # a table with a composite foreign key referencing the parent table + invoice_items = Table('invoice_items', metadata, + Column('item_id', Integer, primary_key=True), + Column('item_name', String(60), nullable=False), + Column('invoice_id', Integer, nullable=False), + Column('ref_num', Integer, nullable=False), + ForeignKeyConstraint(['invoice_id', 'ref_num'], ['invoices.invoice_id', 'invoices.ref_num']) + ) + +Above, the `invoice_items` table will have `ForeignKey` objects automatically added to the `invoice_id` and `ref_num` `Column` objects as a result of the additional `ForeignKeyConstraint` object. + The `MetaData` object supports some handy methods, such as getting a list of Tables in the order (or reverse) of their dependency: {python} @@ -238,7 +259,13 @@ Creating and dropping individual tables can be done via the `create()` and `drop DROP TABLE employees {} -Entire groups of Tables can be created and dropped directly from the `MetaData` object with `create_all()` and `drop_all()`, each of which take an optional `engine` keyword argument which can reference an `Engine` or a `Connection`, else the underlying bound `Engine` is used: +The `create()` and `drop()` methods also support an optional keyword argument `checkfirst` which will issue the database's appropriate pragma statements to check if the table exists before creating or dropping: + + {python} + employees.create(engine=e, checkfirst=True) + employees.drop(checkfirst=False) + +Entire groups of Tables can be created and dropped directly from the `MetaData` object with `create_all()` and `drop_all()`. These methods always check for the existence of each table before creating or dropping. Each method takes an optional `engine` keyword argument which can reference an `Engine` or a `Connection`. If no engine is specified, the underlying bound `Engine`, if any, is used: {python} engine = create_engine('sqlite:///:memory:') diff --git a/lib/sqlalchemy/ansisql.py b/lib/sqlalchemy/ansisql.py index 02423560ba..3ba2965769 100644 --- a/lib/sqlalchemy/ansisql.py +++ b/lib/sqlalchemy/ansisql.py @@ -602,6 +602,10 @@ class ANSICompiler(sql.Compiled): class ANSISchemaGenerator(engine.SchemaIterator): + def __init__(self, engine, proxy, connection=None, checkfirst=False, **params): + super(ANSISchemaGenerator, self).__init__(engine, proxy, **params) + self.checkfirst = checkfirst + self.connection = connection def get_column_specification(self, column, first_pk=False): raise NotImplementedError() @@ -609,6 +613,9 @@ class ANSISchemaGenerator(engine.SchemaIterator): # the single whitespace before the "(" is significant # as its MySQL's method of indicating a table name and not a reserved word. # feel free to localize this logic to the mysql module + if self.checkfirst and self.engine.dialect.has_table(self.connection, table.name): + return + self.append("\nCREATE TABLE " + table.fullname + " (") separator = "\n" @@ -682,6 +689,11 @@ class ANSISchemaGenerator(engine.SchemaIterator): class ANSISchemaDropper(engine.SchemaIterator): + def __init__(self, engine, proxy, connection=None, checkfirst=False, **params): + super(ANSISchemaDropper, self).__init__(engine, proxy, **params) + self.checkfirst = checkfirst + self.connection = connection + def visit_index(self, index): self.append("\nDROP INDEX " + index.name) self.execute() @@ -689,6 +701,8 @@ class ANSISchemaDropper(engine.SchemaIterator): def visit_table(self, table): # NOTE: indexes on the table will be automatically dropped, so # no need to drop them individually + if self.checkfirst and not self.engine.dialect.has_table(self.connection, table.name): + return self.append("\nDROP TABLE " + table.fullname) self.execute() diff --git a/lib/sqlalchemy/engine/base.py b/lib/sqlalchemy/engine/base.py index 048c240098..ea678152e3 100644 --- a/lib/sqlalchemy/engine/base.py +++ b/lib/sqlalchemy/engine/base.py @@ -420,7 +420,7 @@ class ComposedSQLEngine(sql.Engine, Connectable): else: conn = connection try: - element.accept_schema_visitor(visitorcallable(self, conn.proxy, **kwargs)) + element.accept_schema_visitor(visitorcallable(self, conn.proxy, connection=conn, **kwargs)) finally: if connection is None: conn.close() diff --git a/lib/sqlalchemy/schema.py b/lib/sqlalchemy/schema.py index 78dbe38b37..8fce1665d8 100644 --- a/lib/sqlalchemy/schema.py +++ b/lib/sqlalchemy/schema.py @@ -236,17 +236,17 @@ class Table(SchemaItem, sql.TableClause): this does not issue a SQL DROP statement.""" key = _get_table_key(self.name, self.schema) del self.metadata.tables[key] - def create(self, connectable=None): + def create(self, connectable=None, checkfirst=False): if connectable is not None: - connectable.create(self) + connectable.create(self, checkfirst=checkfirst) else: - self.engine.create(self) + self.engine.create(self, checkfirst=checkfirst) return self - def drop(self, connectable=None): + def drop(self, connectable=None, checkfirst=False): if connectable is not None: - connectable.drop(self) + connectable.drop(self, checkfirst=checkfirst) else: - self.engine.drop(self) + self.engine.drop(self, checkfirst=checkfirst) def tometadata(self, metadata, schema=None): """return a copy of this Table associated with a different MetaData.""" try: diff --git a/setup.py b/setup.py index b6f78f08f2..863bf1e33d 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ use_setuptools() from setuptools import setup, find_packages setup(name = "SQLAlchemy", - version = "0.2.5", + version = "0.2.6", description = "Database Abstraction Library", author = "Mike Bayer", author_email = "mike_mp@zzzcomputing.com", diff --git a/test/engine/reflection.py b/test/engine/reflection.py index ec59d652cd..64a645d0f9 100644 --- a/test/engine/reflection.py +++ b/test/engine/reflection.py @@ -155,6 +155,22 @@ class ReflectionTest(PersistTest): finally: meta.drop_all() + def testcheckfirst(self): + meta = BoundMetaData(testbase.db) + + table = Table('checkfirst', meta, + Column('col1', Integer, primary_key=True), + Column('col2', String(40))) + try: + table.create() + table.create(checkfirst=True) + table.drop() + table.drop(checkfirst=True) + table.create(checkfirst=True) + table.drop() + finally: + meta.drop_all() + def testtoengine(self): meta = MetaData('md1') meta2 = MetaData('md2')