8
from include import Validator
10
NoDefault = SQLBuilder.NoDefault
11
True, False = 1==1, 0==1
14
########################################
16
########################################
18
# Col is essentially a column definition, it doesn't have
29
alternateMethodName=None,
42
# This isn't strictly true, since we *could* use backquotes or
43
# " or something (database-specific) around column names, but
44
# why would anyone *want* to use a name like that?
45
# @@: I suppose we could actually add backquotes to the
46
# dbName if we needed to...
47
assert SQLBuilder.sqlIdentifier(name), 'Name must be SQL-safe (letters, numbers, underscores): %s' \
49
assert name != 'id', 'The column name "id" is reserved for SQLObject use (and is implicitly created).'
50
assert name, "You must provide a name for all columns"
52
self.columnDef = columnDef
54
self.immutable = immutable
56
# cascade can be one of:
57
# None: no constraint is generated
58
# True: a CASCADE constraint is generated
59
# False: a RESTRICT constraint is generated
60
self.cascade = cascade
62
if type(constraints) not in (type([]), type(())):
63
constraints = [constraints]
64
self.constraints = self.autoConstraints() + constraints
67
if notNull is not NoDefault:
68
self.notNone = notNull
69
assert notNone is NoDefault or \
70
(not notNone) == (not notNull), \
71
"The notNull and notNone arguments are aliases, and must not conflict. You gave notNull=%r, notNone=%r" % (notNull, notNone)
72
elif notNone is not NoDefault:
73
self.notNone = notNone
75
self.constraints = [Constraints.notNull] + self.constraints
79
self._default = default
80
self.customSQLType = sqlType
82
self.foreignKey = foreignKey
84
#assert self.name.upper().endswith('ID'), "All foreign key columns must end with 'ID' (%s)" % repr(self.name)
85
if not self.name.upper().endswith('ID'):
86
self.foreignName = self.name
87
self.name = self.name + "ID"
89
self.foreignName = self.name[:-2]
91
self.foreignName = None
93
# if they don't give us a specific database name for
94
# the column, we separate the mixedCase into mixed_case
97
self.dbName = soClass.sqlmeta.style.pythonAttrToDBColumn(self.name)
101
# alternateID means that this is a unique column that
102
# can be used to identify rows
103
self.alternateID = alternateID
104
if self.alternateID and alternateMethodName is None:
105
self.alternateMethodName = 'by' + self.name[0].capitalize() + self.name[1:]
107
self.alternateMethodName = alternateMethodName
109
if unique is NoDefault:
110
self.unique = alternateID
114
self.validator = validator
115
self.noCache = noCache
118
def _set_validator(self, value):
119
self._validator = value
121
self.toPython = self._validator.toPython
122
self.fromPython = self._validator.fromPython
125
self.fromPython = None
127
def _get_validator(self):
128
return self._validator
130
validator = property(_get_validator, _set_validator)
132
def autoConstraints(self):
135
def _get_default(self):
136
# A default can be a callback or a plain value,
137
# here we resolve the callback
138
if self._default is NoDefault:
140
elif hasattr(self._default, '__sqlrepr__'):
142
elif callable(self._default):
143
return self._default()
146
default = property(_get_default, None, None)
148
def _get_joinName(self):
149
assert self.name[-2:] == 'ID'
150
return self.name[:-2]
151
joinName = property(_get_joinName, None, None)
154
r = '<%s %s' % (self.__class__.__name__, self.name)
155
if self.default is not NoDefault:
156
r += ' default=%s' % repr(self.default)
158
r += ' connected to %s' % self.foreignKey
166
return ' '.join([self._sqlType() + self._extraSQL()])
170
if self.notNone or self.alternateID:
171
result.append('NOT NULL')
172
if self.unique or self.alternateID:
173
result.append('UNIQUE')
177
if self.customSQLType is None:
178
raise ValueError, ("Col %s (%s) cannot be used for automatic "
179
"schema creation (too abstract)" %
180
(self.name, self.__class__))
182
return self.customSQLType
184
def _mysqlType(self):
185
return self._sqlType()
187
def _postgresType(self):
188
return self._sqlType()
190
def _sqliteType(self):
191
# SQLite is naturally typeless, so as a fallback it uses
194
return self._sqlType()
198
def _sybaseType(self):
199
return self._sqlType()
201
def _firebirdType(self):
202
return self._sqlType()
204
def mysqlCreateSQL(self):
205
return ' '.join([self.dbName, self._mysqlType()] + self._extraSQL())
207
def postgresCreateSQL(self):
208
return ' '.join([self.dbName, self._postgresType()] + self._extraSQL())
210
def sqliteCreateSQL(self):
211
return ' '.join([self.dbName, self._sqliteType()] + self._extraSQL())
213
def sybaseCreateSQL(self):
214
return ' '.join([self.dbName, self._sybaseType()] + self._extraSQL())
216
def firebirdCreateSQL(self):
217
# Ian Sparks pointed out that fb is picky about the order
218
# of the NOT NULL clause in a create statement. So, we handle
219
# them differently for Enum columns.
220
if not isinstance(self, SOEnumCol):
221
return ' '.join([self.dbName, self._firebirdType()] + self._extraSQL())
223
return ' '.join([self.dbName] + self._extraSQL() + [self._firebirdType()])
225
def __get__(self, obj, type=None):
227
# class attribute, return the descriptor itself
229
if obj.sqlmeta.obsolete:
230
raise '@@: figure out the exception for a delete'
231
if obj.sqlmeta.cacheColumns:
232
columns = obj.sqlmeta._columnCache
234
obj.sqlmeta.loadValues()
238
return obj.sqlmeta.loadColumn(self)
240
return obj.sqlmeta.loadColumn(self)
242
def __set__(self, obj, value):
244
raise AttributeError("The column %s.%s is immutable" %
245
(obj.__class__.__name__,
247
obj.sqlmeta.setColumn(self, value)
249
def __delete__(self, obj):
250
raise AttributeError("I can't be deleted from %r" % obj)
257
def __init__(self, name=None, **kw):
259
kw['columnDef'] = self
262
def setName(self, value):
263
assert self.kw['name'] is None, "You cannot change a name after it has already been set (from %s to %s)" % (self.kw['name'], value)
264
self.kw['name'] = value
266
def withClass(self, soClass):
267
return self.baseClass(soClass=soClass, **self.kw)
269
class SOStringCol(SOCol):
271
# 3-03 @@: What about BLOB?
273
def __init__(self, **kw):
274
self.length = popKey(kw, 'length')
275
self.varchar = popKey(kw, 'varchar', 'auto')
277
assert self.varchar == 'auto' or not self.varchar, \
278
"Without a length strings are treated as TEXT, not varchar"
280
elif self.varchar == 'auto':
283
SOCol.__init__(self, **kw)
285
def autoConstraints(self):
286
constraints = [Constraints.isString]
287
if self.length is not None:
288
constraints += [Constraints.MaxLength(self.length)]
295
return 'VARCHAR(%i)' % self.length
297
return 'CHAR(%i)' % self.length
299
def _firebirdType(self):
301
return 'BLOB SUB_TYPE TEXT'
303
return self._sqlType()
305
class StringCol(Col):
306
baseClass = SOStringCol
308
class SOIntCol(SOCol):
310
# 3-03 @@: support precision, maybe max and min directly
312
def autoConstraints(self):
313
return [Constraints.isInt]
321
class BoolValidator(Validator.Validator):
323
def fromPython(self, value, state):
325
return SQLBuilder.TRUE
327
return SQLBuilder.FALSE
329
class SOBoolCol(SOCol):
331
def __init__(self, **kw):
332
SOCol.__init__(self, **kw)
333
self.validator = Validator.All.join(BoolValidator(), self.validator)
335
def autoConstraints(self):
336
return [Constraints.isBool]
338
def _postgresType(self):
341
def _mysqlType(self):
344
def _sybaseType(self):
348
baseClass = SOBoolCol
350
class SOFloatCol(SOCol):
352
# 3-03 @@: support precision (e.g., DECIMAL)
354
def autoConstraints(self):
355
return [Constraints.isFloat]
361
baseClass = SOFloatCol
363
class SOKeyCol(SOCol):
365
# 3-03 @@: this should have a simplified constructor
366
# Should provide foreign key information for other DBs.
368
def _mysqlType(self):
371
def _sqliteType(self):
374
def _postgresType(self):
377
def _sybaseType(self):
380
def _firebirdType(self):
387
class SOForeignKey(SOKeyCol):
389
def __init__(self, **kw):
390
foreignKey = kw['foreignKey']
391
style = kw['soClass']._style
392
if not kw.get('name'):
393
kw['name'] = style.instanceAttrToIDAttr(style.pythonClassToAttr(foreignKey))
395
if not kw['name'].upper().endswith('ID'):
396
kw['name'] = style.instanceAttrToIDAttr(kw['name'])
397
SOKeyCol.__init__(self, **kw)
399
def postgresCreateSQL(self):
400
from SQLObject import findClass
401
sql = SOKeyCol.postgresCreateSQL(self)
402
if self.cascade is not None:
403
other = findClass(self.foreignKey)
405
idName = other._idName
406
action = self.cascade and 'CASCADE' or 'RESTRICT'
407
constraint = ('CONSTRAINT %(tName)s_exists '
408
'FOREIGN KEY(%(colName)s) '
409
'REFERENCES %(tName)s(%(idName)s) '
410
'ON DELETE %(action)s' %
412
'colName':self.dbName,
415
sql = ', '.join([sql, constraint])
418
def sybaseCreateSQL(self):
419
from SQLObject import findClass
420
sql = SOKeyCol.sybaseCreateSQL(self)
421
other = findClass(self.foreignKey)
423
idName = other._idName
424
reference = ('REFERENCES %(tName)s(%(idName)s) ' %
427
sql = ' '.join([sql, reference])
430
class ForeignKey(KeyCol):
432
baseClass = SOForeignKey
434
def __init__(self, foreignKey=None, **kw):
435
KeyCol.__init__(self, foreignKey=foreignKey, **kw)
437
class SOEnumCol(SOCol):
439
def __init__(self, **kw):
440
self.enumValues = popKey(kw, 'enumValues', None)
441
assert self.enumValues is not None, \
442
'You must provide an enumValues keyword argument'
443
SOCol.__init__(self, **kw)
445
def autoConstraints(self):
446
return [Constraints.isString, Constraints.InList(self.enumValues)]
448
def _mysqlType(self):
449
return "ENUM(%s)" % ', '.join([SQLBuilder.sqlrepr(v, 'mysql') for v in self.enumValues])
451
def _postgresType(self):
452
length = max(map(len, self.enumValues))
453
enumValues = ', '.join([SQLBuilder.sqlrepr(v, 'postgres') for v in self.enumValues])
454
checkConstraint = "CHECK (%s in (%s))" % (self.dbName, enumValues)
455
return "VARCHAR(%i) %s" % (length, checkConstraint)
457
def _sqliteType(self):
458
return self._postgresType()
460
def _sybaseType(self):
461
return self._postgresType()
463
def _firebirdType(self):
464
return self._postgresType()
467
baseClass = SOEnumCol
469
class SODateTimeCol(SOCol):
471
# 3-03 @@: provide constraints; right now we let the database
472
# do any parsing and checking. And DATE and TIME?
474
def _mysqlType(self):
477
def _postgresType(self):
480
def _sybaseType(self):
483
class DateTimeCol(Col):
484
baseClass = SODateTimeCol
486
class SODateCol(SOCol):
488
# 3-03 @@: provide constraints; right now we let the database
489
# do any parsing and checking. And DATE and TIME?
491
def _mysqlType(self):
494
def _postgresType(self):
497
def _sybaseType(self):
498
return self._postgresType()
501
baseClass = SODateCol
503
class SODecimalCol(SOCol):
505
def __init__(self, **kw):
506
self.size = popKey(kw, 'size', NoDefault)
507
assert self.size is not NoDefault, \
508
"You must give a size argument"
509
self.precision = popKey(kw, 'precision', NoDefault)
510
assert self.precision is not NoDefault, \
511
"You must give a precision argument"
512
SOCol.__init__(self, **kw)
515
return 'DECIMAL(%i, %i)' % (self.size, self.precision)
517
class DecimalCol(Col):
518
baseClass = SODecimalCol
520
class SOCurrencyCol(SODecimalCol):
522
def __init__(self, **kw):
523
pushKey(kw, 'size', 10)
524
pushKey(kw, 'precision', 2)
525
SODecimalCol.__init__(self, **kw)
527
class CurrencyCol(DecimalCol):
528
baseClass = SOCurrencyCol
530
def popKey(kw, name, default=None):
531
if not kw.has_key(name):
537
def pushKey(kw, name, value):
538
if not kw.has_key(name):
542
for key, value in globals().items():
543
if isinstance(value, type) and issubclass(value, Col):