From: Mike Bayer Date: Wed, 8 Jul 2009 21:10:37 +0000 (+0000) Subject: continued strictness/documentation of the type system X-Git-Tag: rel_0_6_6~143 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a309c15cb5a8aa8e2af61b479a5ee69281b3109f;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git continued strictness/documentation of the type system --- diff --git a/lib/sqlalchemy/dialects/mssql/base.py b/lib/sqlalchemy/dialects/mssql/base.py index 8d695f9881..2d8b31bcf2 100644 --- a/lib/sqlalchemy/dialects/mssql/base.py +++ b/lib/sqlalchemy/dialects/mssql/base.py @@ -229,7 +229,7 @@ from sqlalchemy.engine import default, base, reflection from sqlalchemy import types as sqltypes from decimal import Decimal as _python_Decimal from sqlalchemy.types import INTEGER, BIGINT, SMALLINT, DECIMAL, NUMERIC, \ - FLOAT, TIMESTAMP + FLOAT, TIMESTAMP, DATETIME, DATE from sqlalchemy.dialects.mssql import information_schema as ischema @@ -270,7 +270,7 @@ RESERVED_WORDS = set( ]) -class MSNumeric(sqltypes.Numeric): +class _MSNumeric(sqltypes.Numeric): def result_processor(self, dialect): if self.asdecimal: def process(value): @@ -324,19 +324,17 @@ class REAL(sqltypes.Float): def __init__(self): super(REAL, self).__init__(precision=24) -MSReal = REAL class TINYINT(sqltypes.Integer): __visit_name__ = 'TINYINT' -MSTinyInteger = TINYINT # MSSQL DATE/TIME types have varied behavior, sometimes returning -# strings. MSDate/MSTime check for everything, and always +# strings. MSDate/TIME check for everything, and always # filter bind parameters into datetime objects (required by pyodbc, # not sure about other dialects). -class DATE(sqltypes.DATE): +class _MSDate(sqltypes.Date): def bind_processor(self, dialect): def process(value): if type(value) == datetime.date: @@ -355,7 +353,6 @@ class DATE(sqltypes.DATE): else: return value return process -MSDate = DATE class TIME(sqltypes.TIME): def __init__(self, precision=None, **kwargs): @@ -384,7 +381,6 @@ class TIME(sqltypes.TIME): return value return process -MSTime = TIME class _DateTimeBase(object): def bind_processor(self, dialect): @@ -396,20 +392,17 @@ class _DateTimeBase(object): return value return process -class DATETIME(_DateTimeBase, sqltypes.DATETIME): +class _MSDateTime(_DateTimeBase, sqltypes.DateTime): pass -MSDateTime = DATETIME class SMALLDATETIME(_DateTimeBase, sqltypes.DateTime): __visit_name__ = 'SMALLDATETIME' -MSSmallDateTime = SMALLDATETIME class DATETIME2(_DateTimeBase, sqltypes.DateTime): __visit_name__ = 'DATETIME2' def __init__(self, precision=None, **kwargs): self.precision = precision -MSDateTime2 = DATETIME2 # TODO: is this not an Interval ? @@ -418,7 +411,6 @@ class DATETIMEOFFSET(sqltypes.TypeEngine): def __init__(self, precision=None, **kwargs): self.precision = precision -MSDateTimeOffset = DATETIMEOFFSET class _StringType(object): @@ -454,7 +446,6 @@ class TEXT(_StringType, sqltypes.TEXT): collation = kw.pop('collation', None) _StringType.__init__(self, collation) sqltypes.Text.__init__(self, *args, **kw) -MSText = TEXT class NTEXT(_StringType, sqltypes.UnicodeText): """MSSQL NTEXT type, for variable-length unicode text up to 2^30 @@ -473,7 +464,6 @@ class NTEXT(_StringType, sqltypes.UnicodeText): _StringType.__init__(self, collation) length = kwargs.pop('length', None) sqltypes.UnicodeText.__init__(self, length, **kwargs) -MSNText = NTEXT class VARCHAR(_StringType, sqltypes.VARCHAR): @@ -514,7 +504,6 @@ class VARCHAR(_StringType, sqltypes.VARCHAR): collation = kw.pop('collation', None) _StringType.__init__(self, collation) sqltypes.VARCHAR.__init__(self, *args, **kw) -MSString = VARCHAR class NVARCHAR(_StringType, sqltypes.NVARCHAR): """MSSQL NVARCHAR type. @@ -533,7 +522,6 @@ class NVARCHAR(_StringType, sqltypes.NVARCHAR): collation = kw.pop('collation', None) _StringType.__init__(self, collation) sqltypes.NVARCHAR.__init__(self, *args, **kw) -MSNVarchar = NVARCHAR class CHAR(_StringType, sqltypes.CHAR): """MSSQL CHAR type, for fixed-length non-Unicode data with a maximum @@ -573,7 +561,6 @@ class CHAR(_StringType, sqltypes.CHAR): collation = kw.pop('collation', None) _StringType.__init__(self, collation) sqltypes.CHAR.__init__(self, *args, **kw) -MSChar = CHAR class NCHAR(_StringType, sqltypes.NCHAR): """MSSQL NCHAR type. @@ -592,25 +579,20 @@ class NCHAR(_StringType, sqltypes.NCHAR): collation = kw.pop('collation', None) _StringType.__init__(self, collation) sqltypes.NCHAR.__init__(self, *args, **kw) -MSNChar = NCHAR class BINARY(sqltypes.Binary): __visit_name__ = 'BINARY' -MSBinary = BINARY class VARBINARY(sqltypes.Binary): __visit_name__ = 'VARBINARY' -MSVarBinary = VARBINARY class IMAGE(sqltypes.Binary): __visit_name__ = 'IMAGE' -MSImage = IMAGE class BIT(sqltypes.TypeEngine): __visit_name__ = 'BIT' -MSBit = BIT -class MSBoolean(sqltypes.Boolean): +class _MSBoolean(sqltypes.Boolean): def result_processor(self, dialect): def process(value): if value is None: @@ -632,20 +614,83 @@ class MSBoolean(sqltypes.Boolean): class MONEY(sqltypes.TypeEngine): __visit_name__ = 'MONEY' -MSMoney = MONEY -class SMALLMONEY(MSMoney): +class SMALLMONEY(sqltypes.TypeEngine): __visit_name__ = 'SMALLMONEY' -MSSmallMoney = SMALLMONEY class UNIQUEIDENTIFIER(sqltypes.TypeEngine): __visit_name__ = "UNIQUEIDENTIFIER" -MSUniqueIdentifier = UNIQUEIDENTIFIER class SQL_VARIANT(sqltypes.TypeEngine): __visit_name__ = 'SQL_VARIANT' + +# old names. +MSNumeric = _MSNumeric +MSDateTime = _MSDateTime +MSDate = _MSDate +MSBoolean = _MSBoolean +MSReal = REAL +MSTinyInteger = TINYINT +MSTime = TIME +MSSmallDateTime = SMALLDATETIME +MSDateTime2 = DATETIME2 +MSDateTimeOffset = DATETIMEOFFSET +MSText = TEXT +MSNText = NTEXT +MSString = VARCHAR +MSNVarchar = NVARCHAR +MSChar = CHAR +MSNChar = NCHAR +MSBinary = BINARY +MSVarBinary = VARBINARY +MSImage = IMAGE +MSBit = BIT +MSMoney = MONEY +MSSmallMoney = SMALLMONEY +MSUniqueIdentifier = UNIQUEIDENTIFIER MSVariant = SQL_VARIANT +colspecs = { + sqltypes.Numeric : _MSNumeric, + sqltypes.DateTime : _MSDateTime, + sqltypes.Date : _MSDate, + sqltypes.Time : TIME, + sqltypes.Boolean : _MSBoolean, +} + +ischema_names = { + 'int' : INTEGER, + 'bigint': BIGINT, + 'smallint' : SMALLINT, + 'tinyint' : TINYINT, + 'varchar' : VARCHAR, + 'nvarchar' : NVARCHAR, + 'char' : CHAR, + 'nchar' : NCHAR, + 'text' : TEXT, + 'ntext' : NTEXT, + 'decimal' : DECIMAL, + 'numeric' : NUMERIC, + 'float' : FLOAT, + 'datetime' : DATETIME, + 'datetime2' : DATETIME2, + 'datetimeoffset' : DATETIMEOFFSET, + 'date': DATE, + 'time': TIME, + 'smalldatetime' : SMALLDATETIME, + 'binary' : BINARY, + 'varbinary' : VARBINARY, + 'bit': BIT, + 'real' : REAL, + 'image' : IMAGE, + 'timestamp': TIMESTAMP, + 'money': MONEY, + 'smallmoney': SMALLMONEY, + 'uniqueidentifier': UNIQUEIDENTIFIER, + 'sql_variant': SQL_VARIANT, +} + + class MSTypeCompiler(compiler.GenericTypeCompiler): def _extend(self, spec, type_): """Extend a string-type declaration with standard SQL @@ -843,46 +888,6 @@ class MSExecutionContext(default.DefaultExecutionContext): pass -colspecs = { - sqltypes.Numeric : MSNumeric, - sqltypes.DateTime : DATETIME, - sqltypes.Date : DATE, - sqltypes.Time : TIME, - sqltypes.Boolean : MSBoolean, -} - -ischema_names = { - 'int' : INTEGER, - 'bigint': BIGINT, - 'smallint' : SMALLINT, - 'tinyint' : TINYINT, - 'varchar' : VARCHAR, - 'nvarchar' : NVARCHAR, - 'char' : CHAR, - 'nchar' : NCHAR, - 'text' : TEXT, - 'ntext' : NTEXT, - 'decimal' : DECIMAL, - 'numeric' : NUMERIC, - 'float' : FLOAT, - 'datetime' : DATETIME, - 'datetime2' : DATETIME2, - 'datetimeoffset' : DATETIMEOFFSET, - 'date': DATE, - 'time': TIME, - 'smalldatetime' : SMALLDATETIME, - 'binary' : BINARY, - 'varbinary' : VARBINARY, - 'bit': BIT, - 'real' : REAL, - 'image' : IMAGE, - 'timestamp': TIMESTAMP, - 'money': MONEY, - 'smallmoney': SMALLMONEY, - 'uniqueidentifier': UNIQUEIDENTIFIER, - 'sql_variant': SQL_VARIANT, -} - class MSSQLCompiler(compiler.SQLCompiler): extract_map = compiler.SQLCompiler.extract_map.copy() diff --git a/lib/sqlalchemy/dialects/mysql/base.py b/lib/sqlalchemy/dialects/mysql/base.py index 3ea033fb52..0ebaeca0dc 100644 --- a/lib/sqlalchemy/dialects/mysql/base.py +++ b/lib/sqlalchemy/dialects/mysql/base.py @@ -190,7 +190,7 @@ from sqlalchemy.engine import reflection from sqlalchemy.engine import base as engine_base, default from sqlalchemy import types as sqltypes -from sqlalchemy.types import DATE, DATETIME +from sqlalchemy.types import DATE, DATETIME, BOOLEAN, TIME RESERVED_WORDS = set( ['accessible', 'add', 'all', 'alter', 'analyze','and', 'as', 'asc', @@ -580,7 +580,7 @@ class BIT(sqltypes.TypeEngine): return value return process -class TIME(sqltypes.TIME): +class _MSTime(sqltypes.Time): """MySQL TIME type.""" __visit_name__ = 'TIME' @@ -1094,7 +1094,7 @@ class SET(_StringType): return value return process -class BOOLEAN(sqltypes.Boolean): +class _MSBoolean(sqltypes.Boolean): """MySQL BOOLEAN type.""" __visit_name__ = 'BOOLEAN' @@ -1119,7 +1119,8 @@ class BOOLEAN(sqltypes.Boolean): return process # old names -MSBoolean = BOOLEAN +MSBoolean = _MSBoolean +MSTime = _MSTime MSSet = SET MSEnum = ENUM MSLongBlob = LONGBLOB @@ -1138,7 +1139,6 @@ MSTinyText = TINYTEXT MSText = TEXT MSYear = YEAR MSTimeStamp = TIMESTAMP -MSTime = TIME MSBit = BIT MSSmallInteger = SMALLINT MSTinyInteger = TINYINT @@ -1155,8 +1155,8 @@ colspecs = { sqltypes.Numeric: NUMERIC, sqltypes.Float: FLOAT, sqltypes.Binary: _BinaryType, - sqltypes.Boolean: BOOLEAN, - sqltypes.Time: TIME, + sqltypes.Boolean: _MSBoolean, + sqltypes.Time: _MSTime, } # Everything 3.23 through 5.1 excepting OpenGIS types. diff --git a/lib/sqlalchemy/dialects/oracle/base.py b/lib/sqlalchemy/dialects/oracle/base.py index 36ee65be1d..3140a63d5b 100644 --- a/lib/sqlalchemy/dialects/oracle/base.py +++ b/lib/sqlalchemy/dialects/oracle/base.py @@ -144,7 +144,7 @@ class DOUBLE_PRECISION(sqltypes.Numeric): class LONG(sqltypes.Text): __visit_name__ = 'LONG' -class OracleBoolean(sqltypes.Boolean): +class _OracleBoolean(sqltypes.Boolean): def result_processor(self, dialect): def process(value): if value is None: @@ -165,7 +165,7 @@ class OracleBoolean(sqltypes.Boolean): return process colspecs = { - sqltypes.Boolean : OracleBoolean, + sqltypes.Boolean : _OracleBoolean, } ischema_names = { diff --git a/lib/sqlalchemy/dialects/oracle/cx_oracle.py b/lib/sqlalchemy/dialects/oracle/cx_oracle.py index c83ee69dd5..6f46224dcb 100644 --- a/lib/sqlalchemy/dialects/oracle/cx_oracle.py +++ b/lib/sqlalchemy/dialects/oracle/cx_oracle.py @@ -63,13 +63,13 @@ working successfully but this should be regarded as an experimental feature. """ -from sqlalchemy.dialects.oracle.base import OracleDialect, OracleRaw, OracleBoolean, RESERVED_WORDS +from sqlalchemy.dialects.oracle.base import OracleDialect, RAW, _OracleBoolean, RESERVED_WORDS from sqlalchemy.engine.default import DefaultExecutionContext from sqlalchemy.engine import base from sqlalchemy import types as sqltypes, util import datetime -class OracleDate(sqltypes.Date): +class _OracleDate(sqltypes.Date): def bind_processor(self, dialect): return None @@ -81,7 +81,7 @@ class OracleDate(sqltypes.Date): return value.date() return process -class OracleDateTime(sqltypes.DateTime): +class _OracleDateTime(sqltypes.DateTime): def result_processor(self, dialect): def process(value): if value is None or isinstance(value, datetime.datetime): @@ -98,7 +98,7 @@ class OracleDateTime(sqltypes.DateTime): # Oracle does not support TIME columns # only if cx_oracle contains TIMESTAMP -class OracleTimestamp(sqltypes.TIMESTAMP): +class _OracleTimestamp(sqltypes.TIMESTAMP): def result_processor(self, dialect): def process(value): if value is None or isinstance(value, datetime.datetime): @@ -109,9 +109,9 @@ class OracleTimestamp(sqltypes.TIMESTAMP): value.day,value.hour, value.minute, value.second) return process -class LOBMixin(object): +class _LOBMixin(object): def result_processor(self, dialect): - super_process = super(LOBMixin, self).result_processor(dialect) + super_process = super(_LOBMixin, self).result_processor(dialect) if not dialect.auto_convert_lobs: return super_process lob = dialect.dbapi.LOB @@ -128,16 +128,16 @@ class LOBMixin(object): return value return process -class OracleText(LOBMixin, sqltypes.Text): +class _OracleText(_LOBMixin, sqltypes.Text): def get_dbapi_type(self, dbapi): return dbapi.CLOB -class OracleUnicodeText(LOBMixin, sqltypes.UnicodeText): +class _OracleUnicodeText(_LOBMixin, sqltypes.UnicodeText): def get_dbapi_type(self, dbapi): return dbapi.NCLOB -class OracleBinary(LOBMixin, sqltypes.Binary): +class _OracleBinary(_LOBMixin, sqltypes.Binary): def get_dbapi_type(self, dbapi): return dbapi.BLOB @@ -145,19 +145,19 @@ class OracleBinary(LOBMixin, sqltypes.Binary): return None -class cxOracleRaw(LOBMixin, OracleRaw): +class _OracleRaw(_LOBMixin, RAW): pass colspecs = { - sqltypes.DateTime : OracleDateTime, - sqltypes.Date : OracleDate, - sqltypes.Binary : OracleBinary, - sqltypes.Boolean : OracleBoolean, - sqltypes.Text : OracleText, - sqltypes.UnicodeText : OracleUnicodeText, - sqltypes.TIMESTAMP : OracleTimestamp, - OracleRaw: cxOracleRaw, + sqltypes.DateTime : _OracleDateTime, + sqltypes.Date : _OracleDate, + sqltypes.Binary : _OracleBinary, + sqltypes.Boolean : _OracleBoolean, + sqltypes.Text : _OracleText, + sqltypes.UnicodeText : _OracleUnicodeText, + sqltypes.TIMESTAMP : _OracleTimestamp, + RAW: _OracleRaw, } class Oracle_cx_oracleExecutionContext(DefaultExecutionContext): diff --git a/lib/sqlalchemy/dialects/postgres/pg8000.py b/lib/sqlalchemy/dialects/postgres/pg8000.py index d42fd937c9..0dd166a9d8 100644 --- a/lib/sqlalchemy/dialects/postgres/pg8000.py +++ b/lib/sqlalchemy/dialects/postgres/pg8000.py @@ -24,7 +24,7 @@ from sqlalchemy import util from sqlalchemy import types as sqltypes from sqlalchemy.dialects.postgres.base import PGDialect, PGCompiler -class PGNumeric(sqltypes.Numeric): +class _PGNumeric(sqltypes.Numeric): def bind_processor(self, dialect): return None @@ -62,8 +62,8 @@ class Postgres_pg8000(PGDialect): colspecs = util.update_copy( PGDialect.colspecs, { - sqltypes.Numeric : PGNumeric, - sqltypes.Float: sqltypes.Float, # prevents PGNumeric from being used + sqltypes.Numeric : _PGNumeric, + sqltypes.Float: sqltypes.Float, # prevents _PGNumeric from being used } ) diff --git a/lib/sqlalchemy/dialects/postgres/psycopg2.py b/lib/sqlalchemy/dialects/postgres/psycopg2.py index a3ffdd84bf..9f5ea56868 100644 --- a/lib/sqlalchemy/dialects/postgres/psycopg2.py +++ b/lib/sqlalchemy/dialects/postgres/psycopg2.py @@ -44,7 +44,7 @@ from sqlalchemy.sql import operators as sql_operators from sqlalchemy import types as sqltypes from sqlalchemy.dialects.postgres.base import PGDialect, PGCompiler -class PGNumeric(sqltypes.Numeric): +class _PGNumeric(sqltypes.Numeric): def bind_processor(self, dialect): return None @@ -111,8 +111,8 @@ class Postgres_psycopg2(PGDialect): colspecs = util.update_copy( PGDialect.colspecs, { - sqltypes.Numeric : PGNumeric, - sqltypes.Float: sqltypes.Float, # prevents PGNumeric from being used + sqltypes.Numeric : _PGNumeric, + sqltypes.Float: sqltypes.Float, # prevents _PGNumeric from being used } ) diff --git a/lib/sqlalchemy/dialects/sqlite/base.py b/lib/sqlalchemy/dialects/sqlite/base.py index 40c140569a..1b260aa686 100644 --- a/lib/sqlalchemy/dialects/sqlite/base.py +++ b/lib/sqlalchemy/dialects/sqlite/base.py @@ -15,7 +15,7 @@ SQLite does not have built-in DATE, TIME, or DATETIME types, and pysqlite does n 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:`_SLDateTime`, :class:`_SLDate` and :class:`_SLTime`. 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. @@ -34,7 +34,12 @@ from sqlalchemy import util from sqlalchemy.sql import compiler, functions as sql_functions from sqlalchemy.util import NoneType -class NumericMixin(object): +from sqlalchemy.types import BLOB, BOOLEAN, CHAR, DATE, DATETIME, DECIMAL,\ + FLOAT, INTEGER, NUMERIC, SMALLINT, TEXT, TIME,\ + TIMESTAMP, VARCHAR + + +class _NumericMixin(object): def bind_processor(self, dialect): type_ = self.asdecimal and str or float def process(value): @@ -44,16 +49,16 @@ class NumericMixin(object): return value return process -class SLNumeric(NumericMixin, sqltypes.Numeric): +class _SLNumeric(_NumericMixin, sqltypes.Numeric): pass -class SLFloat(NumericMixin, sqltypes.Float): +class _SLFloat(_NumericMixin, sqltypes.Float): pass # since SQLite has no date types, we're assuming that SQLite via ODBC # or JDBC would similarly have no built in date support, so the "string" based logic # would apply to all implementing dialects. -class DateTimeMixin(object): +class _DateTimeMixin(object): def _bind_processor(self, format, elements): def process(value): if not isinstance(value, (NoneType, datetime.date, datetime.datetime, datetime.time)): @@ -72,7 +77,7 @@ class DateTimeMixin(object): return None return process -class SLDateTime(DateTimeMixin, sqltypes.DateTime): +class _SLDateTime(_DateTimeMixin, sqltypes.DateTime): __legacy_microseconds__ = False def bind_processor(self, dialect): @@ -91,7 +96,7 @@ class SLDateTime(DateTimeMixin, sqltypes.DateTime): def result_processor(self, dialect): return self._result_processor(datetime.datetime, self._reg) -class SLDate(DateTimeMixin, sqltypes.Date): +class _SLDate(_DateTimeMixin, sqltypes.Date): def bind_processor(self, dialect): return self._bind_processor( "%4.4d-%2.2d-%2.2d", @@ -102,7 +107,7 @@ class SLDate(DateTimeMixin, sqltypes.Date): def result_processor(self, dialect): return self._result_processor(datetime.date, self._reg) -class SLTime(DateTimeMixin, sqltypes.Time): +class _SLTime(_DateTimeMixin, sqltypes.Time): __legacy_microseconds__ = False def bind_processor(self, dialect): @@ -122,7 +127,7 @@ class SLTime(DateTimeMixin, sqltypes.Time): return self._result_processor(datetime.time, self._reg) -class SLBoolean(sqltypes.Boolean): +class _SLBoolean(sqltypes.Boolean): def bind_processor(self, dialect): def process(value): if value is None: @@ -138,12 +143,12 @@ class SLBoolean(sqltypes.Boolean): return process colspecs = { - sqltypes.Boolean: SLBoolean, - sqltypes.Date: SLDate, - sqltypes.DateTime: SLDateTime, - sqltypes.Float: SLFloat, - sqltypes.Numeric: SLNumeric, - sqltypes.Time: SLTime, + sqltypes.Boolean: _SLBoolean, + sqltypes.Date: _SLDate, + sqltypes.DateTime: _SLDateTime, + sqltypes.Float: _SLFloat, + sqltypes.Numeric: _SLNumeric, + sqltypes.Time: _SLTime, } ischema_names = { diff --git a/lib/sqlalchemy/dialects/type_migration_guidelines.txt b/lib/sqlalchemy/dialects/type_migration_guidelines.txt index 335311234f..3ee439dd87 100644 --- a/lib/sqlalchemy/dialects/type_migration_guidelines.txt +++ b/lib/sqlalchemy/dialects/type_migration_guidelines.txt @@ -16,58 +16,78 @@ Rules for Migrating TypeEngine classes to 0.6 d. If a TypeEngine class doesn't provide any of this, it should be *removed* from the dialect. -2. the TypeEngine classes are *no longer* used for: +2. the TypeEngine classes are *no longer* used for generating DDL. Dialects +now have a TypeCompiler subclass which uses the same visit_XXX model as +other compilers. - a. generating DDL - 3. the "ischema_names" and "colspecs" dictionaries are now required members on the Dialect class. 4. The names of types within dialects are now important. If a dialect-specific type is a subclass of an existing generic type and is only provided for bind/result behavior, -the current mixed case naming can remain, i.e. PGNumeric for Numeric - in this case, -end users would never need to use PGNumeric directly. However, if a dialect-specific +the current mixed case naming can remain, i.e. _PGNumeric for Numeric - in this case, +end users would never need to use _PGNumeric directly. However, if a dialect-specific type is specifying a type *or* arguments that are not present generically, it should match the real name of the type on that backend, in uppercase. E.g. postgres.INET, mysql.ENUM, postgres.ARRAY. Or follow this handy flowchart: - is the type in types.py as an UPPERCASE type ? - | | - | yes ---> does your type need special behavior or arguments ? no ----> don't make a - | ^ yes type, make sure the dialect's - | no | base.py imports the types.py - | | | UPPERCASE name into its namespace - v is it v - no -------- a native yes -----> build new type - DB type? | - v - is this type native to the database ? - (i.e. BIT is native to MySQL, "Boolean" is *not* native to Oracle) - yes no - | | - v v - name the type THE SAME name the type using - as that of the DB, MixedCase, i.e. - using UPPERCASE OracleBoolean - (i.e. BIT, NCHAR, INTERVAL) | - | | - +----------------+----------------+ - | - v - the type should is the name of this type - subclass the <----- yes identical to an UPPERCASE name - UPPERCASE in types.py? - type in types.py no - (i.e. class BLOB(types.BLOB)) | - v - subclass the closest - MixedCase type types.py, - i.e. - class DATETIME2(types.DateTime), - class BIT(types.TypeEngine) + is the type meant to provide bind/result is the type the same name as an + behavior to a generic type (i.e. MixedCase) ---- no ---> UPPERCASE type in types.py ? + type in types.py ? | | + | no yes + yes | | + | | does your type need special + | +<--- yes --- behavior or arguments ? + | | | + | | no + name the type using | | + _MixedCase, i.e. v V + _OracleBoolean. it name the type don't make a + stays private to the dialect identically as that type, make sure the dialect's + and is invoked *only* via within the DB, base.py imports the types.py + the colspecs dict. using UPPERCASE UPPERCASE name into its namespace + | (i.e. BIT, NCHAR, INTERVAL). + | Users can import it. + | | + v v + subclass the closest is the name of this type + MixedCase type types.py, identical to an UPPERCASE + i.e. <--- no ------- name in types.py ? + class _DateTime(types.DateTime), + class DATETIME2(types.DateTime), | + class BIT(types.TypeEngine). yes + | + v + the type should + subclass the + UPPERCASE + type in types.py + (i.e. class BLOB(types.BLOB)) + + +Example 1. pysqlite needs bind/result processing for the DateTime type in types.py, +which applies to all DateTimes and subclasses. It's named _SLDateTime and +subclasses types.DateTime. + +Example 2. MS-SQL has a TIME type which takes a non-standard "precision" argument +that is rendered within DDL. So it's named TIME in the MS-SQL dialect's base.py, +and subclasses types.TIME. Users can then say mssql.TIME(precision=10). +Example 3. MS-SQL dialects also need special bind/result processing for date +But its DATE type doesn't render DDL differently than that of a plain +DATE, i.e. it takes no special arguments. Therefore we are just adding behavior +to types.Date, so it's named _MSDate in the MS-SQL dialect's base.py, and subclasses +types.Date. + +Example 4. MySQL has a SET type, there's no analogue for this in types.py. So +MySQL names it SET in the dialect's base.py, and it subclasses types.String, since +it ultimately deals with strings. + +Example 5. Postgresql has a DATETIME type. The DBAPIs handle dates correctly, +and no special arguments are used in PG's DDL beyond what types.py provides. +Postgresql dialect therefore imports types.DATETIME into its base.py. Ideally one should be able to specify a schema using names imported completely from a dialect, all matching the real name on that backend: @@ -95,20 +115,14 @@ linked to TypeEngine classes. a. The string name should be matched to the most specific type possible within sqlalchemy.types, unless there is no matching type within sqlalchemy.types in which case it points to a dialect type. *It doesn't matter* if the dialect has it's - own subclass of that type with special bind/result behavior - reflect to the generic type - as much as possible, since the correct bind/result behavior is always invoked when needed - regardless of the type being generic or dialect-specific. + own subclass of that type with special bind/result behavior - reflect to the types.py + UPPERCASE type as much as possible. With very few exceptions, all types + should reflect to an UPPERCASE type. b. If the dialect contains a matching dialect-specific type that takes extra arguments which the generic one does not, then point to the dialect-specific type. E.g. mssql.VARCHAR takes a "collation" parameter which should be preserved. - c. For an exact or almost exact match, point to the uppercase type. i.e. "float" - should point to "FLOAT", "varchar" should point to "VARCHAR" - - d. for a non-match, point to the lowercase type. i.e. "long" should point to "Text", - "special varchar with sprinkles" points to "String". - 5. DDL, or what was formerly issued by "get_col_spec()", is now handled exclusively by a subclass of compiler.GenericTypeCompiler. @@ -118,8 +132,8 @@ a subclass of compiler.GenericTypeCompiler. b. the visit_UPPERCASE methods on GenericTypeCompiler should *not* be overridden with methods that produce a different DDL name. Uppercase types don't do any kind of - "guessing" - if the user says he wants TIMESTAMP, that's the DDL which should render, - regardless of whether the DB accepts it. + "guessing" - if visit_TIMESTAMP is called, the DDL should render as TIMESTAMP in + all cases, regardless of whether or not that type is legal on the backend database. c. the visit_UPPERCASE methods *should* be overridden with methods that add additional arguments and flags to those types. @@ -127,6 +141,5 @@ a subclass of compiler.GenericTypeCompiler. d. the visit_lowercase methods are overridden to provide an interpretation of a generic type. E.g. visit_binary() might be overridden to say "return self.visit_BIT(type_)". - e. when overriding a visit_lowercase method to return a different type, it should not - represent the DDL string within the body of the method; it should call the appropriate - visit_UPPERCASE name. + e. visit_lowercase methods should *never* render strings directly - it should always + be via calling a visit_UPPERCASE() method. diff --git a/test/dialect/test_sqlite.py b/test/dialect/test_sqlite.py index 32f8a9e072..e93428eb6e 100644 --- a/test/dialect/test_sqlite.py +++ b/test/dialect/test_sqlite.py @@ -19,7 +19,7 @@ class TestTypes(TestBase, AssertsExecutionResults): meta = MetaData(testing.db) t = Table('bool_table', meta, Column('id', Integer, primary_key=True), - Column('boo', sqlite.SLBoolean)) + Column('boo', Boolean)) try: meta.create_all() @@ -39,7 +39,7 @@ class TestTypes(TestBase, AssertsExecutionResults): def test_time_microseconds(self): dt = datetime.datetime(2008, 6, 27, 12, 0, 0, 125) # 125 usec eq_(str(dt), '2008-06-27 12:00:00.000125') - sldt = sqlite.SLDateTime() + sldt = sqlite._SLDateTime() bp = sldt.bind_processor(None) eq_(bp(dt), '2008-06-27 12:00:00.000125') diff --git a/test/sql/test_select.py b/test/sql/test_select.py index 02632025c0..8aa62a288b 100644 --- a/test/sql/test_select.py +++ b/test/sql/test_select.py @@ -1370,7 +1370,7 @@ UNION SELECT mytable.myid FROM mytable WHERE mytable.myid = :myid_2)") (table1.c.name, 'name', 'mytable.name', None), (table1.c.myid==12, 'mytable.myid = :myid_1', 'mytable.myid = :myid_1', 'anon_1'), (func.hoho(table1.c.myid), 'hoho(mytable.myid)', 'hoho(mytable.myid)', 'hoho_1'), - (cast(table1.c.name, sqlite.SLNumeric), 'CAST(mytable.name AS NUMERIC)', 'CAST(mytable.name AS NUMERIC)', 'anon_1'), + (cast(table1.c.name, Numeric), 'CAST(mytable.name AS NUMERIC)', 'CAST(mytable.name AS NUMERIC)', 'anon_1'), (t1.c.col1, 'col1', 'mytable.col1', None), (column('some wacky thing'), 'some wacky thing', '"some wacky thing"', '') ): diff --git a/test/sql/test_types.py b/test/sql/test_types.py index 16a94110a8..821a386ffe 100644 --- a/test/sql/test_types.py +++ b/test/sql/test_types.py @@ -13,50 +13,6 @@ from sqlalchemy.test import * class AdaptTest(TestBase): - def testmsnvarchar(self): - # TODO: migrate these tests to dialect modules - - dialect = mssql.dialect() - # run the test twice to ensure the caching step works too - for x in range(0, 1): - col = Column('', Unicode(length=10)) - dialect_type = col.type.dialect_impl(dialect) - assert isinstance(dialect_type, mssql.MSNVarchar) - eq_(dialect.type_compiler.process(dialect_type), 'NVARCHAR(10)') - - def testmysqlbinary(self): - # TODO: migrate these tests to dialect modules - - dialect = mysql.MySQLDialect() - t1 = mysql.MSVarBinary - t2 = mysql.MSVarBinary() - assert isinstance(dialect.type_descriptor(t1), mysql.MSVarBinary) - assert isinstance(dialect.type_descriptor(t2), mysql.MSVarBinary) - - def teststringadapt(self): - """test that String with no size becomes TEXT, *all* others stay as varchar/String""" - - # TODO: migrate these tests to dialect modules - - postgres_dialect = postgres.PGDialect() - firebird_dialect = firebird.FBDialect() - - for dialect, start, test in [ - (postgres_dialect, String(), String), - (postgres_dialect, VARCHAR(), String), - (postgres_dialect, String(50), String), - (postgres_dialect, Unicode(), String), - (postgres_dialect, UnicodeText(), Text), - (postgres_dialect, NCHAR(), String), -# (firebird_dialect, String(), firebird.FBString), -# (firebird_dialect, VARCHAR(), firebird.FBString), -# (firebird_dialect, String(50), firebird.FBString), -# (firebird_dialect, Unicode(), firebird.FBString), -# (firebird_dialect, UnicodeText(), firebird.FBText), -# (firebird_dialect, NCHAR(), firebird.FBString), - ]: - assert isinstance(start.dialect_impl(dialect), test), "wanted %r got %r" % (test, start.dialect_impl(dialect)) - def test_uppercase_rendering(self): """Test that uppercase types from types.py always render as their type.