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
onepage='documentation'
index='index'
title='SQLAlchemy 0.2 Documentation'
- version = '0.2.5'
+ version = '0.2.6'
</%attr>
<%method title>
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}
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:')
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()
# 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"
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()
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()
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()
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:
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",
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')