]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Better reflection of Firebird data types.
authorLele Gaifax <lele@metapensiero.it>
Sun, 9 Dec 2007 23:08:59 +0000 (23:08 +0000)
committerLele Gaifax <lele@metapensiero.it>
Sun, 9 Dec 2007 23:08:59 +0000 (23:08 +0000)
Instead of relying on internal numeric code, lookup the associated real
name. This has the extra benefit of properly handling of DOMAINs.

CHANGES
lib/sqlalchemy/databases/firebird.py
test/dialect/firebird.py

diff --git a/CHANGES b/CHANGES
index ae94094983a6e012c0582fb1c9fa6224f10b3185..aa0c33c4b0704a1f301cf429e363e230ecb87924 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -149,6 +149,8 @@ CHANGES
 
    - MSSQL/PyODBC no longer has a global "set nocount on".
 
+   - Firebird backend does properly reflect domains (partially fixing [ticket:410]).
+
 0.4.1
 -----
 
index 3a13c4b6984746f1e96235c685f7317bd1a8d1c6..1da5e47389351b61b1360f5091aed72a09298e69 100644 (file)
@@ -123,6 +123,23 @@ colspecs = {
 }
 
 
+ischema_names = {
+      'SHORT': lambda r: FBSmallInteger(),
+       'LONG': lambda r: FBInteger(),
+       'QUAD': lambda r: FBFloat(),
+      'FLOAT': lambda r: FBFloat(),
+       'DATE': lambda r: FBDate(),
+       'TIME': lambda r: FBTime(),
+       'TEXT': lambda r: FBString(r['FLEN']),
+      'INT64': lambda r: FBNumeric(precision=r['FPREC'], length=r['FSCALE'] * -1), # This generically handles NUMERIC()
+     'DOUBLE': lambda r: FBFloat(),
+  'TIMESTAMP': lambda r: FBDateTime(),
+    'VARYING': lambda r: FBString(r['FLEN']),
+    'CSTRING': lambda r: FBChar(r['FLEN']),
+       'BLOB': lambda r: r['STYPE']==1 and FBText() or FBBinary
+      }
+
+
 def descriptor():
     return {'name':'firebird',
     'description':'Firebird',
@@ -220,33 +237,20 @@ class FBDialect(default.DefaultDialect):
             return False
 
     def reflecttable(self, connection, table, include_columns):
-        #TODO: map these better
-        column_func = {
-            14 : lambda r: sqltypes.String(r['FLEN']), # TEXT
-            7  : lambda r: sqltypes.Integer(), # SHORT
-            8  : lambda r: r['FPREC']==0 and sqltypes.Integer() or sqltypes.Numeric(precision=r['FPREC'], length=r['FSCALE'] * -1),  #INT or NUMERIC
-            9  : lambda r: sqltypes.Float(), # QUAD
-            10 : lambda r: sqltypes.Float(), # FLOAT
-            27 : lambda r: sqltypes.Float(), # DOUBLE
-            35 : lambda r: sqltypes.DateTime(), # TIMESTAMP
-            37 : lambda r: sqltypes.String(r['FLEN']), # VARYING
-            261: lambda r: sqltypes.TEXT(), # BLOB
-            40 : lambda r: sqltypes.Char(r['FLEN']), # CSTRING
-            12 : lambda r: sqltypes.Date(), # DATE
-            13 : lambda r: sqltypes.Time(), # TIME
-            16 : lambda r: sqltypes.Numeric(precision=r['FPREC'], length=r['FSCALE'] * -1)  #INT64
-            }
         tblqry = """
         SELECT DISTINCT R.RDB$FIELD_NAME AS FNAME,
                   R.RDB$NULL_FLAG AS NULL_FLAG,
                   R.RDB$FIELD_POSITION,
-                  F.RDB$FIELD_TYPE AS FTYPE,
+                  T.RDB$TYPE_NAME AS FTYPE,
                   F.RDB$FIELD_SUB_TYPE AS STYPE,
                   F.RDB$FIELD_LENGTH AS FLEN,
                   F.RDB$FIELD_PRECISION AS FPREC,
                   F.RDB$FIELD_SCALE AS FSCALE
         FROM RDB$RELATION_FIELDS R
-             JOIN RDB$FIELDS F ON R.RDB$FIELD_SOURCE=F.RDB$FIELD_NAME
+             JOIN RDB$FIELDS F
+               ON R.RDB$FIELD_SOURCE=F.RDB$FIELD_NAME
+             JOIN RDB$TYPES T
+               ON T.RDB$TYPE=F.RDB$FIELD_TYPE AND T.RDB$FIELD_NAME='RDB$FIELD_TYPE'
         WHERE F.RDB$SYSTEM_FLAG=0 and R.RDB$RELATION_NAME=?
         ORDER BY R.RDB$FIELD_POSITION"""
         keyqry = """
@@ -293,7 +297,7 @@ class FBDialect(default.DefaultDialect):
 
             kw = {}
             # get the data types and lengths
-            coltype = column_func.get(row['FTYPE'], None)
+            coltype = ischema_names.get(row['FTYPE'].rstrip())
             if coltype is None:
                 warnings.warn(RuntimeWarning("Did not recognize type '%s' of column '%s'" % (str(row['FTYPE']), name)))
                 coltype = sqltypes.NULLTYPE
index 636ab2d058b33307a77bd02d43bb2c7624af33c4..f3cd57aa7334d9a543f9eed1e5d29c8f4c5af01d 100644 (file)
@@ -1,6 +1,7 @@
 import testbase
 from sqlalchemy import *
 from sqlalchemy.databases import firebird
+from sqlalchemy.exceptions import ProgrammingError
 from sqlalchemy.sql import table, column
 from testlib import *
 
@@ -11,6 +12,53 @@ class BasicTest(AssertMixin):
         return True
 
 
+class DomainReflectionTest(AssertMixin):
+    "Test Firebird domains"
+
+    @testing.supported('firebird')
+    def setUpAll(self):
+        con = testbase.db.connect()
+        try:
+            con.execute('CREATE DOMAIN int_domain AS INTEGER DEFAULT 42 NOT NULL')
+            con.execute('CREATE DOMAIN str_domain AS VARCHAR(255)')
+            con.execute('CREATE DOMAIN rem_domain AS BLOB SUB_TYPE TEXT')
+            con.execute('CREATE DOMAIN img_domain AS BLOB SUB_TYPE BINARY')
+        except ProgrammingError, e:
+            if not "attempt to store duplicate value" in str(e):
+                raise e
+        con.execute('''CREATE TABLE testtable (question int_domain,
+                                               answer str_domain,
+                                               remark rem_domain,
+                                               photo img_domain,
+                                               d date,
+                                               t time,
+                                               dt timestamp)''')
+
+    @testing.supported('firebird')
+    def tearDownAll(self):
+        con = testbase.db.connect()
+        con.execute('DROP TABLE testtable')
+        con.execute('DROP DOMAIN int_domain')
+        con.execute('DROP DOMAIN str_domain')
+        con.execute('DROP DOMAIN rem_domain')
+        con.execute('DROP DOMAIN img_domain')
+
+    @testing.supported('firebird')
+    def test_table_is_reflected(self):
+        metadata = MetaData(testbase.db)
+        table = Table('testtable', metadata, autoload=True)
+        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.type.__class__, firebird.FBInteger)
+        self.assertEquals(table.c.answer.type.__class__, firebird.FBString)
+        self.assertEquals(table.c.remark.type.__class__, firebird.FBText)
+        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)
+        self.assertEquals(table.c.t.type.__class__, firebird.FBTime)
+        self.assertEquals(table.c.dt.type.__class__, firebird.FBDateTime)
+
 
 class CompileTest(SQLCompileTest):
     __dialect__ = firebird.FBDialect()