summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES3
-rw-r--r--lib/sqlalchemy/databases/firebird.py33
-rw-r--r--test/dialect/firebird.py15
3 files changed, 47 insertions, 4 deletions
diff --git a/CHANGES b/CHANGES
index ab87481c5..01fa8d271 100644
--- a/CHANGES
+++ b/CHANGES
@@ -20,6 +20,9 @@ CHANGES
- dialects
- finally added PGMacAddr type to postgres [ticket:580]
+ - reflect the sequence associated to a PK field (tipically
+ with a BEFORE INSERT trigger) under Firebird
+
0.4.2p3
------
- general
diff --git a/lib/sqlalchemy/databases/firebird.py b/lib/sqlalchemy/databases/firebird.py
index c83a44e01..17232f5d4 100644
--- a/lib/sqlalchemy/databases/firebird.py
+++ b/lib/sqlalchemy/databases/firebird.py
@@ -262,7 +262,7 @@ class FBExecutionContext(default.DefaultExecutionContext):
class FBDialect(default.DefaultDialect):
"""Firebird dialect"""
- supports_sane_rowcount = False
+ supports_sane_rowcount = True
supports_sane_multi_rowcount = False
max_identifier_length = 31
preexecute_pk_sequences = True
@@ -434,6 +434,22 @@ class FBDialect(default.DefaultDialect):
WHERE rc.rdb$constraint_type=? AND rc.rdb$relation_name=?
ORDER BY se.rdb$index_name, se.rdb$field_position
"""
+ # Heuristic-query to determine the generator associated to a PK field
+ genqry = """
+ SELECT trigdep.rdb$depended_on_name AS fgenerator
+ FROM rdb$dependencies tabdep
+ JOIN rdb$dependencies trigdep ON (tabdep.rdb$dependent_name=trigdep.rdb$dependent_name
+ AND trigdep.rdb$depended_on_type=14
+ AND trigdep.rdb$dependent_type=2)
+ JOIN rdb$triggers trig ON (trig.rdb$trigger_name=tabdep.rdb$dependent_name)
+ WHERE tabdep.rdb$depended_on_name=?
+ AND tabdep.rdb$depended_on_type=0
+ AND trig.rdb$trigger_type=1
+ AND tabdep.rdb$field_name=?
+ AND (SELECT count(*)
+ FROM rdb$dependencies trigdep2
+ WHERE trigdep2.rdb$dependent_name = trigdep.rdb$dependent_name) = 2
+ """
tablename = self._denormalize_name(table.name)
@@ -457,7 +473,7 @@ class FBDialect(default.DefaultDialect):
args = [name]
kw = {}
- # get the data types and lengths
+ # get the data type
coltype = ischema_names.get(row['ftype'].rstrip())
if coltype is None:
util.warn("Did not recognize type '%s' of column '%s'" %
@@ -476,10 +492,21 @@ class FBDialect(default.DefaultDialect):
# does it have a default value?
if row['fdefault'] is not None:
# the value comes down as "DEFAULT 'value'"
+ assert row['fdefault'].startswith('DEFAULT ')
defvalue = row['fdefault'][8:]
args.append(schema.PassiveDefault(sql.text(defvalue)))
- table.append_column(schema.Column(*args, **kw))
+ col = schema.Column(*args, **kw)
+ if kw['primary_key']:
+ # if the PK is a single field, try to see if its linked to
+ # a sequence thru a trigger
+ if len(pkfields)==1:
+ genc = connection.execute(genqry, [tablename, row['fname']])
+ genr = genc.fetchone()
+ if genr is not None:
+ col.sequence = schema.Sequence(self._normalize_name(genr['fgenerator']))
+
+ table.append_column(col)
if not found_table:
raise exceptions.NoSuchTableError(table.name)
diff --git a/test/dialect/firebird.py b/test/dialect/firebird.py
index a377a1caf..6b2a35c87 100644
--- a/test/dialect/firebird.py
+++ b/test/dialect/firebird.py
@@ -21,13 +21,22 @@ class DomainReflectionTest(AssertMixin):
except ProgrammingError, e:
if not "attempt to store duplicate value" in str(e):
raise e
+ con.execute('''CREATE GENERATOR gen_testtable_id''')
con.execute('''CREATE TABLE testtable (question int_domain,
answer str_domain DEFAULT 'no answer',
- remark rem_domain,
+ remark rem_domain DEFAULT '',
photo img_domain,
d date,
t time,
dt timestamp)''')
+ con.execute('''ALTER TABLE testtable
+ ADD CONSTRAINT testtable_pk PRIMARY KEY (question)''')
+ con.execute('''CREATE TRIGGER testtable_autoid FOR testtable
+ ACTIVE BEFORE INSERT AS
+ BEGIN
+ IF (NEW.question IS NULL) THEN
+ NEW.question = gen_id(gen_testtable_id, 1);
+ END''')
def tearDownAll(self):
con = testbase.db.connect()
@@ -36,6 +45,7 @@ class DomainReflectionTest(AssertMixin):
con.execute('DROP DOMAIN str_domain')
con.execute('DROP DOMAIN rem_domain')
con.execute('DROP DOMAIN img_domain')
+ con.execute('DROP GENERATOR gen_testtable_id')
def test_table_is_reflected(self):
metadata = MetaData(testbase.db)
@@ -43,11 +53,14 @@ class DomainReflectionTest(AssertMixin):
self.assertEquals(set(table.columns.keys()),
set(['question', 'answer', 'remark', 'photo', 'd', 't', 'dt']),
"Columns of reflected table didn't equal expected columns")
+ self.assertEquals(table.c.question.primary_key, True)
+ self.assertEquals(table.c.question.sequence.name, 'gen_testtable_id')
self.assertEquals(table.c.question.type.__class__, firebird.FBInteger)
self.assertEquals(table.c.question.default.arg.text, "42")
self.assertEquals(table.c.answer.type.__class__, firebird.FBString)
self.assertEquals(table.c.answer.default.arg.text, "'no answer'")
self.assertEquals(table.c.remark.type.__class__, firebird.FBText)
+ self.assertEquals(table.c.remark.default.arg.text, "''")
self.assertEquals(table.c.photo.type.__class__, firebird.FBBinary)
# The following assume a Dialect 3 database
self.assertEquals(table.c.d.type.__class__, firebird.FBDate)