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 _sqlType(self):
0822        return self.key_type[self.soClass.sqlmeta.idType]
0823
0824    def _sybaseType(self):
0825        key_type = {int: "NUMERIC(18,0) NULL", str: "TEXT"}
0826        return key_type[self.soClass.sqlmeta.idType]
0827
0828    def _mssqlType(self):
0829        key_type = {int: "INT NULL", str: "TEXT"}
0830        return key_type[self.soClass.sqlmeta.idType]
0831
0832class KeyCol(Col):
0833
0834    baseClass = SOKeyCol
0835
0836class SOForeignKey(SOKeyCol):
0837
0838    def __init__(self, **kw):
0839        foreignKey = kw['foreignKey']
0840        style = kw['soClass'].sqlmeta.style
0841        if kw.get('name'):
0842            kw['origName'] = kw['name']
0843            kw['name'] = style.instanceAttrToIDAttr(kw['name'])
0844        else:
0845            kw['name'] = style.instanceAttrToIDAttr(style.pythonClassToAttr(foreignKey))
0846        super(SOForeignKey, self).__init__(**kw)
0847
0848    def sqliteCreateSQL(self):
0849        sql = SOKeyCol.sqliteCreateSQL(self)
0850        other = findClass(self.foreignKey, self.soClass.sqlmeta.registry)
0851        tName = other.sqlmeta.table
0852        idName = self.refColumn or other.sqlmeta.idName
0853        if self.cascade is not None:
0854            if self.cascade == 'null':
0855                action = 'ON DELETE SET NULL'
0856            elif self.cascade:
0857                action = 'ON DELETE CASCADE'
0858            else:
0859                action = 'ON DELETE RESTRICT'
0860        else:
0861            action = ''
0862        constraint = ('CONSTRAINT %(colName)s_exists '
0863                      #'FOREIGN KEY(%(colName)s) '
0864                      'REFERENCES %(tName)s(%(idName)s) '
0865                      '%(action)s' %
0866                      {'tName': tName,
0867                       'colName': self.dbName,
0868                       'idName': idName,
0869                       'action': action})
0870        sql = ' '.join([sql, constraint])
0871        return sql
0872
0873    def postgresCreateSQL(self):
0874        sql = SOKeyCol.postgresCreateSQL(self)
0875        return sql
0876
0877    def postgresCreateReferenceConstraint(self):
0878        sTName = self.soClass.sqlmeta.table
0879        other = findClass(self.foreignKey, self.soClass.sqlmeta.registry)
0880        tName = other.sqlmeta.table
0881        idName = self.refColumn or other.sqlmeta.idName
0882        if self.cascade is not None:
0883            if self.cascade == 'null':
0884                action = 'ON DELETE SET NULL'
0885            elif self.cascade:
0886                action = 'ON DELETE CASCADE'
0887            else:
0888                action = 'ON DELETE RESTRICT'
0889        else:
0890            action = ''
0891        constraint = ('ALTER TABLE %(sTName)s ADD CONSTRAINT %(colName)s_exists '
0892                      'FOREIGN KEY (%(colName)s) '
0893                      'REFERENCES %(tName)s (%(idName)s) '
0894                      '%(action)s' %
0895                      {'tName': tName,
0896                       'colName': self.dbName,
0897                       'idName': idName,
0898                       'action': action,
0899                       'sTName': sTName})
0900        return constraint
0901
0902    def mysqlCreateReferenceConstraint(self):
0903        sTName = self.soClass.sqlmeta.table
0904        sTLocalName = sTName.split('.')[-1]
0905        other = findClass(self.foreignKey, self.soClass.sqlmeta.registry)
0906        tName = other.sqlmeta.table
0907        idName = self.refColumn or other.sqlmeta.idName
0908        if self.cascade is not None:
0909            if self.cascade == 'null':
0910                action = 'ON DELETE SET NULL'
0911            elif self.cascade:
0912                action = 'ON DELETE CASCADE'
0913            else:
0914                action = 'ON DELETE RESTRICT'
0915        else:
0916            action = ''
0917        constraint = ('ALTER TABLE %(sTName)s ADD CONSTRAINT %(sTLocalName)s_%(colName)s_exists '
0918                      'FOREIGN KEY (%(colName)s) '
0919                      'REFERENCES %(tName)s (%(idName)s) '
0920                      '%(action)s' %
0921                      {'tName': tName,
0922                       'colName': self.dbName,
0923                       'idName': idName,
0924                       'action': action,
0925                       'sTName': sTName,
0926                       'sTLocalName': sTLocalName})
0927        return constraint
0928
0929    def mysqlCreateSQL(self, connection=None):
0930        return SOKeyCol.mysqlCreateSQL(self, connection)
0931
0932    def sybaseCreateSQL(self):
0933        sql = SOKeyCol.sybaseCreateSQL(self)
0934        other = findClass(self.foreignKey, self.soClass.sqlmeta.registry)
0935        tName = other.sqlmeta.table
0936        idName = self.refColumn or other.sqlmeta.idName
0937        reference = ('REFERENCES %(tName)s(%(idName)s) ' %
0938                     {'tName':tName,
0939                      'idName':idName})
0940        sql = ' '.join([sql, reference])
0941        return sql
0942
0943    def sybaseCreateReferenceConstraint(self):
0944        # @@: Code from above should be moved here
0945        return None
0946
0947    def mssqlCreateSQL(self, connection=None):
0948        sql = SOKeyCol.mssqlCreateSQL(self, connection)
0949        other = findClass(self.foreignKey, self.soClass.sqlmeta.registry)
0950        tName = other.sqlmeta.table
0951        idName = self.refColumn or other.sqlmeta.idName
0952        reference = ('REFERENCES %(tName)s(%(idName)s) ' %
0953                     {'tName':tName,
0954                      'idName':idName})
0955        sql = ' '.join([sql, reference])
0956        return sql
0957
0958    def mssqlCreateReferenceConstraint(self):
0959        # @@: Code from above should be moved here
0960        return None
0961
0962    def maxdbCreateSQL(self):
0963        other = findClass(self.foreignKey, self.soClass.sqlmeta.registry)
0964        fidName = self.dbName
0965        #I assume that foreign key name is identical to the id of the reference table
0966        sql = ' '.join([fidName, self._maxdbType()])
0967        tName = other.sqlmeta.table
0968        idName  = self.refColumn or other.sqlmeta.idName
0969        sql=sql + ',' + '\n'
0970        sql=sql + 'FOREIGN KEY (%s) REFERENCES %s(%s)'%(fidName,tName,idName)
0971        return sql
0972
0973    def maxdbCreateReferenceConstraint(self):
0974        # @@: Code from above should be moved here
0975        return None
0976
0977class ForeignKey(KeyCol):
0978
0979    baseClass = SOForeignKey
0980
0981    def __init__(self, foreignKey=None, **kw):
0982        super(ForeignKey, self).__init__(foreignKey=foreignKey, **kw)
0983
0984
0985class EnumValidator(SOValidator):
0986
0987    def to_python(self, value, state):
0988        if value in self.enumValues:
0989            if isinstance(value, unicode):
0990                dbEncoding = self.getDbEncoding(state)
0991                value = value.encode(dbEncoding)
0992            return value
0993        elif not self.notNone and value is None:
0994            return None
0995        raise validators.Invalid("expected a member of %r in the EnumCol '%s', got %r instead" %               (self.enumValues, self.name, value), value, state)
0997
0998    from_python = to_python
0999
1000class SOEnumCol(SOCol):
1001
1002    def __init__(self, **kw):
1003        self.enumValues = kw.pop('enumValues', None)
1004        assert self.enumValues is not None,                  'You must provide an enumValues keyword argument'
1006        super(SOEnumCol, self).__init__(**kw)
1007
1008    def autoConstraints(self):
1009        return [constrs.isString, constrs.InList(self.enumValues)]
1010
1011    def createValidators(self):
1012        return [EnumValidator(name=self.name, enumValues=self.enumValues,
1013                              notNone=self.notNone)] +               super(SOEnumCol, self).createValidators()
1015
1016    def _mysqlType(self):
1017        # We need to map None in the enum expression to an appropriate
1018        # condition on NULL
1019        if None in self.enumValues:
1020            return "ENUM(%s)" % ', '.join([sqlbuilder.sqlrepr(v, 'mysql') for v in self.enumValues if v is not None])
1021        else:
1022            return "ENUM(%s) NOT NULL" % ', '.join([sqlbuilder.sqlrepr(v, 'mysql') for v in self.enumValues])
1023
1024    def _postgresType(self):
1025        length = max(map(self._getlength, self.enumValues))
1026        enumValues = ', '.join([sqlbuilder.sqlrepr(v, 'postgres') for v in self.enumValues])
1027        checkConstraint = "CHECK (%s in (%s))" % (self.dbName, enumValues)
1028        return "VARCHAR(%i) %s" % (length, checkConstraint)
1029
1030    _sqliteType = _postgresType
1031
1032    def _sybaseType(self):
1033        return self._postgresType()
1034
1035    def _mssqlType(self):
1036        return self._postgresType()
1037
1038    def _firebirdType(self):
1039        length = max(map(self._getlength, self.enumValues))
1040        enumValues = ', '.join([sqlbuilder.sqlrepr(v, 'firebird') for v in self.enumValues])
1041        checkConstraint = "CHECK (%s in (%s))" % (self.dbName, enumValues)
1042        #NB. Return a tuple, not a string here
1043        return "VARCHAR(%i)" % (length), checkConstraint
1044
1045    def _maxdbType(self):
1046        raise TypeError("Enum type is not supported on MAX DB")
1047
1048    def _getlength(self, obj):
1049        """
1050        None counts as 0; everything else uses len()
1051        """
1052        if obj is None:
1053            return 0
1054        else:
1055            return len(obj)
1056
1057class EnumCol(Col):
1058    baseClass = SOEnumCol
1059
1060
1061class SetValidator(SOValidator):
1062    """
1063    Translates Python tuples into SQL comma-delimited SET strings.
1064    """
1065
1066    def to_python(self, value, state):
1067        if isinstance(value, str):
1068            return tuple(value.split(","))
1069        raise validators.Invalid("expected a string in the SetCol '%s', got %s %r instead" %               (self.name, type(value), value), value, state)
1071
1072    def from_python(self, value, state):
1073        if isinstance(value, basestring):
1074            value = (value,)
1075        try:
1076            return ",".join(value)
1077        except:
1078            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)
1080
1081class SOSetCol(SOCol):
1082    def __init__(self, **kw):
1083        self.setValues = kw.pop('setValues', None)
1084        assert self.setValues is not None,                   'You must provide a setValues keyword argument'
1086        super(SOSetCol, self).__init__(**kw)
1087
1088    def autoConstraints(self):
1089        return [constrs.isString, constrs.InList(self.setValues)]
1090
1091    def createValidators(self):
1092        return [SetValidator(name=self.name, setValues=self.setValues)] +               super(SOSetCol, self).createValidators()
1094
1095    def _mysqlType(self):
1096        return "SET(%s)" % ', '.join([sqlbuilder.sqlrepr(v, 'mysql') for v in self.setValues])
1097
1098class SetCol(Col):
1099    baseClass = SOSetCol
1100
1101
1102class DateTimeValidator(validators.DateValidator):
1103    def to_python(self, value, state):
1104        if value is None:
1105            return None
1106        if isinstance(value, (datetime.datetime, datetime.date, datetime.time, sqlbuilder.SQLExpression)):
1107            return value
1108        if mxdatetime_available:
1109            if isinstance(value, DateTimeType):
1110                # convert mxDateTime instance to datetime
1111                if (self.format.find("%H") >= 0) or (self.format.find("%T")) >= 0:
1112                    return datetime.datetime(value.year, value.month, value.day,
1113                        value.hour, value.minute, int(value.second))
1114                else:
1115                    return datetime.date(value.year, value.month, value.day)
1116            elif isinstance(value, TimeType):
1117                # convert mxTime instance to time
1118                if self.format.find("%d") >= 0:
1119                    return datetime.timedelta(seconds=value.seconds)
1120                else:
1121                    return datetime.time(value.hour, value.minute, int(value.second))
1122        try:
1123            if self.format.find(".%f") >= 0:
1124                if '.' in value:
1125                    _value = value.split('.')
1126                    microseconds = _value[-1]
1127                    _l = len(microseconds)
1128                    if _l < 6:
1129                        _value[-1] = microseconds + '0'*(6 - _l)
1130                    elif _l > 6:
1131                        _value[-1] = microseconds[:6]
1132                    if _l != 6:
1133                        value = '.'.join(_value)
1134                else:
1135                    value += '.0'
1136            return datetime.datetime.strptime(value, self.format)
1137        except:
1138            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)
1140
1141    def from_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 hasattr(value, "strftime"):
1147            return value.strftime(self.format)
1148        raise validators.Invalid("expected a datetime in the DateTimeCol '%s', got %s %r instead" %               (self.name, type(value), value), value, state)
1150
1151if mxdatetime_available:
1152    class MXDateTimeValidator(validators.DateValidator):
1153        def to_python(self, value, state):
1154            if value is None:
1155                return None
1156            if isinstance(value, (DateTimeType, TimeType, sqlbuilder.SQLExpression)):
1157                return value
1158            if isinstance(value, datetime.datetime):
1159                return DateTime.DateTime(value.year, value.month, value.day,
1160                    value.hour, value.minute, value.second)
1161            elif isinstance(value, datetime.date):
1162                return DateTime.Date(value.year, value.month, value.day)
1163            elif isinstance(value, datetime.time):
1164                return DateTime.Time(value.hour, value.minute, value.second)
1165            try:
1166                if self.format.find(".%f") >= 0:
1167                    if '.' in value:
1168                        _value = value.split('.')
1169                        microseconds = _value[-1]
1170                        _l = len(microseconds)
1171                        if _l < 6:
1172                            _value[-1] = microseconds + '0'*(6 - _l)
1173                        elif _l > 6:
1174                            _value[-1] = microseconds[:6]
1175                        if _l != 6:
1176                            value = '.'.join(_value)
1177                    else:
1178                        value += '.0'
1179                value = datetime.datetime.strptime(value, self.format)
1180                return DateTime.DateTime(value.year, value.month, value.day,
1181                    value.hour, value.minute, value.second)
1182            except:
1183                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)
1185
1186        def from_python(self, value, state):
1187            if value is None:
1188                return None
1189            if isinstance(value, (DateTimeType, TimeType, sqlbuilder.SQLExpression)):
1190                return value
1191            if hasattr(value, "strftime"):
1192                return value.strftime(self.format)
1193            raise validators.Invalid("expected a mxDateTime in the DateTimeCol '%s', got %s %r instead" %                   (self.name, type(value), value), value, state)
1195
1196class SODateTimeCol(SOCol):
1197    datetimeFormat = '%Y-%m-%d %H:%M:%S.%f'
1198
1199    def __init__(self, **kw):
1200        datetimeFormat = kw.pop('datetimeFormat', None)
1201        if datetimeFormat:
1202            self.datetimeFormat = datetimeFormat
1203        super(SODateTimeCol, self).__init__(**kw)
1204
1205    def createValidators(self):
1206        _validators = super(SODateTimeCol, self).createValidators()
1207        if default_datetime_implementation == DATETIME_IMPLEMENTATION:
1208            validatorClass = DateTimeValidator
1209        elif default_datetime_implementation == MXDATETIME_IMPLEMENTATION:
1210            validatorClass = MXDateTimeValidator
1211        if default_datetime_implementation:
1212            _validators.insert(0, validatorClass(name=self.name, format=self.datetimeFormat))
1213        return _validators
1214
1215    def _mysqlType(self):
1216        if self.connection and self.connection.can_use_microseconds():
1217            return 'DATETIME(6)'
1218        else:
1219            return 'DATETIME'
1220
1221    def _postgresType(self):
1222        return 'TIMESTAMP'
1223
1224    def _sybaseType(self):
1225        return 'DATETIME'
1226
1227    def _mssqlType(self):
1228        if self.connection and self.connection.can_use_microseconds():
1229            return 'DATETIME2(6)'
1230        else:
1231            return 'DATETIME'
1232
1233    def _sqliteType(self):
1234        return 'TIMESTAMP'
1235
1236    def _firebirdType(self):
1237        return 'TIMESTAMP'
1238
1239    def _maxdbType(self):
1240        return 'TIMESTAMP'
1241
1242class DateTimeCol(Col):
1243    baseClass = SODateTimeCol
1244    @staticmethod
1245    def now():
1246        if default_datetime_implementation == DATETIME_IMPLEMENTATION:
1247            return datetime.datetime.now()
1248        elif default_datetime_implementation == MXDATETIME_IMPLEMENTATION:
1249            return DateTime.now()
1250        else:
1251            assert 0, ("No datetime implementation available "
1252                       "(DATETIME_IMPLEMENTATION=%r)"
1253                       % DATETIME_IMPLEMENTATION)
1254
1255
1256class DateValidator(DateTimeValidator):
1257    def to_python(self, value, state):
1258        if isinstance(value, datetime.datetime):
1259            value = value.date()
1260        if isinstance(value, (datetime.date, sqlbuilder.SQLExpression)):
1261            return value
1262        value = super(DateValidator, self).to_python(value, state)
1263        if isinstance(value, datetime.datetime):
1264            value = value.date()
1265        return value
1266
1267    from_python = to_python
1268
1269class SODateCol(SOCol):
1270    dateFormat = '%Y-%m-%d'
1271
1272    def __init__(self, **kw):
1273        dateFormat = kw.pop('dateFormat', None)
1274        if dateFormat: self.dateFormat = dateFormat
1275        super(SODateCol, self).__init__(**kw)
1276
1277    def createValidators(self):
1278        """Create a validator for the column. Can be overriden in descendants."""
1279        _validators = super(SODateCol, self).createValidators()
1280        if default_datetime_implementation == DATETIME_IMPLEMENTATION:
1281            validatorClass = DateValidator
1282        elif default_datetime_implementation == MXDATETIME_IMPLEMENTATION:
1283            validatorClass = MXDateTimeValidator
1284        if default_datetime_implementation:
1285            _validators.insert(0, validatorClass(name=self.name, format=self.dateFormat))
1286        return _validators
1287
1288    def _mysqlType(self):
1289        return 'DATE'
1290
1291    def _postgresType(self):
1292        return 'DATE'
1293
1294    def _sybaseType(self):
1295        return self._postgresType()
1296
1297    def _mssqlType(self):
1298        """
1299        SQL Server doesn't have  a DATE data type, to emulate we use a vc(10)
1300        """
1301        return 'VARCHAR(10)'
1302
1303    def _firebirdType(self):
1304        return 'DATE'
1305
1306    def _maxdbType(self):
1307        return  'DATE'
1308
1309    def _sqliteType(self):
1310        return 'DATE'
1311
1312class DateCol(Col):
1313    baseClass = SODateCol
1314
1315
1316class TimeValidator(DateTimeValidator):
1317    def to_python(self, value, state):
1318        if isinstance(value, (datetime.time, sqlbuilder.SQLExpression)):
1319            return value
1320        if isinstance(value, datetime.timedelta):
1321            if value.days:
1322                raise validators.Invalid(
1323                    "the value for the TimeCol '%s' must has days=0, it has days=%d" %
1324                        (self.name, value.days), value, state)
1325            return datetime.time(*time.gmtime(value.seconds)[3:6])
1326        value = super(TimeValidator, self).to_python(value, state)
1327        if isinstance(value, datetime.datetime):
1328            value = value.time()
1329        return value
1330
1331    from_python = to_python
1332
1333class SOTimeCol(SOCol):
1334    timeFormat = '%H:%M:%S.%f'
1335
1336    def __init__(self, **kw):
1337        timeFormat = kw.pop('timeFormat', None)
1338        if timeFormat:
1339            self.timeFormat = timeFormat
1340        super(SOTimeCol, self).__init__(**kw)
1341
1342    def createValidators(self):
1343        _validators = super(SOTimeCol, self).createValidators()
1344        if default_datetime_implementation == DATETIME_IMPLEMENTATION:
1345            validatorClass = TimeValidator
1346        elif default_datetime_implementation == MXDATETIME_IMPLEMENTATION:
1347            validatorClass = MXDateTimeValidator
1348        if default_datetime_implementation:
1349            _validators.insert(0, validatorClass(name=self.name, format=self.timeFormat))
1350        return _validators
1351
1352    def _mysqlType(self):
1353        if self.connection and self.connection.can_use_microseconds():
1354            return 'TIME(6)'
1355        else:
1356            return 'TIME'
1357
1358    def _postgresType(self):
1359        return 'TIME'
1360
1361    def _sybaseType(self):
1362        return 'TIME'
1363
1364    def _mssqlType(self):
1365        if self.connection and self.connection.can_use_microseconds():
1366            return 'TIME(6)'
1367        else:
1368            return 'TIME'
1369
1370    def _sqliteType(self):
1371        return 'TIME'
1372
1373    def _firebirdType(self):
1374        return 'TIME'
1375
1376    def _maxdbType(self):
1377        return 'TIME'
1378
1379class TimeCol(Col):
1380    baseClass = SOTimeCol
1381
1382
1383class SOTimestampCol(SODateTimeCol):
1384    """
1385    Necessary to support MySQL's use of TIMESTAMP versus DATETIME types
1386    """
1387
1388    def __init__(self, **kw):
1389        if 'default' not in kw:
1390            kw['default'] = None
1391        SOCol.__init__(self, **kw)
1392
1393    def _mysqlType(self):
1394        if self.connection and self.connection.can_use_microseconds():
1395            return 'TIMESTAMP(6)'
1396        else:
1397            return 'TIMESTAMP'
1398
1399class TimestampCol(Col):
1400    baseClass = SOTimestampCol
1401
1402
1403class TimedeltaValidator(SOValidator):
1404    def to_python(self, value, state):
1405        return value
1406
1407    from_python = to_python
1408
1409class SOTimedeltaCol(SOCol):
1410    def _postgresType(self):
1411        return 'INTERVAL'
1412
1413    def createValidators(self):
1414        return [TimedeltaValidator(name=self.name)] +               super(SOTimedeltaCol, self).createValidators()
1416
1417class TimedeltaCol(Col):
1418    baseClass = SOTimedeltaCol
1419
1420
1421from decimal import Decimal
1422
1423class DecimalValidator(SOValidator):
1424    def to_python(self, value, state):
1425        if value is None:
1426            return None
1427        if isinstance(value, (int, long, Decimal, sqlbuilder.SQLExpression)):
1428            return value
1429        if isinstance(value, float):
1430            value = str(value)
1431        try:
1432            connection = state.connection or state.soObject._connection
1433        except AttributeError:
1434            pass
1435        else:
1436            if hasattr(connection, "decimalSeparator"):
1437                value = value.replace(connection.decimalSeparator, ".")
1438        try:
1439            return Decimal(value)
1440        except:
1441            raise validators.Invalid("expected a Decimal in the DecimalCol '%s', got %s %r instead" %                   (self.name, type(value), value), value, state)
1443
1444    def from_python(self, value, state):
1445        if value is None:
1446            return None
1447        if isinstance(value, float):
1448            value = str(value)
1449        if isinstance(value, basestring):
1450            try:
1451                connection = state.connection or state.soObject._connection
1452            except AttributeError:
1453                pass
1454            else:
1455                if hasattr(connection, "decimalSeparator"):
1456                    value = value.replace(connection.decimalSeparator, ".")
1457            try:
1458                return Decimal(value)
1459            except:
1460                raise validators.Invalid("can not parse Decimal value '%s' in the DecimalCol from '%s'" %
1461                    (value, getattr(state, 'soObject', '(unknown)')), value, state)
1462        if isinstance(value, (int, long, Decimal, sqlbuilder.SQLExpression)):
1463            return value
1464        raise validators.Invalid("expected a Decimal in the DecimalCol '%s', got %s %r instead" %               (self.name, type(value), value), value, state)
1466
1467class SODecimalCol(SOCol):
1468
1469    def __init__(self, **kw):
1470        self.size = kw.pop('size', NoDefault)
1471        assert self.size is not NoDefault,                  "You must give a size argument"
1473        self.precision = kw.pop('precision', NoDefault)
1474        assert self.precision is not NoDefault,                  "You must give a precision argument"
1476        super(SODecimalCol, self).__init__(**kw)
1477
1478    def _sqlType(self):
1479        return 'DECIMAL(%i, %i)' % (self.size, self.precision)
1480
1481    def createValidators(self):
1482        return [DecimalValidator(name=self.name)] +               super(SODecimalCol, self).createValidators()
1484
1485class DecimalCol(Col):
1486    baseClass = SODecimalCol
1487
1488class SOCurrencyCol(SODecimalCol):
1489
1490    def __init__(self, **kw):
1491        pushKey(kw, 'size', 10)
1492        pushKey(kw, 'precision', 2)
1493        super(SOCurrencyCol, self).__init__(**kw)
1494
1495class CurrencyCol(DecimalCol):
1496    baseClass = SOCurrencyCol
1497
1498
1499class DecimalStringValidator(DecimalValidator):
1500    def to_python(self, value, state):
1501        value = super(DecimalStringValidator, self).to_python(value, state)
1502        if self.precision and isinstance(value, Decimal):
1503            assert value < self.max,                       "Value must be less than %s" % int(self.max)
1505            value = value.quantize(self.precision)
1506        return value
1507
1508    def from_python(self, value, state):
1509        value = super(DecimalStringValidator, self).from_python(value, state)
1510        if isinstance(value, Decimal):
1511            if self.precision:
1512                assert value < self.max,                           "Value must be less than %s" % int(self.max)
1514                value = value.quantize(self.precision)
1515            value = value.to_eng_string()
1516        elif isinstance(value, (int, long)):
1517            value = str(value)
1518        return value
1519
1520class SODecimalStringCol(SOStringCol):
1521    def __init__(self, **kw):
1522        self.size = kw.pop('size', NoDefault)
1523        assert (self.size is not NoDefault) and (self.size >= 0),               "You must give a size argument as a positive integer"
1525        self.precision = kw.pop('precision', NoDefault)
1526        assert (self.precision is not NoDefault) and (self.precision >= 0),                  "You must give a precision argument as a positive integer"
1528        kw['length'] = int(self.size) + int(self.precision)
1529        self.quantize = kw.pop('quantize', False)
1530        assert isinstance(self.quantize, bool),                   "quantize argument must be Boolean True/False"
1532        super(SODecimalStringCol, self).__init__(**kw)
1533
1534    def createValidators(self):
1535        if self.quantize:
1536            v = DecimalStringValidator(name=self.name,
1537                precision=Decimal(10) ** (-1 * int(self.precision)),
1538                max=Decimal(10) ** (int(self.size) - int(self.precision)))
1539        else:
1540            v = DecimalStringValidator(name=self.name, precision=0)
1541        return [v] +               super(SODecimalStringCol, self).createValidators(dataType=Decimal)
1543
1544class DecimalStringCol(StringCol):
1545    baseClass = SODecimalStringCol
1546
1547
1548class BinaryValidator(SOValidator):
1549    """
1550    Validator for binary types.
1551
1552    We're assuming that the per-database modules provide some form
1553    of wrapper type for binary conversion.
1554    """
1555
1556    _cachedValue = None
1557
1558    def to_python(self, value, state):
1559        if value is None:
1560            return None
1561        try:
1562            connection = state.connection or state.soObject._connection
1563        except AttributeError:
1564            dbName = None
1565            binaryType = type(None) # Just a simple workaround
1566        else:
1567            dbName = connection.dbName
1568            binaryType = connection._binaryType
1569        if isinstance(value, str):
1570            if dbName == "sqlite":
1571                value = connection.module.decode(value)
1572            return value
1573        if isinstance(value, (buffer, binaryType)):
1574            cachedValue = self._cachedValue
1575            if cachedValue and cachedValue[1] == value:
1576                return cachedValue[0]
1577            if isinstance(value, array): # MySQL
1578                return value.tostring()
1579            return str(value) # buffer => string
1580        raise validators.Invalid("expected a string in the BLOBCol '%s', got %s %r instead" %               (self.name, type(value), value), value, state)
1582
1583    def from_python(self, value, state):
1584        if value is None:
1585            return None
1586        connection = state.connection or state.soObject._connection
1587        binary = connection.createBinary(value)
1588        self._cachedValue = (value, binary)
1589        return binary
1590
1591class SOBLOBCol(SOStringCol):
1592    def __init__(self, **kw):
1593        # Change the default from 'auto' to False - this is a (mostly) binary column
1594        if 'varchar' not in kw: kw['varchar'] = False
1595        super(SOBLOBCol, self).__init__(**kw)
1596
1597    def createValidators(self):
1598        return [BinaryValidator(name=self.name)] +               super(SOBLOBCol, self).createValidators()
1600
1601    def _mysqlType(self):
1602        length = self.length
1603        varchar = self.varchar
1604        if length >= 2**24:
1605            return varchar and "LONGTEXT" or "LONGBLOB"
1606        if length >= 2**16:
1607            return varchar and "MEDIUMTEXT" or "MEDIUMBLOB"
1608        if length >= 2**8:
1609            return varchar and "TEXT" or "BLOB"
1610        return varchar and "TINYTEXT" or "TINYBLOB"
1611
1612    def _postgresType(self):
1613        return 'BYTEA'
1614
1615    def _mssqlType(self):
1616        if self.connection and self.connection.can_use_max_types():
1617            return 'VARBINARY(MAX)'
1618        else:
1619            return "IMAGE"
1620
1621class BLOBCol(StringCol):
1622    baseClass = SOBLOBCol
1623
1624
1625class PickleValidator(BinaryValidator):
1626    """
1627    Validator for pickle types.  A pickle type is simply a binary type
1628    with hidden pickling, so that we can simply store any kind of
1629    stuff in a particular column.
1630
1631    The support for this relies directly on the support for binary for
1632    your database.
1633    """
1634
1635    def to_python(self, value, state):
1636        if value is None:
1637            return None
1638        if isinstance(value, unicode):
1639            dbEncoding = self.getDbEncoding(state, default='ascii')
1640            value = value.encode(dbEncoding)
1641        if isinstance(value, str):
1642            return pickle.loads(value)
1643        raise validators.Invalid("expected a pickle string in the PickleCol '%s', got %s %r instead" %               (self.name, type(value), value), value, state)
1645
1646    def from_python(self, value, state):
1647        if value is None:
1648            return None
1649        return pickle.dumps(value, self.pickleProtocol)
1650
1651class SOPickleCol(SOBLOBCol):
1652
1653    def __init__(self, **kw):
1654        self.pickleProtocol = kw.pop('pickleProtocol', pickle.HIGHEST_PROTOCOL)
1655        super(SOPickleCol, self).__init__(**kw)
1656
1657    def createValidators(self):
1658        return [PickleValidator(name=self.name,
1659                pickleProtocol=self.pickleProtocol)] +               super(SOPickleCol, self).createValidators()
1661
1662    def _mysqlType(self):
1663        length = self.length
1664        if length >= 2**24:
1665            return "LONGBLOB"
1666        if length >= 2**16:
1667            return "MEDIUMBLOB"
1668        return "BLOB"
1669
1670class PickleCol(BLOBCol):
1671    baseClass = SOPickleCol
1672
1673
1674def pushKey(kw, name, value):
1675    if not name in kw:
1676        kw[name] = value
1677
1678all = []
1679for key, value in globals().items():
1680    if isinstance(value, type) and (issubclass(value, (Col, SOCol))):
1681        all.append(key)
1682__all__.extend(all)
1683del all