[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[gnue] r7584 - in trunk/gnue-common/src/datasources: . drivers drivers/B
From: |
johannes |
Subject: |
[gnue] r7584 - in trunk/gnue-common/src/datasources: . drivers drivers/Base drivers/DBSIG2 drivers/interbase drivers/maxdb drivers/maxdb/Schema/Creation drivers/maxdb/sapdb drivers/mysql drivers/oracle/Schema/Creation drivers/postgresql drivers/sqlite |
Date: |
Thu, 9 Jun 2005 08:18:04 -0500 (CDT) |
Author: johannes
Date: 2005-06-09 08:18:02 -0500 (Thu, 09 Jun 2005)
New Revision: 7584
Added:
trunk/gnue-common/src/datasources/drivers/maxdb/
Removed:
trunk/gnue-common/src/datasources/drivers/sapdb/
Modified:
trunk/gnue-common/src/datasources/GSchema.py
trunk/gnue-common/src/datasources/drivers/Base/Behavior.py
trunk/gnue-common/src/datasources/drivers/Base/Connection.py
trunk/gnue-common/src/datasources/drivers/DBSIG2/Behavior.py
trunk/gnue-common/src/datasources/drivers/interbase/Behavior.py
trunk/gnue-common/src/datasources/drivers/maxdb/Behavior.py
trunk/gnue-common/src/datasources/drivers/maxdb/Schema/Creation/Creation.py
trunk/gnue-common/src/datasources/drivers/maxdb/sapdb/Connection.py
trunk/gnue-common/src/datasources/drivers/maxdb/sapdb/__init__.py
trunk/gnue-common/src/datasources/drivers/mysql/Behavior.py
trunk/gnue-common/src/datasources/drivers/oracle/Schema/Creation/Creation.py
trunk/gnue-common/src/datasources/drivers/postgresql/Behavior.py
trunk/gnue-common/src/datasources/drivers/sqlite/Behavior.py
Log:
Added writeSchema () to Behavior and createDatabase () to Base.Connection
Modified: trunk/gnue-common/src/datasources/GSchema.py
===================================================================
--- trunk/gnue-common/src/datasources/GSchema.py 2005-06-08 10:23:12 UTC
(rev 7583)
+++ trunk/gnue-common/src/datasources/GSchema.py 2005-06-09 13:18:02 UTC
(rev 7584)
@@ -117,11 +117,6 @@
for child in buddy._children:
others [child._id_ (maxIdLength)] = child
- print "-" * 60
- print "_ID_ :", self._id_ (maxIdLength), repr (self)
- print "MINE :", mine
- print "OTHER:", others
-
# Find out new and changed items
for (key, item) in others.items ():
childDiff = None
@@ -182,15 +177,54 @@
return result
+ # ---------------------------------------------------------------------------
+ # Merge another object tree with this tree
+ # ---------------------------------------------------------------------------
+
+ def merge (self, other):
+ """
+ Incorporate all subtrees from the given object tree of this instances type.
+ """
+
+ # First find objects of the same type in the other tree
+ candidates = other.findChildrenOfType (self._type, True, True)
+
+ # We keep a mapping of all our children
+ mine = {}
+ for mc in self._children:
+ mine [mc._id_ ()] = mc
+
+ # Iterate over all elements in the other tree having the same type
+ for item in candidates:
+ # and observe all their children ...
+ for otherChild in item._children:
+ # ... wether we have to start a recursive merge ...
+ if otherChild._id_ () in mine:
+ mine [otherChild._id_ ()].merge (otherChild)
+ # ... or we can copy the subtree
+ else:
+ new = otherChild.__class__ (self)
+ new.assign (otherChild, True)
+
+
+# =============================================================================
+#
+# =============================================================================
+
class GSImmutableCollection (GSObject):
+ # ---------------------------------------------------------------------------
+ #
+ # ---------------------------------------------------------------------------
+
def diff (self, goal, maxIdLength = None):
result = GSObject.diff (self, goal, maxIdLength)
+
if result is not None:
for item in result._children [:]:
- # We cannot change a constraint directly, instead we remove the
- # original and add the changed one
+ # We cannot change a child of an immutable collection directly, instead
+ # we remove the original and add the changed one.
if item._action == 'change':
result._children.remove (item)
@@ -236,6 +270,13 @@
def __init__(self, parent, **params):
GSObject.__init__(self, parent, 'GSTable', **params)
+ def fields (self, action = None):
+ for field in self.findChildrenOfType ('GSField', False, True):
+ if action is not None and field._action != action:
+ continue
+ else:
+ yield field
+
class GSFields (GSObject):
def __init__(self, parent, **params):
GSObject.__init__(self, parent, 'GSFields', **params)
@@ -257,139 +298,11 @@
def __init__ (self, parent, **params):
GSImmutableCollection.__init__(self, parent, 'GSConstraints', **params)
-
class GSConstraint(GSObject):
def __init__ (self, parent, **params):
GSObject.__init__ (self, parent, 'GSConstraint', **params)
- self._inits.append (self._validate)
self.__tables = None
-
- # ---------------------------------------------------------------------------
- # Check a constraint definition
- # ---------------------------------------------------------------------------
-
- def _validate (self):
- self.type = self.type.lower ()
-
- try:
- if not self.type in ["unique", "foreignkey"]:
- raise ConstraintTypeError, self.type
-
- return
-
- # TODO: verify which constraint-checks are still usefull in here
- csFields = self.findChildrenOfType ('GSConstraintField')
- self.__checkFields (None, csFields)
-
- if self.type == "foreignkey":
- refFields = self.findChildrenOfType ('GSConstraintRef')
- if refFields is None:
- raise ConstraintRefError, self.name
-
- self.__checkFields (refFields [0].table, refFields)
-
- if len (refFields) <> len (csFields):
- raise ConstraintFieldsError, self.name
-
- self.__typeCheck (csFields, refFields)
-
-
- except Exception, message:
- print message
- setErrorFlag (self)
-
-
- # ---------------------------------------------------------------------------
- # find a table definition in the object hierachy for @tablename
- # ---------------------------------------------------------------------------
-
- def __findTable (self, tablename = None):
- # if no tablename is given we're looking for our parent table
- if tablename is None:
- return self.findParentOfType ('GSTable')
-
- if self.__tables is None:
- self.__tables = self.findParentOfType ('GSTables')
-
- if self.__tables is not None:
- for table in self.__tables.findChildrenOfType ('GSTable'):
- if table.name == tablename:
- return table
-
- return None
-
-
- # ---------------------------------------------------------------------------
- # Check if the table 'tablename' has all fields listed in 'cFields'
- # ---------------------------------------------------------------------------
-
- def __checkFields (self, tablename, cFields):
- """
- This function raises an exception if the table @tablename has not all
- fields listed in @cFields.
- """
- table = self.__findTable (tablename)
- if table is None:
- # if the table is not available in the GSD we assume it's ok
- return
-
- tbFields = table.findChildrenOfType ('GSField', True, True)
-
- if len (cFields) > len (tbFields):
- raise errors.ApplicationError, \
- u_("Constraint '%(name)s' has more fields than the table
'%(table)s'")\
- % {'name' : self.name,
- 'table': table.name}
-
- for check in cFields:
- try:
- for field in tbFields:
- if field.name == check.name:
- raise Exception ('found')
-
- except:
- pass
-
- else:
- raise errors.ApplicationError, \
- u_("Table '%(table)s' has no field '%(field)s'.") \
- % {'table': table.name,
- 'field': check.name}
-
-
- # ---------------------------------------------------------------------------
- # Check if both sides of a reference matches in type
- # ---------------------------------------------------------------------------
-
- def __typeCheck (self, csFields, refFields):
- csTable = self.__findTable ()
- rfTable = self.__findTable (refFields [0].table)
-
- # if the referenced table is not available in the gsd, we assume it's ok
- if rfTable is None:
- return
-
- rfFields = {}
- myFields = {}
-
- for item in csTable.findChildrenOfType ('GSField', True, True):
- myFields [item.name] = item
-
- for item in rfTable.findChildrenOfType ('GSField', True, True):
- rfFields [item.name] = item
-
-
- for ix in range (0, len (csFields)):
- if myFields [csFields [ix].name].type != \
- rfFields [refFields [ix].name].type:
- raise errors.ApplicationError, \
- u_("Constraint '%(name)s': typemismatch in reference field "
- "'%(field)s'.") \
- % {'name' : self.name,
- 'field': csFields [ix].name}
-
-
class GSConstraintField (GSObject):
def __init__ (self, parent, **params):
GSObject.__init__(self, parent, 'GSConstraintField', True, **params)
@@ -757,7 +670,7 @@
'Required': 1,
'Typecast': GTypecast.name },
'unique': {
- 'Default': 0,
+ 'Default': False,
'Typecast': GTypecast.boolean } },
'ParentTags': ('indexes',) },
Modified: trunk/gnue-common/src/datasources/drivers/Base/Behavior.py
===================================================================
--- trunk/gnue-common/src/datasources/drivers/Base/Behavior.py 2005-06-08
10:23:12 UTC (rev 7583)
+++ trunk/gnue-common/src/datasources/drivers/Base/Behavior.py 2005-06-09
13:18:02 UTC (rev 7584)
@@ -21,16 +21,44 @@
#
# $Id$
+from gnue.common.apps import errors
from gnue.common.datasources import GSchema
# =============================================================================
+# Exceptions
+# =============================================================================
+
+class MissingTypeTransformationError (errors.SystemError):
+ def __init__ (self, typename):
+ msg = u_("No type transformation for '%s' found") % typename
+ errors.SystemError.__init__ (self, msg)
+
+
+# =============================================================================
# This class implements the basic schema introspection and creation support
# =============================================================================
class Behavior:
+ """
+ Generic class for schema support
+ @cvar _maxIdLength: maximum length of an identifier or None if no restriction
+ @cvar _type2native: dictionary mapping field-types to native datatypes
+
+ @ivar _current: GSchema instance with the connection's current schema. This
+ tree will be set by writeSchema ()
+ @ivar _new: GSchema instance with the schema as it should look like after
+ writeSchema ()
+ @ivar _diff: GSchema instance containing the difference between _current and
+ _new. All items in this tree have an additional attribute '_action' which
+ determines the item's state within the diff. It can be one of 'add',
+ 'change' or 'remove'.
+ """
+
_maxIdLength = None # Maximum length of an identifier
+ _type2native = {} # Mapping between GSD-Types and natives
+
# ---------------------------------------------------------------------------
# Constructor
# ---------------------------------------------------------------------------
@@ -38,6 +66,8 @@
def __init__ (self, connection):
self.__connection = connection
+ self._lookups = {} # Name-conversion mapping for element names
+ self._elements = {} # Per table mapping of elements
# ---------------------------------------------------------------------------
@@ -60,42 +90,219 @@
# ---------------------------------------------------------------------------
- #
+ # Update the current connection's schema with the given schema
# ---------------------------------------------------------------------------
+ def writeSchema (self, schema):
+ """
+ Generate a command sequence to integrate the given schema tree into the
+ connection's current schema.
+
+ @param schema: GSchema object tree to be integrated in the connection's
+ current schema
+
+ @return: sequence of statements to be executed on the connection in order
+ to create/update the schema
+ """
+
+ self._current = self.readSchema ()
+ self._diff = current.diff (schema, self._maxIdLength)
+ self._new = schema
+
+ self._lookups = self.__getLookups (self._current)
+ self._elements = {}
+
+ return self._writeSchema_ (self._current, self._new, self._diff)
+
+
+ # ---------------------------------------------------------------------------
+ # Create a new database
+ # ---------------------------------------------------------------------------
+
+ def createDatabase (self):
+ """
+ Create a new database specified by the associated connection.
+ """
+
+ raise NotImplementedError
+
+
+ # ---------------------------------------------------------------------------
+ # Merge two tripples of sequences
+ # ---------------------------------------------------------------------------
+
+ def mergeTriple (self, result, source):
+ """
+ Merge the sequences in the given triples and return the first one (which
+ has been changed as a side effect too).
+
+ @param result: triple of sequences which get extended
+ @param source: triple of sequences to extend the result with
+
+ @return: triple of merged sequences
+ """
+
+ if len (result) != len (source):
+ raise errors.SystemError, u_("Cannot merge triples of different length")
+
+ for (dest, src) in zip (result, source):
+ dest.extend (src)
+
+ return result
+
+
+ # ---------------------------------------------------------------------------
+ # Make sure a given identifier doesn't exceed maximum length
+ # ---------------------------------------------------------------------------
+
+ def shortenName (self, name, stripLast = False):
+ """
+ Return the longest usable part of a given identifier.
+
+ @param name: identifier to be checked
+ @param stripLast: if True, the last character is cut off if name has at
+ least maximum length. This way one could append another character
+ without violating length restrictions.
+
+ @return: identifier with extra characters cut off
+ """
+
+ if self.__nameTooLong (name):
+ result = name [:self._maxIdLength - (stripLast and 1 or 0)]
+ else:
+ result = name
+
+ return result
+
+
+ # ---------------------------------------------------------------------------
+ # Virtual method: read the connection's schema tree
+ # ---------------------------------------------------------------------------
+
def _readSchema_ (self, parent):
"""
+ Retrieve the connection's schema information and return it as GSchema
+ object tree. This method must be overriden by a descendant.
+
+ @raises NotImplementedError: unless it get's replaced by a descendant
"""
- return None
+ raise NotImplementedError
# ---------------------------------------------------------------------------
- # Update the current connection's schema with the given schema
+ # Create code for merging the backend's schema to fit the given tree
# ---------------------------------------------------------------------------
- def writeSchema (self, schema):
+ def _writeSchema_ (self, current, new, diff):
+
+ raise NotImplementedError
+
+
+ # ---------------------------------------------------------------------------
+ # Transform a GSField's type attribute into a usable 'native type'
+ # ---------------------------------------------------------------------------
+
+ def _getNativetype_ (self, field):
"""
- Generate a command sequence to integrate the given schema tree into the
- connection's current schema.
+ Get the apropriate native datatype for a given GSField's type attribute.
+ If there is a method of the same name as the GSField's type this function
+ will be called with the GSField as it's argument. If no such method is
+ available, but the GSField's type is listed in the '_type2native'
+ dictionary, that value will be used. Otherwise an exception will be raised.
+
+ @param field: GSField to get a native datatype for
+ @return: string with the native datatype
- @param schema: GSchema object tree to be integrated in the connection's
- current schema
+ @raises MissingTypeTransformationError: if there is no conversion method
+ for the GSField's type
+ """
- @return: sequence of statements to be executed on the connection in order
- to create/update the schema
+ if hasattr (field, 'type'):
+ if hasattr (self, field.type):
+ return getattr (self, field.type) (field)
+
+ elif field.type in self._type2native:
+ return self._type2native [field.type]
+
+ raise MissingTypeTransformationError, field.type
+
+ else:
+ return ""
+
+
+ # ---------------------------------------------------------------------------
+ # Create a usable name for a seuquence like object
+ # ---------------------------------------------------------------------------
+
+ def _getSequenceName_ (self, field):
"""
+ Create a name suitable for a sequence like object using the table- and
+ fieldname.
- current = self.readSchema ()
- delta = current.diff (schema, self._maxIdLength)
+ @param field: GSField instance to create a sequence name for
- return self._writeSchema_ (delta)
+ @return: string with a name for the given sequence
+ """
+ table = field.findParentOfType ('GSTable')
+ result = "%s_%s_seq" % (table.name, field.name)
+ if self.__nameTooLong (result):
+ result = "%s_%s_seq" % (table.name, abs (id (field)))
+
+ if self.__nameTooLong (result):
+ result = "%s_seq" % (abs (id (field)))
+
+ return self.shortenName (result)
+
+
# ---------------------------------------------------------------------------
- #
+ # Create a unique name using a given lookup table
# ---------------------------------------------------------------------------
- def _writeSchema_ (self, delta):
+ def _getSafeName_ (self, name, prefix = None):
+ """
+ Return a unique name based on the current lookup-table, which does not
+ exceed the maximum identifier length. If the optional prefix argument is
+ given it will be used for building lookup-keys.
- return None
+ @param name: name to get a unique identifier for
+ @param prefix: if given use this prefix for lookup-keys
+
+ @return: unique name of at most _maxIdLength characters
+ """
+
+ count = 0
+ pattern = prefix and "%s_%%s" % prefix or "%s"
+ cname = self.shortenName (name)
+
+ while count < 10 and (pattern % cname) in self._lookups:
+ cname = "%s%d" % (self.shortenName (name, True), count)
+ count += 1
+
+ self._lookups [pattern % cname] = None
+
+ return cname
+
+
+ # ---------------------------------------------------------------------------
+ # Check if a given identifier is too long
+ # ---------------------------------------------------------------------------
+
+ def __nameTooLong (self, name):
+
+ return self._maxIdLength is not None and len (name) > self._maxIdLength
+
+
+ # ---------------------------------------------------------------------------
+ # Build a lookup dictionary for all constraints and indices in a schema
+ # ---------------------------------------------------------------------------
+
+ def __getLookups (self, schema):
+
+ result = {}.fromkeys (["CONSTRAINT_%s" % c.name for c in \
+ schema.findChildrenOfType ('GSConstraint', False,
True)])
+ result.update ({}.fromkeys (["INDEX_%s" % i.name for i in \
+ schema.findChildrenOfType ('GSIndex', False, True)]))
+ return result
Modified: trunk/gnue-common/src/datasources/drivers/Base/Connection.py
===================================================================
--- trunk/gnue-common/src/datasources/drivers/Base/Connection.py
2005-06-08 10:23:12 UTC (rev 7583)
+++ trunk/gnue-common/src/datasources/drivers/Base/Connection.py
2005-06-09 13:18:02 UTC (rev 7584)
@@ -517,6 +517,16 @@
# ---------------------------------------------------------------------------
+ # Create a new database for this connection
+ # ---------------------------------------------------------------------------
+
+ def createDatabase (self):
+
+ if self.behavior is not None:
+ self.behavior.createDatabase ()
+
+
+ # ---------------------------------------------------------------------------
# Virtual methods to be implemented by descendants
# ---------------------------------------------------------------------------
Modified: trunk/gnue-common/src/datasources/drivers/DBSIG2/Behavior.py
===================================================================
--- trunk/gnue-common/src/datasources/drivers/DBSIG2/Behavior.py
2005-06-08 10:23:12 UTC (rev 7583)
+++ trunk/gnue-common/src/datasources/drivers/DBSIG2/Behavior.py
2005-06-09 13:18:02 UTC (rev 7584)
@@ -24,6 +24,459 @@
from gnue.common.datasources.drivers import Base
from gnue.common.datasources import GSchema
+# =============================================================================
+# Base class implementing schema support common to all DBSIG2 based drivers
+# =============================================================================
+
class Behavior (Base.Behavior):
- pass
+ """
+ Implementation of schema support (writing) common to all DBSIG2 based backend
+ drivers.
+ @cvar _alterMultiple: boolean flag indicating wether an 'alter table'
+ statement can contain multiple fields or not.
+ @cvar _extraPrimaryKey: boolean flag indicating wether primary keys must be
+ added with an extra command (ie. alter table) or not.
+ @cvar _writableTypes: list of GSTables-types to be handled by writeSchema ().
+ GSTables instances of other types (ie. views) are ignored.
+ @cvar _numbers: triple specifying rules for datatype transformation of
+ numeric types. The first element is a sequence of tuples (maxlen, type). If
+ the numeric field has no precision (it's a whole number) this sequence will
+ be iterated in an ascending order using the first suitable (length <=
+ maxlen) item. The second element of the triple is a fallback-type for whole
+ numbers if the requested length exceeds all tuples in the previous
+ sequence. This type must take a single argument (ie. 'numeric(%s)') which
+ will be set to the requested length. The last element of the triple is a
+ format string used for numeric types with precision (floats). It must
+ contain a '%(length)s' and a '%(scale)s' placeholder, i.e.
+ "numeric (%(length)s,%(scale)s)".
+ """
+
+ _writeableTypes = ['table']
+ _alterMultiple = True # multiple fields in an alter table statement
+ _extraPrimaryKey = False
+
+ _numbers = ([], None, None)
+ _type2native = {'datetime': 'datetime',
+ 'time' : 'time',
+ 'date' : 'date'}
+
+ # ---------------------------------------------------------------------------
+ # Generate a code sequence to match the requested schema
+ # ---------------------------------------------------------------------------
+
+ def _writeSchema_ (self, current, new, diff):
+ """
+ Generate a command sequence to integrate the given schema tree into the
+ connection's current schema.
+
+ @param current: GSchema tree with the current schema at the backend
+ @param new: GSchema tree with the schema to be achieved
+ @param diff: GSchema tree with the differences between current and new
+
+ @return: sequence of commands to be executed for changing the current
+ schema into the new one
+ """
+
+ result = (prolog, main, epilog) = ([], [], [])
+
+ for block in diff.findChildrenOfType ('GSTables', False, True):
+ if not block.type in self._writeableTypes:
+ continue
+
+ for table in block.findChildrenOfType ('GSTable', False, True):
+ # We do not drop tables
+ if table._action in ['add', 'change']:
+ self.mergeTriple (result, self._createTable_ (table))
+
+ return prolog + main + epilog
+
+
+ # ---------------------------------------------------------------------------
+ # Create the command sequence for table creation or modification
+ # ---------------------------------------------------------------------------
+
+ def _createTable_ (self, table):
+ """
+ Generate a code-triple to create or change the given table. Note:
+ writeSchema () does *not* remove (drop) tables. GSTable instances with a
+ 'remove' action are skipped.
+
+ @param table: GSTable instance to be processed
+ @return: code-triple with the needed commands
+ """
+
+ result = (pre, body, post) = ([], [], [])
+
+ # We don't want to drop relations, do we ?!
+ if table._action == 'remove':
+ return result
+
+ # Do we have some constraints or indices to be dropped ?
+ for constraint in table.findChildrenOfType ('GSConstraint', False, True):
+ if constraint._action == "remove":
+ csKey = "CONSTRAINT_%s" % constraint.name
+ if csKey in self._lookups:
+ del self._lookups [csKey]
+
+ pre.extend (self._dropConstraint_ (constraint))
+
+ for index in table.findChildrenOfType ('GSIndex', False, True):
+ if index._action == "remove":
+ ixKey = "INDEX_%s" % index.name
+ if ixKey in self._lookups:
+ del self._lookups [ixKey]
+
+ pre.extend (self._dropIndex_ (index))
+
+ # Create an 'ALTER TABLE' sequence for changed tables
+ if table._action == 'change':
+ fields = [f for f in table.findChildrenOfType ('GSField', False, True) \
+ if f._action in ['add', 'change']]
+
+ if len (fields):
+ if self._alterMultiple:
+ fcode = self._createFields_ (table)
+ code = u"ALTER TABLE %s ADD (%s)" % (table.name, ", ".join
(fcode[1]))
+ self.mergeTriple (result, (fcode [0], [code], fcode [2]))
+
+ else:
+ for field in fields:
+ fcode = self._createFields_ (field)
+ code = u"ALTER TABLE %s ADD %s" \
+ % (table.name, ", ".join (fcode [1]))
+
+ self.mergeTriple (result, (fcode [0], [code], fcode [2]))
+
+ # Create a new table
+ else:
+ fcode = self._createFields_ (table)
+
+ pkey = table.findChildOfType ('GSPrimaryKey')
+ if pkey is not None:
+ triple = self._extraPrimaryKey and result or fcode
+ self.mergeTriple (triple, self._createPrimaryKey_ (pkey))
+
+ code = u"CREATE TABLE %s (%s)" % (table.name, ", ".join (fcode [1]))
+ self.mergeTriple (result, (fcode [0], [code], fcode [2]))
+
+ # build all indices
+ for index in table.findChildrenOfType ('GSIndex', False, True):
+ if index._action == 'add':
+ self.mergeTriple (result, self._createIndex_ (index))
+
+ # build all constraints
+ for constraint in table.findChildrenOfType ('GSConstraint', False, True):
+ if constraint._action == 'add':
+ self.mergeTriple (result, self._createConstraint_ (constraint))
+
+ return result
+
+
+ # ---------------------------------------------------------------------------
+ # Create a fields sequence for the given item
+ # ---------------------------------------------------------------------------
+
+ def _createFields_ (self, item):
+ """
+ Create a code-triple for the given GSTable or GSField.
+
+ @param item: GSTable or GSField instance. For a GSTable instance this
+ method must add all fields to the result, otherwise only the given
+ field.
+ @return: code-triple for the fields in question
+ """
+
+ result = (pre, body, post) = ([], [], [])
+
+ if isinstance (item, GSchema.GSTable):
+ for field in item.findChildrenOfType ('GSField', False, True):
+ self.mergeTriple (result, self._processField_ (field))
+
+ elif isinstance (item, GSchema.GSField):
+ self.mergeTriple (result, self._processField_ (item))
+
+ return result
+
+
+ # ---------------------------------------------------------------------------
+ # Process a given field
+ # ---------------------------------------------------------------------------
+
+ def _processField_ (self, field):
+ """
+ Create a code-triple for a single field. This function handles defaults and
+ nullable flags too.
+
+ @param field: GSField instance to create code for
+ @return: code-triple for the field
+ """
+
+ result = (pre, body, post) = ([], [], [])
+ if field._action == 'remove':
+ return result
+
+ body.append ("%s %s" % (field.name, self._getNativetype_ (field)))
+
+ if hasattr (field, 'defaultwith'):
+ self._defaultwith_ (result, field)
+
+ if hasattr (field, 'default') and field.default:
+ default = field.default
+ if default [:8].upper () != 'DEFAULT ':
+ default = "DEFAULT '%s'" % default
+
+ self._setColumnDefault_ (result, field, default)
+
+ self._integrateNullable_ (result, field)
+
+ return result
+
+
+ # ---------------------------------------------------------------------------
+ # Set a default value for a given column
+ # ---------------------------------------------------------------------------
+
+ def _setColumnDefault_ (self, code, field, default):
+ """
+ Set a default value for a given column. If it is called for a table
+ modification the epilogue of the code-block will be modified. On a table
+ creation, this function assumes the field's code is in the last line of the
+ code-block's body sequence.
+
+ @param code: code-triple to get the result
+ @param field: the GSField instance defining the default value
+ @param default: string with the default value for the column
+ """
+
+ table = field.findParentOfType ('GSTable')
+ if table._action == 'change':
+ code [2].append (u"ALTER TABLE %s ALTER COLUMN %s SET %s" % \
+ (table.name, field.name, default))
+ else:
+ code [1][-1] += " %s" % default
+
+
+ # ---------------------------------------------------------------------------
+ # Handle the nullable flag of a field
+ # ---------------------------------------------------------------------------
+
+ def _integrateNullable_ (self, code, field):
+ """
+ Handle the nullable-flag of a given field. If the field is not
+ nullable the last line of the code's body sequence will be modified on a
+ create-action, or an 'alter table'-statement is added to the code's
+ epilogue. @see: _setColumnDefault ()
+
+ @param code: code-triple to get the result.
+ @param field: the GSField instance defining the nullable-flag
+ """
+
+ if hasattr (field, 'nullable') and not field.nullable:
+ self._setColumnDefault_ (code, field, "NOT NULL")
+
+
+ # ---------------------------------------------------------------------------
+ # Handle special kinds of default values (like functions, sequences, ...)
+ # ---------------------------------------------------------------------------
+
+ def _defaultwith_ (self, result, field):
+ """
+ Process special kinds of default values like sequences, functions and so
+ on. Defaults of type 'constant' are already handled by '_processFields_'.
+
+ @param result: code-triple of the current field as built by _processFields_
+ @param field: GSField instance to process the default for
+ """
+
+ pass
+
+
+ # ---------------------------------------------------------------------------
+ # Create a code sequence for a primary key
+ # ---------------------------------------------------------------------------
+
+ def _createPrimaryKey_ (self, pkey):
+ """
+ Create a code triple for the given primary key. If _extraPrimaryKey is set
+ to True the result's epilogue will contain an 'ALTER TABLE' statement,
+ otherwise the result's body sequence will contain the constraint code.
+
+ @param pkey: GSPrimaryKey instance defining the primary key
+ @return: code-triple with the resulting code
+ """
+
+ result = (pre, body, post) = ([], [], [])
+ keyName = self._getSafeName_ (pkey.name, "PK")
+ fields = ", ".join ([field.name for field in \
+ pkey.findChildrenOfType ('GSPKField', False, True)])
+
+ code = u"CONSTRAINT %s PRIMARY KEY (%s)" % (keyName, fields)
+
+ if self._extraPrimaryKey:
+ table = pkey.findParentOfType ('GSTable')
+ post.append (u"ALTER TABLE %s ADD %s" % (table.name, code))
+ else:
+ body.append (code)
+
+ return result
+
+
+ # ---------------------------------------------------------------------------
+ # Create a code triple for a given index
+ # ---------------------------------------------------------------------------
+
+ def _createIndex_ (self, index):
+ """
+ Create a code triple for the given index. If another GSTable instance
+ wrapping the same table already created the index no code will be
+ generated.
+
+ @param index: GSIndex instance of index to be processed
+ @return: code triple for the index
+ """
+
+ result = (pre, body, post) = ([], [], [])
+
+ table = index.findParentOfType ('GSTable')
+ elements = self._elements.setdefault (table.name, {})
+ ixKey = "INDEX_%s" % index.name
+
+ # If the index was already processed by another GSTable instance of the
+ # same table, just skip it
+ if ixKey in elements:
+ return result
+ else:
+ elements [ixKey] = None
+
+ unique = hasattr (index, 'unique') and index.unique or False
+ ixName = self._getSafeName_ (index.name, "INDEX")
+ fields = index.findChildrenOfType ('GSIndexField', False, True)
+
+ body.append (u"CREATE %sINDEX %s ON %s (%s)" % \
+ (unique and "UNIQUE " or "", ixName, table.name,
+ ", ".join ([f.name for f in fields])))
+
+ return result
+
+
+ # ---------------------------------------------------------------------------
+ # Create a constraint
+ # ---------------------------------------------------------------------------
+
+ def _createConstraint_ (self, constraint):
+ """
+ Create a code triple for the given constraint. By adding all code to the
+ epilogue, the order of processing related tables does not matter, since all
+ new tables are created in the body-part of the code-triples.
+
+ @param constraint: GSConstraint instance to be processed
+ @return: code triple with the command sequences
+ """
+
+ result = (pre, body, post) = ([], [], [])
+
+ table = constraint.findParentOfType ('GSTable')
+ elements = self._elements.setdefault (table.name, {})
+ csKey = "CONSTRAINT_%s" % constraint.name
+
+ # If the constraint was already processed by another GSTable instance of
+ # the same table, just skip it
+ if csKey in elements:
+ return result
+ else:
+ elements [csKey] = None
+
+ csName = self._getSafeName_ (constraint.name, "CONSTRAINT")
+ fields = constraint.findChildrenOfType ('GSConstraintField', False, True)
+ rfields = constraint.findChildrenOfType ('GSConstraintRef', False, True)
+
+ code = u"ALTER TABLE %s ADD CONSTRAINT %s FOREIGN KEY (%s) " \
+ "REFERENCES %s (%s)" \
+ % (table.name, csName, ", ".join ([f.name for f in fields]),
+ rfields [0].table, ", ".join ([r.name for r in rfields]))
+
+ post.append (code)
+
+ return result
+
+
+ # ---------------------------------------------------------------------------
+ # Drop a constraint
+ # ---------------------------------------------------------------------------
+
+ def _dropConstraint_ (self, constraint):
+ """
+ Create a command sequence for dropping the given constraint.
+
+ @param constraint: GSConstraint instance to be dropped
+ @return: command sequence
+ """
+
+ return [u"DROP CONSTRAINT %s" % constraint.name]
+
+
+ # ---------------------------------------------------------------------------
+ # Drop an index
+ # ---------------------------------------------------------------------------
+
+ def _dropIndex_ (self, index):
+ """
+ Create a command sequence for dropping the given index.
+
+ @param index: GSIndex instance to be dropped
+ @return: command sequence
+ """
+
+ return [u"DROP INDEX %s" % index.name]
+
+
+ # ---------------------------------------------------------------------------
+ # A string becomes either varchar or text
+ # ---------------------------------------------------------------------------
+
+ def string (self, field):
+ """
+ Return the native datatype for a string field. If a length is defined it
+ results in a 'varchar'- otherwise in a 'text'-field.
+
+ @param field: GSField instance to get a native datatype for
+ @return: varchar (length) or text
+ """
+
+ if hasattr (field, 'length') and field.length:
+ return "varchar (%s)" % field.length
+ else:
+ return "text"
+
+
+ # ---------------------------------------------------------------------------
+ # Numeric type transformation
+ # ---------------------------------------------------------------------------
+
+ def number (self, field):
+ """
+ Return the native datatye for a numeric field.
+
+ @param field: GSField instance to get the native datatype for
+ @return: string with the native datatype
+ """
+
+ length = hasattr (field, 'length') and field.length or 0
+ scale = hasattr (field, 'precision') and field.precision or 0
+
+ if not scale:
+ self._numbers [0].sort ()
+
+ for (maxlen, ftype) in self._numbers [0]:
+ if maxlen is None:
+ return ftype
+
+ elif length <= maxlen:
+ return ftype
+
+ if self._numbers [1]:
+ return self._numbers [1] % length
+
+ else:
+ return self._numbers [2] % {'length': length, 'scale': scale}
Modified: trunk/gnue-common/src/datasources/drivers/interbase/Behavior.py
===================================================================
--- trunk/gnue-common/src/datasources/drivers/interbase/Behavior.py
2005-06-08 10:23:12 UTC (rev 7583)
+++ trunk/gnue-common/src/datasources/drivers/interbase/Behavior.py
2005-06-09 13:18:02 UTC (rev 7584)
@@ -23,14 +23,15 @@
import re
-from gnue.common.datasources.drivers import Base
+from gnue.common.datasources.GLoginHandler import BasicLoginHandler
+from gnue.common.datasources.drivers import DBSIG2
from gnue.common.datasources import GSchema
# =============================================================================
# This class implements schema support for Interbase/Firebird database backends
# =============================================================================
-class Behavior (Base.Behavior):
+class Behavior (DBSIG2.Behavior):
"""
Limitations:
@@ -45,8 +46,94 @@
_NOW = re.compile ("'(NOW\s*\(\)\s*)'", re.IGNORECASE)
_GENFIELD = re.compile ('^.*NEW\.(\w+)\s*=\s*GEN_ID\s*\(.*\)', re.IGNORECASE)
+ _maxIdLength = 31
+ _alterMultiple = False
+ _maxVarchar = 10921
+ _numbers = [[(4, 'SMALLINT'), (9, 'INTEGER')], "NUMERIC(%s)",
+ "NUMERIC (%(length)s,%(scale)s"]
+
# ---------------------------------------------------------------------------
+ # Constructor
+ # ---------------------------------------------------------------------------
+
+ def __init__ (self, connection):
+
+ DBSIG2.Behavior.__init__ (self, connection)
+ self._type2native ['datetime'] = 'timestamp'
+ self._type2native ['boolean'] = 'boolean'
+
+
+ # ---------------------------------------------------------------------------
+ # Create a new database
+ # ---------------------------------------------------------------------------
+
+ def createDatabase (self):
+ """
+ Create a new database for the associated connection. The password for the
+ SYSDBA will be queried.
+ """
+
+ dbname = self.__connection.parameters.get ('dbname', None)
+ username = self.__connection.parameters.get ('username', 'gnue')
+ password = self.__connection.parameters.get ('password', 'gnue')
+ host = self.__connection.parameters.get ('host', None)
+ gsecbin = self.__connection.parameters.get ('gsecbin', 'gsec')
+
+ loginHandler = BasicLoginHandler ()
+ fields = [(u_("Password"), '_password', 'password', None, None, [])]
+ title = u_("Logon for SYSDBA into Security Database")
+
+ error = None
+ res = {'_password': ''}
+ while not res ['_password']:
+ res = loginHandler.askLogin (title, fields, {}, error)
+ if not res ['_password']:
+ error = u_("Please specify a password")
+
+ syspw = res ['_password']
+
+ if host:
+ dburl = "%s:%s" % (host, dbname)
+ else:
+ dburl = dbname
+
+ code = u"%s -user sysdba -password %s -delete %s" % \
+ (gsecbin, syspw, username)
+
+ try:
+ os.system (code)
+ except:
+ pass
+
+ code = u"%s -user sysdba -password %s -add %s -pw %s" % \
+ (gsecbin, syspw, username, password)
+
+ try:
+ # if creating the user fails we try to create the db anyway. Maybe this
+ # is done from a remote system where no gsec is available, but the given
+ # credentials are valid on the given server.
+ os.system (code)
+ except:
+ pass
+
+ self.__connection._driver.create_database (\
+ u"create database '%s' user '%s' password '%s' " \
+ "default character set UNICODE_FSS" % (dburl, username, password))
+
+ self.__connection.manager.loginToConnection (self.__connection)
+
+ code = u"CREATE DOMAIN boolean AS smallint " \
+ "CHECK (value IN (0,1) OR value IS NULL);"
+ self.__connection.makecursor (code)
+
+ code = u"DECLARE EXTERNAL FUNCTION lower CSTRING(255) " \
+ "RETURNS CSTRING(255) FREE_IT " \
+ "ENTRY_POINT 'IB_UDF_lower' MODULE_NAME 'ib_udf';"
+ self.__connection.makecursor (code)
+ self.__connection.commit ()
+
+ # ---------------------------------------------------------------------------
# Read the current connection's schema
# ---------------------------------------------------------------------------
@@ -309,3 +396,51 @@
return result
+
+ # ---------------------------------------------------------------------------
+ # Process a defaultwith attribute
+ # ---------------------------------------------------------------------------
+
+ def _defaultwith_ (self, code, field):
+ """
+ Process special kinds of default values like sequences, functions and so
+ on. Defaults of type 'constant' are already handled by '_processFields_'.
+
+ @param code: code-triple of the current field as built by _processFields_
+ @param field: GSField instance to process the default for
+ """
+
+ if field.defaultwith == 'serial':
+ table = field.findParentOfType ('GSTable')
+ seq = self._getSequenceName_ (field)
+
+ code [0].append (u"CREATE GENERATOR %s" % seq)
+ code [2].append ( \
+ u"CREATE TRIGGER trg_%s FOR %s ACTIVE BEFORE INSERT POSITION 0 AS " \
+ "BEGIN IF (NEW.%s IS NULL) THEN NEW.%s = GEN_ID (%s,1); END" \
+ % (field.name, table.name, field.name, field.name, seq))
+
+ elif field.defaultwith == 'timestamp':
+ field.default = "NOW"
+
+
+ # ---------------------------------------------------------------------------
+ # Create a native type representation for strings
+ # ---------------------------------------------------------------------------
+
+ def string (self, field):
+ """
+ Return the native datatype for a string-field.
+
+ @param field: GSField instance to get the native datatype for
+ @return: string with the native datatype
+ """
+
+ if hasattr (field, 'length') and field.length <= self._maxVarchar:
+ return "varchar (%s)" % field.length
+
+ elif not hasattr (field, 'length'):
+ return "varchar (%s)" % self._maxVarchar
+
+ else:
+ return "blob"
Copied: trunk/gnue-common/src/datasources/drivers/maxdb (from rev 7583,
trunk/gnue-common/src/datasources/drivers/sapdb)
Modified: trunk/gnue-common/src/datasources/drivers/maxdb/Behavior.py
===================================================================
--- trunk/gnue-common/src/datasources/drivers/sapdb/Behavior.py 2005-06-08
10:23:12 UTC (rev 7583)
+++ trunk/gnue-common/src/datasources/drivers/maxdb/Behavior.py 2005-06-09
13:18:02 UTC (rev 7584)
@@ -21,6 +21,7 @@
#
# $Id$
+from gnue.common.datasources.GLoginHandler import BasicLoginHandler
from gnue.common.datasources.drivers import DBSIG2
from gnue.common.datasources import GSchema
@@ -40,7 +41,98 @@
# 'RESULT' : {'type': 'result' , 'name': _('Result Table')}
}
+ _maxIdLength = 32
+ _maxVarchar = 3999
+ _numbers = [[(5, 'SMALLINT'), (10, 'INTEGER')],
+ "FIXED (%s)",
+ "FIXED (%(length)s,%(scale)s)"]
+
# ---------------------------------------------------------------------------
+ # Constructor
+ # ---------------------------------------------------------------------------
+
+ def __init__ (self, connection):
+
+ DBSIG2.Behavior.__init__ (self, connection)
+
+ self._type2native.update ({'boolean' : 'BOOLEAN',
+ 'datetime': 'TIMESTAMP'})
+
+
+ # ---------------------------------------------------------------------------
+ # Create a new database instance
+ # ---------------------------------------------------------------------------
+
+ def createDatabase (self):
+ """
+ Create a new database instance as defined by the connection's parameters.
+ The user will be asked for a username and password who is member of the
+ SDBA group on the backend system and theirfore allowed to create new
+ instances. If the database already exists no action takes place.
+ """
+
+ # Import here so epydoc can import this module even if sapdb is not
+ # installed
+ import sapdb.dbm
+
+ host = self.__connection.parameters.get ('host', 'localhost')
+ dbname = self.__connection.parameters.get ('dbname', None)
+ username = self.__connection.parameters.get ('username', 'gnue')
+ password = self.__connection.parameters.get ('password', 'gnue')
+
+ title = u_("OS User for host %s") % host
+ fields = [(u_("User Name"), '_username', 'string', None, None, []),
+ (u_("Password"), '_password', 'password', None, None, [])]
+ res = BasicLoginHandler ().askLogin (title, fields, {})
+
+ try:
+ session = sapdb.dbm.DBM (host, '', '',
+ "%s,%s" % (res ['_username'], res ['_password']))
+
+ except sapdb.dbm.CommunicationError, err:
+ raise errors.AdminError, \
+ u_("Unable to establish session: %s") % errors.getException () [2]
+
+ try:
+ result = session.cmd ('db_enum')
+
+ for entry in result.split ('\n'):
+ if entry.split ('\t') [0] == dbname.upper ():
+ return
+
+ print o(u_("Creating database instance %s") % dbname)
+ session.cmd ("db_create %s %s,%s" % (dbname, res ['_username'],
+ res ['_password']))
+
+ print o(u_("Setting up parameters ..."))
+ session.cmd ("param_startsession")
+ session.cmd ("param_init OLTP")
+ session.cmd ("param_put MAXUSERTASKS 10")
+ session.cmd ("param_put CACHE_SIZE 20000")
+ session.cmd ("param_put _UNICODE YES")
+ session.cmd ("param_checkall")
+ session.cmd ("param_commitsession")
+
+ print o(u_("Adding log- and data-volumes ..."))
+ session.cmd ("param_adddevspace 1 LOG LOG_001 F 1000")
+ session.cmd ("param_adddevspace 1 DATA DAT_001 F 2500")
+
+ print o(u_("Entering administration mode"))
+ session.cmd ("db_admin")
+
+ print o(u_("Activating instance with initial user %s") % (username))
+ session.cmd ("db_activate %s,%s" % (username, password))
+
+ print o(u_("Loading system tables ..."))
+ session.cmd ("load_systab -ud domp")
+
+ print o(u_("Database instance created."))
+
+ finally:
+ session.release ()
+
+
+ # ---------------------------------------------------------------------------
# Read the current connection's schema
# ---------------------------------------------------------------------------
@@ -240,3 +332,44 @@
finally:
cursor.close ()
+
+
+ # ---------------------------------------------------------------------------
+ # Handle special defaults
+ # ---------------------------------------------------------------------------
+
+ def _defaultwith_ (self, code, field):
+ """
+ Handle special defaults like 'serial' and 'timestamp'.
+
+ @param code: code-triple to merge the result in
+ @param field: GSField instance with the default definition
+ """
+
+ if field.defaultwith == 'serial':
+ field.default = 'DEFAULT SERIAL'
+
+ elif field.defaultwith == 'timestamp':
+ field.default = 'DEFAULT TIMESTAMP'
+
+
+ # ---------------------------------------------------------------------------
+ # Get an apropriate type for strings
+ # ---------------------------------------------------------------------------
+
+ def string (self, field):
+ """
+ Return the native datatype for string fields
+
+ @param field: GSField instance to get a native datatype for
+ @return: 'VARCHAR (?)' or 'LONG'
+ """
+
+ length = hasattr (field, 'length') and field.length or self._maxVarchar
+
+ if length <= self._maxVarchar:
+ return "VARCHAR (%d)" % length
+
+ else:
+ return "LONG"
+
Modified:
trunk/gnue-common/src/datasources/drivers/maxdb/Schema/Creation/Creation.py
===================================================================
--- trunk/gnue-common/src/datasources/drivers/sapdb/Schema/Creation/Creation.py
2005-06-08 10:23:12 UTC (rev 7583)
+++ trunk/gnue-common/src/datasources/drivers/maxdb/Schema/Creation/Creation.py
2005-06-09 13:18:02 UTC (rev 7584)
@@ -52,7 +52,7 @@
# Import here so epydoc can import this module even if sapdb is not
# installed
- import sdb.dbm
+ import sapdb.dbm
host = self.connection.parameters.get ('host', 'localhost')
dbname = self.connection.parameters.get ('dbname', None)
@@ -65,10 +65,10 @@
res = BasicLoginHandler ().askLogin (title, fields, {})
try:
- session = sdb.dbm.DBM (host, '', '',
+ session = sapdb.dbm.DBM (host, '', '',
"%s,%s" % (res ['_username'], res ['_password']))
- except sdb.dbm.CommunicationError, err:
+ except sapdb.dbm.CommunicationError, err:
raise errors.AdminError, \
u_("Unable to establish session: %s") % errors.getException () [2]
Modified: trunk/gnue-common/src/datasources/drivers/maxdb/sapdb/Connection.py
===================================================================
--- trunk/gnue-common/src/datasources/drivers/sapdb/sapdb/Connection.py
2005-06-08 10:23:12 UTC (rev 7583)
+++ trunk/gnue-common/src/datasources/drivers/maxdb/sapdb/Connection.py
2005-06-09 13:18:02 UTC (rev 7584)
@@ -37,7 +37,7 @@
Connection class for MaxDB and SAP-DB databases.
"""
- _drivername = 'sdb.dbapi'
+ _drivername = 'sapdb.dbapi'
_named_as_sequence = True
Modified: trunk/gnue-common/src/datasources/drivers/maxdb/sapdb/__init__.py
===================================================================
--- trunk/gnue-common/src/datasources/drivers/sapdb/sapdb/__init__.py
2005-06-08 10:23:12 UTC (rev 7583)
+++ trunk/gnue-common/src/datasources/drivers/maxdb/sapdb/__init__.py
2005-06-09 13:18:02 UTC (rev 7584)
@@ -39,6 +39,7 @@
def __initplugin__ ():
from gnue.common.datasources import GConnections
try:
- import sdb.dbapi
+ import sapdb.dbapi
+
except ImportError:
raise GConnections.DependencyError, ('sapdb', None)
Modified: trunk/gnue-common/src/datasources/drivers/mysql/Behavior.py
===================================================================
--- trunk/gnue-common/src/datasources/drivers/mysql/Behavior.py 2005-06-08
10:23:12 UTC (rev 7583)
+++ trunk/gnue-common/src/datasources/drivers/mysql/Behavior.py 2005-06-09
13:18:02 UTC (rev 7584)
@@ -21,14 +21,16 @@
#
# $Id$
-from gnue.common.datasources.drivers import Base
+import os
+
+from gnue.common.datasources.drivers import DBSIG2
from gnue.common.datasources import GSchema
# =============================================================================
# This class implements schema support for MySQL database backends
# =============================================================================
-class Behavior (Base.Behavior):
+class Behavior (DBSIG2.Behavior):
"""
Known bugs/problems:
@@ -40,7 +42,7 @@
CREATE TABLE or SHOW TABLE STATUS)
3. MySQL does *not* store the name of a primary key, instead this is always
- 'PRIMARY'. That's why we loos the original primary key's names.
+ 'PRIMARY'. That's why we lose the original primary key's names.
"""
_TYPEMAP = {'string' : ('string', 'string'),
@@ -49,21 +51,79 @@
'timestamp': ('date', 'timestamp'),
'datetime' : ('date', 'datetime')}
+ _maxIdLength = 64
+ _numbers = [[(4, 'smallint'), (9, 'int'), (18, 'bigint')],
+ "decimal (%s,0)", "decimal (%(length)s,%(scale)s)"]
+
+
# ---------------------------------------------------------------------------
# Constructor
# ---------------------------------------------------------------------------
def __init__ (self, *args, **kwargs):
- Base.Behavior.__init__ (self, *args, **kwargs)
+ DBSIG2.Behavior.__init__ (self, *args, **kwargs)
# Update the typemap with numeric types
for t in ['int','integer','bigint','mediumint',
'smallint','tinyint','float','real', 'double','decimal']:
self._TYPEMAP [t] = ('number', 'number')
+ self._type2native ['boolean'] = "tinyint (1) unsigned"
+
# ---------------------------------------------------------------------------
+ # Create a new database
+ # ---------------------------------------------------------------------------
+
+ def createDatabase (self):
+ """
+ Create a new database for the associated connection. In order to be
+ successfull the current account must have enough privileges to create new
+ databases.
+ """
+
+ dbname = self.__connection.parameters.get ('dbname')
+ username = self.__connection.parameters.get ('username', 'gnue')
+ password = self.__connection.parameters.get ('password')
+ host = self.__connection.parameters.get ('host')
+ port = self.__connection.parameters.get ('port')
+
+ createdb = u"mysqladmin %(site)s%(port)s create %(db)s" \
+ % {'db' : dbname,
+ 'site': host and "--host=%s " % host or '',
+ 'port': port and "--port=%s " % port or ''}
+
+ os.system (createdb)
+
+ sql = u"GRANT ALL PRIVILEGES ON %(db)s.* TO '%(user)s'@'%%' %(pass)s" \
+ % {'db' : dbname,
+ 'user': username,
+ 'pass': password and "IDENTIFIED BY '%s'" % password or ""}
+
+ grant = 'mysql %(host)s%(port)s -e "%(sql)s" -s %(db)s' \
+ % {'sql' : sql,
+ 'host': host and "--host=%s " % host or '',
+ 'port': port and "--port=%s " % port or '',
+ 'db' : dbname}
+ os.system (grant)
+
+ sql = u"GRANT ALL PRIVILEGES ON %(db)s.* TO '%(user)s'@'localhost' " \
+ "%(pass)s" \
+ % {'db': dbname,
+ 'user': username,
+ 'pass': password and "IDENTIFIED BY '%s'" % password or ""}
+
+ grant = 'mysql %(host)s%(port)s -e "%(sql)s" -s %(db)s' \
+ % {'sql' : sql,
+ 'host': host and "--host=%s " % host or '',
+ 'port': port and "--port=%s " % port or '',
+ 'db' : dbname}
+
+ os.system (grant)
+
+
+ # ---------------------------------------------------------------------------
# Read the current connection's schema
# ---------------------------------------------------------------------------
@@ -187,7 +247,7 @@
fClass = GSchema.GSPKField
index = table.findChildOfType ('GSPrimaryKey')
if index is None:
- index = GSchema.GSPrimaryKey (table, name = name)
+ index = GSchema.GSPrimaryKey (table, name = "pk_%s" % table.name)
else:
fClass = GSchema.GSIndexField
parent = table.findChildOfType ('GSIndexes')
@@ -201,3 +261,87 @@
fClass (index, name = column)
+ # ---------------------------------------------------------------------------
+ # Handle special defaults
+ # ---------------------------------------------------------------------------
+
+ def _defaultwith_ (self, code, field):
+ """
+ This function adds 'auto_increment' for 'serials' and checks for the proper
+ fieldtype on 'timestamps'
+
+ @param code: code-tuple to merge the result in
+ @param tableName: name of the table
+ @param fieldDef: dictionary describing the field with the default
+ @param forAlter: TRUE if the definition is used in a table modification
+ """
+
+ if field.defaultwith == 'serial':
+ code [1] [-1] += " AUTO_INCREMENT"
+
+ elif field.defaultwith == 'timestamp':
+ if field.type != 'timestamp':
+ field.type = 'timestamp'
+
+ code [1].pop ()
+ code [1].append ("%s timestamp" % field.name)
+
+ print u_("WARNING: changing column type of '%(table)s.%(column)s' "
+ "to 'timestamp'") \
+ % {'table': field.findParentOfType ('GSTable').name,
+ 'column': field.name}
+
+
+ # ---------------------------------------------------------------------------
+ # Drop an old index
+ # ---------------------------------------------------------------------------
+
+ def _dropIndex_ (self, index):
+ """
+ Drop the given index
+
+ @param index: name of the table to drop an index from
+ """
+
+ table = index.findParentOfType ('GSTable')
+ return [u"DROP INDEX %s ON %s" % (index.name, table.name)]
+
+
+ # ---------------------------------------------------------------------------
+ # Translate a string into an apropriate native type
+ # ---------------------------------------------------------------------------
+
+ def string (self, field):
+ """
+ Return the native type for a string. If the length is given and below 255
+ character the result is a varchar, otherwist text.
+
+ @param field: GSField instance to get a native datatype for
+ @return: string with the native datatype
+ """
+
+ if hasattr (field, 'length') and field.length <= 255:
+ return "varchar (%s)" % field.length
+ else:
+ return "text"
+
+
+ # ---------------------------------------------------------------------------
+ # MySQL has a timestamp, which is needed for 'defaultwith timestamp'
+ # ---------------------------------------------------------------------------
+
+ def timestamp (self, field):
+ """
+ In MySQL timestamps are used for default values, otherwise we map to
+ 'datetime'
+
+ @param field: GSField instance to get a native datatype for
+ @return: string with the native datatype
+ """
+
+ if hasattr (field, 'defaultwith') and field.defaultwith == 'timestamp':
+ return "timestamp"
+
+ else:
+ return "datetime"
+
Modified:
trunk/gnue-common/src/datasources/drivers/oracle/Schema/Creation/Creation.py
===================================================================
---
trunk/gnue-common/src/datasources/drivers/oracle/Schema/Creation/Creation.py
2005-06-08 10:23:12 UTC (rev 7583)
+++
trunk/gnue-common/src/datasources/drivers/oracle/Schema/Creation/Creation.py
2005-06-09 13:18:02 UTC (rev 7584)
@@ -18,7 +18,7 @@
#
# Copyright 2001-2005 Free Software Foundation
#
-# $Id: $
+# $Id$
import os
from gnue.common.datasources.drivers.DBSIG2.Schema.Creation import \
Modified: trunk/gnue-common/src/datasources/drivers/postgresql/Behavior.py
===================================================================
--- trunk/gnue-common/src/datasources/drivers/postgresql/Behavior.py
2005-06-08 10:23:12 UTC (rev 7583)
+++ trunk/gnue-common/src/datasources/drivers/postgresql/Behavior.py
2005-06-09 13:18:02 UTC (rev 7584)
@@ -21,15 +21,27 @@
#
# $Id$
+import os
+
+from gnue.common.apps import errors
from gnue.common.datasources.drivers import DBSIG2
from gnue.common.datasources import GSchema
+# =============================================================================
+# Schema support for PostgreSQL backends
+# =============================================================================
class Behavior (DBSIG2.Behavior):
_RELKIND = {'v': {'type': 'view', 'name': _("Views")},
'r': {'type': 'table', 'name': _("Tables")}}
+ _maxIdLength = 31
+ _alterMultiple = False
+ _numbers = [[(4, 'smallint'), (9, 'integer'), (18, 'bigint')],
+ "numeric (%s,0)", "numberic (%(length)s,%(scale)s)"]
+
+
# ---------------------------------------------------------------------------
# Constructor
# ---------------------------------------------------------------------------
@@ -53,8 +65,65 @@
for item in ['timestamp', 'abstime']:
self._TYPEMAP [item] = ('date', 'datetime')
+ self._type2native.update ({'boolean' : 'boolean',
+ 'datetime': 'timestamp without time zone'})
+
# ---------------------------------------------------------------------------
+ # Create a new database
+ # ---------------------------------------------------------------------------
+
+ def createDatabase (self):
+ """
+ Create the requested user and database using the tools 'createuser',
+ 'createdb' and 'dropuser'. Of course this function should better make use
+ of the template1 database using a connection object.
+ """
+
+ dbname = self.__connection.parameters.get ('dbname')
+ username = self.__connection.parameters.get ('username', 'gnue')
+ password = self.__connection.parameters.get ('password')
+ host = self.__connection.parameters.get ('host')
+ port = self.__connection.parameters.get ('port')
+
+ site = ""
+ if host is not None:
+ site += " --host=%s" % host
+ if port is not None:
+ site += " --port=%s" % port
+
+ # TODO: use a connection object to the template1 database instead of the
+ # shell-scripts. Note: CREATE DATABASE statements must NOT run within a
+ # transaction block, so we cannot use the default connection mechanisms.
+
+ try:
+ os.system (u"dropuser %s%s 2>/dev/null" % (username, site))
+
+ except:
+ pass
+
+ try:
+ createuser = u"createuser %s --createdb --adduser %s" % (site, username)
+ os.system (createuser)
+ except:
+ pass
+
+ createdb = u"createdb %s --owner=%s --encoding=UNICODE %s" \
+ % (site, username, dbname)
+
+ if os.system (createdb):
+ raise errors.ApplicationError, u_("Database creation failed")
+
+
+ self.__connection.manager.loginToConnection (self.__connection)
+
+ if password is not None and password:
+ alterUser = u"ALTER USER %s WITH PASSWORD '%s';" % (username, password)
+ self.__connection.makecursor (alterUser)
+ self.__connection.commit ()
+
+
+ # ---------------------------------------------------------------------------
# Read the current connection's schema
# ---------------------------------------------------------------------------
@@ -310,3 +379,23 @@
finally:
cursor.close ()
+
+ # ---------------------------------------------------------------------------
+ # Handle special defaults
+ # ---------------------------------------------------------------------------
+
+ def _defaultwith_ (self, code, field):
+ """
+ Create a sequence for 'serials' and set the default for 'timestamps'.
+
+ @param code: code-triple to get the result
+ @param field: GSField instance of the field having the default
+ """
+
+ if field.defaultwith == 'serial':
+ seq = self._getSequenceName_ (field)
+ code [0].append (u"CREATE SEQUENCE %s" % seq)
+ field.default = "DEFAULT nextval ('%s')" % seq
+
+ elif field.defaultwith == 'timestamp':
+ field.default = "DEFAULT now()"
Modified: trunk/gnue-common/src/datasources/drivers/sqlite/Behavior.py
===================================================================
--- trunk/gnue-common/src/datasources/drivers/sqlite/Behavior.py
2005-06-08 10:23:12 UTC (rev 7583)
+++ trunk/gnue-common/src/datasources/drivers/sqlite/Behavior.py
2005-06-09 13:18:02 UTC (rev 7584)
@@ -23,7 +23,8 @@
import re
-from gnue.common.datasources.drivers import Base
+from gnue.common.apps import errors
+from gnue.common.datasources.drivers import DBSIG2
from gnue.common.datasources import GSchema
@@ -45,15 +46,32 @@
_VIEWCODE = re.compile ('^\s*CREATE\s+VIEW\s+\w+\s+AS\s+(.*)\s*$', re.I)
_DEFAULT = re.compile ('.*\s+DEFAULT\s+(.*)', re.I)
_SQLCODE = re.compile ('\s*SELECT\s+(.*)\s+FROM\s+(\w+).*', re.I)
+_CMD = re.compile ('(.*?)\((.*)\)(.*)')
RELTYPE = {'table': {'type': 'table', 'name': _("Tables")},
'view' : {'type': 'view', 'name': _("Views")}}
+
# =============================================================================
-#
+# Excpetions
# =============================================================================
-class Behavior (Base.Behavior):
+class MissingTableError (errors.AdminError):
+ def __init__ (self, table):
+ msg = u_("Cannot find table '%s' anymore") % table
+ errors.AdminError.__init__ (self, msg)
+
+class InvalidSQLCommand (errors.SystemError):
+ def __init__ (self, sql):
+ msg = u_("Cannot split SQL command: '%s'") % sql
+ errors.SystemError.__init__ (self, msg)
+
+
+# =============================================================================
+# This class implements schema support for SQLite backends
+# =============================================================================
+
+class Behavior (DBSIG2.Behavior):
"""
Limitations:
@@ -65,7 +83,34 @@
* Name of Primary Keys is not available
"""
+ _maxIdLength = 31
+ _alterMutliple = False
+ _numbers = [[(None, 'integer')], "", "numeric (%(length)s,%(scale)s)"]
+
# ---------------------------------------------------------------------------
+ # Constructor
+ # ---------------------------------------------------------------------------
+
+ def __init__ (self, connection):
+
+ DBSIG2.Behavior.__init__ (self, connection)
+ self._type2native.update ({'boolean': 'integer'})
+
+
+ # ---------------------------------------------------------------------------
+ # Create a new database
+ # ---------------------------------------------------------------------------
+
+ def createDatabase (self):
+ """
+ Create a new SQLite database for the associated connection.
+ """
+
+ dbname = self.__connection.parameters.get ('dbname')
+ self.__connection.manager.loginToConnection (self.__connection)
+
+
+ # ---------------------------------------------------------------------------
# Read the current connection's schema
# ---------------------------------------------------------------------------
@@ -101,7 +146,8 @@
masters [reltype] = GSchema.GSTables (parent, **RELTYPE [reltype])
key = name.lower ()
- result [key] = GSchema.GSTable (masters [reltype], name = name)
+ result [key] = GSchema.GSTable (masters [reltype], name = name,
+ sql = sql)
if reltype == 'table':
self.__parseFields (result [key], sql)
@@ -283,3 +329,122 @@
if hasattr (tf, key):
setattr (vf, key, getattr (tf, key))
+
+ # ---------------------------------------------------------------------------
+ # Create constraint definition
+ # ---------------------------------------------------------------------------
+
+ def _createConstraint_ (self, constraint):
+ """
+ SQLite does not support referential constraints, so this function returns
+ an empty code-triple.
+ """
+
+ return ([], [], [])
+
+
+ # ---------------------------------------------------------------------------
+ # Drop a given constraint
+ # ---------------------------------------------------------------------------
+
+ def _dropConstraint (self, constraint):
+ """
+ SQLite does not support referential constraints, so this function returns
+ an empty code-triple.
+ """
+ return ([], [], [])
+
+
+ # ---------------------------------------------------------------------------
+ # Create a primary key definition
+ # ---------------------------------------------------------------------------
+
+ def _createPrimaryKey_ (self, pkey):
+ """
+ Create a code-triple for the given primary key
+
+ @param pkey: GSPrimaryKey instance to create a code-sequence for
+ @return: code-triple for the primary key
+ """
+
+ fields = pkey.findChildrenOfType ('GSPKField', False, True)
+ code = u"PRIMARY KEY (%s)" % ", ".join ([f.name for f in fields])
+
+ return ([], [code], [])
+
+
+ # ---------------------------------------------------------------------------
+ # Create a command sequence for creating/modifying tables
+ # ---------------------------------------------------------------------------
+
+ def _createTable_ (self, table):
+ """
+ """
+
+ # We need to know if the diff contains new fields. If not, we can use the
+ # DBSIG2 way for code-generation
+ newFields = [f.name for f in table.fields ('add')]
+
+ # DBSIG2 behavior can handle new or removed tables well
+ if table._action != 'change' or not newFields:
+ result = DBSIG2.Behavior._createTable_ (self, table)
+ return result
+
+ # But since SQLite does not support ALTER TABLE statements we've to handle
+ # that situation here
+ else:
+ result = (pre, body, post) = ([], [], [])
+ original = None
+
+ # First find the original table
+ for item in self._current.findChildrenOfType ('GSTable', False, True):
+ if item._id_ (self._maxIdLength) == table._id_ (self._maxIdLength):
+ original = item
+ break
+
+ if original is None:
+ raise MissingTableError, table.name
+
+ parts = _CMD.match (original.sql)
+ if not parts:
+ raise InvalidSQLCommand, original.sql
+
+ fields = [f.name for f in original.fields ()]
+ nfields = ["NULL" for f in table.fields ()]
+ nfields.extend (fields)
+
+ # Build the temporary table, populate it with all rows and finally
+ # drop the table. This will drop all indices too.
+ body.append (u"CREATE TEMPORARY TABLE t1_backup (%s)" % ",".join
(fields))
+ body.append (u"INSERT INTO t1_backup SELECT %(fields)s FROM %(table)s" \
+ % {'fields': ", ".join (fields),
+ 'table' : self.shortenName (table.name)})
+ body.append (u"DROP TABLE %s" % self.shortenName (table.name))
+
+ # Build the new table using all new fields concatenated with the old
+ # SQL-command.
+ fcode = self._createFields_ (table)
+ self.mergeTriple (result, (fcode [0], [], fcode [2]))
+
+ oldSQL = parts.groups ()
+ newBody = [", ".join (fcode [1])]
+ if len (oldSQL [1]):
+ newBody.append (oldSQL [1])
+
+ cmd = u"%s (%s)%s" % (oldSQL [0], ", ".join (newBody), oldSQL [2])
+
+ body.append (cmd)
+ body.append (u"INSERT INTO %(table)s SELECT %(fields)s FROM t1_backup" \
+ % {'table' : self.shortenName (table.name),
+ 'fields': ",".join (nfields)})
+ body.append (u"DROP TABLE t1_backup")
+
+ # Finally create all indices as given by the new table
+ for item in self._new.findChildrenOfType ('GSTable', False, True):
+ if item._id_ (self._maxIdLength) == table._id_ (self._maxIdLength):
+ for index in item.findChildrenOfType ('GSIndex', False, True):
+ self.mergeTriple (result, self._createIndex_ (index))
+
+ break
+
+ return result
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [gnue] r7584 - in trunk/gnue-common/src/datasources: . drivers drivers/Base drivers/DBSIG2 drivers/interbase drivers/maxdb drivers/maxdb/Schema/Creation drivers/maxdb/sapdb drivers/mysql drivers/oracle/Schema/Creation drivers/postgresql drivers/sqlite,
johannes <=