0001"""
0002Col -- SQLObject columns
0003
0004Note that each column object is named BlahBlahCol, and these are used
0005in class definitions.  But there's also a corresponding SOBlahBlahCol
0006object, which is used in SQLObject *classes*.
0007
0008An explanation: when a SQLObject subclass is created, the metaclass
0009looks through your class definition for any subclasses of Col.  It
0010collects them together, and indexes them to do all the database stuff
0011you like, like the magic attributes and whatnot.  It then asks the Col
0012object to create an SOCol object (usually a subclass, actually).  The
0013SOCol object contains all the interesting logic, as well as a record
0014of the attribute name you used and the class it is bound to (set by
0015the metaclass).
0016
0017So, in summary: Col objects are what you define, but SOCol objects
0018are what gets used.
0019"""
0020
0021from array import array
0022from itertools import count
0023import re
0024import sys
0025import time
0026try:
0027    import cPickle as pickle
0028except ImportError:
0029    import pickle
0030import weakref
0031from formencode import compound, validators
0032from classregistry import findClass
0033# Sadly the name "constraints" conflicts with many of the function
0034# arguments in this module, so we rename it:
0035import constraints as constrs
0036import converters
0037import sqlbuilder
0038from styles import capword
0039
0040NoDefault = sqlbuilder.NoDefault
0041
0042import datetime
0043datetime_available = True
0044
0045try:
0046    from mx import DateTime
0047except ImportError:
0048    try:
0049        import DateTime # old version of mxDateTime, or Zope's Version if we're running with Zope
0050    except ImportError:
0051        mxdatetime_available = False
0052    else:
0053        mxdatetime_available = True
0054else:
0055    mxdatetime_available = True
0056
0057DATETIME_IMPLEMENTATION = "datetime"
0058MXDATETIME_IMPLEMENTATION = "mxDateTime"
0059
0060if mxdatetime_available:
0061    if hasattr(DateTime, "Time"):
0062        DateTimeType = type(DateTime.now())
0063        TimeType = type(DateTime.Time())
0064    else: # Zope
0065        DateTimeType = type(DateTime.DateTime())
0066        TimeType = type(DateTime.DateTime.Time(DateTime.DateTime()))
0067
0068default_datetime_implementation = DATETIME_IMPLEMENTATION
0069
0070__all__ = ["datetime_available", "mxdatetime_available",
0071        "default_datetime_implementation", "DATETIME_IMPLEMENTATION"]
0072
0073if mxdatetime_available:
0074    __all__.append("MXDATETIME_IMPLEMENTATION")
0075
0076
0077def use_microseconds(use=True):
0078    if use:
0079        SODateTimeCol.datetimeFormat = '%Y-%m-%d %H:%M:%S.%f'
0080        SOTimeCol.timeFormat = '%H:%M:%S.%f'
0081        dt_types = [(datetime.datetime, converters.DateTimeConverterMS),
0082                    (datetime.time, converters.TimeConverterMS)]
0083    else:
0084        SODateTimeCol.datetimeFormat = '%Y-%m-%d %H:%M:%S'
0085        SOTimeCol.timeFormat = '%H:%M:%S'
0086        dt_types = [(datetime.datetime, converters.DateTimeConverter),
0087                    (datetime.time, converters.TimeConverter)]
0088    for dt_type, converter in dt_types:
0089        converters.registerConverter(dt_type, converter)
0090
0091
0092__all__.append("use_microseconds")
0093
0094
0095creationOrder = count()
0096
0097########################################
0098## Columns
0099########################################
0100
0101# Col is essentially a column definition, it doesn't have
0102# much logic to it.
0103class SOCol(object):
0104
0105    def __init__(self,
0106                 name,
0107                 soClass,
0108                 creationOrder,
0109                 dbName=None,
0110                 default=NoDefault,
0111                 defaultSQL=None,
0112                 foreignKey=None,
0113                 alternateID=False,
0114                 alternateMethodName=None,
0115                 constraints=None,
0116                 notNull=NoDefault,
0117                 notNone=NoDefault,
0118                 unique=NoDefault,
0119                 sqlType=None,
0120                 columnDef=None,
0121                 validator=None,
0122                 validator2=None,
0123                 immutable=False,
0124                 cascade=None,
0125                 lazy=False,
0126                 noCache=False,
0127                 forceDBName=False,
0128                 title=None,
0129                 tags=[],
0130                 origName=None,
0131                 dbEncoding=None,
0132                 extra_vars=None):
0133
0134        super(SOCol, self).__init__()
0135
0136        # This isn't strictly true, since we *could* use backquotes or
0137        # " or something (database-specific) around column names, but
0138        # why would anyone *want* to use a name like that?
0139        # @@: I suppose we could actually add backquotes to the
0140        # dbName if we needed to...
0141        if not forceDBName:
0142            assert sqlbuilder.sqlIdentifier(name), 'Name must be SQL-safe (letters, numbers, underscores): %s (or use forceDBName=True)'                  % repr(name)
0144        assert name != 'id', 'The column name "id" is reserved for SQLObject use (and is implicitly created).'
0145        assert name, "You must provide a name for all columns"
0146
0147        self.columnDef = columnDef
0148        self.creationOrder = creationOrder
0149
0150        self.immutable = immutable
0151
0152        # cascade can be one of:
0153        # None: no constraint is generated
0154        # True: a CASCADE constraint is generated
0155        # False: a RESTRICT constraint is generated
0156        # 'null': a SET NULL trigger is generated
0157        if isinstance(cascade, str):
0158            assert cascade == 'null', (
0159                "The only string value allowed for cascade is 'null' (you gave: %r)" % cascade)
0160        self.cascade = cascade
0161
0162        if not isinstance(constraints, (list, tuple)):
0163            constraints = [constraints]
0164        self.constraints = self.autoConstraints() + constraints
0165
0166        self.notNone = False
0167        if notNull is not NoDefault:
0168            self.notNone = notNull
0169            assert notNone is NoDefault or                      (not notNone) == (not notNull),                      "The notNull and notNone arguments are aliases, and must not conflict.  You gave notNull=%r, notNone=%r" % (notNull, notNone)
0172        elif notNone is not NoDefault:
0173            self.notNone = notNone
0174        if self.notNone:
0175            self.constraints = [constrs.notNull] + self.constraints
0176
0177        self.name = name
0178        self.soClass = soClass
0179        self._default = default
0180        self.defaultSQL = defaultSQL
0181        self.customSQLType = sqlType
0182
0183        # deal with foreign keys
0184        self.foreignKey = foreignKey
0185        if self.foreignKey:
0186            if origName is not None:
0187                idname = soClass.sqlmeta.style.instanceAttrToIDAttr(origName)
0188            else:
0189                idname = soClass.sqlmeta.style.instanceAttrToIDAttr(name)
0190            if self.name != idname:
0191                self.foreignName = self.name
0192                self.name = idname
0193            else:
0194                self.foreignName = soClass.sqlmeta.style.instanceIDAttrToAttr(self.name)
0195        else:
0196            self.foreignName = None
0197
0198        # if they don't give us a specific database name for
0199        # the column, we separate the mixedCase into mixed_case
0200        # and assume that.
0201        if dbName is None:
0202            self.dbName = soClass.sqlmeta.style.pythonAttrToDBColumn(self.name)
0203        else:
0204            self.dbName = dbName
0205
0206        # alternateID means that this is a unique column that
0207        # can be used to identify rows
0208        self.alternateID = alternateID
0209
0210        if unique is NoDefault:
0211            self.unique = alternateID
0212        else:
0213            self.unique = unique
0214        if self.unique and alternateMethodName is None:
0215            self.alternateMethodName = 'by' + capword(self.name)
0216        else:
0217            self.alternateMethodName = alternateMethodName
0218
0219        _validators = self.createValidators()
0220        if validator: _validators.append(validator)
0221        if validator2: _validators.insert(0, validator2)
0222        _vlen = len(_validators)
0223        if _vlen:
0224            for _validator in _validators:
0225                _validator.soCol=weakref.proxy(self)
0226        if _vlen == 0:
0227            self.validator = None # Set sef.{from,to}_python
0228        elif _vlen == 1:
0229            self.validator = _validators[0]
0230        elif _vlen > 1:
0231            self.validator = compound.All.join(_validators[0], *_validators[1:])
0232        self.noCache = noCache
0233        self.lazy = lazy
0234        # this is in case of ForeignKey, where we rename the column
0235        # and append an ID
0236        self.origName = origName or name
0237        self.title = title
0238        self.tags = tags
0239        self.dbEncoding = dbEncoding
0240
0241        if extra_vars:
0242            for name, value in extra_vars.items():
0243                setattr(self, name, value)
0244
0245    def _set_validator(self, value):
0246        self._validator = value
0247        if self._validator:
0248            self.to_python = self._validator.to_python
0249            self.from_python = self._validator.from_python
0250        else:
0251            self.to_python = None
0252            self.from_python = None
0253
0254    def _get_validator(self):
0255        return self._validator
0256
0257    validator = property(_get_validator, _set_validator)
0258
0259    def createValidators(self):
0260        """Create a list of validators for the column."""
0261        return []
0262
0263    def autoConstraints(self):
0264        return []
0265
0266    def _get_default(self):
0267        # A default can be a callback or a plain value,
0268        # here we resolve the callback
0269        if self._default is NoDefault:
0270            return NoDefault
0271        elif hasattr(self._default, '__sqlrepr__'):
0272            return self._default
0273        elif callable(self._default):
0274            return self._default()
0275        else:
0276            return self._default
0277    default = property(_get_default, None, None)
0278
0279    def _get_joinName(self):
0280        return self.soClass.sqlmeta.style.instanceIDAttrToAttr(self.name)
0281    joinName = property(_get_joinName, None, None)
0282
0283    def __repr__(self):
0284        r = '<%s %s' % (self.__class__.__name__, self.name)
0285        if self.default is not NoDefault:
0286            r += ' default=%s' % repr(self.default)
0287        if self.foreignKey:
0288            r += ' connected to %s' % self.foreignKey
0289        if self.alternateID:
0290            r += ' alternate ID'
0291        if self.notNone:
0292            r += ' not null'
0293        return r + '>'
0294
0295    def createSQL(self):
0296        return ' '.join([self._sqlType()] + self._extraSQL())
0297
0298    def _extraSQL(self):
0299        result = []
0300        if self.notNone or self.alternateID:
0301            result.append('NOT NULL')
0302        if self.unique or self.alternateID:
0303            result.append('UNIQUE')
0304        if self.defaultSQL is not None:
0305            result.append("DEFAULT %s" % self.defaultSQL)
0306        return result
0307
0308    def _sqlType(self):
0309        if self.customSQLType is None:
0310            raise ValueError, ("Col %s (%s) cannot be used for automatic "
0311                               "schema creation (too abstract)" %
0312                               (self.name, self.__class__))
0313        else:
0314            return self.customSQLType
0315
0316    def _mysqlType(self):
0317        return self._sqlType()
0318
0319    def _postgresType(self):
0320        return self._sqlType()
0321
0322    def _sqliteType(self):
0323        # SQLite is naturally typeless, so as a fallback it uses
0324        # no type.
0325        try:
0326            return self._sqlType()
0327        except ValueError:
0328            return ''
0329
0330    def _sybaseType(self):
0331        return self._sqlType()
0332
0333    def _mssqlType(self):
0334        return self._sqlType()
0335
0336    def _firebirdType(self):
0337        return self._sqlType()
0338
0339    def _maxdbType(self):
0340        return self._sqlType()
0341
0342    def mysqlCreateSQL(self, connection=None):
0343        self.connection = connection
0344        return ' '.join([self.dbName, self._mysqlType()] + self._extraSQL())
0345
0346    def postgresCreateSQL(self):
0347        return ' '.join([self.dbName, self._postgresType()] + self._extraSQL())
0348
0349    def sqliteCreateSQL(self):
0350        return ' '.join([self.dbName, self._sqliteType()] + self._extraSQL())
0351
0352    def sybaseCreateSQL(self):
0353        return ' '.join([self.dbName, self._sybaseType()] + self._extraSQL())
0354
0355    def mssqlCreateSQL(self, connection=None):
0356        self.connection = connection
0357        return ' '.join([self.dbName, self._mssqlType()] + self._extraSQL())
0358
0359    def firebirdCreateSQL(self):
0360        # Ian Sparks pointed out that fb is picky about the order
0361        # of the NOT NULL clause in a create statement.  So, we handle
0362        # them differently for Enum columns.
0363        if not isinstance(self, SOEnumCol):
0364            return ' '.join([self.dbName, self._firebirdType()] + self._extraSQL())
0365        else:
0366            return ' '.join([self.dbName] + [self._firebirdType()[0]] + self._extraSQL() + [self._firebirdType()[1]])
0367
0368    def maxdbCreateSQL(self):
0369       return ' '.join([self.dbName, self._maxdbType()] + self._extraSQL())
0370
0371    def __get__(self, obj, type=None):
0372        if obj is None:
0373            # class attribute, return the descriptor itself
0374            return self
0375        if obj.sqlmeta._obsolete:
0376            raise RuntimeError('The object <%s %s> is obsolete' % (
0377                obj.__class__.__name__, obj.id))
0378        if obj.sqlmeta.cacheColumns:
0379            columns = obj.sqlmeta._columnCache
0380            if columns is None:
0381                obj.sqlmeta.loadValues()
0382            try:
0383                return columns[name]
0384            except KeyError:
0385                return obj.sqlmeta.loadColumn(self)
0386        else:
0387            return obj.sqlmeta.loadColumn(self)
0388
0389    def __set__(self, obj, value):
0390        if self.immutable:
0391            raise AttributeError("The column %s.%s is immutable" %
0392                                 (obj.__class__.__name__,
0393                                  self.name))
0394        obj.sqlmeta.setColumn(self, value)
0395
0396    def __delete__(self, obj):
0397        raise AttributeError("I can't be deleted from %r" % obj)
0398
0399    def getDbEncoding(self, state, default='utf-8'):
0400        if self.dbEncoding:
0401            return self.dbEncoding
0402        dbEncoding = state.soObject.sqlmeta.dbEncoding
0403        if dbEncoding:
0404            return dbEncoding
0405        try:
0406            connection = state.connection or state.soObject._connection
0407        except AttributeError:
0408            dbEncoding = None
0409        else:
0410            dbEncoding = getattr(connection, "dbEncoding", None)
0411        if not dbEncoding:
0412            dbEncoding = default
0413        return dbEncoding
0414
0415
0416class Col(object):
0417
0418    baseClass = SOCol
0419
0420    def __init__(self, name=None, **kw):
0421        super(Col, self).__init__()
0422        self.__dict__['_name'] = name
0423        self.__dict__['_kw'] = kw
0424        self.__dict__['creationOrder'] = creationOrder.next()
0425        self.__dict__['_extra_vars'] = {}
0426
0427    def _set_name(self, value):
0428        assert self._name is None or self._name == value, (
0429            "You cannot change a name after it has already been set "
0430            "(from %s to %s)" % (self.name, value))
0431        self.__dict__['_name'] = value
0432
0433    def _get_name(self):
0434        return self._name
0435
0436    name = property(_get_name, _set_name)
0437
0438    def withClass(self, soClass):
0439        return self.baseClass(soClass=soClass, name=self._name,
0440                              creationOrder=self.creationOrder,
0441                              columnDef=self,
0442                              extra_vars=self._extra_vars,
0443                              **self._kw)
0444
0445    def __setattr__(self, var, value):
0446        if var == 'name':
0447            super(Col, self).__setattr__(var, value)
0448            return
0449        self._extra_vars[var] = value
0450
0451    def __repr__(self):
0452        return '<%s %s %s>' % (
0453            self.__class__.__name__, hex(abs(id(self)))[2:],
0454            self._name or '(unnamed)')
0455
0456
0457class SOValidator(validators.Validator):
0458    def getDbEncoding(self, state, default='utf-8'):
0459        try:
0460            return self.dbEncoding
0461        except AttributeError:
0462            return self.soCol.getDbEncoding(state, default=default)
0463
0464
0465class SOStringLikeCol(SOCol):
0466    """A common ancestor for SOStringCol and SOUnicodeCol"""
0467    def __init__(self, **kw):
0468        self.length = kw.pop('length', None)
0469        self.varchar = kw.pop('varchar', 'auto')
0470        self.char_binary = kw.pop('char_binary', None) # A hack for MySQL
0471        if not self.length:
0472            assert self.varchar == 'auto' or not self.varchar,                      "Without a length strings are treated as TEXT, not varchar"
0474            self.varchar = False
0475        elif self.varchar == 'auto':
0476            self.varchar = True
0477
0478        super(SOStringLikeCol, self).__init__(**kw)
0479
0480    def autoConstraints(self):
0481        constraints = [constrs.isString]
0482        if self.length is not None:
0483            constraints += [constrs.MaxLength(self.length)]
0484        return constraints
0485
0486    def _sqlType(self):
0487        if self.customSQLType is not None:
0488            return self.customSQLType
0489        if not self.length:
0490            return 'TEXT'
0491        elif self.varchar:
0492            return 'VARCHAR(%i)' % self.length
0493        else:
0494            return 'CHAR(%i)' % self.length
0495
0496    def _check_case_sensitive(self, db):
0497        if self.char_binary:
0498            raise ValueError, "%s does not support binary character columns" % db
0499
0500    def _mysqlType(self):
0501        type = self._sqlType()
0502        if self.char_binary:
0503            type += " BINARY"
0504        return type
0505
0506    def _postgresType(self):
0507        self._check_case_sensitive("PostgreSQL")
0508        return super(SOStringLikeCol, self)._postgresType()
0509
0510    def _sqliteType(self):
0511        self._check_case_sensitive("SQLite")
0512        return super(SOStringLikeCol, self)._sqliteType()
0513
0514    def _sybaseType(self):
0515        self._check_case_sensitive("SYBASE")
0516        type = self._sqlType()
0517        if not self.notNone and not self.alternateID:
0518            type += ' NULL'
0519        return type
0520
0521    def _mssqlType(self):
0522        if self.customSQLType is not None:
0523            return self.customSQLType
0524        if not self.length:
0525            if self.connection and self.connection.can_use_max_types():
0526                type = 'VARCHAR(MAX)'
0527            else:
0528                type = 'VARCHAR(4000)'
0529        elif self.varchar:
0530            type = 'VARCHAR(%i)' % self.length
0531        else:
0532            type = 'CHAR(%i)' % self.length
0533        if not self.notNone and not self.alternateID:
0534            type += ' NULL'
0535        return type
0536
0537    def _firebirdType(self):
0538        self._check_case_sensitive("FireBird")
0539        if not self.length:
0540            return 'BLOB SUB_TYPE TEXT'
0541        else:
0542            return self._sqlType()
0543
0544    def _maxdbType(self):
0545        self._check_case_sensitive("SAP DB/MaxDB")
0546        if not self.length:
0547            return 'LONG ASCII'
0548        else:
0549            return self._sqlType()
0550
0551
0552class StringValidator(SOValidator):
0553
0554    def to_python(self, value, state):
0555        if value is None:
0556            return None
0557        try:
0558            connection = state.connection or state.soObject._connection
0559            binaryType = connection._binaryType
0560        except AttributeError:
0561            binaryType = type(None) # Just a simple workaround
0562        dbEncoding = self.getDbEncoding(state, default='ascii')
0563        if isinstance(value, unicode):
0564            return value.encode(dbEncoding)
0565        if self.dataType and isinstance(value, self.dataType):
0566            return value
0567        if isinstance(value, (str, buffer, binaryType, sqlbuilder.SQLExpression)):
0568            return value
0569        if hasattr(value, '__unicode__'):
0570            return unicode(value).encode(dbEncoding)
0571        raise validators.Invalid("expected a str in the StringCol '%s', got %s %r instead" %               (self.name, type(value), value), value, state)
0573
0574    from_python = to_python
0575
0576class SOStringCol(SOStringLikeCol):
0577
0578    def createValidators(self, dataType=None):
0579        return [StringValidator(name=self.name, dataType=dataType)] +               super(SOStringCol, self).createValidators()
0581
0582class StringCol(Col):
0583    baseClass = SOStringCol
0584
0585
0586class NQuoted(sqlbuilder.SQLExpression):
0587    def __init__(self, value):
0588        assert isinstance(value, unicode)
0589        self.value = value
0590    def __hash__(self):
0591        return hash(self.value)
0592    def __sqlrepr__(self, db):
0593        assert db == 'mssql'
0594        return "N" + sqlbuilder.sqlrepr(self.value, db)
0595
0596class UnicodeStringValidator(SOValidator):
0597
0598    def to_python(self, value, state):
0599        if value is None:
0600            return None
0601        if isinstance(value, (unicode, sqlbuilder.SQLExpression)):
0602            return value
0603        if isinstance(value, str):
0604            return value.decode(self.getDbEncoding(state))
0605        if isinstance(value, array): # MySQL
0606            return value.tostring().decode(self.getDbEncoding(state))
0607        if hasattr(value, '__unicode__'):
0608            return unicode(value)
0609        raise validators.Invalid("expected a str or a unicode in the UnicodeCol '%s', got %s %r instead" %               (self.name, type(value), value), value, state)
0611
0612    def from_python(self, value, state):
0613        if value is None:
0614            return None
0615        if isinstance(value, (str, sqlbuilder.SQLExpression)):
0616            return value
0617        if isinstance(value, unicode):
0618            try:
0619                connection = state.connection or state.soObject._connection
0620            except AttributeError:
0621                pass
0622            else:
0623                if connection.dbName == 'mssql':
0624                    return NQuoted(value)
0625            return value.encode(self.getDbEncoding(state))
0626        if hasattr(value, '__unicode__'):
0627            return unicode(value).encode(self.getDbEncoding(state))
0628        raise validators.Invalid("expected a str or a unicode in the UnicodeCol '%s', got %s %r instead" %               (self.name, type(value), value), value, state)
0630
0631class SOUnicodeCol(SOStringLikeCol):
0632    def _mssqlType(self):
0633        if self.customSQLType is not None:
0634            return self.customSQLType
0635        return 'N' + super(SOUnicodeCol, self)._mssqlType()
0636
0637    def createValidators(self):
0638        return [UnicodeStringValidator(name=self.name)] +               super(SOUnicodeCol, self).createValidators()
0640
0641class UnicodeCol(Col):
0642    baseClass = SOUnicodeCol
0643
0644
0645class IntValidator(SOValidator):
0646
0647    def to_python(self, value, state):
0648        if value is None:
0649            return None
0650        if isinstance(value, (int, long, sqlbuilder.SQLExpression)):
0651            return value
0652        for converter, attr_name in (int, '__int__'), (long, '__long__'):
0653            if hasattr(value, attr_name):
0654                try:
0655                    return converter(value)
0656                except:
0657                    break
0658        raise validators.Invalid("expected an int in the IntCol '%s', got %s %r instead" %                   (self.name, type(value), value), value, state)
0660
0661    from_python = to_python
0662
0663class SOIntCol(SOCol):
0664    # 3-03 @@: support precision, maybe max and min directly
0665    def __init__(self, **kw):
0666        self.length = kw.pop('length', None)
0667        self.unsigned = bool(kw.pop('unsigned', None))
0668        self.zerofill = bool(kw.pop('zerofill', None))
0669        SOCol.__init__(self, **kw)
0670
0671    def autoConstraints(self):
0672        return [constrs.isInt]
0673
0674    def createValidators(self):
0675        return [IntValidator(name=self.name)] +               super(SOIntCol, self).createValidators()
0677
0678    def addSQLAttrs(self, str):
0679        _ret = str
0680        if str is None or len(str) < 1:
0681            return None
0682
0683        if self.length >= 1:
0684            _ret = "%s(%d)" % (_ret, self.length)
0685        if self.unsigned:
0686            _ret = _ret + " UNSIGNED"
0687        if self.zerofill:
0688            _ret = _ret + " ZEROFILL"
0689        return _ret
0690
0691    def _sqlType(self):
0692        return self.addSQLAttrs("INT")
0693
0694class IntCol(Col):
0695    baseClass = SOIntCol
0696
0697class SOTinyIntCol(SOIntCol):
0698    def _sqlType(self):
0699        return self.addSQLAttrs("TINYINT")
0700
0701class TinyIntCol(Col):
0702    baseClass = SOTinyIntCol
0703
0704class SOSmallIntCol(SOIntCol):
0705    def _sqlType(self):
0706        return self.addSQLAttrs("SMALLINT")
0707
0708class SmallIntCol(Col):
0709    baseClass = SOSmallIntCol
0710
0711class SOMediumIntCol(SOIntCol):
0712    def _sqlType(self):
0713        return self.addSQLAttrs("MEDIUMINT")
0714
0715class MediumIntCol(Col):
0716    baseClass = SOMediumIntCol
0717
0718class SOBigIntCol(SOIntCol):
0719    def _sqlType(self):
0720        return self.addSQLAttrs("BIGINT")
0721
0722class BigIntCol(Col):
0723    baseClass = SOBigIntCol
0724
0725
0726class BoolValidator(SOValidator):
0727
0728    def to_python(self, value, state):
0729        if value is None:
0730            return None
0731        if isinstance(value, (bool, sqlbuilder.SQLExpression)):
0732            return value
0733        if isinstance(value, (int, long)) or hasattr(value, '__nonzero__'):
0734            return bool(value)
0735        raise validators.Invalid("expected a bool or an int in the BoolCol '%s', got %s %r instead" %               (self.name, type(value), value), value, state)
0737
0738    from_python = to_python
0739
0740class SOBoolCol(SOCol):
0741    def autoConstraints(self):
0742        return [constrs.isBool]
0743
0744    def createValidators(self):
0745        return [BoolValidator(name=self.name)] +               super(SOBoolCol, self).createValidators()
0747
0748    def _postgresType(self):
0749        return 'BOOL'
0750
0751    def _mysqlType(self):
0752        return "BOOL"
0753
0754    def _sybaseType(self):
0755        return "BIT"
0756
0757    def _mssqlType(self):
0758        return "BIT"
0759
0760    def _firebirdType(self):
0761        return 'INT'
0762
0763    def _maxdbType(self):
0764        return "BOOLEAN"
0765
0766    def _sqliteType(self):
0767        return "BOOLEAN"
0768
0769class BoolCol(Col):
0770    baseClass = SOBoolCol
0771
0772
0773class FloatValidator(SOValidator):
0774
0775    def to_python(self, value, state):
0776        if value is None:
0777            return None
0778        if isinstance(value, (float, int, long, sqlbuilder.SQLExpression)):
0779            return value
0780        for converter, attr_name in  (float, '__float__'), (int, '__int__'), (long, '__long__'):
0781            if hasattr(value, attr_name):
0782                try:
0783                    return converter(value)
0784                except:
0785                    break
0786        raise validators.Invalid("expected a float in the FloatCol '%s', got %s %r instead" %               (self.name, type(value), value), value, state)
0788
0789    from_python = to_python
0790
0791class SOFloatCol(SOCol):
0792    # 3-03 @@: support precision (e.g., DECIMAL)
0793
0794    def autoConstraints(self):
0795        return [constrs.isFloat]
0796
0797    def createValidators(self):
0798        return [FloatValidator(name=self.name)] +               super(SOFloatCol, self).createValidators()
0800
0801    def _sqlType(self):
0802        return 'FLOAT'
0803
0804    def _mysqlType(self):
0805        return "DOUBLE PRECISION"
0806
0807class FloatCol(Col):
0808    baseClass = SOFloatCol
0809
0810
0811class SOKeyCol(SOCol):
0812    key_type = {int: "INT", str: "TEXT"}
0813
0814    # 3-03 @@: this should have a simplified constructor
0815    # Should provide foreign key information for other DBs.
0816
0817    def __init__(self, **kw):
0818        self.refColumn = kw.pop('refColumn', None)
0819        super(SOKeyCol, self).__init__(**kw)
0820
0821    def _idType(self):
0822        return self.soClass.sqlmeta.idType
0823
0824    def _sqlType(self):
0825        return self.key_type[self._idType()]
0826
0827    def _sybaseType(self):
0828        key_type = {int: "NUMERIC(18,0) NULL", str: "TEXT"}
0829        return key_type[self._idType()]
0830
0831    def _mssqlType(self):
0832        key_type = {int: "INT NULL", str: "TEXT"}
0833        return key_type[self._idType()]
0834
0835class KeyCol(Col):
0836
0837    baseClass = SOKeyCol
0838
0839class ForeignKeyValidator(SOValidator):
0840
0841    def __init__(self, *args, **kw):
0842        super(ForeignKeyValidator, self).__init__(*args, **kw)
0843        self.fkIDType = None
0844
0845    def from_python(self, value, state):
0846        if value is None:
0847            return None
0848        # Avoid importing the main module
0849        # to get the SQLObject class for isinstance
0850        if hasattr(value, 'sqlmeta'):
0851            return value
0852        if self.fkIDType is None:
0853            otherTable = findClass(self.soCol.foreignKey,
0854                                   self.soCol.soClass.sqlmeta.registry)
0855            self.fkIDType = otherTable.sqlmeta.idType
0856        try:
0857            value = self.fkIDType(value)
0858            return value
0859        except (ValueError, TypeError):
0860            pass
0861        raise validators.Invalid("expected a %r for the ForeignKey '%s', "
0862                                 "got %s %r instead" %
0863                                 (self.fkIDType, self.name,
0864                                  type(value), value), value, state)
0865
0866class SOForeignKey(SOKeyCol):
0867
0868    def __init__(self, **kw):
0869        foreignKey = kw['foreignKey']
0870        style = kw['soClass'].sqlmeta.style
0871        if kw.get('name'):
0872            kw['origName'] = kw['name']
0873            kw['name'] = style.instanceAttrToIDAttr(kw['name'])
0874        else:
0875            kw['name'] = style.instanceAttrToIDAttr(style.pythonClassToAttr(foreignKey))
0876        super(SOForeignKey, self).__init__(**kw)
0877
0878    def createValidators(self):
0879        return [ForeignKeyValidator(name=self.name)] +               super(SOForeignKey, self).createValidators()
0881
0882    def _idType(self):
0883        other = findClass(self.foreignKey, self.soClass.sqlmeta.registry)
0884        return other.sqlmeta.idType
0885
0886    def sqliteCreateSQL(self):
0887        sql = SOKeyCol.sqliteCreateSQL(self)
0888        other = findClass(self.foreignKey, self.soClass.sqlmeta.registry)
0889        tName = other.sqlmeta.table
0890        idName = self.refColumn or other.sqlmeta.idName
0891        if self.cascade is not None:
0892            if self.cascade == 'null':
0893                action = 'ON DELETE SET NULL'
0894            elif self.cascade:
0895                action = 'ON DELETE CASCADE'
0896            else:
0897                action = 'ON DELETE RESTRICT'
0898        else:
0899            action = ''
0900        constraint = ('CONSTRAINT %(colName)s_exists '
0901                      #'FOREIGN KEY(%(colName)s) '
0902                      'REFERENCES %(tName)s(%(idName)s) '
0903                      '%(action)s' %
0904                      {'tName': tName,
0905                       'colName': self.dbName,
0906                       'idName': idName,
0907                       'action': action})
0908        sql = ' '.join([sql, constraint])
0909        return sql
0910
0911    def postgresCreateSQL(self):
0912        sql = SOKeyCol.postgresCreateSQL(self)
0913        return sql
0914
0915    def postgresCreateReferenceConstraint(self):
0916        sTName = self.soClass.sqlmeta.table
0917        other = findClass(self.foreignKey, self.soClass.sqlmeta.registry)
0918        tName = other.sqlmeta.table
0919        idName = self.refColumn or other.sqlmeta.idName
0920        if self.cascade is not None:
0921            if self.cascade == 'null':
0922                action = 'ON DELETE SET NULL'
0923            elif self.cascade:
0924                action = 'ON DELETE CASCADE'
0925            else:
0926                action = 'ON DELETE RESTRICT'
0927        else:
0928            action = ''
0929        constraint = ('ALTER TABLE %(sTName)s ADD CONSTRAINT %(colName)s_exists '
0930                      'FOREIGN KEY (%(colName)s) '
0931                      'REFERENCES %(tName)s (%(idName)s) '
0932                      '%(action)s' %
0933                      {'tName': tName,
0934                       'colName': self.dbName,
0935                       'idName': idName,
0936                       'action': action,
0937                       'sTName': sTName})
0938        return constraint
0939
0940    def mysqlCreateReferenceConstraint(self):
0941        sTName = self.soClass.sqlmeta.table
0942        sTLocalName = sTName.split('.')[-1]
0943        other = findClass(self.foreignKey, self.soClass.sqlmeta.registry)
0944        tName = other.sqlmeta.table
0945        idName = self.refColumn or other.sqlmeta.idName
0946        if self.cascade is not None:
0947            if self.cascade == 'null':
0948                action = 'ON DELETE SET NULL'
0949            elif self.cascade:
0950                action = 'ON DELETE CASCADE'
0951            else:
0952                action = 'ON DELETE RESTRICT'
0953        else:
0954            action = ''
0955        constraint = ('ALTER TABLE %(sTName)s ADD CONSTRAINT %(sTLocalName)s_%(colName)s_exists '
0956                      'FOREIGN KEY (%(colName)s) '
0957                      'REFERENCES %(tName)s (%(idName)s) '
0958                      '%(action)s' %
0959                      {'tName': tName,
0960                       'colName': self.dbName,
0961                       'idName': idName,
0962                       'action': action,
0963                       'sTName': sTName,
0964                       'sTLocalName': sTLocalName})
0965        return constraint
0966
0967    def mysqlCreateSQL(self, connection=None):
0968        return SOKeyCol.mysqlCreateSQL(self, connection)
0969
0970    def sybaseCreateSQL(self):
0971        sql = SOKeyCol.sybaseCreateSQL(self)
0972        other = findClass(self.foreignKey, self.soClass.sqlmeta.registry)
0973        tName = other.sqlmeta.table
0974        idName = self.refColumn or other.sqlmeta.idName
0975        reference = ('REFERENCES %(tName)s(%(idName)s) ' %
0976                     {'tName':tName,
0977                      'idName':idName})
0978        sql = ' '.join([sql, reference])
0979        return sql
0980
0981    def sybaseCreateReferenceConstraint(self):
0982        # @@: Code from above should be moved here
0983        return None
0984
0985    def mssqlCreateSQL(self, connection=None):
0986        sql = SOKeyCol.mssqlCreateSQL(self, connection)
0987        other = findClass(self.foreignKey, self.soClass.sqlmeta.registry)
0988        tName = other.sqlmeta.table
0989        idName = self.refColumn or other.sqlmeta.idName
0990        reference = ('REFERENCES %(tName)s(%(idName)s) ' %
0991                     {'tName':tName,
0992                      'idName':idName})
0993        sql = ' '.join([sql, reference])
0994        return sql
0995
0996    def mssqlCreateReferenceConstraint(self):
0997        # @@: Code from above should be moved here
0998        return None
0999
1000    def maxdbCreateSQL(self):
1001        other = findClass(self.foreignKey, self.soClass.sqlmeta.registry)
1002        fidName = self.dbName
1003        #I assume that foreign key name is identical to the id of the reference table
1004        sql = ' '.join([fidName, self._maxdbType()])
1005        tName = other.sqlmeta.table
1006        idName  = self.refColumn or other.sqlmeta.idName
1007        sql=sql + ',' + '\n'
1008        sql=sql + 'FOREIGN KEY (%s) REFERENCES %s(%s)'%(fidName,tName,idName)
1009        return sql
1010
1011    def maxdbCreateReferenceConstraint(self):
1012        # @@: Code from above should be moved here
1013        return None
1014
1015class ForeignKey(KeyCol):
1016
1017    baseClass = SOForeignKey
1018
1019    def __init__(self, foreignKey=None, **kw):
1020        super(ForeignKey, self).__init__(foreignKey=foreignKey, **kw)
1021
1022
1023class EnumValidator(SOValidator):
1024
1025    def to_python(self, value, state):
1026        if value in self.enumValues:
1027            if isinstance(value, unicode):
1028                dbEncoding = self.getDbEncoding(state)
1029                value = value.encode(dbEncoding)
1030            return value
1031        elif not self.notNone and value is None:
1032            return None
1033        raise validators.Invalid("expected a member of %r in the EnumCol '%s', got %r instead" %               (self.enumValues, self.name, value), value, state)
1035
1036    from_python = to_python
1037
1038class SOEnumCol(SOCol):
1039
1040    def __init__(self, **kw):
1041        self.enumValues = kw.pop('enumValues', None)
1042        assert self.enumValues is not None,                  'You must provide an enumValues keyword argument'
1044        super(SOEnumCol, self).__init__(**kw)
1045
1046    def autoConstraints(self):
1047        return [constrs.isString, constrs.InList(self.enumValues)]
1048
1049    def createValidators(self):
1050        return [EnumValidator(name=self.name, enumValues=self.enumValues,
1051                              notNone=self.notNone)] +               super(SOEnumCol, self).createValidators()
1053
1054    def _mysqlType(self):
1055        # We need to map None in the enum expression to an appropriate
1056        # condition on NULL
1057        if None in self.enumValues:
1058            return "ENUM(%s)" % ', '.join([sqlbuilder.sqlrepr(v, 'mysql') for v in self.enumValues if v is not None])
1059        else:
1060            return "ENUM(%s) NOT NULL" % ', '.join([sqlbuilder.sqlrepr(v, 'mysql') for v in self.enumValues])
1061
1062    def _postgresType(self):
1063        length = max(map(self._getlength, self.enumValues))
1064        enumValues = ', '.join([sqlbuilder.sqlrepr(v, 'postgres') for v in self.enumValues])
1065        checkConstraint = "CHECK (%s in (%s))" % (self.dbName, enumValues)
1066        return "VARCHAR(%i) %s" % (length, checkConstraint)
1067
1068    _sqliteType = _postgresType
1069
1070    def _sybaseType(self):
1071        return self._postgresType()
1072
1073    def _mssqlType(self):
1074        return self._postgresType()
1075
1076    def _firebirdType(self):
1077        length = max(map(self._getlength, self.enumValues))
1078        enumValues = ', '.join([sqlbuilder.sqlrepr(v, 'firebird') for v in self.enumValues])
1079        checkConstraint = "CHECK (%s in (%s))" % (self.dbName, enumValues)
1080        #NB. Return a tuple, not a string here
1081        return "VARCHAR(%i)" % (length), checkConstraint
1082
1083    def _maxdbType(self):
1084        raise TypeError("Enum type is not supported on MAX DB")
1085
1086    def _getlength(self, obj):
1087        """
1088        None counts as 0; everything else uses len()
1089        """
1090        if obj is None:
1091            return 0
1092        else:
1093            return len(obj)
1094
1095class EnumCol(Col):
1096    baseClass = SOEnumCol
1097
1098
1099class SetValidator(SOValidator):
1100    """
1101    Translates Python tuples into SQL comma-delimited SET strings.
1102    """
1103
1104    def to_python(self, value, state):
1105        if isinstance(value, str):
1106            return tuple(value.split(","))
1107        raise validators.Invalid("expected a string in the SetCol '%s', got %s %r instead" %               (self.name, type(value), value), value, state)
1109
1110    def from_python(self, value, state):
1111        if isinstance(value, basestring):
1112            value = (value,)
1113        try:
1114            return ",".join(value)
1115        except:
1116            raise validators.Invalid("expected a string or a sequence of stringsin the SetCol '%s', got %s %r instead" %                   (self.name, type(value), value), value, state)
1118
1119class SOSetCol(SOCol):
1120    def __init__(self, **kw):
1121        self.setValues = kw.pop('setValues', None)
1122        assert self.setValues is not None,                   'You must provide a setValues keyword argument'
1124        super(SOSetCol, self).__init__(**kw)
1125
1126    def autoConstraints(self):
1127        return [constrs.isString, constrs.InList(self.setValues)]
1128
1129    def createValidators(self):
1130        return [SetValidator(name=self.name, setValues=self.setValues)] +               super(SOSetCol, self).createValidators()
1132
1133    def _mysqlType(self):
1134        return "SET(%s)" % ', '.join([sqlbuilder.sqlrepr(v, 'mysql') for v in self.setValues])
1135
1136class SetCol(Col):
1137    baseClass = SOSetCol
1138
1139
1140class DateTimeValidator(validators.DateValidator):
1141    def to_python(self, value, state):
1142        if value is None:
1143            return None
1144        if isinstance(value, (datetime.datetime, datetime.date, datetime.time, sqlbuilder.SQLExpression)):
1145            return value
1146        if mxdatetime_available:
1147            if isinstance(value, DateTimeType):
1148                # convert mxDateTime instance to datetime
1149                if (self.format.find("%H") >= 0) or (self.format.find("%T")) >= 0:
1150                    return datetime.datetime(value.year, value.month, value.day,
1151                        value.hour, value.minute, int(value.second))
1152                else:
1153                    return datetime.date(value.year, value.month, value.day)
1154            elif isinstance(value, TimeType):
1155                # convert mxTime instance to time
1156                if self.format.find("%d") >= 0:
1157                    return datetime.timedelta(seconds=value.seconds)
1158                else:
1159                    return datetime.time(value.hour, value.minute, int(value.second))
1160        try:
1161            if self.format.find(".%f") >= 0:
1162                if '.' in value:
1163                    _value = value.split('.')
1164                    microseconds = _value[-1]
1165                    _l = len(microseconds)
1166                    if _l < 6:
1167                        _value[-1] = microseconds + '0'*(6 - _l)
1168                    elif _l > 6:
1169                        _value[-1] = microseconds[:6]
1170                    if _l != 6:
1171                        value = '.'.join(_value)
1172                else:
1173                    value += '.0'
1174            return datetime.datetime.strptime(value, self.format)
1175        except:
1176            raise validators.Invalid("expected a date/time string of the '%s' format in the DateTimeCol '%s', got %s %r instead" %                   (self.format, self.name, type(value), value), value, state)
1178
1179    def from_python(self, value, state):
1180        if value is None:
1181            return None
1182        if isinstance(value, (datetime.datetime, datetime.date, datetime.time, sqlbuilder.SQLExpression)):
1183            return value
1184        if hasattr(value, "strftime"):
1185            return value.strftime(self.format)
1186        raise validators.Invalid("expected a datetime in the DateTimeCol '%s', got %s %r instead" %               (self.name, type(value), value), value, state)
1188
1189if mxdatetime_available:
1190    class MXDateTimeValidator(validators.DateValidator):
1191        def to_python(self, value, state):
1192            if value is None:
1193                return None
1194            if isinstance(value, (DateTimeType, TimeType, sqlbuilder.SQLExpression)):
1195                return value
1196            if isinstance(value, datetime.datetime):
1197                return DateTime.DateTime(value.year, value.month, value.day,
1198                    value.hour, value.minute, value.second)
1199            elif isinstance(value, datetime.date):
1200                return DateTime.Date(value.year, value.month, value.day)
1201            elif isinstance(value, datetime.time):
1202                return DateTime.Time(value.hour, value.minute, value.second)
1203            try:
1204                if self.format.find(".%f") >= 0:
1205                    if '.' in value:
1206                        _value = value.split('.')
1207                        microseconds = _value[-1]
1208                        _l = len(microseconds)
1209                        if _l < 6:
1210                            _value[-1] = microseconds + '0'*(6 - _l)
1211                        elif _l > 6:
1212                            _value[-1] = microseconds[:6]
1213                        if _l != 6:
1214                            value = '.'.join(_value)
1215                    else:
1216                        value += '.0'
1217                value = datetime.datetime.strptime(value, self.format)
1218                return DateTime.DateTime(value.year, value.month, value.day,
1219                    value.hour, value.minute, value.second)
1220            except:
1221                raise validators.Invalid("expected a date/time string of the '%s' format in the DateTimeCol '%s', got %s %r instead" %                       (self.format, self.name, type(value), value), value, state)
1223
1224        def from_python(self, value, state):
1225            if value is None:
1226                return None
1227            if isinstance(value, (DateTimeType, TimeType, sqlbuilder.SQLExpression)):
1228                return value
1229            if hasattr(value, "strftime"):
1230                return value.strftime(self.format)
1231            raise validators.Invalid("expected a mxDateTime in the DateTimeCol '%s', got %s %r instead" %                   (self.name, type(value), value), value, state)
1233
1234class SODateTimeCol(SOCol):
1235    datetimeFormat = '%Y-%m-%d %H:%M:%S.%f'
1236
1237    def __init__(self, **kw):
1238        datetimeFormat = kw.pop('datetimeFormat', None)
1239        if datetimeFormat:
1240            self.datetimeFormat = datetimeFormat
1241        super(SODateTimeCol, self).__init__(**kw)
1242
1243    def createValidators(self):
1244        _validators = super(SODateTimeCol, self).createValidators()
1245        if default_datetime_implementation == DATETIME_IMPLEMENTATION:
1246            validatorClass = DateTimeValidator
1247        elif default_datetime_implementation == MXDATETIME_IMPLEMENTATION:
1248            validatorClass = MXDateTimeValidator
1249        if default_datetime_implementation:
1250            _validators.insert(0, validatorClass(name=self.name, format=self.datetimeFormat))
1251        return _validators
1252
1253    def _mysqlType(self):
1254        if self.connection and self.connection.can_use_microseconds():
1255            return 'DATETIME(6)'
1256        else:
1257            return 'DATETIME'
1258
1259    def _postgresType(self):
1260        return 'TIMESTAMP'
1261
1262    def _sybaseType(self):
1263        return 'DATETIME'
1264
1265    def _mssqlType(self):
1266        if self.connection and self.connection.can_use_microseconds():
1267            return 'DATETIME2(6)'
1268        else:
1269            return 'DATETIME'
1270
1271    def _sqliteType(self):
1272        return 'TIMESTAMP'
1273
1274    def _firebirdType(self):
1275        return 'TIMESTAMP'
1276
1277    def _maxdbType(self):
1278        return 'TIMESTAMP'
1279
1280class DateTimeCol(Col):
1281    baseClass = SODateTimeCol
1282    @staticmethod
1283    def now():
1284        if default_datetime_implementation == DATETIME_IMPLEMENTATION:
1285            return datetime.datetime.now()
1286        elif default_datetime_implementation == MXDATETIME_IMPLEMENTATION:
1287            return DateTime.now()
1288        else:
1289            assert 0, ("No datetime implementation available "
1290                       "(DATETIME_IMPLEMENTATION=%r)"
1291                       % DATETIME_IMPLEMENTATION)
1292
1293
1294class DateValidator(DateTimeValidator):
1295    def to_python(self, value, state):
1296        if isinstance(value, datetime.datetime):
1297            value = value.date()
1298        if isinstance(value, (datetime.date, sqlbuilder.SQLExpression)):
1299            return value
1300        value = super(DateValidator, self).to_python(value, state)
1301        if isinstance(value, datetime.datetime):
1302            value = value.date()
1303        return value
1304
1305    from_python = to_python
1306
1307class SODateCol(SOCol):
1308    dateFormat = '%Y-%m-%d'
1309
1310    def __init__(self, **kw):
1311        dateFormat = kw.pop('dateFormat', None)
1312        if dateFormat: self.dateFormat = dateFormat
1313        super(SODateCol, self).__init__(**kw)
1314
1315    def createValidators(self):
1316        """Create a validator for the column. Can be overriden in descendants."""
1317        _validators = super(SODateCol, self).createValidators()
1318        if default_datetime_implementation == DATETIME_IMPLEMENTATION:
1319            validatorClass = DateValidator
1320        elif default_datetime_implementation == MXDATETIME_IMPLEMENTATION:
1321            validatorClass = MXDateTimeValidator
1322        if default_datetime_implementation:
1323            _validators.insert(0, validatorClass(name=self.name, format=self.dateFormat))
1324        return _validators
1325
1326    def _mysqlType(self):
1327        return 'DATE'
1328
1329    def _postgresType(self):
1330        return 'DATE'
1331
1332    def _sybaseType(self):
1333        return self._postgresType()
1334
1335    def _mssqlType(self):
1336        """
1337        SQL Server doesn't have  a DATE data type, to emulate we use a vc(10)
1338        """
1339        return 'VARCHAR(10)'
1340
1341    def _firebirdType(self):
1342        return 'DATE'
1343
1344    def _maxdbType(self):
1345        return  'DATE'
1346
1347    def _sqliteType(self):
1348        return 'DATE'
1349
1350class DateCol(Col):
1351    baseClass = SODateCol
1352
1353
1354class TimeValidator(DateTimeValidator):
1355    def to_python(self, value, state):
1356        if isinstance(value, (datetime.time, sqlbuilder.SQLExpression)):
1357            return value
1358        if isinstance(value, datetime.timedelta):
1359            if value.days:
1360                raise validators.Invalid(
1361                    "the value for the TimeCol '%s' must has days=0, it has days=%d" %
1362                        (self.name, value.days), value, state)
1363            return datetime.time(*time.gmtime(value.seconds)[3:6])
1364        value = super(TimeValidator, self).to_python(value, state)
1365        if isinstance(value, datetime.datetime):
1366            value = value.time()
1367        return value
1368
1369    from_python = to_python
1370
1371class SOTimeCol(SOCol):
1372    timeFormat = '%H:%M:%S.%f'
1373
1374    def __init__(self, **kw):
1375        timeFormat = kw.pop('timeFormat', None)
1376        if timeFormat:
1377            self.timeFormat = timeFormat
1378        super(SOTimeCol, self).__init__(**kw)
1379
1380    def createValidators(self):
1381        _validators = super(SOTimeCol, self).createValidators()
1382        if default_datetime_implementation == DATETIME_IMPLEMENTATION:
1383            validatorClass = TimeValidator
1384        elif default_datetime_implementation == MXDATETIME_IMPLEMENTATION:
1385            validatorClass = MXDateTimeValidator
1386        if default_datetime_implementation:
1387            _validators.insert(0, validatorClass(name=self.name, format=self.timeFormat))
1388        return _validators
1389
1390    def _mysqlType(self):
1391        if self.connection and self.connection.can_use_microseconds():
1392            return 'TIME(6)'
1393        else:
1394            return 'TIME'
1395
1396    def _postgresType(self):
1397        return 'TIME'
1398
1399    def _sybaseType(self):
1400        return 'TIME'
1401
1402    def _mssqlType(self):
1403        if self.connection and self.connection.can_use_microseconds():
1404            return 'TIME(6)'
1405        else:
1406            return 'TIME'
1407
1408    def _sqliteType(self):
1409        return 'TIME'
1410
1411    def _firebirdType(self):
1412        return 'TIME'
1413
1414    def _maxdbType(self):
1415        return 'TIME'
1416
1417class TimeCol(Col):
1418    baseClass = SOTimeCol
1419
1420
1421class SOTimestampCol(SODateTimeCol):
1422    """
1423    Necessary to support MySQL's use of TIMESTAMP versus DATETIME types
1424    """
1425
1426    def __init__(self, **kw):
1427        if 'default' not in kw:
1428            kw['default'] = None
1429        SOCol.__init__(self, **kw)
1430
1431    def _mysqlType(self):
1432        if self.connection and self.connection.can_use_microseconds():
1433            return 'TIMESTAMP(6)'
1434        else:
1435            return 'TIMESTAMP'
1436
1437class TimestampCol(Col):
1438    baseClass = SOTimestampCol
1439
1440
1441class TimedeltaValidator(SOValidator):
1442    def to_python(self, value, state):
1443        return value
1444
1445    from_python = to_python
1446
1447class SOTimedeltaCol(SOCol):
1448    def _postgresType(self):
1449        return 'INTERVAL'
1450
1451    def createValidators(self):
1452        return [TimedeltaValidator(name=self.name)] +               super(SOTimedeltaCol, self).createValidators()
1454
1455class TimedeltaCol(Col):
1456    baseClass = SOTimedeltaCol
1457
1458
1459from decimal import Decimal
1460
1461class DecimalValidator(SOValidator):
1462    def to_python(self, value, state):
1463        if value is None:
1464            return None
1465        if isinstance(value, (int, long, Decimal, sqlbuilder.SQLExpression)):
1466            return value
1467        if isinstance(value, float):
1468            value = str(value)
1469        try:
1470            connection = state.connection or state.soObject._connection
1471        except AttributeError:
1472            pass
1473        else:
1474            if hasattr(connection, "decimalSeparator"):
1475                value = value.replace(connection.decimalSeparator, ".")
1476        try:
1477            return Decimal(value)
1478        except:
1479            raise validators.Invalid("expected a Decimal in the DecimalCol '%s', got %s %r instead" %                   (self.name, type(value), value), value, state)
1481
1482    def from_python(self, value, state):
1483        if value is None:
1484            return None
1485        if isinstance(value, float):
1486            value = str(value)
1487        if isinstance(value, basestring):
1488            try:
1489                connection = state.connection or state.soObject._connection
1490            except AttributeError:
1491                pass
1492            else:
1493                if hasattr(connection, "decimalSeparator"):
1494                    value = value.replace(connection.decimalSeparator, ".")
1495            try:
1496                return Decimal(value)
1497            except:
1498                raise validators.Invalid("can not parse Decimal value '%s' in the DecimalCol from '%s'" %
1499                    (value, getattr(state, 'soObject', '(unknown)')), value, state)
1500        if isinstance(value, (int, long, Decimal, sqlbuilder.SQLExpression)):
1501            return value
1502        raise validators.Invalid("expected a Decimal in the DecimalCol '%s', got %s %r instead" %               (self.name, type(value), value), value, state)
1504
1505class SODecimalCol(SOCol):
1506
1507    def __init__(self, **kw):
1508        self.size = kw.pop('size', NoDefault)
1509        assert self.size is not NoDefault,                  "You must give a size argument"
1511        self.precision = kw.pop('precision', NoDefault)
1512        assert self.precision is not NoDefault,                  "You must give a precision argument"
1514        super(SODecimalCol, self).__init__(**kw)
1515
1516    def _sqlType(self):
1517        return 'DECIMAL(%i, %i)' % (self.size, self.precision)
1518
1519    def createValidators(self):
1520        return [DecimalValidator(name=self.name)] +               super(SODecimalCol, self).createValidators()
1522
1523class DecimalCol(Col):
1524    baseClass = SODecimalCol
1525
1526class SOCurrencyCol(SODecimalCol):
1527
1528    def __init__(self, **kw):
1529        pushKey(kw, 'size', 10)
1530        pushKey(kw, 'precision', 2)
1531        super(SOCurrencyCol, self).__init__(**kw)
1532
1533class CurrencyCol(DecimalCol):
1534    baseClass = SOCurrencyCol
1535
1536
1537class DecimalStringValidator(DecimalValidator):
1538    def to_python(self, value, state):
1539        value = super(DecimalStringValidator, self).to_python(value, state)
1540        if self.precision and isinstance(value, Decimal):
1541            assert value < self.max,                       "Value must be less than %s" % int(self.max)
1543            value = value.quantize(self.precision)
1544        return value
1545
1546    def from_python(self, value, state):
1547        value = super(DecimalStringValidator, self).from_python(value, state)
1548        if isinstance(value, Decimal):
1549            if self.precision:
1550                assert value < self.max,                           "Value must be less than %s" % int(self.max)
1552                value = value.quantize(self.precision)
1553            value = value.to_eng_string()
1554        elif isinstance(value, (int, long)):
1555            value = str(value)
1556        return value
1557
1558class SODecimalStringCol(SOStringCol):
1559    def __init__(self, **kw):
1560        self.size = kw.pop('size', NoDefault)
1561        assert (self.size is not NoDefault) and (self.size >= 0),               "You must give a size argument as a positive integer"
1563        self.precision = kw.pop('precision', NoDefault)
1564        assert (self.precision is not NoDefault) and (self.precision >= 0),                  "You must give a precision argument as a positive integer"
1566        kw['length'] = int(self.size) + int(self.precision)
1567        self.quantize = kw.pop('quantize', False)
1568        assert isinstance(self.quantize, bool),                   "quantize argument must be Boolean True/False"
1570        super(SODecimalStringCol, self).__init__(**kw)
1571
1572    def createValidators(self):
1573        if self.quantize:
1574            v = DecimalStringValidator(name=self.name,
1575                precision=Decimal(10) ** (-1 * int(self.precision)),
1576                max=Decimal(10) ** (int(self.size) - int(self.precision)))
1577        else:
1578            v = DecimalStringValidator(name=self.name, precision=0)
1579        return [v] +               super(SODecimalStringCol, self).createValidators(dataType=Decimal)
1581
1582class DecimalStringCol(StringCol):
1583    baseClass = SODecimalStringCol
1584
1585
1586class BinaryValidator(SOValidator):
1587    """
1588    Validator for binary types.
1589
1590    We're assuming that the per-database modules provide some form
1591    of wrapper type for binary conversion.
1592    """
1593
1594    _cachedValue = None
1595
1596    def to_python(self, value, state):
1597        if value is None:
1598            return None
1599        try:
1600            connection = state.connection or state.soObject._connection
1601        except AttributeError:
1602            dbName = None
1603            binaryType = type(None) # Just a simple workaround
1604        else:
1605            dbName = connection.dbName
1606            binaryType = connection._binaryType
1607        if isinstance(value, str):
1608            if dbName == "sqlite":
1609                value = connection.module.decode(value)
1610            return value
1611        if isinstance(value, (buffer, binaryType)):
1612            cachedValue = self._cachedValue
1613            if cachedValue and cachedValue[1] == value:
1614                return cachedValue[0]
1615            if isinstance(value, array): # MySQL
1616                return value.tostring()
1617            return str(value) # buffer => string
1618        raise validators.Invalid("expected a string in the BLOBCol '%s', got %s %r instead" %               (self.name, type(value), value), value, state)
1620
1621    def from_python(self, value, state):
1622        if value is None:
1623            return None
1624        connection = state.connection or state.soObject._connection
1625        binary = connection.createBinary(value)
1626        self._cachedValue = (value, binary)
1627        return binary
1628
1629class SOBLOBCol(SOStringCol):
1630    def __init__(self, **kw):
1631        # Change the default from 'auto' to False - this is a (mostly) binary column
1632        if 'varchar' not in kw: kw['varchar'] = False
1633        super(SOBLOBCol, self).__init__(**kw)
1634
1635    def createValidators(self):
1636        return [BinaryValidator(name=self.name)] +               super(SOBLOBCol, self).createValidators()
1638
1639    def _mysqlType(self):
1640        length = self.length
1641        varchar = self.varchar
1642        if length >= 2**24:
1643            return varchar and "LONGTEXT" or "LONGBLOB"
1644        if length >= 2**16:
1645            return varchar and "MEDIUMTEXT" or "MEDIUMBLOB"
1646        if length >= 2**8:
1647            return varchar and "TEXT" or "BLOB"
1648        return varchar and "TINYTEXT" or "TINYBLOB"
1649
1650    def _postgresType(self):
1651        return 'BYTEA'
1652
1653    def _mssqlType(self):
1654        if self.connection and self.connection.can_use_max_types():
1655            return 'VARBINARY(MAX)'
1656        else:
1657            return "IMAGE"
1658
1659class BLOBCol(StringCol):
1660    baseClass = SOBLOBCol
1661
1662
1663class PickleValidator(BinaryValidator):
1664    """
1665    Validator for pickle types.  A pickle type is simply a binary type
1666    with hidden pickling, so that we can simply store any kind of
1667    stuff in a particular column.
1668
1669    The support for this relies directly on the support for binary for
1670    your database.
1671    """
1672
1673    def to_python(self, value, state):
1674        if value is None:
1675            return None
1676        if isinstance(value, unicode):
1677            dbEncoding = self.getDbEncoding(state, default='ascii')
1678            value = value.encode(dbEncoding)
1679        if isinstance(value, str):
1680            return pickle.loads(value)
1681        raise validators.Invalid("expected a pickle string in the PickleCol '%s', got %s %r instead" %               (self.name, type(value), value), value, state)
1683
1684    def from_python(self, value, state):
1685        if value is None:
1686            return None
1687        return pickle.dumps(value, self.pickleProtocol)
1688
1689class SOPickleCol(SOBLOBCol):
1690
1691    def __init__(self, **kw):
1692        self.pickleProtocol = kw.pop('pickleProtocol', pickle.HIGHEST_PROTOCOL)
1693        super(SOPickleCol, self).__init__(**kw)
1694
1695    def createValidators(self):
1696        return [PickleValidator(name=self.name,
1697                pickleProtocol=self.pickleProtocol)] +               super(SOPickleCol, self).createValidators()
1699
1700    def _mysqlType(self):
1701        length = self.length
1702        if length >= 2**24:
1703            return "LONGBLOB"
1704        if length >= 2**16:
1705            return "MEDIUMBLOB"
1706        return "BLOB"
1707
1708class PickleCol(BLOBCol):
1709    baseClass = SOPickleCol
1710
1711
1712def pushKey(kw, name, value):
1713    if not name in kw:
1714        kw[name] = value
1715
1716all = []
1717for key, value in globals().items():
1718    if isinstance(value, type) and (issubclass(value, (Col, SOCol))):
1719        all.append(key)
1720__all__.extend(all)
1721del all