Commits

Mike Bayer committed be3c185

- Added new datatype :class:`.oracle.DATE`, which is a subclass of
:class:`.DateTime`. As Oracle has no "datetime" type per se,
it instead has only ``DATE``, it is appropriate here that the
``DATE`` type as present in the Oracle dialect be an instance of
:class:`.DateTime`. This issue doesn't change anything as far as
the behavior of the type, as data conversion is handled by the
DBAPI in any case, however the improved subclass layout will help
the use cases of inspecting types for cross-database compatibility.
Also removed uppercase ``DATETIME`` from the Oracle dialect as this
type isn't functional in that context. fixes #2987

Comments (0)

Files changed (7)

doc/build/changelog/changelog_09.rst

     :version: 0.9.4
 
     .. change::
+        :tags: bug, oracle
+        :tickets: 2987
+
+        Added new datatype :class:`.oracle.DATE`, which is a subclass of
+        :class:`.DateTime`.  As Oracle has no "datetime" type per se,
+        it instead has only ``DATE``, it is appropriate here that the
+        ``DATE`` type as present in the Oracle dialect be an instance of
+        :class:`.DateTime`.  This issue doesn't change anything as far as
+        the behavior of the type, as data conversion is handled by the
+        DBAPI in any case, however the improved subclass layout will help
+        the use cases of inspecting types for cross-database compatibility.
+        Also removed uppercase ``DATETIME`` from the Oracle dialect as this
+        type isn't functional in that context.
+
+    .. change::
         :tags: bug, sql
         :tickets: 2988
         :pullreq: github:78

doc/build/dialects/oracle.rst

 they originate from :mod:`sqlalchemy.types` or from the local dialect::
 
     from sqlalchemy.dialects.oracle import \
-                BFILE, BLOB, CHAR, CLOB, DATE, DATETIME, \
+                BFILE, BLOB, CHAR, CLOB, DATE, \
                 DOUBLE_PRECISION, FLOAT, INTERVAL, LONG, NCLOB, \
                 NUMBER, NVARCHAR, NVARCHAR2, RAW, TIMESTAMP, VARCHAR, \
                 VARCHAR2
 
 .. autoclass:: BFILE
   :members: __init__
-   
+
+.. autoclass:: DATE
+   :members: __init__
 
 .. autoclass:: DOUBLE_PRECISION
    :members: __init__
-    
+
 
 .. autoclass:: INTERVAL
   :members: __init__
-   
+
 
 .. autoclass:: NCLOB
   :members: __init__
-   
+
 
 .. autoclass:: NUMBER
    :members: __init__
-    
+
 
 .. autoclass:: LONG
   :members: __init__
-   
+
 
 .. autoclass:: RAW
   :members: __init__
-   
+
 
 cx_Oracle
 ----------

lib/sqlalchemy/dialects/oracle/__init__.py

 base.dialect = cx_oracle.dialect
 
 from sqlalchemy.dialects.oracle.base import \
-    VARCHAR, NVARCHAR, CHAR, DATE, DATETIME, NUMBER,\
+    VARCHAR, NVARCHAR, CHAR, DATE, NUMBER,\
     BLOB, BFILE, CLOB, NCLOB, TIMESTAMP, RAW,\
     FLOAT, DOUBLE_PRECISION, LONG, dialect, INTERVAL,\
     VARCHAR2, NVARCHAR2, ROWID, dialect
 
 
 __all__ = (
-'VARCHAR', 'NVARCHAR', 'CHAR', 'DATE', 'DATETIME', 'NUMBER',
+'VARCHAR', 'NVARCHAR', 'CHAR', 'DATE', 'NUMBER',
 'BLOB', 'BFILE', 'CLOB', 'NCLOB', 'TIMESTAMP', 'RAW',
 'FLOAT', 'DOUBLE_PRECISION', 'LONG', 'dialect', 'INTERVAL',
 'VARCHAR2', 'NVARCHAR2', 'ROWID'

lib/sqlalchemy/dialects/oracle/base.py

 
 If synonyms are not in use, this flag should be left disabled.
 
+DateTime Compatibility
+----------------------
+
+Oracle has no datatype known as ``DATETIME``, it instead has only ``DATE``,
+which can actually store a date and time value.  For this reason, the Oracle
+dialect provides a type :class:`.oracle.DATE` which is a subclass of
+:class:`.DateTime`.   This type has no special behavior, and is only
+present as a "marker" for this type; additionally, when a database column
+is reflected and the type is reported as ``DATE``, the time-supporting
+:class:`.oracle.DATE` type is used.
+
+.. versionchanged:: 0.9.4 Added :class:`.oracle.DATE` to subclass
+   :class:`.DateTime`.  This is a change as previous versions
+   would reflect a ``DATE`` column as :class:`.types.DATE`, which subclasses
+   :class:`.Date`.   The only significance here is for schemes that are
+   examining the type of column for use in special Python translations or
+   for migrating schemas to other database backends.
+
 """
 
 import re
 from sqlalchemy.sql import compiler, visitors, expression
 from sqlalchemy.sql import operators as sql_operators, functions as sql_functions
 from sqlalchemy import types as sqltypes, schema as sa_schema
-from sqlalchemy.types import VARCHAR, NVARCHAR, CHAR, DATE, DATETIME, \
+from sqlalchemy.types import VARCHAR, NVARCHAR, CHAR, \
                 BLOB, CLOB, TIMESTAMP, FLOAT
 
 RESERVED_WORDS = \
 class LONG(sqltypes.Text):
     __visit_name__ = 'LONG'
 
+class DATE(sqltypes.DateTime):
+    """Provide the oracle DATE type.
+
+    This type has no special Python behavior, except that it subclasses
+    :class:`.types.DateTime`; this is to suit the fact that the Oracle
+    ``DATE`` type supports a time value.
+
+    .. versionadded:: 0.9.4
+
+    """
+    __visit_name__ = 'DATE'
+
+
+    def _compare_type_affinity(self, other):
+        return other._type_affinity in (sqltypes.DateTime, sqltypes.Date)
+
 
 class INTERVAL(sqltypes.TypeEngine):
     __visit_name__ = 'INTERVAL'
 colspecs = {
     sqltypes.Boolean: _OracleBoolean,
     sqltypes.Interval: INTERVAL,
+    sqltypes.DateTime: DATE
 }
 
 ischema_names = {

lib/sqlalchemy/dialects/oracle/cx_oracle.py

     colspecs = colspecs = {
         sqltypes.Numeric: _OracleNumeric,
         sqltypes.Date: _OracleDate,  # generic type, assume datetime.date is desired
-        oracle.DATE: oracle.DATE,  # non generic type - passthru
         sqltypes.LargeBinary: _OracleBinary,
         sqltypes.Boolean: oracle._OracleBoolean,
         sqltypes.Interval: _OracleInterval,

test/dialect/test_oracle.py

             (oracle.OracleRaw(), cx_oracle._OracleRaw),
             (String(), String),
             (VARCHAR(), cx_oracle._OracleString),
-            (DATE(), DATE),
+            (DATE(), cx_oracle._OracleDate),
+            (oracle.DATE(), oracle.DATE),
             (String(50), cx_oracle._OracleString),
             (Unicode(), cx_oracle._OracleNVarChar),
             (Text(), cx_oracle._OracleText),
         metadata = self.metadata
         Table(
             "date_types", metadata,
-            Column('d1', DATE),
-            Column('d2', TIMESTAMP),
-            Column('d3', TIMESTAMP(timezone=True)),
-            Column('d4', oracle.INTERVAL(second_precision=5)),
+            Column('d1', sqltypes.DATE),
+            Column('d2', oracle.DATE),
+            Column('d3', TIMESTAMP),
+            Column('d4', TIMESTAMP(timezone=True)),
+            Column('d5', oracle.INTERVAL(second_precision=5)),
         )
         metadata.create_all()
         m = MetaData(testing.db)
         t1 = Table(
             "date_types", m,
             autoload=True)
-        assert isinstance(t1.c.d1.type, DATE)
-        assert isinstance(t1.c.d2.type, TIMESTAMP)
-        assert not t1.c.d2.type.timezone
+        assert isinstance(t1.c.d1.type, oracle.DATE)
+        assert isinstance(t1.c.d1.type, DateTime)
+        assert isinstance(t1.c.d2.type, oracle.DATE)
+        assert isinstance(t1.c.d2.type, DateTime)
         assert isinstance(t1.c.d3.type, TIMESTAMP)
-        assert t1.c.d3.type.timezone
-        assert isinstance(t1.c.d4.type, oracle.INTERVAL)
+        assert not t1.c.d3.type.timezone
+        assert isinstance(t1.c.d4.type, TIMESTAMP)
+        assert t1.c.d4.type.timezone
+        assert isinstance(t1.c.d5.type, oracle.INTERVAL)
 
     def test_reflect_all_types_schema(self):
         types_table = Table('all_types', MetaData(testing.db),

test/engine/test_reflection.py

             users_v = Table("users_v", m2, autoload=True)
             addresses_v = Table("email_addresses_v", m2, autoload=True)
 
-            for c1, c2 in zip(users.c, users_v.c):
+            for c1, c2 in zip(users_v.c, users.c):
                 eq_(c1.name, c2.name)
                 self.assert_types_base(c1, c2)
 
-            for c1, c2 in zip(addresses.c, addresses_v.c):
+            for c1, c2 in zip(addresses_v.c, addresses.c):
                 eq_(c1.name, c2.name)
                 self.assert_types_base(c1, c2)
         finally: