method call counts when fetching columns that have no
type-level processing applied. Provides a 100% speed
improvement when fetching large result sets with no unicode
- conversion. Many thanks to Elixir's Gaëtan de Menten
+ conversion as tuples. Many thanks to Elixir's Gaëtan de Menten
for this dramatic improvement ! [ticket:1586]
- setting echo=False on create_engine() now sets the loglevel
- using new dialect.initialize() feature to set up
version-dependent behavior.
-- new dialects
- - postgresql+pg8000
- - postgresql+pypostgresql (partial)
- - postgresql+zxjdbc
- - mysql+pyodbc
- - mysql+zxjdbc
-
- mssql
- MSSQL + Pyodbc + FreeTDS now works for the most part,
with possible exceptions regarding binary data as well as
enabled through the use of "default=Sequence()". See
the MSSQL dialect documentation for more information.
+- sqlite
+ - DATE, TIME and DATETIME types can now take optional storage_format and
+ regexp argument. storage_format can be used to store those types using
+ a custom string format. regexp allows to use a custom regular expression
+ to match string values from the database.
+ - Time and DateTime types now use by a default a stricter regular
+ expression to match strings from the database. Use the regexp argument
+ if you are using data stored in a legacy format.
+ - __legacy_microseconds__ on SQLite Time and DateTime types is not
+ supported anymore. You should use the storage_format argument instead.
+ - Date, Time and DateTime types are now stricter in what they accept as
+ bind parameters: Date type only accepts date objects (and datetime ones,
+ because they inherit from date), Time only accepts time objects, and
+ DateTime only accepts date and datetime objects.
+
+- new dialects
+ - postgresql+pg8000
+ - postgresql+pypostgresql (partial)
+ - postgresql+zxjdbc
+ - mysql+pyodbc
+ - mysql+zxjdbc
+
- types
- The construction of types within dialects has been totally
overhauled. Dialects now define publically available types
out of the box functionality for translating values between Python `datetime` objects
and a SQLite-supported format. SQLAlchemy's own :class:`~sqlalchemy.types.DateTime`
and related types provide date formatting and parsing functionality when SQlite is used.
-The implementation classes are :class:`_SLDateTime`, :class:`_SLDate` and :class:`_SLTime`.
+The implementation classes are :class:`DATETIME`, :class:`DATE` and :class:`TIME`.
These types represent dates and times as ISO formatted strings, which also nicely
support ordering. There's no reliance on typical "libc" internals for these functions
so historical dates are fully supported.
# or JDBC would similarly have no built in date support, so the "string" based logic
# would apply to all implementing dialects.
class _DateTimeMixin(object):
- def _bind_processor(self, format, elements):
- def process(value):
- if not isinstance(value, (NoneType, datetime.date, datetime.datetime, datetime.time)):
- raise TypeError("SQLite Date, Time, and DateTime types only accept Python datetime objects as input.")
- elif value is not None:
- return format % tuple([getattr(value, attr, 0) for attr in elements])
- else:
- return None
- return process
+ _reg = None
+ _storage_format = None
- def _result_processor(self, fn, regexp):
- rmatch = regexp.match
+ def __init__(self, storage_format=None, regexp=None, **kwargs):
+ if regexp is not None:
+ self._reg = re.compile(regexp)
+ if storage_format is not None:
+ self._storage_format = storage_format
+
+ def _result_processor(self, fn):
+ rmatch = self._reg.match
# Even on python2.6 datetime.strptime is both slower than this code
# and it does not support microseconds.
def process(value):
if value is not None:
- return fn(*[int(x or 0) for x in rmatch(value).groups()])
+ return fn(*map(int, rmatch(value).groups(0)))
else:
return None
return process
-class _SLDateTime(_DateTimeMixin, sqltypes.DateTime):
- __legacy_microseconds__ = False
-
+class DATETIME(_DateTimeMixin, sqltypes.DateTime):
+ _reg = re.compile(r"(\d+)-(\d+)-(\d+) (\d+):(\d+):(\d+)\.(\d+)")
+ _storage_format = "%04d-%02d-%02d %02d:%02d:%02d.%06d"
+
def bind_processor(self, dialect):
- if self.__legacy_microseconds__:
- return self._bind_processor(
- "%4.4d-%2.2d-%2.2d %2.2d:%2.2d:%2.2d.%s",
- ("year", "month", "day", "hour", "minute", "second", "microsecond")
- )
- else:
- return self._bind_processor(
- "%4.4d-%2.2d-%2.2d %2.2d:%2.2d:%2.2d.%06d",
- ("year", "month", "day", "hour", "minute", "second", "microsecond")
- )
+ datetime_datetime = datetime.datetime
+ datetime_date = datetime.date
+ format = self._storage_format
+ def process(value):
+ if value is None:
+ return None
+ elif isinstance(value, datetime_datetime):
+ return format % (value.year, value.month, value.day,
+ value.hour, value.minute, value.second,
+ value.microsecond)
+ elif isinstance(value, datetime_date):
+ return format % (value.year, value.month, value.day,
+ 0, 0, 0, 0)
+ else:
+ raise TypeError("SQLite DateTime type only accepts Python "
+ "datetime and date objects as input.")
+ return process
- _reg = re.compile(r"(\d+)-(\d+)-(\d+)(?: (\d+):(\d+):(\d+)(?:\.(\d+))?)?")
def result_processor(self, dialect, coltype):
- return self._result_processor(datetime.datetime, self._reg)
-
-class _SLDate(_DateTimeMixin, sqltypes.Date):
- def bind_processor(self, dialect):
- return self._bind_processor(
- "%4.4d-%2.2d-%2.2d",
- ("year", "month", "day")
- )
+ return self._result_processor(datetime.datetime)
+class DATE(_DateTimeMixin, sqltypes.Date):
_reg = re.compile(r"(\d+)-(\d+)-(\d+)")
+ _storage_format = "%04d-%02d-%02d"
+
+ def bind_processor(self, dialect):
+ datetime_date = datetime.date
+ format = self._storage_format
+ def process(value):
+ if value is None:
+ return None
+ elif isinstance(value, datetime_date):
+ return format % (value.year, value.month, value.day)
+ else:
+ raise TypeError("SQLite Date type only accepts Python "
+ "date objects as input.")
+ return process
+
def result_processor(self, dialect, coltype):
- return self._result_processor(datetime.date, self._reg)
+ return self._result_processor(datetime.date)
-class _SLTime(_DateTimeMixin, sqltypes.Time):
- __legacy_microseconds__ = False
+class TIME(_DateTimeMixin, sqltypes.Time):
+ _reg = re.compile(r"(\d+):(\d+):(\d+)\.(\d+)")
+ _storage_format = "%02d:%02d:%02d.%06d"
def bind_processor(self, dialect):
- if self.__legacy_microseconds__:
- return self._bind_processor(
- "%2.2d:%2.2d:%2.2d.%s",
- ("hour", "minute", "second", "microsecond")
- )
- else:
- return self._bind_processor(
- "%2.2d:%2.2d:%2.2d.%06d",
- ("hour", "minute", "second", "microsecond")
- )
-
- _reg = re.compile(r"(\d+):(\d+):(\d+)(?:\.(\d+))?")
+ datetime_time = datetime.time
+ format = self._storage_format
+ def process(value):
+ if value is None:
+ return None
+ elif isinstance(value, datetime_time):
+ return format % (value.hour, value.minute, value.second,
+ value.microsecond)
+ else:
+ raise TypeError("SQLite Time type only accepts Python "
+ "time objects as input.")
+ return process
+
def result_processor(self, dialect, coltype):
- return self._result_processor(datetime.time, self._reg)
-
+ return self._result_processor(datetime.time)
class _SLBoolean(sqltypes.Boolean):
def bind_processor(self, dialect):
colspecs = {
sqltypes.Boolean: _SLBoolean,
- sqltypes.Date: _SLDate,
- sqltypes.DateTime: _SLDateTime,
+ sqltypes.Date: DATE,
+ sqltypes.DateTime: DATETIME,
sqltypes.Float: _SLFloat,
sqltypes.Numeric: _SLNumeric,
- sqltypes.Time: _SLTime,
+ sqltypes.Time: TIME,
}
ischema_names = {