From: Mike Bayer Date: Thu, 3 Oct 2019 18:09:54 +0000 (-0400) Subject: call setinputsizes for cx_Oracle.DATETIME X-Git-Tag: rel_1_4_0b1~704^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e21afbcd8c78e4e3f9400da1a2565472682de825;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git call setinputsizes for cx_Oracle.DATETIME Restored adding cx_Oracle.DATETIME to the setinputsizes() call when a SQLAlchemy :class:`.Date`, :class:`.DateTime` or :class:`.Time` datatype is used, so that in the case where a bound parameter is passed as NULL in some complex queries (in particular this happens with some lazy load situations), the type is still present. This was removed in the 1.2 series for arbitrary reasons. Also adds a suite test for this generic situation. What's not clear is that do we really need setinputsizes() for all datatypes if we are supporting NULL in bound parameters. Fixes: #4886 Change-Id: If99215c31861f9ea6f60a30d47f2f320adc4797f --- diff --git a/doc/build/changelog/unreleased_13/4886.rst b/doc/build/changelog/unreleased_13/4886.rst new file mode 100644 index 0000000000..285bbddff5 --- /dev/null +++ b/doc/build/changelog/unreleased_13/4886.rst @@ -0,0 +1,8 @@ +.. change:: + :tags: bug, oracle + :tickets: 4886 + + Restored adding cx_Oracle.DATETIME to the setinputsizes() call when a + SQLAlchemy :class:`.Date`, :class:`.DateTime` or :class:`.Time` datatype is + used, as some complex queries require this to be present. This was removed + in the 1.2 series for arbitrary reasons. diff --git a/lib/sqlalchemy/dialects/oracle/cx_oracle.py b/lib/sqlalchemy/dialects/oracle/cx_oracle.py index fbacb622e5..2f37b43a17 100644 --- a/lib/sqlalchemy/dialects/oracle/cx_oracle.py +++ b/lib/sqlalchemy/dialects/oracle/cx_oracle.py @@ -782,6 +782,7 @@ class OracleDialect_cx_oracle(OracleDialect): ) self._include_setinputsizes = { + cx_Oracle.DATETIME, cx_Oracle.NCLOB, cx_Oracle.CLOB, cx_Oracle.LOB, diff --git a/lib/sqlalchemy/testing/requirements.py b/lib/sqlalchemy/testing/requirements.py index ca7a42db47..62e442b6b4 100644 --- a/lib/sqlalchemy/testing/requirements.py +++ b/lib/sqlalchemy/testing/requirements.py @@ -192,6 +192,14 @@ class SuiteRequirements(Requirements): """ return exclusions.closed() + @property + def standalone_null_binds_whereclause(self): + """target database/driver supports bound parameters with NULL in the + WHERE clause, in situations where it has to be typed. + + """ + return exclusions.open() + @property def intersect(self): """Target database must support INTERSECT or equivalent.""" diff --git a/lib/sqlalchemy/testing/suite/test_types.py b/lib/sqlalchemy/testing/suite/test_types.py index 37428c5454..435ab4689d 100644 --- a/lib/sqlalchemy/testing/suite/test_types.py +++ b/lib/sqlalchemy/testing/suite/test_types.py @@ -14,7 +14,9 @@ from ..schema import Column from ..schema import Table from ... import and_ from ... import BigInteger +from ... import bindparam from ... import Boolean +from ... import case from ... import cast from ... import Date from ... import DateTime @@ -291,6 +293,33 @@ class _DateFixture(_LiteralRoundTripFixture): compare = self.compare or self.data self._literal_round_trip(self.datatype, [self.data], [compare]) + @testing.requires.standalone_null_binds_whereclause + def test_null_bound_comparison(self): + # this test is based on an Oracle issue observed in #4886. + # passing NULL for an expression that needs to be interpreted as + # a certain type, does the DBAPI have the info it needs to do this. + date_table = self.tables.date_table + with config.db.connect() as conn: + result = conn.execute( + date_table.insert(), {"date_data": self.data} + ) + id_ = result.inserted_primary_key[0] + stmt = select([date_table.c.id]).where( + case( + [ + ( + bindparam("foo", type_=self.datatype) != None, + bindparam("foo", type_=self.datatype), + ) + ], + else_=date_table.c.date_data, + ) + == date_table.c.date_data + ) + + row = conn.execute(stmt, {"foo": None}).first() + eq_(row[0], id_) + class DateTimeTest(_DateFixture, fixtures.TablesTest): __requires__ = ("datetime",)