1
# GNU Enterprise Common - Base DB Driver - Schema Creation
3
# Copyright 2001-2005 Free Software Foundation
5
# This file is part of GNU Enterprise
7
# GNU Enterprise is free software; you can redistribute it
8
# and/or modify it under the terms of the GNU General Public
9
# License as published by the Free Software Foundation; either
10
# version 2, or (at your option) any later version.
12
# GNU Enterprise is distributed in the hope that it will be
13
# useful, but WITHOUT ANY WARRANTY; without even the implied
14
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15
# PURPOSE. See the GNU General Public License for more details.
17
# You should have received a copy of the GNU General Public
18
# License along with program; see the file COPYING. If not,
19
# write to the Free Software Foundation, Inc., 59 Temple Place
20
# - Suite 330, Boston, MA 02111-1307, USA.
22
# $Id: Creation.py 6851 2005-01-03 20:59:28Z jcater $
26
# =============================================================================
28
# =============================================================================
30
class Error (gException):
33
class NoCreationError (Error):
35
msg = _("Database creation not implemented by this driver")
36
Error.__init__ (self, msg)
38
class DefinitionError (Error):
41
class MissingKeyError (DefinitionError):
42
MSG = u_("The definition has no attribute '%s'")
43
def __init__ (self, attribute):
44
DefinitionError.__init__ (self, self.MSG % attribute)
46
class TableDefinitionError (MissingKeyError):
47
MSG = u_("The table definition has no attribute '%s'")
49
class FieldDefinitionError (MissingKeyError):
50
MSG = u_("The field definition has no attribute '%s'")
52
class PrimaryKeyDefinitionError (MissingKeyError):
53
MSG = u_("Primarykey definition has no attribute '%s'")
55
class PrimaryKeyFieldsError (Error):
56
def __init__ (self, table, name):
57
msg = u_("Primarykey '%(name)s' of table '%(table)s' has no fields") \
60
Error.__init__ (self, msg)
62
class PrimaryKeyError (DefinitionError):
63
def __init__ (self, table):
64
msg = u_("Table '%s' has a primary key which is not allowed on "
65
"table modification") % table
66
DefinitionError.__init__ (self, msg)
68
class IndexDefinitionError (MissingKeyError):
69
MSG = u_("Index definition has no attribute '%s'")
71
class IndexFieldsError (Error):
72
def __init__ (self, table, name):
73
msg = u_("Index '%(name)s' of table '%(table)s' has no fields") \
76
Error.__init__ (self, msg)
78
class ConstraintDefinitionError (MissingKeyError):
79
MSG = u_("Constraint definition has no attribute '%s'")
81
class ConstraintFieldsError (Error):
82
def __init__ (self, table, name, fields):
83
msg = u_("Constraint '%(name)s' of table '%(table)s' has no '%(fields)s'")\
87
Error.__init__ (self, msg)
89
class ConstraintTypeError (Error):
90
def __init__ (self, table, name, cType):
91
msg = u_("Type '%(type)s' of constraint '%(name)s' in table '%(table)s' "
96
Error.__init__ (self, msg)
98
class MissingTypeTransformationError (Error):
99
def __init__ (self, typename):
100
msg = u_("No type transformation for '%s' found") % typename
101
Error.__init__ (self, msg)
104
class LengthError (Error):
105
def __init__ (self, identifier, maxlen):
106
msg = u_("The idendifier '%(identifier)s' exceeds the maximum length "
107
"of %(maxlength)d characters") \
108
% {'identifier': identifier,
109
'maxlength': maxlen or 0}
110
Error.__init__ (self, msg)
113
# =============================================================================
114
# Base class for drivers schema creation support
115
# =============================================================================
118
MAX_NAME_LENGTH = None # Max. length of an identifier
119
END_COMMAND = "" # Character used for command termination
121
# ---------------------------------------------------------------------------
123
# ---------------------------------------------------------------------------
125
def __init__ (self, connection = None, introspector = None):
126
self.connection = connection
127
self.introspector = introspector
130
if connection is not None and introspector is None:
131
self.introspector = connection.introspector
134
# ---------------------------------------------------------------------------
136
# ---------------------------------------------------------------------------
138
def createDatabase (self):
140
Descendants can override this function to create a database. Usually all
141
information needed could be gathered from the connection object.
143
raise NoCreationError
146
# ---------------------------------------------------------------------------
147
# Create a table from a table definition
148
# ---------------------------------------------------------------------------
150
def createTable (self, tableDefinition, codeOnly = False):
152
This function creates a table using the given definition and returns a
153
code-tuple, which can be used to to this.
155
@param tableDefinition: a dictionary of the table to be created
156
@param codeOnly: if TRUE no operation takes place, but only the code will
158
@return: a tuple of sequences (prologue, body, epliogue) containing the
159
code to perform the action.
161
self._validateTable (tableDefinition)
165
# ---------------------------------------------------------------------------
166
# Create a primary key
167
# ---------------------------------------------------------------------------
169
def createPrimaryKey (self, tableName, keyDefinition, codeOnly = False):
171
This function creates a primary key for the given table using the primary
174
@param tableName: name of the table for which a key should be created
175
@param keyDefinition: a dictionary of the primary key to be created
176
@param codeOnly: if TRUE no operation takes place, but only the code will
178
@return: a tuple of sequences (prologue, body, epliogue) containing the
179
code to perform the action.
181
self._validatePrimaryKey (tableName, keyDefinition)
185
# ---------------------------------------------------------------------------
187
# ---------------------------------------------------------------------------
189
def createIndex (self, tableName, indexDefinition, codeOnly = False):
191
This function creates an index for the given table using the index
194
@param tableName: name of the table for which an index should be created
195
@param indexDefinition: a dictionary of the index to be created
196
@param codeOnly: if TRUE no operation takes place, but only the code will
198
@return: a tuple of sequences (prologue, body, epliogue) containing the
199
code to perform the action.
201
self._validateIndex (tableName, indexDefinition)
205
# ---------------------------------------------------------------------------
207
# ---------------------------------------------------------------------------
209
def dropIndex (self, tableName, indexName, codeOnly = False):
211
This function drops an index from the given table using the index
214
@param tableName: name of the table to drop an index from
215
@param indexName: name of the index to be dropped
216
@param codeOnly: if TRUE no operation takes place, but only the code will
218
@return: a tuple of sequences (prologue, body, epliogue) containing the
219
code to perform the action.
224
# ---------------------------------------------------------------------------
225
# Create a constraint
226
# ---------------------------------------------------------------------------
228
def createConstraint (self, tableName, constraintDef, codeOnly = False):
230
This function creates a constraint for the given table using the constraint
233
@param tableName: name of the table for which an index should be created
234
@param constraintDef: a dictionary of the constraint to be created
235
@param codeOnly: if TRUE no operation takes place, but only the code will
237
@return: a tuple of sequences (prologue, body, epliogue) containing the
238
code to perform the action.
240
self._validateConstraint (tableName, constraintDef)
244
# ---------------------------------------------------------------------------
246
# ---------------------------------------------------------------------------
248
def modifyTable (self, tableDefinition, codeOnly = False):
250
This function modifies a table according to the given definition.
252
@param tableDefinition: a dictionary of the table to be modified
253
@param codeOnly: if TRUE no operation takes place, but only the code will
255
@return: a tuple of sequences (prologue, body, epliogue) containing the
256
code to perform the action.
258
self._validateTable (tableDefinition, True)
262
# ---------------------------------------------------------------------------
263
# Create fields for a table
264
# ---------------------------------------------------------------------------
266
def createFields (self, tableName, fields, forAlter = False):
268
This function creates all listed fields in the given table. If forAlter is
269
TRUE this function should create the fields for a table modification.
271
@param tableName: name of the table for which fields should be created or
273
@param fields: a list of field definition dictionaries, describing the
274
fields to be created or modified.
275
@param forAlter: if TRUE the fields should be modified, otherwise created
276
@return: a tuple of sequences (prologue, body, epliogue) containing the
277
code to perform the action.
280
self._validateField (tableName, field)
284
# ---------------------------------------------------------------------------
285
# Check wether an element exists or not
286
# ---------------------------------------------------------------------------
288
def exists (self, elementName, elementType = None):
290
This function examines, wether an element exists in a datamodel or not.
291
It's doing this using the given introspector. If no introspecor is
292
available the result is FALSE.
294
@param elementName: name of the element to be examined
295
@param elementType: type of the element to be examined (optional)
296
@return: TRUE if the element was found, otherwise FALSE
298
if self.introspector is not None:
299
return self.introspector.find (name = elementName, type = elementType)
304
# ---------------------------------------------------------------------------
305
# Validate a given table definition
306
# ---------------------------------------------------------------------------
308
def validate (self, tableDef):
310
This function validates all parts of a table definition.
311
@param tableDef: dictionary describing the table and it's parts.
313
self._validateTable (tableDef)
314
tableName = tableDef['name']
316
if tableDef.has_key ('primarykey'):
317
self._validatePrimaryKey (tableName, tableDef ['primarykey'])
319
if tableDef.has_key ('fields'):
320
for field in tableDef ['fields']:
321
self._validateField (tableName, field)
323
if tableDef.has_key ('indices'):
324
for index in tableDef ['indices']:
325
self._validateIndex (tableName, index)
327
if tableDef.has_key ('constraints'):
328
for constraint in tableDef ['constraints']:
329
self._validateConstraint (tableName, constraint)
332
# ---------------------------------------------------------------------------
333
# Make sure to release all references
334
# ---------------------------------------------------------------------------
338
This function releases all circular references held by the creator instance
341
self.introspector = None
342
self.connection = None
346
# ---------------------------------------------------------------------------
347
# Call the appropriate method for a type-transformation
348
# ---------------------------------------------------------------------------
350
def _translateType (self, fieldDefinition):
352
This function calls the appropriate method for a type-conversion according
353
to the field definition's datatype and returns this method's result.
355
@param fieldDefinition: dictionary describing the field.
356
@return: a string with the native data type for the field definition.
358
if not fieldDefinition.has_key ('type'):
359
raise FieldDefinitionError, ('type')
361
aMethod = self.__findMethod (self.__class__, fieldDefinition ['type'])
363
raise MissingTypeTransformationError, (fieldDefinition ['type'])
365
return aMethod (self, fieldDefinition)
368
# ---------------------------------------------------------------------------
369
# Create code for a single field
370
# ---------------------------------------------------------------------------
372
def _processField (self, tableName, fieldDef, forAlter = False):
374
This function creates a portion of code which defines the given field in
377
@param tableName: the table this field belongs to.
378
@param fieldDef: the dictionary describing the field.
379
@param forAlter: If TRUE this function produces code for a table
380
modification, otherwise for a table creation.
381
@return: a tuple of sequences (prologue, body, epliogue) containing the
382
code to perform the action.
387
# ---------------------------------------------------------------------------
388
# Create a usable name for a seuquence like object
389
# ---------------------------------------------------------------------------
391
def _getSequenceName (self, tableName, fieldDefinition):
393
This function creates a name for a sequence like object using the table-
394
and fieldname. It respects a given restriction of identifier length.
396
@param tableName: name of the table
397
@param fieldDefinition: dictionary describing the field
398
@return: string with a name for the given sequence
401
res = "%s_%s_seq" % (tableName, fieldDefinition ['name'])
402
if self._nameTooLong (res):
403
res = "%s_%s_seq" % (tableName, id (fieldDefinition))
405
if self._nameTooLong (res):
406
res = "%s_seq" % (id (fieldDefinition))
408
return self._shortenName (res)
411
# ---------------------------------------------------------------------------
412
# Check if an identifier is too long
413
# ---------------------------------------------------------------------------
415
def _nameTooLong (self, aName):
417
This function returns TRUE if @aName exceeds MAX_NAME_LENGTH, otherwise
420
return (self.MAX_NAME_LENGTH is not None) and \
421
(len (aName) > self.MAX_NAME_LENGTH)
424
# ---------------------------------------------------------------------------
425
# Make sure a given identifier doesn't exceed maximum length
426
# ---------------------------------------------------------------------------
428
def _shortenName (self, aName):
430
This function makes sure the given name doesn't exceed the maximum
432
@param aName: identifier to be checked
433
@return: identifier with extra characters cut off
435
if self._nameTooLong (aName):
436
return aName [:self.MAX_NAME_LENGTH]
441
# ---------------------------------------------------------------------------
442
# Merge all sequences in the given tuples
443
# ---------------------------------------------------------------------------
445
def mergeTuple (self, mergeInto, mergeFrom):
447
This function merges the sequences in the given tuples and returns the
448
first one (which is changes as a side effect too).
449
@param mergeInto: tuple with sequences which gets extended
450
@param mergeFrom: tuple with sequences which mergeInto gets extended with
451
@return: tuple of the same length as mergeInto with all sequences merged
454
for ix in range (len (mergeInto)):
455
mergeInto [ix].extend (mergeFrom [ix])
459
# ---------------------------------------------------------------------------
460
# Validate a table definition
461
# ---------------------------------------------------------------------------
463
def _validateTable (self, tableDef, forAlter = False):
465
This function validates a table definition.
466
@param tableDef: dictionary describing the table
468
@raise TableDefinitionError: If tableDef has no key 'name'
470
self.__validateDefinition (tableDef, ['name'], TableDefinitionError)
471
if self._nameTooLong (tableDef ['name']):
472
raise LengthError, (tableDef ['name'], self.MAX_NAME_LENGTH)
474
if forAlter and tableDef.has_key ('primarykey'):
475
raise PrimaryKeyError, (tableDef ['name'])
478
# ---------------------------------------------------------------------------
479
# Validate a given primary key definition
480
# ---------------------------------------------------------------------------
482
def _validatePrimaryKey (self, tableName, keyDefinition):
484
This function validates a primarykey definition.
485
@param tableName: name of the table the primary key belongs to
486
@param keyDefinition: dictionary describing the primary key
488
@raise PrimaryKeyDefinitionError: if 'name' or 'fields' are missing in the
490
@raise PrimaryKeyFieldsError: if 'fields' is an empty sequence
492
self.__validateDefinition (keyDefinition, ['name', 'fields'],
493
PrimaryKeyDefinitionError)
495
if not len (keyDefinition ['fields']):
496
raise PrimaryKeyFieldsError, (tableName, keyDefinition ['name'])
498
for field in keyDefinition ['fields']:
499
if self._nameTooLong (field):
500
raise LengthError, (field, self.MAX_NAME_LENGTH)
503
# ---------------------------------------------------------------------------
504
# Validate a given index definition
505
# ---------------------------------------------------------------------------
507
def _validateIndex (self, tableName, indexDefinition):
509
This function validates an index definition.
510
@param tableName: name of the table
511
@param indexDefinition: dictionary describing the index
513
@raise IndexDefinitionError: if 'name' or 'fields' are missing in the
515
@raise IndexFieldsError: if 'fields' is an empty sequence
517
self.__validateDefinition (indexDefinition, ['name', 'fields'],
518
IndexDefinitionError)
519
if not len (indexDefinition ['fields']):
520
raise IndexFieldsError, (tableName, indexDefinition ['name'])
522
for field in indexDefinition ['fields']:
523
if self._nameTooLong (field):
524
raise LengthError, (field, self.MAX_NAME_LENGTH)
527
# ---------------------------------------------------------------------------
528
# Validate a given constraint definition
529
# ---------------------------------------------------------------------------
531
def _validateConstraint (self, tableName, constDef):
533
This function validates a constraint definition.
534
@param tableName: name of the table the constraint belongs to
535
@param constDef: the dictionary describing the constraint
537
@raise ConstraintDefinitionError: if 'name' or 'fields' are missing in the
539
@raise ConstraintFieldsError: if 'fields' or 'reffields' is an empty
542
self.__validateDefinition (constDef,
543
['name', 'fields', 'reftable', 'reffields'], ConstraintDefinitionError)
545
if not len (constDef ['fields']):
546
raise ConstraintFieldsError, (tableName, constDef ['name'], 'fields')
547
if not len (constDef ['reffields']):
548
raise ConstraintFieldsError, (tableName, constDef ['name'], 'reffields')
550
if constDef.has_key ('type') and constDef ['type'] != 'foreignkey':
551
raise ConstraintTypeError, (tableName, constDef ['name'],
554
if self._nameTooLong (constDef ['reftable']):
555
raise LengthError, (constDef ['reftable'], self.MAX_NAME_LENGTH)
557
for field in constDef ['fields'] + constDef ['reffields']:
558
if self._nameTooLong (field):
559
raise LengthError, (field, self.MAX_NAME_LENGTH)
562
# ---------------------------------------------------------------------------
563
# Validate a field definition
564
# ---------------------------------------------------------------------------
566
def _validateField (self, tableName, fieldDef):
568
This function validates a field definition.
569
@param tableName: name of the table
570
@param fieldDef: dictionary describing the field
572
@raise FieldDefinitionError: If the dictionary has no 'name' and 'type'
575
self.__validateDefinition (fieldDef, ['name', 'type'], FieldDefinitionError)
576
if self._nameTooLong (fieldDef ['name']):
577
raise LengthError, (fieldDef ['name'], self.MAX_NAME_LENGTH)
580
# ---------------------------------------------------------------------------
581
# Validate all keys in an arbitrary definition
582
# ---------------------------------------------------------------------------
584
def __validateDefinition (self, definition, keys, defError):
586
This function raises an exception if a key in the given sequence is missing
588
@param definition: dictionary to be checked
589
@param keys: sequence of keys which must exist in definition
590
@param defError: DefinitionError class raised on a missing key
593
if not definition.has_key (key):
594
raise defError, (key)
597
# ---------------------------------------------------------------------------
598
# find a method in a class or its' superclasses
599
# ---------------------------------------------------------------------------
601
def __findMethod (self, aClass, aMethod):
603
This function looks for a method in a class and all its' superclasses.
605
@param aClass: the class where the search starts
606
@param aMethod: name of the method to be looked for
607
@return: function pointer to the method found or None if search failed.
610
if aClass.__dict__.has_key (aMethod):
611
return aClass.__dict__ [aMethod]
613
for base in aClass.__bases__:
614
result = self.__findMethod (base, aMethod)
615
if result is not None: