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