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
0029
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
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:
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
0091
0092
0093
0094
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
0127
0128
0129
0130
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
0143
0144
0145
0146
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
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
0189
0190
0191 if dbName is None:
0192 self.dbName = soClass.sqlmeta.style.pythonAttrToDBColumn(self.name)
0193 else:
0194 self.dbName = dbName
0195
0196
0197
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
0218
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
0250
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
0306
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
0342
0343
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
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)
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