commit-gnue
[Top][All Lists]
Advanced

[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





reply via email to

[Prev in Thread] Current Thread [Next in Thread]