-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Michael Bayer ha scritto: > On Feb 28, 2010, at 8:12 PM, Manlio Perillo wrote: > > Michael Bayer ha scritto: >>>> [...] >>>> * I have noted that some database objects not directly attached to a >>>> metadata, like a Sequence, can leave the database in an inconsistent >>>> state if the associated test suite fails. >>>> >>>> I have the same problem with the Schema object. >>>> If, for some reason, in the test suite there is an error before the >>>> schema is dropped, the successive run of the test suite will fail, >>>> since the test will try to create an already existing schema. >>>> >>>> Is there a good solution? >>>> >>>>> the tests support a --dropfirst option that will remove any errant Table >>>>> objects. I haven't observed errant sequences to be an issue except >>>>> perhaps on Oracle, but this system could be enhanced to drop all the >>>>> sequences too. >>>>> for testing a CREATE SCHEMA construct I would just check the SQL string >>>>> and not actually execute anything. unit tests should not create any >>>>> schemas. see the existing schema-listing tests that just use the three >>>>> which are part of the documented testing environment. > I can't, since I have also to check the `has_schema` method in the Dialect. > >> call has_schema() on "test_schema", which will exist in a full test >> environment. then call it on something like "sa_fake_schema_123" to test >> the "not found" condition. >
Ok, done. I'm not sure, however, if `test_schema` method test should go in `test/engine/test_reflection.py`, since it is not directly related to reflection. With the old patch (http://www.sqlalchemy.org/trac/attachment/ticket/1679/schema.patch) the code was in the HasSchemaTest class, so it was ok (the same is done for HasSequenceTest). P.S.: I have removed the supports_schema flag. Thanks Manlio -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.10 (GNU/Linux) Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org iEYEARECAAYFAkuLuxsACgkQscQJ24LbaURcuQCfRwMZWyW+zPEacm9g/BVURSqq R50AnRY7AJx/OjQhhz/xUFZaEIKLgNe2 =/PF3 -----END PGP SIGNATURE-----
-- You received this message because you are subscribed to the Google Groups "sqlalchemy" group. To post to this group, send email to sqlalch...@googlegroups.com. To unsubscribe from this group, send email to sqlalchemy+unsubscr...@googlegroups.com. For more options, visit this group at http://groups.google.com/group/sqlalchemy?hl=en.
diff --git a/lib/sqlalchemy/__init__.py b/lib/sqlalchemy/__init__.py --- a/lib/sqlalchemy/__init__.py +++ b/lib/sqlalchemy/__init__.py @@ -104,6 +104,7 @@ PrimaryKeyConstraint, Sequence, Table, + Schema, ThreadLocalMetaData, UniqueConstraint, ) diff --git a/lib/sqlalchemy/dialects/postgresql/base.py b/lib/sqlalchemy/dialects/postgresql/base.py --- a/lib/sqlalchemy/dialects/postgresql/base.py +++ b/lib/sqlalchemy/dialects/postgresql/base.py @@ -650,6 +650,19 @@ def _get_default_schema_name(self, connection): return connection.scalar("select current_schema()") + def has_schema(self, connection, schema): + cursor = connection.execute( + sql.text( + "select nspname from pg_namespace where lower(nspname)=:schema", + bindparams=[ + sql.bindparam( + 'schema', unicode(schema.lower()), + type_=sqltypes.Unicode)] + ) + ) + + return bool(cursor.first()) + def has_table(self, connection, table_name, schema=None): # seems like case gets folded in pg_class... if schema is None: diff --git a/lib/sqlalchemy/schema.py b/lib/sqlalchemy/schema.py --- a/lib/sqlalchemy/schema.py +++ b/lib/sqlalchemy/schema.py @@ -80,6 +80,45 @@ else: return schema + "." + name +class Schema(SchemaItem): + """Represent a schema in a database. + + e.g.:: + + myschema = Schema("myschema") + + Constructor arguments are as follows: + + :param name: The name of this schema as represented in the database. + + Names which contain no upper case characters + will be treated as case insensitive names, and will not be quoted + unless they are a reserved word. Names with any number of upper + case characters will be quoted and sent exactly. Note that this + behavior applies even for databases which standardize upper + case names as case insensitive such as Oracle. + + :param info: A dictionary which defaults to ``{}``. A space to store application + specific data. This must be a dictionary. + + :param quote: Force quoting of this schema's name on or off, corresponding + to ``True`` or ``False``. When left at its default of ``None``, + the schema identifier will be quoted according to whether the name is + case sensitive (identifiers with at least one upper case character are + treated as case sensitive), or if it's a reserved word. This flag + is only needed to force quoting of a reserved word which is not known + by the SQLAlchemy dialect. + + """ + + __visit_name__ = 'schema' + + def __init__(self, name, **kwargs): + self.name = name + self.quote = kwargs.pop('quote', None) + self.info = kwargs.pop('info', {}) + + class Table(SchemaItem, expression.TableClause): """Represent a table in a database. @@ -2309,6 +2348,20 @@ """ return False +class CreateSchema(_CreateDropBase): + """Represent a CREATE SCHEMA statement.""" + + __visit_name__ = "create_schema" + +class DropSchema(_CreateDropBase): + """Represent a DROP SCHEMA statement.""" + + __visit_name__ = "drop_schema" + + def __init__(self, element, cascade=False, **kw): + self.cascade = cascade + super(DropSchema, self).__init__(element, **kw) + class CreateTable(_CreateDropBase): """Represent a CREATE TABLE statement.""" diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py --- a/lib/sqlalchemy/sql/compiler.py +++ b/lib/sqlalchemy/sql/compiler.py @@ -680,6 +680,9 @@ text += " OFFSET " + str(select._offset) return text + def visit_schema(self, schema): + return None + def visit_table(self, table, asfrom=False, **kwargs): if asfrom: if getattr(table, "schema", None): @@ -978,6 +981,15 @@ return ddl.statement % context + def visit_create_schema(self, create): + return "CREATE SCHEMA " + self.preparer.format_schema(create.element) + + def visit_drop_schema(self, drop): + text = "DROP SCHEMA " + self.preparer.format_schema(drop.element) + if drop.cascade: + text += " CASCADE" + return text + def visit_create_table(self, create): table = create.element preparer = self.dialect.identifier_preparer @@ -1421,6 +1433,13 @@ def format_constraint(self, constraint): return self.quote(constraint.name, constraint.quote) + def format_schema(self, schema, name=None): + """Prepare a quoted schema name.""" + + if name is None: + name = schema.name + return self.quote(name, schema.quote) + def format_table(self, table, use_schema=True, name=None): """Prepare a quoted table and schema name.""" diff --git a/test/engine/test_reflection.py b/test/engine/test_reflection.py --- a/test/engine/test_reflection.py +++ b/test/engine/test_reflection.py @@ -833,6 +833,16 @@ class SchemaTest(TestBase): + def test_schema(self): + s = schema.Schema('sa_schema') + t1 = str(schema.CreateSchema(s).compile(bind=testing.db)) + t2 = str(schema.DropSchema(s).compile(bind=testing.db)) + t3 = str(schema.DropSchema(s, cascade=True).compile(bind=testing.db)) + + assert t1 == "CREATE SCHEMA sa_schema" + assert t2 == "DROP SCHEMA sa_schema" + assert t3 == "DROP SCHEMA sa_schema CASCADE" + def test_iteration(self): metadata = MetaData() table1 = Table('table1', metadata, @@ -926,7 +936,15 @@ testing.db.execute(schema.DropSequence(s2)) eq_(testing.db.dialect.has_sequence(testing.db, 'user_id_seq', schema=test_schema), False) eq_(testing.db.dialect.has_sequence(testing.db, 'user_id_seq'), False) - + + +class HasSchemaTest(TestBase): + + @testing.requires.schemas + def test_has_schema(self): + eq_(testing.db.dialect.has_schema(testing.db, 'test_schema'), True) + eq_(testing.db.dialect.has_sequence(testing.db, 'sa_fake_schema_123'), False) + # Tests related to engine.reflection