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