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