]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Fix errors from initial CE run
authorThomas Stephenson <ovangle@gmail.com>
Fri, 16 May 2025 03:13:19 +0000 (13:13 +1000)
committerThomas Stephenson <ovangle@gmail.com>
Fri, 16 May 2025 09:31:07 +0000 (19:31 +1000)
- fix mypy and linter issues
- expand BitString tests for int and bytes conversion
- Only run BIT test types on postgresql dialect
- Fix problems in `BitString.from_int` and `BitString.from_bytes`
- Add `BitString.to_bytes` method to allow specifying the byte
  length of the returned instances
- Fixed problems testing operators in BIT type tests

lib/sqlalchemy/dialects/postgresql/asyncpg.py
lib/sqlalchemy/dialects/postgresql/bitstring.py
lib/sqlalchemy/dialects/postgresql/types.py
test/dialect/postgresql/test_bitstring.py
test/dialect/postgresql/test_types.py

index 12be23635d3a1edf25781e47dcb033a628152d28..9ca642818939f114952357ca25b1dbf6ef09398a 100644 (file)
@@ -247,11 +247,11 @@ class AsyncpgBit(BIT):
         asyncpg_BitString = dialect.dbapi.asyncpg.BitString
 
         def to_bind(value):
-            print(f'processing bound value \'{value}\'')
+            print(f"processing bound value '{value}'")
             if isinstance(value, str):
                 value = BitString(value)
                 r = asyncpg_BitString.from_int(int(value), len(value))
-                print(f'returning {r}')
+                print(f"returning {r}")
                 return r
             return value
 
@@ -260,11 +260,8 @@ class AsyncpgBit(BIT):
     def result_processor(self, dialect, coltype):
         def to_result(value):
             if value is not None:
-                print(f'result {value} length {len(value)}')
-                value = BitString.from_int(
-                    value.to_int(),
-                    length=len(value)
-                )
+                print(f"result {value} length {len(value)}")
+                value = BitString.from_int(value.to_int(), length=len(value))
             return value
 
         return to_result
index 803255d8408e5374067ea9291192e5530aa1e0c5..d796bcb69684dd128ad8bb939046bd65ace9a657 100644 (file)
@@ -1,4 +1,4 @@
-# dialects/postgresql/types.py
+# dialects/postgresql/bitstring.py
 # Copyright (C) 2013-2025 the SQLAlchemy authors and contributors
 # <see AUTHORS file>
 #
@@ -7,9 +7,12 @@
 from __future__ import annotations
 
 import functools
+import itertools
 import math
-from typing import SupportsIndex, cast
+from typing import Any
+from typing import cast
 from typing import Literal
+from typing import SupportsIndex
 
 
 @functools.total_ordering
@@ -20,16 +23,15 @@ class BitString(str):
         b = BitString('101')
     """
 
-    def __new__(cls, _value: str, _check=True):
+    def __new__(cls, _value: str, _check: bool = True) -> BitString:
         if not isinstance(_value, BitString) and (
             _check and _value and any(c not in "01" for c in _value)
         ):
-            print(f'value: {_value}')
             raise ValueError("BitString must only contain '0' and '1' chars")
         return super().__new__(cls, _value)
 
     @classmethod
-    def from_int(cls, value: int, length: int):
+    def from_int(cls, value: int, length: int) -> BitString:
         """
         Returns a BitString consisting of the bits in the little-endian
         representation of the given python int ``value``. A ``ValueError``
@@ -40,27 +42,22 @@ class BitString(str):
         will be padded on the left by ``'0'`` to bits to produce a
         """
         if value < 0:
-            raise ValueError("value must be a postive integer")
+            raise ValueError("value must be non-negative")
+        if length < 0:
+            raise ValueError("length must be non-negative")
 
-        if length >= 0:
-            if length > 0:
-                template_str = f'{{0:0{length}b}}' if length > 0 else ''
-                r = template_str.format(value)
-            else:
-                # f'{0:00b}'.format(0) == '0'
-                r = ''
-
-            if len(r) > length:
-                raise ValueError(
-                    f"Cannot encode {value} as a BitString of length {length}"
-                )
-        else:
-            r = '{0:b}'.format(value)
+        template_str = f"{{0:0{length}b}}" if length > 0 else ""
+        r = template_str.format(value)
+
+        if (length == 0 and value > 0) or len(r) > length:
+            raise ValueError(
+                f"Cannot encode {value} as a BitString of length {length}"
+            )
 
         return cls(r)
 
     @classmethod
-    def from_bytes(cls, value: bytes, length: int = -1):
+    def from_bytes(cls, value: bytes, length: int = -1) -> BitString:
         """
         Returns a ``BitString`` consisting of the bits in the given ``value``
         bytes.
@@ -72,19 +69,20 @@ class BitString(str):
         cannot be represented in a string of this length, then a ``ValueError``
         will be raised.
         """
-        str_v: str = "".join(f"{c:08b}" for c in value)
+        str_v: str = "".join(f"{int(c):08b}" for c in value)
         if length >= 0:
-            str_v = str_v.lstrip('0')
+            str_v = str_v.lstrip("0")
 
-            if len(str_v) >= length:
+            if len(str_v) > length:
                 raise ValueError(
-                    f"Cannot encode {value} as a BitString of length {length}"
+                    f"Cannot encode {value!r} as a BitString of "
+                    f"length {length}"
                 )
             str_v = str_v.zfill(length)
 
         return cls(str_v)
 
-    def get_bit(self, index) -> Literal["0", "1"]:
+    def get_bit(self, index: int) -> Literal["0", "1"]:
         """
         Returns the value of the flag at the given index
 
@@ -93,14 +91,14 @@ class BitString(str):
         return cast(Literal["0", "1"], super().__getitem__(index))
 
     @property
-    def bit_length(self):
+    def bit_length(self) -> int:
         return len(self)
 
     @property
-    def octet_length(self):
+    def octet_length(self) -> int:
         return math.ceil(len(self) / 8)
 
-    def has_bit(self, index) -> bool:
+    def has_bit(self, index: int) -> bool:
         return self.get_bit(index) == "1"
 
     def set_bit(
@@ -121,154 +119,136 @@ class BitString(str):
             return self
 
         return BitString(
-            "".join([self[:index], value, self[index + 1:]]), False
+            "".join([self[:index], value, self[index + 1 :]]), False
         )
 
-    # These methods probably should return str and not override the fillchar
-    # def ljust(self, width, fillchar=None) -> BitString:
-    #     """
-    #     Returns the BitString left justified in a string of length width.
-    #     Padding is done using the provided fillchar (default is '0').
-
-    #     If the width is shorter than the length, then the original BitString
-    #     is returned.
-    #     """
-    #     if width < len(self):
-    #         return self
-
-    #     fillchar = fillchar or "0"
-    #     if str(fillchar) not in "01":
-    #         raise ValueError("fillchar must be either '0' or '1'")
-
-    #     return BitString(super().ljust(width, fillchar or "0"))
-
-    # def rjust(self, width, fillchar=None) -> BitString:
-    #     if width < len(self):
-    #         return self
-
-    #     fillchar = fillchar or "0"
-    #     if str(fillchar) not in "01":
-    #         raise ValueError("fillchar must be either '0' or '1'")
-
-    #     return BitString(super().rjust(width, fillchar))
-
-    def lstrip(self, char=None) -> BitString:
+    def lstrip(self, char: str | None = None) -> BitString:
         """
         Returns a copy of the BitString with leading characters removed.
 
         If omitted or None, 'chars' defaults '0'
 
         e.g.
-        BitString('00010101000').lstrip() === BitString('00010101')
-        BitString('11110101111').lstrip('1') === BitString('1111010')
+            BitString('00010101000').lstrip() === BitString('00010101')
+            BitString('11110101111').lstrip('1') === BitString('1111010')
         """
         if char is None:
             char = "0"
         return BitString(super().lstrip(char), False)
 
-    def rstrip(self, char=None) -> BitString:
+    def rstrip(self, char: str | None = "0") -> BitString:
         """
         Returns a copy of the BitString with trailing characters removed.
 
-        If omitted or None, 'chars' trailing '0'
+        If omitted or None, 'char' defaults to "0"
 
         e.g.
-        BitString('00010101000').rstrip() === BitString('10101000')
-        BitString('11110101111').rstrip('1') === BitString('10101111')
+            BitString('00010101000').rstrip() === BitString('10101000')
+            BitString('11110101111').rstrip('1') === BitString('10101111')
         """
         if char is None:
             char = "0"
         return BitString(super().rstrip(char), False)
 
-    def strip(self, char=None) -> BitString:
+    def strip(self, char: str | None = "0") -> BitString:
         """
         Returns a copy of the BitString with both leading and trailing
         characters removed.
-        If ommitted or None, char defaults to '0'
+        If ommitted or None, char defaults to "0"
 
         e.g.
-        BitString('00010101000').rstrip() === BitString('10101')
-        BitString('11110101111').rstrip('1') === BitString('1010')
+            BitString('00010101000').rstrip() === BitString('10101')
+            BitString('11110101111').rstrip('1') === BitString('1010')
         """
         if char is None:
             char = "0"
         return BitString(super().strip(char))
 
-    def partition(self, sep: str = "0") -> tuple[BitString, str, BitString]:
-        """
-        Split the string after the first appearance of sep
-        (which defaults to '0') and return a 3-tuple containing
-        the portion of the string before the separator.
-
-        """
-        prefix, _, suffix = super().partition(sep)
-        return (BitString(prefix, False), sep, BitString(suffix, False))
-
     def removeprefix(self, prefix: str, /) -> BitString:
         return BitString(super().removeprefix(prefix), False)
 
     def removesuffix(self, suffix: str, /) -> BitString:
         return BitString(super().removesuffix(suffix), False)
 
-    def replace(self, old, new, count: SupportsIndex = -1) -> BitString:
+    def replace(
+        self,
+        old: str,
+        new: str,
+        count: SupportsIndex = -1,
+    ) -> BitString:
         new = BitString(new)
         return BitString(super().replace(old, new, count=count), False)
 
-    def split(  # type: ignore
-            self,
-            sep=None,
-            maxsplit: SupportsIndex = -1,
-    ) -> list[BitString]:
+    def split(
+        self,
+        sep: str | None = None,
+        maxsplit: SupportsIndex = -1,
+    ) -> list[str]:
         return [BitString(word) for word in super().split(sep, maxsplit)]
 
-    def zfill(self, width) -> BitString:
+    def zfill(self, width: SupportsIndex) -> BitString:
         return BitString(super().zfill(width), False)
 
-    def __repr__(self):
+    def __repr__(self) -> str:
         return f'BitString("{self.__str__()}")'
 
-    def __int__(self):
+    def __int__(self) -> int:
         return int(self, 2) if self else 0
 
-    def __bytes__(self):
+    def to_bytes(self, length: int = -1) -> bytes:
         s = str(self)
-        bs = []
+        bs: list[int] = []
         while s:
-            bs.append(int(s[-8:], 2))
+            bs.insert(0, int(s[-8:], 2))
             s = s[:-8]
+        if length >= 0:
+            bs = list(itertools.dropwhile(lambda c: c == 0, bs))
+            if len(bs) > length:
+                raise ValueError(
+                    f"Cannot fit a BitString of length {len(self)} into a "
+                    f"bytes instance of length {length}"
+                )
+            # "zfill" the result with 0 bytes
+            bs = [0] * (length - len(bs)) + bs
         return bytes(bs)
 
-    def __lt__(self, o):
+    def __bytes__(self) -> bytes:
+        return self.to_bytes()
+
+    def __lt__(self, o: object) -> bool:
         if isinstance(o, BitString):
             return super().__lt__(o)
         return NotImplemented
 
-    def __eq__(self, o):
+    def __eq__(self, o: object) -> bool:
         return isinstance(o, BitString) and super().__eq__(o)
 
-    def __hash__(self):
+    def __hash__(self) -> int:
         return hash(BitString) ^ super().__hash__()
 
-    def __getitem__(self, key):
+    def __getitem__(self, key: SupportsIndex | slice[Any, Any, Any]) -> str:
         return BitString(super().__getitem__(key), False)
 
-    def __add__(self, o):
+    def __add__(self, o: str) -> BitString:
         """Return self + o"""
         if not isinstance(o, str):
-            raise TypeError((
-                "Can only concatenate str "
-                "(not '{0}') to BitString"
-            ).format(type(o)))
-        return BitString(''.join([self, o]))
+            raise TypeError(
+                ("Can only concatenate str (not '{0}') to BitString").format(
+                    type(o)
+                )
+            )
+        return BitString("".join([self, o]))
 
-    def __radd__(self, o):
+    def __radd__(self, o: str) -> BitString:
         if not isinstance(o, str):
-            raise TypeError((
-                "Can only concatenate str (not '{0}') to BitString"
-            ).format(type(o)))
-        return BitString(''.join([o, self]))
+            raise TypeError(
+                (f"Can only concatenate str (not '{0}') to BitString").format(
+                    type(o)
+                )
+            )
+        return BitString("".join([o, self]))
 
-    def __lshift__(self, amount: int):
+    def __lshift__(self, amount: int) -> BitString:
         """
         Shifts each the bitstring to the left by the given amount.
         String length is preserved.
@@ -276,10 +256,10 @@ class BitString(str):
         i.e. BitString('000101') << 1 == BitString('001010')
         """
         return BitString(
-            "".join([self, *("0" for _ in range(amount))])[-len(self):], False
+            "".join([self, *("0" for _ in range(amount))])[-len(self) :], False
         )
 
-    def __rshift__(self, amount: int):
+    def __rshift__(self, amount: int) -> BitString:
         """
         Shifts each bit in the bitstring to the right by the given amount.
         String length is preserved.
@@ -288,7 +268,7 @@ class BitString(str):
         """
         return BitString(self[:-amount], False).zfill(width=len(self))
 
-    def __invert__(self):
+    def __invert__(self) -> BitString:
         """
         Inverts (~) each bit in the bitstring
 
@@ -296,7 +276,7 @@ class BitString(str):
         """
         return BitString("".join("1" if x == "0" else "0" for x in self))
 
-    def __and__(self, o):
+    def __and__(self, o: str) -> BitString:
         """
         Performs a bitwise and (``&``) with the given operand.
         A ``ValueError`` is raised if the operand is not the same length.
@@ -318,7 +298,7 @@ class BitString(str):
             False,
         )
 
-    def __or__(self, o):
+    def __or__(self, o: str) -> BitString:
         """
         Performs a bitwise or (``|``) with the given operand.
         A ``ValueError`` is raised if the operand is not the same length.
@@ -340,7 +320,7 @@ class BitString(str):
             False,
         )
 
-    def __xor__(self, o):
+    def __xor__(self, o: str) -> BitString:
         """
         Performs a bitwise xor (``^``) with the given operand.
         A ``ValueError`` is raised if the operand is not the same length.
index 4296df35e38e541c27b37ae33f14a5f0d14bb5b3..054111832e6f205aae881a8ed874de35f3399bd9 100644 (file)
@@ -15,16 +15,18 @@ from typing import Type
 from typing import TYPE_CHECKING
 from uuid import UUID as _python_UUID
 
+from .bitstring import BitString
 from ...sql import sqltypes
 from ...sql import type_api
 from ...sql.type_api import TypeEngine
 
-from .bitstring import BitString
-
 if TYPE_CHECKING:
     from ...engine.interfaces import Dialect
+    from ...sql.operators import ColumnOperators
     from ...sql.operators import OperatorType
+    from ...sql.type_api import _BindProcessorType
     from ...sql.type_api import _LiteralProcessorType
+    from ...sql.type_api import _ResultProcessorType
 
 _DECIMAL_TYPES = (1231, 1700)
 _FLOAT_TYPES = (700, 701, 1021, 1022)
@@ -273,45 +275,59 @@ class BIT(sqltypes.TypeEngine[BitString]):
             self.length = length or 1
         self.varying = varying
 
-    def bind_processor(self, dialect):
-        def bound_value(value):
+    def bind_processor(
+        self, dialect: Dialect
+    ) -> _BindProcessorType[BitString]:
+        def bound_value(value: Any) -> Any:
             if isinstance(value, BitString):
                 return str(value)
             return value
+
         return bound_value
 
-    def result_processor(self, dialect, coltype):
-        def from_result_value(value):
+    def result_processor(
+        self, dialect: Dialect, coltype: object
+    ) -> _ResultProcessorType[BitString]:
+        def from_result_value(value: Any) -> Any:
             if value is not None:
                 value = BitString(value)
             return value
+
         return from_result_value
 
-    def coerce_compared_value(self, op, value) -> TypeEngine[Any]:
+    def coerce_compared_value(
+        self, op: OperatorType | None, value: Any
+    ) -> TypeEngine[Any]:
         if isinstance(value, str):
             return self
         return super().coerce_compared_value(op, value)
 
     @property
-    def python_type(self):
+    def python_type(self) -> type[Any]:
         return BitString
 
     class comparator_factory(TypeEngine.Comparator[BitString]):
-        def __lshift__(self, other: Any):
+        def __lshift__(self, other: Any) -> ColumnOperators:
             return self.bitwise_lshift(other)
 
-        def __rshift__(self, other: Any):
+        def __rshift__(self, other: Any) -> ColumnOperators:
             return self.bitwise_rshift(other)
 
-        def __and__(self, other: Any):
+        def __and__(self, other: Any) -> ColumnOperators:
             return self.bitwise_and(other)
 
-        def __or__(self, other: Any):
+        def __or__(self, other: Any) -> ColumnOperators:
             return self.bitwise_or(other)
 
-        def __invert__(self):
+        # __xor__ is not defined on sql.operators.ColumnOperators.
+        # Use `bitwise_xor` directly instead.
+        # def __xor__(self, other: Any) -> ColumnOperators:
+        #     return self.bitwise_xor(other)
+
+        def __invert__(self) -> ColumnOperators:
             return self.bitwise_not()
 
+
 PGBit = BIT
 
 
index 2a5b879c1435274705b7e8ff33d57fe6555814b5..2cba388d3d259cc1c468badfb5edbbe89010319f 100644 (file)
@@ -1,18 +1,18 @@
-from sqlalchemy.testing import fixtures
-
+from sqlalchemy import testing
 from sqlalchemy.dialects.postgresql import BitString
+from sqlalchemy.testing import fixtures
+from sqlalchemy.testing.assertions import assert_raises
 from sqlalchemy.testing.assertions import eq_
 from sqlalchemy.testing.assertions import is_false
 from sqlalchemy.testing.assertions import is_true
-from sqlalchemy.testing.assertions import assert_raises
 
 
 class BitStringTests(fixtures.TestBase):
+    __only_on__ = "postgresql"
 
-    def test_ctor(self):
+    def test_str_conversion(self):
         x = BitString("1110111")
         eq_(str(x), "1110111")
-        eq_(int(x), 119)
 
         eq_(BitString("111"), BitString("111"))
         is_false(BitString("111") == "111")
@@ -22,27 +22,67 @@ class BitStringTests(fixtures.TestBase):
 
         eq_(BitString("011")[1], BitString("1"))
 
-    def test_int_conversion(self):
-        assert_raises(ValueError, lambda: BitString.from_int(127, length=6))
-
-        eq_(BitString.from_int(127, length=8), BitString("01111111"))
-        eq_(int(BitString.from_int(127, length=8)), 127)
-
-        eq_(BitString.from_int(119, length=10), BitString("0001110111"))
-        eq_(int(BitString.from_int(119, length=10)), 119)
-
-    def test_bytes_conversion(self):
-        eq_(BitString.from_bytes(b"\x01"), BitString("0000001"))
-        eq_(BitString.from_bytes(b"\x01", 4), BitString("00000001"))
-
-        eq_(BitString.from_bytes(b"\xaf\x04"), BitString("101011110010"))
-        eq_(
-            BitString.from_bytes(b"\xaf\x04", 12),
-            BitString("0000101011110010"),
-        )
-        assert_raises(
-            ValueError, lambda: BitString.from_bytes(b"\xaf\x04", 4), 1
-        )
+        assert_raises(ValueError, lambda: BitString("1246"))
+
+    @testing.combinations(
+        (0, 0, BitString("")),
+        (0, 1, BitString("0")),
+        (1, 1, BitString("1")),
+        (1, 0, ValueError),
+        (1, -1, ValueError),
+        (2, 1, ValueError),
+        (-1, 4, ValueError),
+        (1, 4, BitString("0001")),
+        (1, 10, BitString("0000000001")),
+        (127, 8, BitString("01111111")),
+        (127, 10, BitString("0001111111")),
+        (1404, 8, ValueError),
+        (1404, 12, BitString("010101111100")),
+        argnames="source, bitlen, result_or_error",
+    )
+    def test_int_conversion(self, source, bitlen, result_or_error):
+        if isinstance(result_or_error, type):
+            assert_raises(
+                result_or_error, lambda: BitString.from_int(source, bitlen)
+            )
+            return
+
+        result = result_or_error
+
+        bits = BitString.from_int(source, bitlen)
+        eq_(bits, result)
+        eq_(int(bits), source)
+
+    @testing.combinations(
+        (b"", -1, BitString("")),
+        (b"", 4, BitString("0000")),
+        (b"\x00", 1, BitString("0")),
+        (b"\x01", 1, BitString("1")),
+        (b"\x01", 4, BitString("0001")),
+        (b"\x01", 10, BitString("0000000001")),
+        (b"\x01", -1, BitString("00000001")),
+        (b"\xff", 10, BitString("0011111111")),
+        (b"\xaf\x04", 8, ValueError),
+        (b"\xaf\x04", 16, BitString("1010111100000100")),
+        (b"\xaf\x04", 20, BitString("00001010111100000100")),
+        argnames="source, bitlen, result_or_error",
+    )
+    def test_bytes_conversion(self, source, bitlen, result_or_error):
+        if isinstance(result_or_error, type):
+            assert_raises(
+                result_or_error,
+                lambda: BitString.from_bytes(source, length=bitlen),
+            )
+            return
+        result = result_or_error
+
+        bits = BitString.from_bytes(source, bitlen)
+        eq_(bits, result)
+
+        # Expecting a roundtrip conversion in this case is nonsensical
+        if source == b"" and bitlen > 0:
+            return
+        eq_(bits.to_bytes(len(source)), source)
 
     def test_get_set_bit(self):
         eq_(BitString("1010").get_bit(2), "1")
@@ -55,12 +95,6 @@ class BitStringTests(fixtures.TestBase):
 
     def test_string_methods(self):
 
-        # Which of these methods should be overridden to produce BitStrings?
-        eq_(BitString("111").center(8), "  111   ")
-
-        eq_(BitString("0101").ljust(8), "0101    ")
-        eq_(BitString("0110").rjust(8), "    0110")
-
         eq_(BitString("01100").lstrip(), BitString("1100"))
         eq_(BitString("01100").rstrip(), BitString("011"))
         eq_(BitString("01100").strip(), BitString("11"))
@@ -83,7 +117,7 @@ class BitStringTests(fixtures.TestBase):
         eq_(BitString("0110").split("11"), [BitString("0"), BitString("0")])
         eq_(BitString("111").zfill(8), BitString("00000111"))
 
-    def test_str_ops(self):
+    def test_string_operators(self):
         is_true("1" in BitString("001"))
         is_true("0" in BitString("110"))
         is_false("1" in BitString("000"))
@@ -95,7 +129,7 @@ class BitStringTests(fixtures.TestBase):
         eq_(BitString("010") + "001", BitString("010001"))
         eq_("001" + BitString("010"), BitString("001010"))
 
-    def test_bitwise_ops(self):
+    def test_bitwise_operators(self):
         eq_(~BitString("0101"), BitString("1010"))
         eq_(BitString("010") & BitString("011"), BitString("010"))
         eq_(BitString("010") | BitString("011"), BitString("011"))
index 6fa07a109419bbf45407c09b6e4296f7651a5ce4..3d3c93bc89506f81e08fc96390193228154c5ecb 100644 (file)
@@ -46,8 +46,8 @@ from sqlalchemy.dialects.postgresql import array
 from sqlalchemy.dialects.postgresql import array_agg
 from sqlalchemy.dialects.postgresql import asyncpg
 from sqlalchemy.dialects.postgresql import base
-from sqlalchemy.dialects.postgresql import BitString
 from sqlalchemy.dialects.postgresql import BIT
+from sqlalchemy.dialects.postgresql import BitString
 from sqlalchemy.dialects.postgresql import BYTEA
 from sqlalchemy.dialects.postgresql import CITEXT
 from sqlalchemy.dialects.postgresql import DATEMULTIRANGE
@@ -3553,12 +3553,11 @@ class SpecialTypesTest(fixtures.TablesTest, ComparesTables):
         assert t.c.bitstring_varying.type.length is None
 
         assert t.c.bitstring_varying_6.type.varying is True
-        assert t.c.bitstring_varying_6.type.length  == 6
+        assert t.c.bitstring_varying_6.type.length == 6
 
         assert t.c.bitstring_4.type.varying is False
         assert t.c.bitstring_4.type.length == 4
 
-
     @testing.combinations(
         (postgresql.INET, "127.0.0.1"),
         (postgresql.CIDR, "192.168.100.128/25"),
@@ -3600,18 +3599,20 @@ class SpecialTypesTest(fixtures.TablesTest, ComparesTables):
             "bits",
             metadata,
             Column("name", String),
-            Column("value", column_type)
+            Column("value", column_type),
         )
         t.create(connection)
 
         connection.execute(t.insert(), {"name": "test", "value": value})
-        print('value type affinity', t.c.value.type._type_affinity)
+        print("value type affinity", t.c.value.type._type_affinity)
         eq_(
             connection.scalar(select(t.c.name).where(t.c.value == value)),
             "test",
         )
 
-        result_value = connection.scalar(select(t.c.value).where(t.c.name == "test"))
+        result_value = connection.scalar(
+            select(t.c.value).where(t.c.name == "test")
+        )
         assert isinstance(result_value, BitString)
         assert str(result_value) == str(value)
 
@@ -4217,104 +4218,109 @@ class HStoreRoundTripTest(fixtures.TablesTest):
 
 
 class BitTests(fixtures.TestBase):
+    __dialect__ = "postgresql"
+    __only_on__ = "postgresql"
+
     def test_concatenation(self, connection):
         coltype = BIT(varying=True)
 
         q = select(
-            literal(BitString('1111'), coltype).concat(BitString('0000'))
+            literal(BitString("1111"), coltype).concat(BitString("0000"))
         )
         r = connection.execute(q).first()
-        eq_(r[0], BitString('11110000'))
+        eq_(r[0], BitString("11110000"))
 
-    @testing.skip("compiler bug")
+    @testing.skip_if("postgresql", "sql compiler bug")
     def test_invert_operator(self, connection):
         coltype = BIT(4)
 
-        q = select(
-            literal(BitString('0010'), coltype).bitwise_not()
-        )
+        q = select(literal(BitString("0010"), coltype).bitwise_not())
         r = connection.execute(q).first()
 
         # Observing r[0] == '1101' here.
+        #
         # See: sql.compiler.Compiler._label_select_column
         # The unary operator does not "wrap a column expression"
         # and it isn't a from clause of the select,
         # the compiler doesn't actually add the column to the select's
         # result_columns and thus the type's result_processor never gets
         # called.
-        eq_(r[0], BitString('1101'))
+        eq_(r[0], BitString("1101"))
 
     def test_and_operator(self, connection):
         coltype = BIT(6)
 
         q1 = select(
-            literal(BitString('001010'), coltype)
-            & literal(BitString('010111'), coltype)
+            literal(BitString("001010"), coltype)
+            & literal(BitString("010111"), coltype)
         )
         r1 = connection.execute(q1).first()
 
-        eq_(r1[0], BitString('000010'))
+        eq_(r1[0], BitString("000010"))
 
         q2 = select(
-            literal(BitString('010101'), coltype) & BitString('001011')
+            literal(BitString("010101"), coltype) & BitString("001011")
         )
         r2 = connection.execute(q2).first()
-        eq_(r2[0], BitString('000001'))
+        eq_(r2[0], BitString("000001"))
 
     def test_or_operator(self, connection):
         coltype = BIT(6)
 
         q1 = select(
-            literal(BitString('001010'), coltype)
-            & literal(BitString('010111'), coltype)
+            literal(BitString("001010"), coltype)
+            | literal(BitString("010111"), coltype)
         )
         r1 = connection.execute(q1).first()
 
-        eq_(r1[0], BitString('011111'))
+        eq_(r1[0], BitString("011111"))
 
         q2 = select(
-            literal(BitString('010101')) & BitString('001001')
+            literal(BitString("010101"), coltype) | BitString("001011")
         )
         r2 = connection.execute(q2).first()
-        eq_(r2[0], BitString('011101'))
+        eq_(r2[0], BitString("011111"))
 
     def test_xor_operator(self, connection):
         coltype = BIT(6)
 
         q1 = select(
-            literal(BitString('001010'), coltype)
-            & literal(BitString('010111'), coltype)
+            literal(BitString("001010"), coltype).bitwise_xor(
+                literal(BitString("010111"), coltype)
+            )
         )
         r1 = connection.execute(q1).first()
-        eq_(r1[0], BitString('001101'))
+        eq_(r1[0], BitString("011101"))
 
         q2 = select(
-            literal(BitString('010101'), coltype) & BitString('001011')
+            literal(BitString("010101"), coltype).bitwise_xor(
+                BitString("001011")
+            )
         )
         r2 = connection.execute(q2).first()
-        eq_(r2[0], BitString('011110'))
+        eq_(r2[0], BitString("011110"))
 
     def test_lshift_operator(self, connection):
         coltype = BIT(6)
 
         q = select(
-            literal(BitString('001010'), coltype),
-            literal(BitString('001010'), coltype) << 1,
+            literal(BitString("001010"), coltype),
+            literal(BitString("001010"), coltype) << 1,
         )
 
         r = connection.execute(q).first()
-        eq_(tuple(r), (BitString('001010'), BitString('010100')))
+        eq_(tuple(r), (BitString("001010"), BitString("010100")))
 
     def test_rshift_operator(self, connection):
         coltype = BIT(6)
 
         q = select(
-            literal(BitString('001010'), coltype),
-            literal(BitString('001010'), coltype) >> 1
+            literal(BitString("001010"), coltype),
+            literal(BitString("001010"), coltype) >> 1,
         )
 
         r = connection.execute(q).first()
-        eq_(tuple(r), (BitString('001010'), BitString('000101')))
+        eq_(tuple(r), (BitString("001010"), BitString("000101")))
 
 
 class RangeMiscTests(fixtures.TestBase):