]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Use cx_Oracle.FIXED_NCHAR for sqltypes.NCHAR
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 15 Oct 2019 01:53:44 +0000 (21:53 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 15 Oct 2019 15:57:19 +0000 (11:57 -0400)
The :class:`.sqltypes.NCHAR` datatype will now bind to the
``cx_Oracle.FIXED_NCHAR`` DBAPI data bindings when used in a bound
parameter, which supplies proper comparison behavior against a
variable-length string.  Previously, the :class:`.sqltypes.NCHAR` datatype
would bind to ``cx_oracle.NCHAR`` which is not fixed length; the
:class:`.sqltypes.CHAR` datatype already binds to ``cx_Oracle.FIXED_CHAR``
so it is now consistent that :class:`.sqltypes.NCHAR` binds to
``cx_Oracle.FIXED_NCHAR``.

Fixes: #4913
Change-Id: I5bb111f2e06bbdd525bc5f716579baad31bbb3db

doc/build/changelog/unreleased_13/4913.rst [new file with mode: 0644]
lib/sqlalchemy/dialects/oracle/cx_oracle.py
test/dialect/oracle/test_types.py

diff --git a/doc/build/changelog/unreleased_13/4913.rst b/doc/build/changelog/unreleased_13/4913.rst
new file mode 100644 (file)
index 0000000..de94888
--- /dev/null
@@ -0,0 +1,14 @@
+.. change::
+    :tags: bug, oracle
+    :tickets: 4913
+
+    The :class:`.sqltypes.NCHAR` datatype will now bind to the
+    ``cx_Oracle.FIXED_NCHAR`` DBAPI data bindings when used in a bound
+    parameter, which supplies proper comparison behavior against a
+    variable-length string.  Previously, the :class:`.sqltypes.NCHAR` datatype
+    would bind to ``cx_oracle.NCHAR`` which is not fixed length; the
+    :class:`.sqltypes.CHAR` datatype already binds to ``cx_Oracle.FIXED_CHAR``
+    so it is now consistent that :class:`.sqltypes.NCHAR` binds to
+    ``cx_Oracle.FIXED_NCHAR``.
+
+
index 2f37b43a173fe09937eb8f99437464abb85c6205..2572a79b377d5087628c04c4cae473b31dc12385 100644 (file)
@@ -429,11 +429,18 @@ class _OracleDate(sqltypes.Date):
         return process
 
 
+# TODO: the names used across CHAR / VARCHAR / NCHAR / NVARCHAR
+# here are inconsistent and not very good
 class _OracleChar(sqltypes.CHAR):
     def get_dbapi_type(self, dbapi):
         return dbapi.FIXED_CHAR
 
 
+class _OracleNChar(sqltypes.NCHAR):
+    def get_dbapi_type(self, dbapi):
+        return dbapi.FIXED_NCHAR
+
+
 class _OracleUnicodeStringNCHAR(oracle.NVARCHAR2):
     def get_dbapi_type(self, dbapi):
         return dbapi.NCHAR
@@ -722,12 +729,12 @@ class OracleDialect_cx_oracle(OracleDialect):
         sqltypes.String: _OracleString,
         sqltypes.UnicodeText: _OracleUnicodeTextCLOB,
         sqltypes.CHAR: _OracleChar,
+        sqltypes.NCHAR: _OracleNChar,
         sqltypes.Enum: _OracleEnum,
         oracle.LONG: _OracleLong,
         oracle.RAW: _OracleRaw,
         sqltypes.Unicode: _OracleUnicodeStringCHAR,
         sqltypes.NVARCHAR: _OracleUnicodeStringNCHAR,
-        sqltypes.NCHAR: _OracleUnicodeStringNCHAR,
         oracle.NCLOB: _OracleUnicodeTextNCLOB,
         oracle.ROWID: _OracleRowid,
     }
index af9719130a970f8d93ae436a05a79f23ee606516..0d5cdc371ed96d731e6b937a58a3a7e48307a4fb 100644 (file)
@@ -41,11 +41,13 @@ from sqlalchemy.testing import assert_raises_message
 from sqlalchemy.testing import AssertsCompiledSQL
 from sqlalchemy.testing import eq_
 from sqlalchemy.testing import fixtures
+from sqlalchemy.testing import is_
 from sqlalchemy.testing import mock
 from sqlalchemy.testing.engines import testing_engine
 from sqlalchemy.testing.schema import Column
 from sqlalchemy.testing.schema import Table
 from sqlalchemy.util import b
+from sqlalchemy.util import py2k
 from sqlalchemy.util import u
 
 
@@ -88,7 +90,8 @@ class DialectTypesTest(fixtures.TestBase, AssertsCompiledSQL):
             (Unicode(), cx_oracle._OracleUnicodeStringCHAR),
             (Text(), cx_oracle._OracleText),
             (UnicodeText(), cx_oracle._OracleUnicodeTextCLOB),
-            (NCHAR(), cx_oracle._OracleUnicodeStringNCHAR),
+            (CHAR(), cx_oracle._OracleChar),
+            (NCHAR(), cx_oracle._OracleNChar),
             (NVARCHAR(), cx_oracle._OracleUnicodeStringNCHAR),
             (oracle.RAW(50), cx_oracle._OracleRaw),
         ]:
@@ -106,7 +109,7 @@ class DialectTypesTest(fixtures.TestBase, AssertsCompiledSQL):
             (Unicode(), cx_oracle._OracleUnicodeStringNCHAR),
             (Text(), cx_oracle._OracleText),
             (UnicodeText(), cx_oracle._OracleUnicodeTextNCLOB),
-            (NCHAR(), cx_oracle._OracleUnicodeStringNCHAR),
+            (NCHAR(), cx_oracle._OracleNChar),
             (NVARCHAR(), cx_oracle._OracleUnicodeStringNCHAR),
         ]:
             assert isinstance(
@@ -181,40 +184,49 @@ class TypesTest(fixtures.TestBase):
     __dialect__ = oracle.OracleDialect()
     __backend__ = True
 
-    @testing.fails_on("+zxjdbc", "zxjdbc lacks the FIXED_CHAR dbapi type")
     def test_fixed_char(self):
-        m = MetaData(testing.db)
+        self._test_fixed_char(CHAR)
+
+    def test_fixed_nchar(self):
+        self._test_fixed_char(NCHAR)
+
+    @testing.provide_metadata
+    def _test_fixed_char(self, char_type):
+        m = self.metadata
         t = Table(
             "t1",
             m,
             Column("id", Integer, primary_key=True),
-            Column("data", CHAR(30), nullable=False),
+            Column("data", char_type(30), nullable=False),
         )
 
-        t.create()
-        try:
-            t.insert().execute(
-                dict(id=1, data="value 1"),
-                dict(id=2, data="value 2"),
-                dict(id=3, data="value 3"),
+        if py2k and char_type is NCHAR:
+            v1, v2, v3 = u"value 1", u"value 2", u"value 3"
+        else:
+            v1, v2, v3 = "value 1", "value 2", "value 3"
+
+        with testing.db.begin() as conn:
+            t.create(conn)
+            conn.execute(
+                t.insert(),
+                dict(id=1, data=v1),
+                dict(id=2, data=v2),
+                dict(id=3, data=v3),
             )
 
             eq_(
-                t.select().where(t.c.data == "value 2").execute().fetchall(),
+                conn.execute(t.select().where(t.c.data == v2)).fetchall(),
                 [(2, "value 2                       ")],
             )
 
-            m2 = MetaData(testing.db)
-            t2 = Table("t1", m2, autoload=True)
-            assert type(t2.c.data.type) is CHAR
+            m2 = MetaData()
+            t2 = Table("t1", m2, autoload_with=conn)
+            is_(type(t2.c.data.type), char_type)
             eq_(
-                t2.select().where(t2.c.data == "value 2").execute().fetchall(),
+                conn.execute(t2.select().where(t2.c.data == v2)).fetchall(),
                 [(2, "value 2                       ")],
             )
 
-        finally:
-            t.drop()
-
     @testing.requires.returning
     @testing.provide_metadata
     def test_int_not_float(self):
@@ -804,7 +816,7 @@ class TypesTest(fixtures.TestBase):
 
             assert isinstance(
                 t2.c.c_data.type.dialect_impl(testing.db.dialect),
-                cx_oracle._OracleUnicodeStringNCHAR,
+                cx_oracle._OracleNChar,
             )
 
         data = u("m’a réveillé.")
@@ -1231,7 +1243,7 @@ class SetInputSizesTest(fixtures.TestBase):
 
     def test_nchar(self):
         self._test_setinputsizes(
-            NCHAR(30), u("test"), testing.db.dialect.dbapi.NCHAR
+            NCHAR(30), u("test"), testing.db.dialect.dbapi.FIXED_NCHAR
         )
 
     def test_long(self):