From: Mike Bayer Date: Tue, 23 Dec 2008 04:47:52 +0000 (+0000) Subject: - Added Index reflection support to Postgres, using a X-Git-Tag: rel_0_5_0~74 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=864644bee4e3acc5c63eac6e639ae39d1c3c8393;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - Added Index reflection support to Postgres, using a great patch we long neglected, submitted by Ken Kuhlman. [ticket:714] --- diff --git a/CHANGES b/CHANGES index 955301f2dc..020459071d 100644 --- a/CHANGES +++ b/CHANGES @@ -227,6 +227,10 @@ CHANGES - Calling alias.execute() in conjunction with server_side_cursors won't raise AttributeError. + - Added Index reflection support to Postgres, using a + great patch we long neglected, submitted by + Ken Kuhlman. [ticket:714] + - oracle - Adjusted the format of create_xid() to repair two-phase commit. We now have field reports diff --git a/lib/sqlalchemy/databases/postgres.py b/lib/sqlalchemy/databases/postgres.py index dd9fa52930..8ac9bfabf3 100644 --- a/lib/sqlalchemy/databases/postgres.py +++ b/lib/sqlalchemy/databases/postgres.py @@ -619,6 +619,44 @@ class PGDialect(default.DefaultDialect): table.append_constraint(schema.ForeignKeyConstraint(constrained_columns, refspec, conname)) + # Indexes + IDX_SQL = """ + SELECT c.relname, i.indisunique, i.indexprs, i.indpred, + a.attname + FROM pg_index i, pg_class c, pg_attribute a + WHERE i.indrelid = :table AND i.indexrelid = c.oid + AND a.attrelid = i.indexrelid AND i.indisprimary = 'f' + ORDER BY c.relname, a.attnum + """ + t = sql.text(IDX_SQL, typemap={'attname':sqltypes.Unicode}) + c = connection.execute(t, table=table_oid) + indexes = {} + sv_idx_name = None + for row in c.fetchall(): + idx_name, unique, expr, prd, col = row + + if expr and not idx_name == sv_idx_name: + util.warn( + "Skipped unsupported reflection of expression-based index %s" + % idx_name) + sv_idx_name = idx_name + continue + if prd and not idx_name == sv_idx_name: + util.warn( + "Predicate of partial index %s ignored during reflection" + % idx_name) + sv_idx_name = idx_name + + if not indexes.has_key(idx_name): + indexes[idx_name] = [unique, []] + indexes[idx_name][1].append(col) + + for name, (unique, columns) in indexes.items(): + schema.Index(name, *[table.columns[c] for c in columns], + **dict(unique=unique)) + + + def _load_domains(self, connection): ## Load data types for domains: SQL_DOMAINS = """ diff --git a/test/dialect/postgres.py b/test/dialect/postgres.py index 8298b81f63..75c0918b81 100644 --- a/test/dialect/postgres.py +++ b/test/dialect/postgres.py @@ -618,6 +618,54 @@ class MiscTest(TestBase, AssertsExecutionResults): finally: testing.db.execute("drop table speedy_users", None) + @testing.emits_warning() + def test_index_reflection(self): + """ Reflecting partial & expression-based indexes should warn """ + import warnings + def capture_warnings(*args, **kw): + capture_warnings._orig_showwarning(*args, **kw) + capture_warnings.warnings.append(args) + capture_warnings._orig_showwarning = warnings.warn + capture_warnings.warnings = [] + + m1 = MetaData(testing.db) + t1 = Table('party', m1, + Column('id', String(10), nullable=False), + Column('name', String(20), index=True) + ) + m1.create_all() + testing.db.execute(""" + create index idx1 on party ((id || name)) + """, None) + testing.db.execute(""" + create unique index idx2 on party (id) where name = 'test' + """, None) + try: + m2 = MetaData(testing.db) + + warnings.warn = capture_warnings + t2 = Table('party', m2, autoload=True) + + wrn = capture_warnings.warnings + assert str(wrn[0][0]) == ( + "Skipped unsupported reflection of expression-based index idx1") + assert str(wrn[1][0]) == ( + "Predicate of partial index idx2 ignored during reflection") + assert len(t2.indexes) == 2 + # Make sure indexes are in the order we expect them in + tmp = [(idx.name, idx) for idx in t2.indexes] + tmp.sort() + r1, r2 = [idx[1] for idx in tmp] + + assert r1.name == 'idx2' + assert r1.unique == True + assert r2.unique == False + assert [t2.c.id] == r1.columns + assert [t2.c.name] == r2.columns + finally: + warnings.warn = capture_warnings._orig_showwarning + m1.drop_all() + def test_create_partial_index(self): tbl = Table('testtbl', MetaData(), Column('data',Integer)) idx = Index('test_idx1', tbl.c.data, postgres_where=and_(tbl.c.data > 5, tbl.c.data < 10)) diff --git a/test/engine/reflection.py b/test/engine/reflection.py index a04e218541..6445e55d5d 100644 --- a/test/engine/reflection.py +++ b/test/engine/reflection.py @@ -418,7 +418,7 @@ class ReflectionTest(TestBase, ComparesTables): @testing.crashes('oracle', 'FIXME: unknown, confirm not fails_on') - def testreserved(self): + def test_reserved(self): # check a table that uses an SQL reserved name doesn't cause an error meta = MetaData(testing.db) table_a = Table('select', meta, @@ -525,6 +525,37 @@ class ReflectionTest(TestBase, ComparesTables): m9.reflect() self.assert_(not m9.tables) + @testing.fails_on_everything_except('postgres', 'mysql') + def test_index_reflection(self): + m1 = MetaData(testing.db) + t1 = Table('party', m1, + Column('id', Integer, nullable=False), + Column('name', String(20), index=True) + ) + i1 = Index('idx1', t1.c.id, unique=True) + i2 = Index('idx2', t1.c.name, t1.c.id, unique=False) + m1.create_all() + try: + m2 = MetaData(testing.db) + t2 = Table('party', m2, autoload=True) + + print len(t2.indexes), t2.indexes + assert len(t2.indexes) == 3 + # Make sure indexes are in the order we expect them in + tmp = [(idx.name, idx) for idx in t2.indexes] + tmp.sort() + r1, r2, r3 = [idx[1] for idx in tmp] + + assert r1.name == 'idx1' + assert r2.name == 'idx2' + assert r1.unique == True + assert r2.unique == False + assert r3.unique == False + assert [t2.c.id] == r1.columns + assert [t2.c.name, t2.c.id] == r2.columns + assert [t2.c.name] == r3.columns + finally: + m1.drop_all() class CreateDropTest(TestBase): def setUpAll(self):