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            return datetime.datetime.strptime(value, self.format)
1117        except:
1118            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)
1120
1121    def from_python(self, value, state):
1122        if value is None:
1123            return None
1124        if isinstance(value, (datetime.datetime, datetime.date, datetime.time, sqlbuilder.SQLExpression)):
1125            return value
1126        if hasattr(value, "strftime"):
1127            return value.strftime(self.format)
1128        raise validators.Invalid("expected a datetime in the DateTimeCol '%s', got %s %r instead" %               (self.name, type(value), value), value, state)
1130
1131if mxdatetime_available:
1132    class MXDateTimeValidator(validators.DateValidator):
1133        def to_python(self, value, state):
1134            if value is None:
1135                return None
1136            if isinstance(value, (DateTimeType, TimeType, sqlbuilder.SQLExpression)):
1137                return value
1138            if isinstance(value, datetime.datetime):
1139                return DateTime.DateTime(value.year, value.month, value.day,
1140                    value.hour, value.minute, value.second)
1141            elif isinstance(value, datetime.date):
1142                return DateTime.Date(value.year, value.month, value.day)
1143            elif isinstance(value, datetime.time):
1144                return DateTime.Time(value.hour, value.minute, value.second)
1145            try:
1146                if self.format.find(".%f") >= 0:
1147                    if '.' in value:
1148                        _value = value.split('.')
1149                        microseconds = _value[-1]
1150                        _l = len(microseconds)
1151                        if _l < 6:
1152                            _value[-1] = '%06d' % int(microseconds)
1153                        elif _l > 6:
1154                            _value[-1] = microseconds[:6]
1155                        if _l != 6:
1156                            value = '.'.join(_value)
1157                    else:
1158                        value += '.0'
1159                value = datetime.datetime.strptime(value, self.format)
1160                return DateTime.DateTime(value.year, value.month, value.day,
1161                    value.hour, value.minute, value.second)
1162            except:
1163                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)
1165
1166        def from_python(self, value, state):
1167            if value is None:
1168                return None
1169            if isinstance(value, (DateTimeType, TimeType, sqlbuilder.SQLExpression)):
1170                return value
1171            if hasattr(value, "strftime"):
1172                return value.strftime(self.format)
1173            raise validators.Invalid("expected a mxDateTime in the DateTimeCol '%s', got %s %r instead" %                   (self.name, type(value), value), value, state)
1175
1176class SODateTimeCol(SOCol):
1177    datetimeFormat = '%Y-%m-%d %H:%M:%S.%f'
1178
1179    def __init__(self, **kw):
1180        datetimeFormat = kw.pop('datetimeFormat', None)
1181        if datetimeFormat:
1182            self.datetimeFormat = datetimeFormat
1183        super(SODateTimeCol, self).__init__(**kw)
1184
1185    def createValidators(self):
1186        _validators = super(SODateTimeCol, self).createValidators()
1187        if default_datetime_implementation == DATETIME_IMPLEMENTATION:
1188            validatorClass = DateTimeValidator
1189        elif default_datetime_implementation == MXDATETIME_IMPLEMENTATION:
1190            validatorClass = MXDateTimeValidator
1191        if default_datetime_implementation:
1192            _validators.insert(0, validatorClass(name=self.name, format=self.datetimeFormat))
1193        return _validators
1194
1195    def _mysqlType(self):
1196        return 'DATETIME'
1197
1198    def _postgresType(self):
1199        return 'TIMESTAMP'
1200
1201    def _sybaseType(self):
1202        return 'DATETIME'
1203
1204    def _mssqlType(self):
1205        return 'DATETIME'
1206
1207    def _sqliteType(self):
1208        return 'TIMESTAMP'
1209
1210    def _firebirdType(self):
1211        return 'TIMESTAMP'
1212
1213    def _maxdbType(self):
1214        return 'TIMESTAMP'
1215
1216class DateTimeCol(Col):
1217    baseClass = SODateTimeCol
1218    @staticmethod
1219    def now():
1220        if default_datetime_implementation == DATETIME_IMPLEMENTATION:
1221            return datetime.datetime.now()
1222        elif default_datetime_implementation == MXDATETIME_IMPLEMENTATION:
1223            return DateTime.now()
1224        else:
1225            assert 0, ("No datetime implementation available "
1226                       "(DATETIME_IMPLEMENTATION=%r)"
1227                       % DATETIME_IMPLEMENTATION)
1228
1229
1230class DateValidator(DateTimeValidator):
1231    def to_python(self, value, state):
1232        if isinstance(value, datetime.datetime):
1233            value = value.date()
1234        if isinstance(value, (datetime.date, sqlbuilder.SQLExpression)):
1235            return value
1236        value = super(DateValidator, self).to_python(value, state)
1237        if isinstance(value, datetime.datetime):
1238            value = value.date()
1239        return value
1240
1241    from_python = to_python
1242
1243class SODateCol(SOCol):
1244    dateFormat = '%Y-%m-%d'
1245
1246    def __init__(self, **kw):
1247        dateFormat = kw.pop('dateFormat', None)
1248        if dateFormat: self.dateFormat = dateFormat
1249        super(SODateCol, self).__init__(**kw)
1250
1251    def createValidators(self):
1252        """Create a validator for the column. Can be overriden in descendants."""
1253        _validators = super(SODateCol, self).createValidators()
1254        if default_datetime_implementation == DATETIME_IMPLEMENTATION:
1255            validatorClass = DateValidator
1256        elif default_datetime_implementation == MXDATETIME_IMPLEMENTATION:
1257            validatorClass = MXDateTimeValidator
1258        if default_datetime_implementation:
1259            _validators.insert(0, validatorClass(name=self.name, format=self.dateFormat))
1260        return _validators
1261
1262    def _mysqlType(self):
1263        return 'DATE'
1264
1265    def _postgresType(self):
1266        return 'DATE'
1267
1268    def _sybaseType(self):
1269        return self._postgresType()
1270
1271    def _mssqlType(self):
1272        """
1273        SQL Server doesn't have  a DATE data type, to emulate we use a vc(10)
1274        """
1275        return 'VARCHAR(10)'
1276
1277    def _firebirdType(self):
1278        return 'DATE'
1279
1280    def _maxdbType(self):
1281        return  'DATE'
1282
1283    def _sqliteType(self):
1284        return 'DATE'
1285
1286class DateCol(Col):
1287    baseClass = SODateCol
1288
1289
1290class TimeValidator(DateTimeValidator):
1291    def to_python(self, value, state):
1292        if isinstance(value, (datetime.time, sqlbuilder.SQLExpression)):
1293            return value
1294        if isinstance(value, datetime.timedelta):
1295            if value.days:
1296                raise validators.Invalid(
1297                    "the value for the TimeCol '%s' must has days=0, it has days=%d" %
1298                        (self.name, value.days), value, state)
1299            return datetime.time(*time.gmtime(value.seconds)[3:6])
1300        value = super(TimeValidator, self).to_python(value, state)
1301        if isinstance(value, datetime.datetime):
1302            value = value.time()
1303        return value
1304
1305    from_python = to_python
1306
1307class SOTimeCol(SOCol):
1308    timeFormat = '%H:%M:%S.%f'
1309
1310    def __init__(self, **kw):
1311        timeFormat = kw.pop('timeFormat', None)
1312        if timeFormat:
1313            self.timeFormat = timeFormat
1314        super(SOTimeCol, self).__init__(**kw)
1315
1316    def createValidators(self):
1317        _validators = super(SOTimeCol, self).createValidators()
1318        if default_datetime_implementation == DATETIME_IMPLEMENTATION:
1319            validatorClass = TimeValidator
1320        elif default_datetime_implementation == MXDATETIME_IMPLEMENTATION:
1321            validatorClass = MXDateTimeValidator
1322        if default_datetime_implementation:
1323            _validators.insert(0, validatorClass(name=self.name, format=self.timeFormat))
1324        return _validators
1325
1326    def _mysqlType(self):
1327        return 'TIME'
1328
1329    def _postgresType(self):
1330        return 'TIME'
1331
1332    def _sybaseType(self):
1333        return 'TIME'
1334
1335    def _sqliteType(self):
1336        return 'TIME'
1337
1338    def _firebirdType(self):
1339        return 'TIME'
1340
1341    def _maxdbType(self):
1342        return 'TIME'
1343
1344class TimeCol(Col):
1345    baseClass = SOTimeCol
1346
1347
1348class SOTimestampCol(SODateTimeCol):
1349    """
1350    Necessary to support MySQL's use of TIMESTAMP versus DATETIME types
1351    """
1352
1353    def __init__(self, **kw):
1354        if 'default' not in kw:
1355            kw['default'] = None
1356        SOCol.__init__(self, **kw)
1357
1358    def _mysqlType(self):
1359        return 'TIMESTAMP'
1360
1361class TimestampCol(Col):
1362    baseClass = SOTimestampCol
1363
1364
1365class TimedeltaValidator(SOValidator):
1366    def to_python(self, value, state):
1367        return value
1368
1369    from_python = to_python
1370
1371class SOTimedeltaCol(SOCol):
1372    def _postgresType(self):
1373        return 'INTERVAL'
1374
1375    def createValidators(self):
1376        return [TimedeltaValidator(name=self.name)] +               super(SOTimedeltaCol, self).createValidators()
1378
1379class TimedeltaCol(Col):
1380    baseClass = SOTimedeltaCol
1381
1382
1383from decimal import Decimal
1384
1385class DecimalValidator(SOValidator):
1386    def to_python(self, value, state):
1387        if value is None:
1388            return None
1389        if isinstance(value, (int, long, Decimal, sqlbuilder.SQLExpression)):
1390            return value
1391        if isinstance(value, float):
1392            value = str(value)
1393        try:
1394            connection = state.connection or state.soObject._connection
1395        except AttributeError:
1396            pass
1397        else:
1398            if hasattr(connection, "decimalSeparator"):
1399                value = value.replace(connection.decimalSeparator, ".")
1400        try:
1401            return Decimal(value)
1402        except:
1403            raise validators.Invalid("expected a Decimal in the DecimalCol '%s', got %s %r instead" %                   (self.name, type(value), value), value, state)
1405
1406    def from_python(self, value, state):
1407        if value is None:
1408            return None
1409        if isinstance(value, float):
1410            value = str(value)
1411        if isinstance(value, basestring):
1412            try:
1413                connection = state.connection or state.soObject._connection
1414            except AttributeError:
1415                pass
1416            else:
1417                if hasattr(connection, "decimalSeparator"):
1418                    value = value.replace(connection.decimalSeparator, ".")
1419            try:
1420                return Decimal(value)
1421            except:
1422                raise validators.Invalid("can not parse Decimal value '%s' in the DecimalCol from '%s'" %
1423                    (value, getattr(state, 'soObject', '(unknown)')), value, state)
1424        if isinstance(value, (int, long, Decimal, sqlbuilder.SQLExpression)):
1425            return value
1426        raise validators.Invalid("expected a Decimal in the DecimalCol '%s', got %s %r instead" %               (self.name, type(value), value), value, state)
1428
1429class SODecimalCol(SOCol):
1430
1431    def __init__(self, **kw):
1432        self.size = kw.pop('size', NoDefault)
1433        assert self.size is not NoDefault,                  "You must give a size argument"
1435        self.precision = kw.pop('precision', NoDefault)
1436        assert self.precision is not NoDefault,                  "You must give a precision argument"
1438        super(SODecimalCol, self).__init__(**kw)
1439
1440    def _sqlType(self):
1441        return 'DECIMAL(%i, %i)' % (self.size, self.precision)
1442
1443    def createValidators(self):
1444        return [DecimalValidator(name=self.name)] +               super(SODecimalCol, self).createValidators()
1446
1447class DecimalCol(Col):
1448    baseClass = SODecimalCol
1449
1450class SOCurrencyCol(SODecimalCol):
1451
1452    def __init__(self, **kw):
1453        pushKey(kw, 'size', 10)
1454        pushKey(kw, 'precision', 2)
1455        super(SOCurrencyCol, self).__init__(**kw)
1456
1457class CurrencyCol(DecimalCol):
1458    baseClass = SOCurrencyCol
1459
1460
1461class DecimalStringValidator(DecimalValidator):
1462    def to_python(self, value, state):
1463        value = super(DecimalStringValidator, self).to_python(value, state)
1464        if self.precision and isinstance(value, Decimal):
1465            assert value < self.max,                       "Value must be less than %s" % int(self.max)
1467            value = value.quantize(self.precision)
1468        return value
1469
1470    def from_python(self, value, state):
1471        value = super(DecimalStringValidator, self).from_python(value, state)
1472        if isinstance(value, Decimal):
1473            if self.precision:
1474                assert value < self.max,                           "Value must be less than %s" % int(self.max)
1476                value = value.quantize(self.precision)
1477            value = value.to_eng_string()
1478        elif isinstance(value, (int, long)):
1479            value = str(value)
1480        return value
1481
1482class SODecimalStringCol(SOStringCol):
1483    def __init__(self, **kw):
1484        self.size = kw.pop('size', NoDefault)
1485        assert (self.size is not NoDefault) and (self.size >= 0),               "You must give a size argument as a positive integer"
1487        self.precision = kw.pop('precision', NoDefault)
1488        assert (self.precision is not NoDefault) and (self.precision >= 0),                  "You must give a precision argument as a positive integer"
1490        kw['length'] = int(self.size) + int(self.precision)
1491        self.quantize = kw.pop('quantize', False)
1492        assert isinstance(self.quantize, bool),                   "quantize argument must be Boolean True/False"
1494        super(SODecimalStringCol, self).__init__(**kw)
1495
1496    def createValidators(self):
1497        if self.quantize:
1498            v = DecimalStringValidator(name=self.name,
1499                precision=Decimal(10) ** (-1 * int(self.precision)),
1500                max=Decimal(10) ** (int(self.size) - int(self.precision)))
1501        else:
1502            v = DecimalStringValidator(name=self.name, precision=0)
1503        return [v] +               super(SODecimalStringCol, self).createValidators(dataType=Decimal)
1505
1506class DecimalStringCol(StringCol):
1507    baseClass = SODecimalStringCol
1508
1509
1510class BinaryValidator(SOValidator):
1511    """
1512    Validator for binary types.
1513
1514    We're assuming that the per-database modules provide some form
1515    of wrapper type for binary conversion.
1516    """
1517
1518    _cachedValue = None
1519
1520    def to_python(self, value, state):
1521        if value is None:
1522            return None
1523        try:
1524            connection = state.connection or state.soObject._connection
1525        except AttributeError:
1526            dbName = None
1527            binaryType = type(None) # Just a simple workaround
1528        else:
1529            dbName = connection.dbName
1530            binaryType = connection._binaryType
1531        if isinstance(value, str):
1532            if dbName == "sqlite":
1533                value = connection.module.decode(value)
1534            return value
1535        if isinstance(value, (buffer, binaryType)):
1536            cachedValue = self._cachedValue
1537            if cachedValue and cachedValue[1] == value:
1538                return cachedValue[0]
1539            if isinstance(value, array): # MySQL
1540                return value.tostring()
1541            return str(value) # buffer => string
1542        raise validators.Invalid("expected a string in the BLOBCol '%s', got %s %r instead" %               (self.name, type(value), value), value, state)
1544
1545    def from_python(self, value, state):
1546        if value is None:
1547            return None
1548        connection = state.connection or state.soObject._connection
1549        binary = connection.createBinary(value)
1550        self._cachedValue = (value, binary)
1551        return binary
1552
1553class SOBLOBCol(SOStringCol):
1554    def __init__(self, **kw):
1555        # Change the default from 'auto' to False - this is a (mostly) binary column
1556        if 'varchar' not in kw: kw['varchar'] = False
1557        super(SOBLOBCol, self).__init__(**kw)
1558
1559    def createValidators(self):
1560        return [BinaryValidator(name=self.name)] +               super(SOBLOBCol, self).createValidators()
1562
1563    def _mysqlType(self):
1564        length = self.length
1565        varchar = self.varchar
1566        if length >= 2**24:
1567            return varchar and "LONGTEXT" or "LONGBLOB"
1568        if length >= 2**16:
1569            return varchar and "MEDIUMTEXT" or "MEDIUMBLOB"
1570        if length >= 2**8:
1571            return varchar and "TEXT" or "BLOB"
1572        return varchar and "TINYTEXT" or "TINYBLOB"
1573
1574    def _postgresType(self):
1575        return 'BYTEA'
1576
1577    def _mssqlType(self):
1578        if self.connection and self.connection.can_use_max_types():
1579            return 'VARBINARY(MAX)'
1580        else:
1581            return "IMAGE"
1582
1583class BLOBCol(StringCol):
1584    baseClass = SOBLOBCol
1585
1586
1587class PickleValidator(BinaryValidator):
1588    """
1589    Validator for pickle types.  A pickle type is simply a binary type
1590    with hidden pickling, so that we can simply store any kind of
1591    stuff in a particular column.
1592
1593    The support for this relies directly on the support for binary for
1594    your database.
1595    """
1596
1597    def to_python(self, value, state):
1598        if value is None:
1599            return None
1600        if isinstance(value, unicode):
1601            dbEncoding = self.getDbEncoding(state, default='ascii')
1602            value = value.encode(dbEncoding)
1603        if isinstance(value, str):
1604            return pickle.loads(value)
1605        raise validators.Invalid("expected a pickle string in the PickleCol '%s', got %s %r instead" %               (self.name, type(value), value), value, state)
1607
1608    def from_python(self, value, state):
1609        if value is None:
1610            return None
1611        return pickle.dumps(value, self.pickleProtocol)
1612
1613class SOPickleCol(SOBLOBCol):
1614
1615    def __init__(self, **kw):
1616        self.pickleProtocol = kw.pop('pickleProtocol', pickle.HIGHEST_PROTOCOL)
1617        super(SOPickleCol, self).__init__(**kw)
1618
1619    def createValidators(self):
1620        return [PickleValidator(name=self.name,
1621                pickleProtocol=self.pickleProtocol)] +               super(SOPickleCol, self).createValidators()
1623
1624    def _mysqlType(self):
1625        length = self.length
1626        if length >= 2**24:
1627            return "LONGBLOB"
1628        if length >= 2**16:
1629            return "MEDIUMBLOB"
1630        return "BLOB"
1631
1632class PickleCol(BLOBCol):
1633    baseClass = SOPickleCol
1634
1635
1636def pushKey(kw, name, value):
1637    if not name in kw:
1638        kw[name] = value
1639
1640all = []
1641for key, value in globals().items():
1642    if isinstance(value, type) and (issubclass(value, (Col, SOCol))):
1643        all.append(key)
1644__all__.extend(all)
1645del all