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
0021import re, time
0022from array import array
0023try:
0024    import cPickle as pickle
0025except ImportError:
0026    import pickle
0027import sqlbuilder
0028# Sadly the name "constraints" conflicts with many of the function
0029# arguments in this module, so we rename it:
0030import constraints as consts
0031from formencode import compound
0032from formencode import validators
0033from classregistry import findClass
0034from itertools import count
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
0075class SQLValidator(compound.All):
0076    def attemptConvert(self, value, state, validate):
0077        if validate is validators.to_python:
0078            vlist = list(self.validators[:])
0079            vlist.reverse()
0080        elif validate is validators.from_python:
0081            vlist = self.validators
0082        else:
0083            raise RuntimeError
0084        for validator in vlist:
0085            value = validate(validator, value, state)
0086        return value
0087
0088
0089########################################
0090## Columns
0091########################################
0092
0093# Col is essentially a column definition, it doesn't have
0094# much logic to it.
0095class SOCol(object):
0096
0097    def __init__(self,
0098                 name,
0099                 soClass,
0100                 creationOrder,
0101                 dbName=None,
0102                 default=NoDefault,
0103                 defaultSQL=None,
0104                 foreignKey=None,
0105                 alternateID=False,
0106                 alternateMethodName=None,
0107                 constraints=None,
0108                 notNull=NoDefault,
0109                 notNone=NoDefault,
0110                 unique=NoDefault,
0111                 sqlType=None,
0112                 columnDef=None,
0113                 validator=None,
0114                 immutable=False,
0115                 cascade=None,
0116                 lazy=False,
0117                 noCache=False,
0118                 forceDBName=False,
0119                 title=None,
0120                 tags=[],
0121                 origName=None,
0122                 extra_vars=None):
0123
0124        super(SOCol, self).__init__()
0125
0126        # This isn't strictly true, since we *could* use backquotes or
0127        # " or something (database-specific) around column names, but
0128        # why would anyone *want* to use a name like that?
0129        # @@: I suppose we could actually add backquotes to the
0130        # dbName if we needed to...
0131        if not forceDBName:
0132            assert sqlbuilder.sqlIdentifier(name), 'Name must be SQL-safe (letters, numbers, underscores): %s (or use forceDBName=True)'                  % repr(name)
0134        assert name != 'id', 'The column name "id" is reserved for SQLObject use (and is implicitly created).'
0135        assert name, "You must provide a name for all columns"
0136
0137        self.columnDef = columnDef
0138        self.creationOrder = creationOrder
0139
0140        self.immutable = immutable
0141
0142        # cascade can be one of:
0143        # None: no constraint is generated
0144        # True: a CASCADE constraint is generated
0145        # False: a RESTRICT constraint is generated
0146        # 'null': a SET NULL trigger is generated
0147        if isinstance(cascade, str):
0148            assert cascade == 'null', (
0149                "The only string value allowed for cascade is 'null' (you gave: %r)" % cascade)
0150        self.cascade = cascade
0151
0152        if not isinstance(constraints, (list, tuple)):
0153            constraints = [constraints]
0154        self.constraints = self.autoConstraints() + constraints
0155
0156        self.notNone = False
0157        if notNull is not NoDefault:
0158            self.notNone = notNull
0159            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)
0162        elif notNone is not NoDefault:
0163            self.notNone = notNone
0164        if self.notNone:
0165            self.constraints = [consts.notNull] + self.constraints
0166
0167        self.name = name
0168        self.soClass = soClass
0169        self._default = default
0170        self.defaultSQL = defaultSQL
0171        self.customSQLType = sqlType
0172
0173        # deal with foreign keys
0174        self.foreignKey = foreignKey
0175        if self.foreignKey:
0176            if origName is not None:
0177                idname = soClass.sqlmeta.style.instanceAttrToIDAttr(origName)
0178            else:
0179                idname = soClass.sqlmeta.style.instanceAttrToIDAttr(name)
0180            if self.name != idname:
0181                self.foreignName = self.name
0182                self.name = idname
0183            else:
0184                self.foreignName = soClass.sqlmeta.style.instanceIDAttrToAttr(self.name)
0185        else:
0186            self.foreignName = None
0187
0188        # if they don't give us a specific database name for
0189        # the column, we separate the mixedCase into mixed_case
0190        # and assume that.
0191        if dbName is None:
0192            self.dbName = soClass.sqlmeta.style.pythonAttrToDBColumn(self.name)
0193        else:
0194            self.dbName = dbName
0195
0196        # alternateID means that this is a unique column that
0197        # can be used to identify rows
0198        self.alternateID = alternateID
0199        if self.alternateID and alternateMethodName is None:
0200            self.alternateMethodName = 'by' + self.name[0].capitalize() + self.name[1:]
0201        else:
0202            self.alternateMethodName = alternateMethodName
0203
0204        if unique is NoDefault:
0205            self.unique = alternateID
0206        else:
0207            self.unique = unique
0208
0209        _validators = self.createValidators()
0210        if _validators:
0211            if validator: _validators.append(validator)
0212            self.validator = SQLValidator.join(_validators[0], *_validators[1:])
0213        else:
0214            self.validator = validator
0215        self.noCache = noCache
0216        self.lazy = lazy
0217        # this is in case of ForeignKey, where we rename the column
0218        # and append an ID
0219        self.origName = origName or name
0220        self.title = title
0221        self.tags = tags
0222
0223        if extra_vars:
0224            for name, value in extra_vars.items():
0225                setattr(self, name, value)
0226
0227    def _set_validator(self, value):
0228        self._validator = value
0229        if self._validator:
0230            self.to_python = self._validator.to_python
0231            self.from_python = self._validator.from_python
0232        else:
0233            self.to_python = None
0234            self.from_python = None
0235
0236    def _get_validator(self):
0237        return self._validator
0238
0239    validator = property(_get_validator, _set_validator)
0240
0241    def createValidators(self):
0242        """Create a list of validators for the column."""
0243        return []
0244
0245    def autoConstraints(self):
0246        return []
0247
0248    def _get_default(self):
0249        # A default can be a callback or a plain value,
0250        # here we resolve the callback
0251        if self._default is NoDefault:
0252            return NoDefault
0253        elif hasattr(self._default, '__sqlrepr__'):
0254            return self._default
0255        elif callable(self._default):
0256            return self._default()
0257        else:
0258            return self._default
0259    default = property(_get_default, None, None)
0260
0261    def _get_joinName(self):
0262        return self.soClass.sqlmeta.style.instanceIDAttrToAttr(self.name)
0263    joinName = property(_get_joinName, None, None)
0264
0265    def __repr__(self):
0266        r = '<%s %s' % (self.__class__.__name__, self.name)
0267        if self.default is not NoDefault:
0268            r += ' default=%s' % repr(self.default)
0269        if self.foreignKey:
0270            r += ' connected to %s' % self.foreignKey
0271        if self.alternateID:
0272            r += ' alternate ID'
0273        if self.notNone:
0274            r += ' not null'
0275        return r + '>'
0276
0277    def createSQL(self):
0278        return ' '.join([self._sqlType()] + self._extraSQL())
0279
0280    def _extraSQL(self):
0281        result = []
0282        if self.notNone or self.alternateID:
0283            result.append('NOT NULL')
0284        if self.unique or self.alternateID:
0285            result.append('UNIQUE')
0286        if self.defaultSQL is not None:
0287            result.append("DEFAULT %s" % self.defaultSQL)
0288        return result
0289
0290    def _sqlType(self):
0291        if self.customSQLType is None:
0292            raise ValueError, ("Col %s (%s) cannot be used for automatic "
0293                               "schema creation (too abstract)" %
0294                               (self.name, self.__class__))
0295        else:
0296            return self.customSQLType
0297
0298    def _mysqlType(self):
0299        return self._sqlType()
0300
0301    def _postgresType(self):
0302        return self._sqlType()
0303
0304    def _sqliteType(self):
0305        # SQLite is naturally typeless, so as a fallback it uses
0306        # no type.
0307        try:
0308            return self._sqlType()
0309        except ValueError:
0310            return ''
0311
0312    def _sybaseType(self):
0313        return self._sqlType()
0314
0315    def _mssqlType(self):
0316        return self._sqlType()
0317
0318    def _firebirdType(self):
0319        return self._sqlType()
0320
0321    def _maxdbType(self):
0322        return self._sqlType()
0323
0324    def mysqlCreateSQL(self):
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 '@@: figure out the exception for a delete'
0358        if obj.sqlmeta.cacheColumns:
0359            columns = obj.sqlmeta._columnCache
0360            if columns is None:
0361                obj.sqlmeta.loadValues()
0362            try:
0363                return columns[name]
0364            except KeyError:
0365                return obj.sqlmeta.loadColumn(self)
0366        else:
0367            return obj.sqlmeta.loadColumn(self)
0368
0369    def __set__(self, obj, value):
0370        if self.immutable:
0371            raise AttributeError("The column %s.%s is immutable" %
0372                                 (obj.__class__.__name__,
0373                                  self.name))
0374        obj.sqlmeta.setColumn(self, value)
0375
0376    def __delete__(self, obj):
0377        raise AttributeError("I can't be deleted from %r" % obj)
0378
0379
0380class Col(object):
0381
0382    baseClass = SOCol
0383
0384    def __init__(self, name=None, **kw):
0385        super(Col, self).__init__()
0386        self.__dict__['_name'] = name
0387        self.__dict__['_kw'] = kw
0388        self.__dict__['creationOrder'] = creationOrder.next()
0389        self.__dict__['_extra_vars'] = {}
0390
0391    def _set_name(self, value):
0392        assert self._name is None or self._name == value, (
0393            "You cannot change a name after it has already been set "
0394            "(from %s to %s)" % (self.name, value))
0395        self.__dict__['_name'] = value
0396
0397    def _get_name(self):
0398        return self._name
0399
0400    name = property(_get_name, _set_name)
0401
0402    def withClass(self, soClass):
0403        return self.baseClass(soClass=soClass, name=self._name,
0404                              creationOrder=self.creationOrder,
0405                              columnDef=self,
0406                              extra_vars=self._extra_vars,
0407                              **self._kw)
0408
0409    def __setattr__(self, var, value):
0410        if var == 'name':
0411            super(Col, self).__setattr__(var, value)
0412            return
0413        self._extra_vars[var] = value
0414
0415    def __repr__(self):
0416        return '<%s %s %s>' % (
0417            self.__class__.__name__, hex(abs(id(self)))[2:],
0418            self._name or '(unnamed)')
0419
0420
0421
0422class SOStringLikeCol(SOCol):
0423    """A common ancestor for SOStringCol and SOUnicodeCol"""
0424    def __init__(self, **kw):
0425        self.length = kw.pop('length', None)
0426        self.varchar = kw.pop('varchar', 'auto')
0427        self.char_binary = kw.pop('char_binary', None) # A hack for MySQL
0428        if not self.length:
0429            assert self.varchar == 'auto' or not self.varchar,                      "Without a length strings are treated as TEXT, not varchar"
0431            self.varchar = False
0432        elif self.varchar == 'auto':
0433            self.varchar = True
0434
0435        super(SOStringLikeCol, self).__init__(**kw)
0436
0437    def autoConstraints(self):
0438        constraints = [consts.isString]
0439        if self.length is not None:
0440            constraints += [consts.MaxLength(self.length)]
0441        return constraints
0442
0443    def _sqlType(self):
0444        if self.customSQLType is not None:
0445            return self.customSQLType
0446        if not self.length:
0447            return 'TEXT'
0448        elif self.varchar:
0449            return 'VARCHAR(%i)' % self.length
0450        else:
0451            return 'CHAR(%i)' % self.length
0452
0453    def _check_case_sensitive(self, db):
0454        if self.char_binary:
0455            raise ValueError, "%s does not support binary character columns" % db
0456
0457    def _mysqlType(self):
0458        type = self._sqlType()
0459        if self.char_binary:
0460            type += " BINARY"
0461        return type
0462
0463    def _postgresType(self):
0464        self._check_case_sensitive("PostgreSQL")
0465        return super(SOStringLikeCol, self)._postgresType()
0466
0467    def _sqliteType(self):
0468        self._check_case_sensitive("SQLite")
0469        return super(SOStringLikeCol, self)._sqliteType().replace("CHAR(", "CHAR (")
0470
0471    def _sybaseType(self):
0472        self._check_case_sensitive("SYBASE")
0473        type = self._sqlType()
0474        if not self.notNone and not self.alternateID:
0475            type += ' NULL'
0476        return type
0477
0478    def _mssqlType(self):
0479        if self.customSQLType is not None:
0480            return self.customSQLType
0481        if not self.length:
0482            if self.connection