commit-gnue
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

r6801 - in trunk/gnue-common/src/schema: . scripter


From: johannes
Subject: r6801 - in trunk/gnue-common/src/schema: . scripter
Date: Mon, 13 Dec 2004 07:11:41 -0600 (CST)

Author: johannes
Date: 2004-12-13 07:11:40 -0600 (Mon, 13 Dec 2004)
New Revision: 6801

Modified:
   trunk/gnue-common/src/schema/GSParser.py
   trunk/gnue-common/src/schema/Objects.py
   trunk/gnue-common/src/schema/scripter/Scripter.py
Log:
gnue-schema asks before it modifies the backend database.
gnue-schema is now able to automatically order table operations to avoid
referential constraint conflicts
gnue-schema is now able to automatically order data insertions (even for
tables with multiple fishooks). So the order of tabledata does no longer
matter.


Modified: trunk/gnue-common/src/schema/GSParser.py
===================================================================
--- trunk/gnue-common/src/schema/GSParser.py    2004-12-12 11:14:20 UTC (rev 
6800)
+++ trunk/gnue-common/src/schema/GSParser.py    2004-12-13 13:11:40 UTC (rev 
6801)
@@ -1,6 +1,9 @@
+# GNU Enterprise Common - GNUe Schema Definition - Sax base XML Parser
 #
-# This file is part of GNU Enterprise.
+# Copyright 2001-2004 Free Software Foundation
 #
+# This file is part of GNU Enterprise
+#
 # GNU Enterprise is free software; you can redistribute it
 # and/or modify it under the terms of the GNU General Public
 # License as published by the Free Software Foundation; either
@@ -16,60 +19,45 @@
 # write to the Free Software Foundation, Inc., 59 Temple Place
 # - Suite 330, Boston, MA 02111-1307, USA.
 #
-# Copyright 2002-2004 Free Software Foundation
-#
-# FILE:
-# GSParser.py
-#
-# DESCRIPTION:
-# Class that contains a SAX-based xml processor for GNUe Schema Definitions
-#
-# NOTES:
-#
 # $Id$
 
 import Objects
+import copy
+import types
+
 from gnue.common.formatting import GTypecast
 from gnue.common.definitions import GParser
-import copy, types
 
 
+xmlElements = None
 
+# =============================================================================
+# load an XML object tree from a given stream and return it's root object
+# =============================================================================
 
+def loadFile (buffer, app = None, initialize = True):
+  """
+  This function loads an XML object tree from a given stream and return it's
+  root object.
+  """
 
-
-########
-########  Please keep this file neat !!!
-########
-
-
-
-
-
-
-
-#######################################################
-# This method loads a report from an XML file and returns
-# a GSSchema object.
-#######################################################
-
-def loadFile(buffer, app=None, initialize=1):
   return GParser.loadXMLObject (buffer, xmlSchemaHandler, 'GSSchema', 'schema',
-           initialize, attributes={'_app': app})
+           initialize, attributes = {'_app': app})
 
 
+# =============================================================================
+# Build a dictionary tree with all available XML elements
+# =============================================================================
 
-xmlElements = None
+def getXMLelements ():
+  """
+  This function creates a dictionary tree with all valid xml elements. This
+  dictionary tree is available via global variable 'xmlElements'
+  """
 
-
-def getXMLelements():
-
   global xmlElements
 
-  if xmlElements == None:
-
-    #
-    #
+  if xmlElements is None:
     xmlElements = {
       'schema':       {
          'BaseClass': Objects.GSSchema,
@@ -213,7 +201,7 @@
                'Required': 1,
                'Typecast': GTypecast.name },
             'unique': {
-               'Default': 0, 
+               'Default': 0,
                'Typecast': GTypecast.boolean } },
          'ParentTags':  ('indexes',) },
 
@@ -287,7 +275,7 @@
                'Default' : 0}
             },
          'ParentTags':  ('row',),
-         'MixedContent': 1, 
+         'MixedContent': 1,
          'KeepWhitespace': 1},
 
       'description':   {
@@ -299,21 +287,21 @@
 
     }
 
-  return GParser.buildImportableTags('schema',xmlElements)
+  return GParser.buildImportableTags ('schema', xmlElements)
 
 
-#######################################################
-#
-# xmlSchemaHandler
-#
-# This class is called by the XML parser to
-# process the xml file.
-#
-#######################################################
+# =============================================================================
+# Class called by the XML parser to process the XML file
+# =============================================================================
 
 class xmlSchemaHandler (GParser.xmlHandler):
-  def __init__(self):
-    GParser.xmlHandler.__init__(self)
 
-    self.xmlElements = getXMLelements()
+  # ---------------------------------------------------------------------------
+  # Constructor
+  # ---------------------------------------------------------------------------
 
+  def __init__ (self):
+
+    GParser.xmlHandler.__init__(self)
+    self.xmlElements = getXMLelements ()
+

Modified: trunk/gnue-common/src/schema/Objects.py
===================================================================
--- trunk/gnue-common/src/schema/Objects.py     2004-12-12 11:14:20 UTC (rev 
6800)
+++ trunk/gnue-common/src/schema/Objects.py     2004-12-13 13:11:40 UTC (rev 
6801)
@@ -1,6 +1,9 @@
+# GNU Enterprise Common - GNUe Schema Definition - XML Object Tree elements
 #
-# This file is part of GNU Enterprise.
+# Copyright 2001-2004 Free Software Foundation
 #
+# This file is part of GNU Enterprise
+#
 # GNU Enterprise is free software; you can redistribute it
 # and/or modify it under the terms of the GNU General Public
 # License as published by the Free Software Foundation; either
@@ -16,22 +19,13 @@
 # write to the Free Software Foundation, Inc., 59 Temple Place
 # - Suite 330, Boston, MA 02111-1307, USA.
 #
-# Copyright 2002-2004 Free Software Foundation
-#
-# FILE:
-# Objects.py
-#
-# DESCRIPTION:
-# GObjects for the Schema definitions
-#
-# NOTES:
-#
 # $Id$
 
 from gnue.common.definitions.GObjects import GObj
 from gnue.common.definitions.GRootObj import GRootObj
 from gnue.common.schema.GSData import verifyDataType, valueToNative
 from gnue.common.apps import errors
+
 import GSParser
 import types
 
@@ -140,6 +134,9 @@
       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)
 

Modified: trunk/gnue-common/src/schema/scripter/Scripter.py
===================================================================
--- trunk/gnue-common/src/schema/scripter/Scripter.py   2004-12-12 11:14:20 UTC 
(rev 6800)
+++ trunk/gnue-common/src/schema/scripter/Scripter.py   2004-12-13 13:11:40 UTC 
(rev 6801)
@@ -1,6 +1,9 @@
+# GNU Enterprise Common - GNUe Schema Definition - Schema & Script Generator
 #
-# This file is part of GNU Enterprise.
+# Copyright 2001-2004 Free Software Foundation
 #
+# This file is part of GNU Enterprise
+#
 # GNU Enterprise is free software; you can redistribute it
 # and/or modify it under the terms of the GNU General Public
 # License as published by the Free Software Foundation; either
@@ -16,32 +19,22 @@
 # write to the Free Software Foundation, Inc., 59 Temple Place
 # - Suite 330, Boston, MA 02111-1307, USA.
 #
-# Copyright 2002-2004 Free Software Foundation
-#
 # $Id$
 
-from gnue.common import VERSION
+import string
+
 from gnue.common.schema import GSParser
 from gnue.common.utils.FileUtils import openResource
 from gnue.common.apps.GClientApp import *
-from gnue.common.datasources import GDataSource, GConditions
+from gnue.common.datasources import GDataSource
 from gnue.common.apps import errors
 
-from time import strftime
-from string import join
 
-import sys
-import os
-import re
-import copy
-import types
-
-
 # =============================================================================
 # Exceptions
 # =============================================================================
 
-class Error (gException):
+class Error (errors.SystemError):
   pass
 
 class MissingKeyError (errors.ApplicationError):
@@ -49,16 +42,27 @@
     msg = u_("Data row of table '%s' has no key fields") % tableName
     errors.ApplicationError.__init__ (self, msg)
 
+class CircularReferenceError (errors.ApplicationError):
+  def __init__ (self):
+    msg = u_("Tables have circular or unresolveable references")
+    errors.ApplicationError.__init__ (self, msg)
+
+class CircularFishHookError (errors.ApplicationError):
+  def __init__ (self, table):
+    msg = u_("The table '%s' contains circular data-references") % table
+    errors.ApplicationError.__init__ (self, msg)
+
 # =============================================================================
 # Load GNUe Schema Definition files and create a database schema for it
 # =============================================================================
 
 class Scripter (GClientApp):
 
-  VERSION         = VERSION
-  COMMAND         = "gnue-schema"
-  NAME            = "GNUe Schema Scripter"
-  USAGE           = "[options] gsd-file [gsd-file gsd-file ...]"
+  from gnue.common import VERSION
+
+  COMMAND = "gnue-schema"
+  NAME    = "GNUe Schema Scripter"
+  USAGE   = "[options] gsd-file [gsd-file gsd-file ...]"
   SUMMARY = _("GNUe Schema Scripter creates database schemas based on GNUe "
               "Schema Definitions.")
 
@@ -99,6 +103,11 @@
                  "command line. This user becomes the owner of the database "
                  "and will be implicitly created."))
 
+    self.addCommandOption ('yes', 'y', default = False,
+        help = _("If this option is set, the program runs in batch-mode, "
+                 "which means all questions are answered with 'yes' "
+                 "automatically."))
+
     ConfigOptions = {}
     GClientApp.__init__ (self, connections, 'schema', ConfigOptions)
 
@@ -116,49 +125,58 @@
     """
 
     self.__checkOptions ()
+    self.__loadInputFiles ()
 
-    try:
-      self.tables    = []
-      self.tabledata = []
 
-      for item in range (len (self._files)):
-        print o (u_("Loading gsd file '%s' ...") % self.ARGUMENTS [item])
+    # If we have to process data rows let's have a look if there's a key
+    # defined for all tables and rows
+    if self.__doData and len (self.tabledata.keys ()):
+      self.verifyDataKeys ()
 
+
+    # If the user wants to create the database, first go and ask if she's
+    # really sure about
+    if self.OPTIONS ['createdb']:
+      res = self.__ask (u_("You are about to create the new database '%s'. " \
+                           "Continue") % self.connection.name,
+                           [u_("y"), u_("n")], "n")
+      if res == u_("n"):
+        return
+
+      creator = self.connection.getSchemaCreator ()
+      if creator is not None:
         try:
-          schema = GSParser.loadFile (self._files [item])
-          schema.walk (self.__iterateObjects)
+          creator.createDatabase ()
 
         finally:
-          self._files [item].close ()
+          creator.close ()
 
-    except Exception:
-      print sys.exc_info () [1]
+    # Process schema information (if requested)
+    asked = False
+    if self.__doSchema:
+      if not self.OPTIONS ['file-only']:
+        asked = True
+        res   = self.__ask (u_("You are about to change the database '%s'. " \
+            "Continue") % self.connection.name, [u_("y"), u_("n")], u_("n"))
+        if res == u_("n"):
+          return
 
-    else:
-      try:
-        if self.__doData and len (self.tabledata):
-          self.verifyDataKeys ()
+      self.executeAndGenerateCode ()
 
-        if self.OPTIONS ['createdb']:
-          creator = self.connection.getSchemaCreator ()
-          if creator is not None:
-            try:
-              creator.createDatabase ()
 
-            finally:
-              creator.close ()
+    # Process data information (if requested)
+    if self.__doData and len (self.tabledata.keys ()):
+      if not asked:
+        res = self.__ask (u_("You are about to change the database '%s'. " \
+            "Continue") % self.connection.name, [u_("y"), u_("n")], u_("n"))
+        if res == u_("n"):
+          return
 
-        if self.__doSchema:
-          self.executeAndGenerateCode ()
+      self.updateData ()
 
-        if self.__doData and len (self.tabledata):
-          self.updateData ()
 
-      except Exception:
-        print sys.exc_info () [1]
 
 
-
   # ---------------------------------------------------------------------------
   # Walk through all command line options
   # ---------------------------------------------------------------------------
@@ -216,8 +234,106 @@
     self.connection.parameters ['password'] = password
 
 
+  # ---------------------------------------------------------------------------
+  # Load all input files
+  # ---------------------------------------------------------------------------
 
+  def __loadInputFiles (self):
+    """
+    This function loads all files given in the argument list and builds the
+    dictionaries with the tabledefinition and -data. The sequence 'tableOrder'
+    lists all tablenames in an order to be created without violating
+    referential constraints.
+    """
+
+    self.tables    = {}
+    self.tabledata = {}
+
+    for item in range (len (self._files)):
+      print o (u_("Loading gsd file '%s' ...") % self.ARGUMENTS [item])
+
+      try:
+        schema = GSParser.loadFile (self._files [item])
+        schema.walk (self.__iterateObjects)
+
+      finally:
+        self._files [item].close ()
+
+    tables      = {}
+    self.fishes = {}
+
+    for table in self.tables.values ():
+      # first add the table itself to the dictionary
+      tablename = table ['name'].lower ()
+      if not tables.has_key (tablename):
+        tables [tablename] = []
+
+      # if the table has constraints, add a reference to this table
+      if table.has_key ('constraints') and len (table ['constraints']):
+        for constraint in table ['constraints']:
+          ref = constraint.get ('reftable')
+          if ref:
+            refname = ref.lower ()
+            if refname == tablename:
+              if not self.fishes.has_key (tablename):
+                self.fishes [tablename] = []
+
+              self.fishes [tablename].append ((constraint ['fields'],
+                                               constraint ['reffields']))
+            else:
+              tables [tablename].append (refname)
+
+    # now get the proper order to create/modify the tables
+    self.tableOrder = []
+    add = self.__shrinkList (tables, CircularReferenceError)
+    while len (add):
+      self.tableOrder.extend (add)
+      add = self.__shrinkList (tables, CircularReferenceError)
+
+
   # ---------------------------------------------------------------------------
+  # Return all items from the dictionary which have no dependencies
+  # ---------------------------------------------------------------------------
+
+  def __shrinkList (self, dataDict, circularError, *args):
+    """
+    This function returns a sequence of all keys without any dependencies.
+    If no such items were found but there are still entries in the dictionary
+    a CircularReferenceError will be raised.
+
+    @param dataDict: dictionary to extract items from. The values are sequences
+        with keys which are referencing to an item
+    @param circularError: exception class which will be raised if there are
+        circular references
+    @return: sequence of all keys which do not have any dependency
+    """
+
+    result = []
+
+    for (key, references) in dataDict.items ():
+      if not len (references):
+        # if an item has no dependencies add it to the result
+        result.append (key)
+
+        # remove it from all other entries it is referred to
+        for ref in dataDict.values ():
+          while key in ref:
+            ref.remove (key)
+
+        # and finally remove it from the dictionary
+        del dataDict [key]
+
+    # if no entry without a dependency was found, but there are still entries
+    # in the dictionary, they *must* have circular references
+    if not len (result) and len (dataDict.keys ()):
+      raise circularError, args
+
+    return result
+
+
+
+
+  # ---------------------------------------------------------------------------
   # Get a dictionary with all keys listed in tags and values from sObject
   # ---------------------------------------------------------------------------
 
@@ -248,15 +364,23 @@
 
     @param sObject: current Schema object to be processed
     """
+
     if sObject._type == "GSTable":
-      self.tables.append ({'name': sObject.name})
-      sObject.walk (self.__schemaFields, defs = self.tables [-1])
+      tableKey = sObject.name.lower ()
+      if not self.tables.has_key (tableKey):
+        self.tables [tableKey] = {'name': sObject.name}
 
+      sObject.walk (self.__schemaFields, defs = self.tables [tableKey])
+
     elif sObject._type == "GSTableData":
-      self.tabledata.append ({'name': sObject.tablename, 'rows': [],
-          'defi': {'fields': [], 'key': []}})
-      sObject.walk (self.__dataRows, defs = self.tabledata [-1]['rows'])
+      tableKey = sObject.tablename.lower ()
+      if not self.tabledata.has_key (tableKey):
+        self.tabledata [tableKey] = {'name': sObject.tablename,
+                                     'rows': [],
+                                     'defi': {'fields': [], 'key': []}}
 
+      sObject.walk (self.__dataRows, defs = self.tabledata [tableKey])
+
     return
 
 
@@ -281,8 +405,16 @@
                         'defaultwith', 'length', 'precision', 'nullable'])
       if not defs.has_key ('fields'):
         defs ['fields'] = []
-      defs ['fields'].append (fDef)
 
+      found = False
+      for item in defs ['fields']:
+        if item ['name'].lower () == sObject.name.lower ():
+          found = True
+          break
+
+      if not found:
+        defs ['fields'].append (fDef)
+
     # add a primary key definition to the table definition
     elif sObject._type == "GSPrimaryKey":
       pkDef = {'name': sObject.name, 'fields': []}
@@ -294,13 +426,20 @@
       if not defs.has_key ('indices'):
         defs ['indices'] = []
 
-      indexDef = {'name': sObject.name,
-                  'unique': hasattr (sObject, "unique") and sObject.unique,
-                  'fields': []}
-      defs ['indices'].append (indexDef)
+      found = False
+      for item in defs ['indices']:
+        if item ['name'].lower () == sObject.name.lower ():
+          found = True
+          break
 
-      sObject.walk (self.__schemaIndex, defs = indexDef)
+      if not found:
+        indexDef = {'name'  : sObject.name,
+                    'unique': hasattr (sObject, "unique") and sObject.unique,
+                    'fields': []}
+        defs ['indices'].append (indexDef)
 
+        sObject.walk (self.__schemaIndex, defs = indexDef)
+
     # create constraints
     elif sObject._type == "GSConstraint":
       # for unique-constraints we use a 'unique index'
@@ -308,23 +447,38 @@
         if not defs.has_key ('indices'):
           defs ['indices'] = []
 
-        cDef = {'name'  : sObject.name,
-                'unique': True,
-                'fields': []}
-        defs ['indices'].append (cDef)
+        found = False
+        for item in defs ['indices']:
+          if item ['name'].lower () == sObject.name.lower ():
+            found = True
+            break
 
+        if not found:
+          cDef = {'name'  : sObject.name,
+                  'unique': True,
+                  'fields': []}
+          defs ['indices'].append (cDef)
+
       # for all other types of constraints we use a ConstraintDefinition
       else:
         if not defs.has_key ('constraints'):
           defs ['constraints'] = []
 
-        cDef = self.fetchTags (sObject, ['name', 'type'])
-        cDef ['fields'] = []
-        defs ['constraints'].append (cDef)
+        found = False
+        for item in defs ['constraints']:
+          if item ['name'].lower () == sObject.name.lower ():
+            found = True
+            break
 
-      sObject.walk (self.__schemaConstraint, defs = cDef)
+        if not found:
+          cDef = self.fetchTags (sObject, ['name', 'type'])
+          cDef ['fields'] = []
+          defs ['constraints'].append (cDef)
 
+      if not found:
+        sObject.walk (self.__schemaConstraint, defs = cDef)
 
+
   # ---------------------------------------------------------------------------
   # Iterate over all fields of a primary key
   # ---------------------------------------------------------------------------
@@ -392,11 +546,11 @@
     @param defs: sequence of row definitions of the corresponding table
     """
     if sObject._type == "GSRow":
-      defs.append ({'key': [], 'fields': []})
-      sObject.walk (self.__dataValues, defs [-1])
+      defs ['rows'].append ({'key': [], 'fields': {}})
+      sObject.walk (self.__dataValues, defs ['rows'][-1])
 
     elif sObject._type == "GSColumn":
-      defi = self.tabledata [-1]['defi']
+      defi = defs ['defi']
       defi ['fields'].append (sObject.field)
 
       if hasattr (sObject, 'key') and sObject.key:
@@ -415,7 +569,7 @@
     @param defs: row definition dictionary to be extended.
     """
     if sObject._type == "GSValue":
-      defs ['fields'].append ({'name' : sObject.field, 'value': sObject.value})
+      defs ['fields'][sObject.field] = sObject.value
       if hasattr (sObject, "key") and sObject.key:
         defs ['key'].append (sObject.field)
 
@@ -435,7 +589,7 @@
     @raise MissingKeyError: If no key information could be found for all rows
         of a table this exception will be raised.
     """
-    for item in self.tabledata:
+    for item in self.tabledata.values ():
       tableName = item ['name']
       rows      = item ['rows']
       tableKey  = None
@@ -458,10 +612,11 @@
 
       # is there a table definition with a valid primary key available ?
       if tableKey is None:
-        for item in self.tables:
-          if item ['name'] == tableName and item.has_key ('primarykey'):
-            tableKey = item['primarykey']['fields']
+        tableDef = self.tables.get (tableName.lower ())
+        if tableDef is not None and tableDef.has_key ('primarykey'):
+          tableKey = tableDef ['primarykey']['fields']
 
+
       # if at least one key was available, fill up all missing keys with this
       # one.
       if tableKey is not None:
@@ -487,9 +642,16 @@
 
     self.connections.loginToConnection (self.connection)
 
-    print _("Updating schema ...")
-    code = self.connection.updateSchema (self.tables,
-                                         self.OPTIONS ['file-only'])
+    print o(u_("Updating schema ..."))
+
+    # create a list of table definitions ordered to avoid integrity errors
+    tables = []
+    for name in self.tableOrder:
+      if self.tables.has_key (name):
+        tables.append (self.tables [name])
+
+    code = self.connection.updateSchema (tables, self.OPTIONS ['file-only'])
+
     if self.outfile is not None:
       dest = open (self.outfile, 'w')
 
@@ -510,9 +672,15 @@
     specified key-fields and updated if it exists otherwise inserted.
     """
 
-    print _("Updating data ...")
+    print o(u_("Updating data ..."))
 
-    for table in self.tabledata:
+    # make sure to use the proper table order for inserting data
+    tablelist = []
+    for name in self.tableOrder:
+      if self.tabledata.has_key (name):
+        tablelist.append (self.tabledata [name])
+
+    for table in tablelist:
       tablename = table ['name']
       rows      = table ['rows']
 
@@ -524,20 +692,18 @@
       print o (u_("  updating table '%s' ...") % tablename)
       stat = [0, 0, 0]
 
-      for row in table ['rows']:
-        cList = ['and']
+      if self.fishes.has_key (tablename.lower ()):
+        rows = self.__fishSort (table)
+      else:
+        rows = table ['rows']
 
+      for row in rows:
+        condition = {}
+
         for keyField in keyFields:
-          for field in row ['fields']:
-            if field ['name'] == keyField:
-              kvalue = field ['value']
-              break
+          condition [keyField] = row ['fields'][keyField]
 
-          cList.append (['eq', ['field', keyField], ['const', kvalue]])
-
-        gDebug (4, "Condition: %s" % cList)
-
-        condition = GConditions.buildTreeFromList (cList)
+        gDebug (4, "Condition: %s" % condition)
         resultSet = datasource.createResultSet (condition)
 
         if resultSet.firstRecord () is None:
@@ -548,10 +714,7 @@
 
         doPost = False
 
-        for field in row ['fields']:
-          fieldName  = field ['name']
-          fieldValue = field ['value']
-
+        for (fieldName, fieldValue) in row ['fields'].items ():
           if resultSet.current.getField (fieldName) != fieldValue:
             resultSet.current.setField (fieldName, fieldValue)
             doPost = True
@@ -576,11 +739,21 @@
   # ---------------------------------------------------------------------------
 
   def __openSource (self, table):
+    """
+    Create a new datasource instance for the given table using all fields
+    requested either by a table definition, or a table description.
+
+    @param table: name of the table to create a datasource for
+    @return: tuple with datasource instance and a sequence of all key-fields
+    """
+
+    # First we use all fields and keys as described by a table definition
     fieldList = table ['defi']['fields']
     keyFields = table ['defi']['key']
 
+    # Now add all fields, which are only given in the rows of tabledata tags
     for row in table ['rows']:
-      for field in [f ['name'] for f in row ['fields']]:
+      for field in row ['fields'].keys ():
         if not field in fieldList:
           fieldList.append (field)
 
@@ -598,6 +771,118 @@
     return (source, keyFields)
 
 
+  # ---------------------------------------------------------------------------
+  # Sort rows of table with a fish-hook
+  # ---------------------------------------------------------------------------
+
+  def __fishSort (self, tabledef):
+    """
+    If a table has fishhooks, make sure to re-order all data rows so they can
+    be inserted without conflicting constraints.
+
+    @param tabledef: tabledata definition to sort the rows of
+    @return: sequence of row-dictionaries
+    """
+    
+    tablename = tabledef ['name']
+    fishes    = self.fishes [tablename.lower ()]
+
+    # First create an empty map of all fields to keep track of
+    fishdict  = {}
+    for (fields, reffields) in fishes:
+      for field in fields:
+        fishdict [field] = {}
+
+    # Now we create a dictionary of all data rows, where we use a tuple of all
+    # key-fields as dictionary key. Additionaly update the mapping with the
+    # backreferences (fishdict)
+    rowByKey = {}
+
+    for row in tabledef ['rows']:
+      key = tuple ([row ['fields'][item] for item in row ['key']])
+      rowByKey [key] = row ['fields']
+
+      for (fields, reffields) in fishes:
+        for ix in range (len (fields)):
+          fishdict [fields [ix]][row ['fields'][reffields [ix]]] = key
+            
+    # In the next step we create a dictionary to track all dependencies between
+    # the rows.
+    sortdict = {}
+
+    for (key, data) in rowByKey.items ():
+      if not sortdict.has_key (key):
+        sortdict [key] = []
+
+      for (fields, reffields) in fishes:
+        for item in fields:
+          # If a reference field has a value, we get the key of that record,
+          # and add it as a dependency of the current row, since that record
+          # has to be inserted before the current one.
+          if data.get (item) is not None:
+            ref = fishdict [item].get (data [item])
+            if ref is not None:
+              sortdict [key].append (ref)
+
+    # Now, since we have a dictionary mapping all dependencies, let's create an
+    # ordered list of all rows. All items holding an empty sequence, do *not*
+    # depend on another row, so we can just add them. Aside from that we can
+    # remove all these rows from the remaining dependency-sequences, since this
+    # dependency is fullfilled.
+    order = []
+    add   = self.__shrinkList (sortdict, CircularFishHookError, tablename)
+
+    while len (add):
+      order.extend (add)
+      add = self.__shrinkList (sortdict, CircularFishHookError, tablename)
+
+    # The sequence 'order' now contains all rows in the proper order to be
+    # inserted. To be done, we have to create a sequence of rows-dicts.
+    result = []
+    for key in order:
+      result.append ({'fields': rowByKey [key]})
+
+    return result
+
+
+
+  # ---------------------------------------------------------------------------
+  # Ask a question with a set of valid options and a default
+  # ---------------------------------------------------------------------------
+
+  def __ask (self, question, options, default):
+    """
+    This function asks for a question, allowing a set of answers, using a
+    default-value if the user just presses <Enter>.
+
+    @param question: string with the question to ask
+    @param options: sequence of allowed options, i.e. ['y', 'n']
+    @param default: string with the default option
+
+    @return: string with the option selected
+    """
+
+    if self.OPTIONS ['yes']:
+      return u_("y")
+
+    answer  = None
+    default = default.lower ()
+    lopts   = map (string.lower, options)
+
+    dopts   = []
+    for item in lopts:
+      dopts.append (item == default and item.upper () or item)
+
+    while True:
+      print o(question), o("[%s]:" % string.join (dopts, ",")),
+      answer = raw_input ().lower () or default
+
+      if answer in lopts:
+        break
+
+    return answer
+
+
 # =============================================================================
 # If executed directly, start the scripter
 # =============================================================================





reply via email to

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