Skip to content

Commit 952383f

Browse files
committed
implement iso date literals for all backends
Added modified ISO-8601 rendering (i.e. ISO-8601 with the T converted to a space) when using ``literal_binds`` with the SQL compilers provided by the PostgreSQL, MySQL, MariaDB, MSSQL, Oracle dialects. For Oracle, the ISO format is wrapped inside of an appropriate TO_DATE() function call. Previously this rendering was not implemented for dialect-specific compilation. Fixes: sqlalchemy#5052 Change-Id: I7af15a51fedf5c5a8e76e645f7c3be997ece35f0
1 parent 4acf50c commit 952383f

File tree

13 files changed

+99
-69
lines changed

13 files changed

+99
-69
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.. change::
2+
:tags: usecase, datatypes
3+
:tickets: 5052
4+
5+
Added modified ISO-8601 rendering (i.e. ISO-8601 with the T converted to a
6+
space) when using ``literal_binds`` with the SQL compilers provided by the
7+
PostgreSQL, MySQL, MariaDB, MSSQL, Oracle dialects. For Oracle, the ISO
8+
format is wrapped inside of an appropriate TO_DATE() function call.
9+
Previously this rendering was not implemented for dialect-specific
10+
compilation.

lib/sqlalchemy/dialects/mssql/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -823,7 +823,6 @@ class MyTable(Base):
823823
from ... import Sequence
824824
from ... import sql
825825
from ... import text
826-
from ... import types as sqltypes
827826
from ... import util
828827
from ...engine import cursor as _cursor
829828
from ...engine import default
@@ -835,6 +834,7 @@ class MyTable(Base):
835834
from ...sql import func
836835
from ...sql import quoted_name
837836
from ...sql import roles
837+
from ...sql import sqltypes
838838
from ...sql import util as sql_util
839839
from ...sql._typing import is_sql_compiler
840840
from ...types import BIGINT

lib/sqlalchemy/dialects/mssql/pyodbc.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,7 @@ def provide_token(dialect, conn_rec, cargs, cparams):
310310
import re
311311
import struct
312312

313+
from .base import _MSDateTime
313314
from .base import BINARY
314315
from .base import DATETIMEOFFSET
315316
from .base import MSDialect
@@ -447,7 +448,7 @@ def process(value):
447448
return process
448449

449450

450-
class _ODBCDateTime(_ODBCDateTimeBindProcessor, sqltypes.DateTime):
451+
class _ODBCDateTime(_ODBCDateTimeBindProcessor, _MSDateTime):
451452
pass
452453

453454

lib/sqlalchemy/dialects/mysql/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1020,7 +1020,6 @@ class MyClass(Base):
10201020
from ... import log
10211021
from ... import schema as sa_schema
10221022
from ... import sql
1023-
from ... import types as sqltypes
10241023
from ... import util
10251024
from ...engine import default
10261025
from ...engine import reflection
@@ -1030,6 +1029,7 @@ class MyClass(Base):
10301029
from ...sql import functions
10311030
from ...sql import operators
10321031
from ...sql import roles
1032+
from ...sql import sqltypes
10331033
from ...sql import util as sql_util
10341034
from ...sql.sqltypes import Unicode
10351035
from ...types import BINARY

lib/sqlalchemy/dialects/mysql/types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
import datetime
99

1010
from ... import exc
11-
from ... import types as sqltypes
1211
from ... import util
12+
from ...sql import sqltypes
1313

1414

1515
class _NumericType:

lib/sqlalchemy/dialects/oracle/base.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -688,7 +688,28 @@ class LONG(sqltypes.Text):
688688
__visit_name__ = "LONG"
689689

690690

691-
class DATE(sqltypes.DateTime):
691+
class _OracleDateLiteralRender:
692+
def literal_processor(self, dialect):
693+
def process(value):
694+
if value is not None:
695+
if getattr(value, "microsecond", None):
696+
value = (
697+
f"""TO_TIMESTAMP"""
698+
f"""('{value.isoformat().replace("T", " ")}', """
699+
"""'YYYY-MM-DD HH24:MI:SS.FF')"""
700+
)
701+
else:
702+
value = (
703+
f"""TO_DATE"""
704+
f"""('{value.isoformat().replace("T", " ")}', """
705+
"""'YYYY-MM-DD HH24:MI:SS')"""
706+
)
707+
return value
708+
709+
return process
710+
711+
712+
class DATE(_OracleDateLiteralRender, sqltypes.DateTime):
692713
"""Provide the oracle DATE type.
693714
694715
This type has no special Python behavior, except that it subclasses
@@ -705,6 +726,10 @@ def _compare_type_affinity(self, other):
705726
return other._type_affinity in (sqltypes.DateTime, sqltypes.Date)
706727

707728

729+
class _OracleDate(_OracleDateLiteralRender, sqltypes.Date):
730+
pass
731+
732+
708733
class INTERVAL(sqltypes.NativeForEmulated, sqltypes._AbstractInterval):
709734
__visit_name__ = "INTERVAL"
710735

@@ -763,6 +788,7 @@ def get_dbapi_type(self, dbapi):
763788
sqltypes.Boolean: _OracleBoolean,
764789
sqltypes.Interval: INTERVAL,
765790
sqltypes.DateTime: DATE,
791+
sqltypes.Date: _OracleDate,
766792
}
767793

768794
ischema_names = {

lib/sqlalchemy/dialects/oracle/cx_oracle.py

Lines changed: 35 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -440,11 +440,11 @@ def _remove_clob(inputsizes, cursor, statement, parameters, context):
440440
from .base import OracleDialect
441441
from .base import OracleExecutionContext
442442
from ... import exc
443-
from ... import types as sqltypes
444443
from ... import util
445444
from ...engine import cursor as _cursor
446445
from ...engine import interfaces
447446
from ...engine import processors
447+
from ...sql import sqltypes
448448
from ...sql._typing import is_sql_compiler
449449

450450

@@ -563,7 +563,7 @@ class _OracleNUMBER(_OracleNumeric):
563563
is_number = True
564564

565565

566-
class _OracleDate(sqltypes.Date):
566+
class _CXOracleDate(oracle._OracleDate):
567567
def bind_processor(self, dialect):
568568
return None
569569

@@ -577,6 +577,10 @@ def process(value):
577577
return process
578578

579579

580+
class _CXOracleTIMESTAMP(oracle._OracleDateLiteralRender, sqltypes.TIMESTAMP):
581+
pass
582+
583+
580584
# TODO: the names used across CHAR / VARCHAR / NCHAR / NVARCHAR
581585
# here are inconsistent and not very good
582586
class _OracleChar(sqltypes.CHAR):
@@ -847,31 +851,35 @@ class OracleDialect_cx_oracle(OracleDialect):
847851

848852
driver = "cx_oracle"
849853

850-
colspecs = {
851-
sqltypes.Numeric: _OracleNumeric,
852-
sqltypes.Float: _OracleNumeric,
853-
oracle.BINARY_FLOAT: _OracleBINARY_FLOAT,
854-
oracle.BINARY_DOUBLE: _OracleBINARY_DOUBLE,
855-
sqltypes.Integer: _OracleInteger,
856-
oracle.NUMBER: _OracleNUMBER,
857-
sqltypes.Date: _OracleDate,
858-
sqltypes.LargeBinary: _OracleBinary,
859-
sqltypes.Boolean: oracle._OracleBoolean,
860-
sqltypes.Interval: _OracleInterval,
861-
oracle.INTERVAL: _OracleInterval,
862-
sqltypes.Text: _OracleText,
863-
sqltypes.String: _OracleString,
864-
sqltypes.UnicodeText: _OracleUnicodeTextCLOB,
865-
sqltypes.CHAR: _OracleChar,
866-
sqltypes.NCHAR: _OracleNChar,
867-
sqltypes.Enum: _OracleEnum,
868-
oracle.LONG: _OracleLong,
869-
oracle.RAW: _OracleRaw,
870-
sqltypes.Unicode: _OracleUnicodeStringCHAR,
871-
sqltypes.NVARCHAR: _OracleUnicodeStringNCHAR,
872-
oracle.NCLOB: _OracleUnicodeTextNCLOB,
873-
oracle.ROWID: _OracleRowid,
874-
}
854+
colspecs = OracleDialect.colspecs
855+
colspecs.update(
856+
{
857+
sqltypes.TIMESTAMP: _CXOracleTIMESTAMP,
858+
sqltypes.Numeric: _OracleNumeric,
859+
sqltypes.Float: _OracleNumeric,
860+
oracle.BINARY_FLOAT: _OracleBINARY_FLOAT,
861+
oracle.BINARY_DOUBLE: _OracleBINARY_DOUBLE,
862+
sqltypes.Integer: _OracleInteger,
863+
oracle.NUMBER: _OracleNUMBER,
864+
sqltypes.Date: _CXOracleDate,
865+
sqltypes.LargeBinary: _OracleBinary,
866+
sqltypes.Boolean: oracle._OracleBoolean,
867+
sqltypes.Interval: _OracleInterval,
868+
oracle.INTERVAL: _OracleInterval,
869+
sqltypes.Text: _OracleText,
870+
sqltypes.String: _OracleString,
871+
sqltypes.UnicodeText: _OracleUnicodeTextCLOB,
872+
sqltypes.CHAR: _OracleChar,
873+
sqltypes.NCHAR: _OracleNChar,
874+
sqltypes.Enum: _OracleEnum,
875+
oracle.LONG: _OracleLong,
876+
oracle.RAW: _OracleRaw,
877+
sqltypes.Unicode: _OracleUnicodeStringCHAR,
878+
sqltypes.NVARCHAR: _OracleUnicodeStringNCHAR,
879+
oracle.NCLOB: _OracleUnicodeTextNCLOB,
880+
oracle.ROWID: _OracleRowid,
881+
}
882+
)
875883

876884
execute_sequence_format = list
877885

lib/sqlalchemy/dialects/postgresql/pg8000.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,9 @@
108108
from .json import JSONB
109109
from .json import JSONPathType
110110
from ... import exc
111-
from ... import types as sqltypes
112111
from ... import util
113112
from ...engine import processors
113+
from ...sql import sqltypes
114114
from ...sql.elements import quoted_name
115115

116116

lib/sqlalchemy/dialects/postgresql/psycopg.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,9 @@
6969
from .json import JSONB
7070
from .json import JSONPathType
7171
from ... import pool
72-
from ... import types as sqltypes
7372
from ... import util
7473
from ...engine import AdaptedConnection
74+
from ...sql import sqltypes
7575
from ...util.concurrency import await_fallback
7676
from ...util.concurrency import await_only
7777

lib/sqlalchemy/engine/default.py

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,10 @@
4747
from .. import event
4848
from .. import exc
4949
from .. import pool
50-
from .. import types as sqltypes
5150
from .. import util
5251
from ..sql import compiler
5352
from ..sql import expression
53+
from ..sql import type_api
5454
from ..sql._typing import is_tuple_type
5555
from ..sql.compiler import DDLCompiler
5656
from ..sql.compiler import SQLCompiler
@@ -498,7 +498,7 @@ def type_descriptor(self, typeobj):
498498
and passes on to :func:`_types.adapt_type`.
499499
500500
"""
501-
return sqltypes.adapt_type(typeobj, self.colspecs)
501+
return type_api.adapt_type(typeobj, self.colspecs)
502502

503503
def has_index(self, connection, table_name, index_name, schema=None):
504504
if not self.has_table(connection, table_name, schema=schema):
@@ -746,26 +746,6 @@ def get_driver_connection(self, connection):
746746
return connection
747747

748748

749-
class _RendersLiteral:
750-
def literal_processor(self, dialect):
751-
def process(value):
752-
return "'%s'" % value
753-
754-
return process
755-
756-
757-
class _StrDateTime(_RendersLiteral, sqltypes.DateTime):
758-
pass
759-
760-
761-
class _StrDate(_RendersLiteral, sqltypes.Date):
762-
pass
763-
764-
765-
class _StrTime(_RendersLiteral, sqltypes.Time):
766-
pass
767-
768-
769749
class StrCompileDialect(DefaultDialect):
770750

771751
statement_compiler = compiler.StrSQLCompiler
@@ -787,12 +767,6 @@ class StrCompileDialect(DefaultDialect):
787767
supports_multivalues_insert = True
788768
supports_simple_order_by_label = True
789769

790-
colspecs = {
791-
sqltypes.DateTime: _StrDateTime,
792-
sqltypes.Date: _StrDate,
793-
sqltypes.Time: _StrTime,
794-
}
795-
796770

797771
class DefaultExecutionContext(ExecutionContext):
798772
isinsert = False

0 commit comments

Comments
 (0)