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