]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Added support for ``FILESTREAM`` in MSSQL.
authorFederico Caselli <cfederico87@gmail.com>
Wed, 19 Jan 2022 22:31:13 +0000 (23:31 +0100)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 20 Jan 2022 22:55:43 +0000 (17:55 -0500)
Fixes: #7243
Change-Id: I99880f429dbaac525bdf7d44438aaab6bc8d0ca6

doc/build/changelog/unreleased_14/7243.rst [new file with mode: 0644]
doc/build/dialects/mssql.rst
lib/sqlalchemy/dialects/mssql/base.py
test/dialect/mssql/test_types.py
test/requirements.py

diff --git a/doc/build/changelog/unreleased_14/7243.rst b/doc/build/changelog/unreleased_14/7243.rst
new file mode 100644 (file)
index 0000000..b4661c2
--- /dev/null
@@ -0,0 +1,10 @@
+.. change::
+    :tags: mssql
+    :tickets: 7243
+
+    Added support for ``FILESTREAM`` when using ``VARBINARY(max)``
+    in MSSQL.
+
+    .. seealso::
+
+        :paramref:`_mssql.VARBINARY.filestream`
index f372ed6cb3ed592f7d425a67f7a66df88bed5619..9de56338ff9e392453687fa3a980e1779d8795ea 100644 (file)
@@ -108,6 +108,9 @@ construction arguments, are as follows:
    :members: __init__
 
 
+.. autoclass:: VARBINARY
+   :members: __init__
+
 .. autoclass:: VARCHAR
    :members: __init__
 
index d48cca2a88ced34c64dcfc6ffe04a807ea071dec..2aff7d8b6df9c65ef4d3aaaf521a488acf80f46d 100644 (file)
@@ -1283,9 +1283,10 @@ class NTEXT(sqltypes.UnicodeText):
 class VARBINARY(sqltypes.VARBINARY, sqltypes.LargeBinary):
     """The MSSQL VARBINARY type.
 
-    This type is present to support "deprecate_large_types" mode where
-    either ``VARBINARY(max)`` or IMAGE is rendered.   Otherwise, this type
-    object is redundant vs. :class:`_types.VARBINARY`.
+    This type adds additional features to the core :class:`_types.VARBINARY`
+    type, including "deprecate_large_types" mode where
+    either ``VARBINARY(max)`` or IMAGE is rendered, as well as the SQL
+    Server ``FILESTREAM`` option.
 
     .. versionadded:: 1.0.0
 
@@ -1293,12 +1294,33 @@ class VARBINARY(sqltypes.VARBINARY, sqltypes.LargeBinary):
 
         :ref:`mssql_large_type_deprecation`
 
-
-
     """
 
     __visit_name__ = "VARBINARY"
 
+    def __init__(self, length=None, filestream=False):
+        """
+        Construct a VARBINARY type.
+
+        :param length: optional, a length for the column for use in
+          DDL statements, for those binary types that accept a length,
+          such as the MySQL BLOB type.
+
+        :param filestream=False: if True, renders the ``FILESTREAM`` keyword
+          in the table definition. In this case ``length`` must be ``None``
+          or ``'max'``.
+
+          .. versionadded:: 1.4.31
+
+        """
+
+        self.filestream = filestream
+        if self.filestream and length not in (None, "max"):
+            raise ValueError(
+                "length must be None or 'max' when setting filestream"
+            )
+        super(VARBINARY, self).__init__(length=length)
+
 
 class IMAGE(sqltypes.LargeBinary):
     __visit_name__ = "IMAGE"
@@ -1566,7 +1588,10 @@ class MSTypeCompiler(compiler.GenericTypeCompiler):
         return "XML"
 
     def visit_VARBINARY(self, type_, **kw):
-        return self._extend("VARBINARY", type_, length=type_.length or "max")
+        text = self._extend("VARBINARY", type_, length=type_.length or "max")
+        if getattr(type_, "filestream", False):
+            text += " FILESTREAM"
+        return text
 
     def visit_boolean(self, type_, **kw):
         return self.visit_BIT(type_)
index 63c6cf26d76e3af5e3d23f665d862089c264703c..2ef8b76daeeb3990815132e7f254e28570abdcec 100644 (file)
@@ -48,6 +48,7 @@ from sqlalchemy.testing import AssertsExecutionResults
 from sqlalchemy.testing import ComparesTables
 from sqlalchemy.testing import engines
 from sqlalchemy.testing import eq_
+from sqlalchemy.testing import expect_raises_message
 from sqlalchemy.testing import fixtures
 from sqlalchemy.testing import is_
 from sqlalchemy.testing import is_not
@@ -529,6 +530,12 @@ class TypeDDLTest(fixtures.TestBase):
             (mssql.MSVarBinary, [10], {}, "VARBINARY(10)"),
             (types.VARBINARY, [10], {}, "VARBINARY(10)"),
             (types.VARBINARY, [], {}, "VARBINARY(max)"),
+            (
+                mssql.MSVarBinary,
+                [],
+                {"filestream": True},
+                "VARBINARY(max) FILESTREAM",
+            ),
             (mssql.MSImage, [], {}, "IMAGE"),
             (mssql.IMAGE, [], {}, "IMAGE"),
             (types.LargeBinary, [], {}, "IMAGE"),
@@ -552,6 +559,17 @@ class TypeDDLTest(fixtures.TestBase):
             )
             self.assert_(repr(col))
 
+    def test_VARBINARY_init(self):
+        d = mssql.dialect()
+        t = mssql.MSVarBinary(length=None, filestream=True)
+        eq_(str(t.compile(dialect=d)), "VARBINARY(max) FILESTREAM")
+        t = mssql.MSVarBinary(length="max", filestream=True)
+        eq_(str(t.compile(dialect=d)), "VARBINARY(max) FILESTREAM")
+        with expect_raises_message(
+            ValueError, "length must be None or 'max' when setting filestream"
+        ):
+            mssql.MSVarBinary(length=1000, filestream=True)
+
 
 class TypeRoundTripTest(
     fixtures.TestBase, AssertsExecutionResults, ComparesTables
@@ -1007,6 +1025,15 @@ class TypeRoundTripTest(
             ),
         ]
 
+        if testing.requires.mssql_filestream.enabled:
+            columns.append(
+                (
+                    mssql.MSVarBinary,
+                    [],
+                    {"filestream": True},
+                    "VARBINARY(max) FILESTREAM",
+                )
+            )
         engine = engines.testing_engine(
             options={"deprecate_large_types": deprecate_large_types}
         )
@@ -1233,6 +1260,15 @@ class BinaryTest(fixtures.TestBase):
             None,
             False,
         ),
+        (
+            mssql.VARBINARY(filestream=True),
+            "binary_data_one.dat",
+            None,
+            True,
+            None,
+            False,
+            testing.requires.mssql_filestream,
+        ),
         (
             sqltypes.LargeBinary,
             "binary_data_one.dat",
index b42bab7d35408e4bd1dec4dac60df4136913d6f1..b93cc4cb4c4e700df558a88cd47394df76f934b4 100644 (file)
@@ -1703,3 +1703,17 @@ class DefaultRequirements(SuiteRequirements):
     def json_deserializer_binary(self):
         "indicates if the json_deserializer function is called with bytes"
         return only_on(["postgresql+psycopg"])
+
+    @property
+    def mssql_filestream(self):
+        "returns if mssql supports filestream"
+
+        def check(config):
+            with config.db.connect() as conn:
+                res = conn.exec_driver_sql(
+                    "SELECT [type] FROM sys.master_files WHERE "
+                    "database_id = DB_ID() AND [type] = 2"
+                ).scalar()
+                return res is not None
+
+        return only_on(["mssql"]) + only_if(check)