]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Added in a new MSGenericBinary type.
authorMichael Trier <mtrier@gmail.com>
Sun, 28 Dec 2008 21:07:57 +0000 (21:07 +0000)
committerMichael Trier <mtrier@gmail.com>
Sun, 28 Dec 2008 21:07:57 +0000 (21:07 +0000)
This maps to the Binary type so it can implement the specialized behavior of
treating length specified types as fixed-width Binary types and non-length
types as an unbound variable length Binary type.

CHANGES
lib/sqlalchemy/databases/mssql.py
test/dialect/mssql.py
test/sql/testtypes.py

diff --git a/CHANGES b/CHANGES
index 4f2f07f679409dddb3ad0e90021447af33978d4a..791833d6fa52dc349c39ac92a7ca3397a2756d25 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -43,7 +43,13 @@ CHANGES
       sent to connection.execute() and friends.  [ticket:935]
       
 - mssql
+    - Added in a new MSGenericBinary type. This maps to the Binary
+      type so it can implement the specialized behavior of treating
+      length specified types as fixed-width Binary types and
+      non-length types as an unbound variable length Binary type.
+
     - Added in new types: MSVarBinary and MSImage. [ticket:1249]
+
     - Added in the MSReal and MSNText types.
 
 - bugfixes, behavioral changes
index 0844745de06bcbf8f882ceeccb08cc806293750e..4d2176f854fed120f1d865f798a230b4826a2968 100644 (file)
@@ -676,7 +676,23 @@ class MSNChar(_StringType, sqltypes.NCHAR):
             return self._extend("NCHAR")
 
 
-class MSBinary(sqltypes.Binary):
+class MSGenericBinary(sqltypes.Binary):
+    """The Binary type assumes that a Binary specification without a length
+    is an unbound Binary type whereas one with a length specification results
+    in a fixed length Binary type.
+
+    If you want standard MSSQL ``BINARY`` behavior use the ``MSBinary`` type.
+
+    """
+
+    def get_col_spec(self):
+        if self.length:
+            return "BINARY(%s)" % self.length
+        else:
+            return "IMAGE"
+
+
+class MSBinary(MSGenericBinary):
     def get_col_spec(self):
         if self.length:
             return "BINARY(%s)" % self.length
@@ -684,7 +700,7 @@ class MSBinary(sqltypes.Binary):
             return "BINARY"
 
 
-class MSVarBinary(MSBinary):
+class MSVarBinary(MSGenericBinary):
     def get_col_spec(self):
         if self.length:
             return "VARBINARY(%s)" % self.length
@@ -692,7 +708,7 @@ class MSVarBinary(MSBinary):
             return "VARBINARY"
 
 
-class MSImage(MSBinary):
+class MSImage(MSGenericBinary):
     def get_col_spec(self):
         return "IMAGE"
 
@@ -720,22 +736,27 @@ class MSBoolean(sqltypes.Boolean):
                 return value and True or False
         return process
 
+
 class MSTimeStamp(sqltypes.TIMESTAMP):
     def get_col_spec(self):
         return "TIMESTAMP"
 
+
 class MSMoney(sqltypes.TypeEngine):
     def get_col_spec(self):
         return "MONEY"
 
+
 class MSSmallMoney(MSMoney):
     def get_col_spec(self):
         return "SMALLMONEY"
 
+
 class MSUniqueIdentifier(sqltypes.TypeEngine):
     def get_col_spec(self):
         return "UNIQUEIDENTIFIER"
 
+
 class MSVariant(sqltypes.TypeEngine):
     def get_col_spec(self):
         return "SQL_VARIANT"
@@ -851,7 +872,7 @@ class MSSQLDialect(default.DefaultDialect):
         sqltypes.Date : MSDate,
         sqltypes.Time : MSTime,
         sqltypes.String : MSString,
-        sqltypes.Binary : MSBinary,
+        sqltypes.Binary : MSGenericBinary,
         sqltypes.Boolean : MSBoolean,
         sqltypes.Text : MSText,
         sqltypes.UnicodeText : MSNText,
index c4056fd9be26ba7d53b1eea09b1740935a7d0e69..f63787e7484f0a203e4f8026fdee0810ef2f5f9f 100755 (executable)
@@ -1,8 +1,8 @@
 import testenv; testenv.configure_for_tests()
-import re
+import os, pickleable, re
 from sqlalchemy import *
+from sqlalchemy import types, exc
 from sqlalchemy.orm import *
-from sqlalchemy import exc
 from sqlalchemy.sql import table, column
 from sqlalchemy.databases import mssql
 import sqlalchemy.engine.url as url
@@ -826,5 +826,77 @@ def colspec(c):
         testing.db, None, None).get_column_specification(c)
 
 
+class BinaryTest(TestBase, AssertsExecutionResults):
+    """Test the Binary and VarBinary types"""
+    def setUpAll(self):
+        global binary_table, MyPickleType
+
+        class MyPickleType(types.TypeDecorator):
+            impl = PickleType
+
+            def process_bind_param(self, value, dialect):
+                if value:
+                    value.stuff = 'this is modified stuff'
+                return value
+
+            def process_result_value(self, value, dialect):
+                if value:
+                    value.stuff = 'this is the right stuff'
+                return value
+
+        binary_table = Table('binary_table', MetaData(testing.db),
+        Column('primary_id', Integer, Sequence('binary_id_seq', optional=True), primary_key=True),
+        Column('data', mssql.MSVarBinary(8000)),
+        Column('data_image', mssql.MSImage),
+        Column('data_slice', Binary(100)),
+        Column('misc', String(30)),
+        # construct PickleType with non-native pickle module, since cPickle uses relative module
+        # loading and confuses this test's parent package 'sql' with the 'sqlalchemy.sql' package relative
+        # to the 'types' module
+        Column('pickled', PickleType),
+        Column('mypickle', MyPickleType)
+        )
+        binary_table.create()
+
+    def tearDown(self):
+        binary_table.delete().execute()
+
+    def tearDownAll(self):
+        binary_table.drop()
+
+    def testbinary(self):
+        testobj1 = pickleable.Foo('im foo 1')
+        testobj2 = pickleable.Foo('im foo 2')
+        testobj3 = pickleable.Foo('im foo 3')
+
+        stream1 =self.load_stream('binary_data_one.dat')
+        stream2 =self.load_stream('binary_data_two.dat')
+        binary_table.insert().execute(primary_id=1, misc='binary_data_one.dat', data=stream1, data_image=stream1, data_slice=stream1[0:100], pickled=testobj1, mypickle=testobj3)
+        binary_table.insert().execute(primary_id=2, misc='binary_data_two.dat', data=stream2, data_image=stream2, data_slice=stream2[0:99], pickled=testobj2)
+        binary_table.insert().execute(primary_id=3, misc='binary_data_two.dat', data=None, data_image=None, data_slice=stream2[0:99], pickled=None)
+
+        for stmt in (
+            binary_table.select(order_by=binary_table.c.primary_id),
+            text("select * from binary_table order by binary_table.primary_id", typemap={'pickled':PickleType, 'mypickle':MyPickleType}, bind=testing.db)
+        ):
+            l = stmt.execute().fetchall()
+            self.assertEquals(list(stream1), list(l[0]['data']))
+
+            paddedstream = list(stream1[0:100])
+            paddedstream.extend(['\x00'] * (100 - len(paddedstream)))
+            self.assertEquals(paddedstream, list(l[0]['data_slice']))
+
+            self.assertEquals(list(stream2), list(l[1]['data']))
+            self.assertEquals(list(stream2), list(l[1]['data_image']))
+            self.assertEquals(testobj1, l[0]['pickled'])
+            self.assertEquals(testobj2, l[1]['pickled'])
+            self.assertEquals(testobj3.moredata, l[0]['mypickle'].moredata)
+            self.assertEquals(l[0]['mypickle'].stuff, 'this is the right stuff')
+
+    def load_stream(self, name, len=3000):
+        f = os.path.join(os.path.dirname(testenv.__file__), name)
+        return file(f).read(len)
+
+
 if __name__ == "__main__":
     testenv.main()
index d7fa5987c6dd2b58b8719db988118af662a3fdb3..ef4a355d3c89988c2770af9c99604462698f58c9 100644 (file)
@@ -433,6 +433,7 @@ class BinaryTest(TestBase, AssertsExecutionResults):
     def tearDownAll(self):
         binary_table.drop()
 
+    @testing.fails_on('mssql', 'MSSQl BINARY type right pads the fixed length with \x00')
     def testbinary(self):
         testobj1 = pickleable.Foo('im foo 1')
         testobj2 = pickleable.Foo('im foo 2')