SchemaGeneration.py :  » Database » Modeling-Framework » Modeling-0.9 » Modeling » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » Database » Modeling Framework 
Modeling Framework » Modeling 0.9 » Modeling » SchemaGeneration.py
# -*- coding: iso-8859-1 -*-
#-----------------------------------------------------------------------------
# Modeling Framework: an Object-Relational Bridge for python
#
# Copyright (c) 2001-2004 Sbastien Bigaret <sbigaret@users.sourceforge.net>
# All rights reserved.
#
# This file is part of the Modeling Framework.
#
# This code is distributed under a "3-clause BSD"-style license;
# see the LICENSE file for details.
#-----------------------------------------------------------------------------


"""
SchemaGeneration

  This module holds the necessary logic to generate database schema from the
  informations contained in a Model.

  CVS information

    $Id: SchemaGeneration.py 932 2004-07-20 06:21:57Z sbigaret $
  
"""

__version__='$Revision: 932 $'[11:-2]

from StringIO import StringIO

#
from utils import staticmethod,eliminate_multiple_occurences

# Interfaces
from Modeling.interfaces.SchemaGeneration import ISchemaGeneration

# Constants
CreateDatabaseKey              = 'CreateDatabaseKey'
CreatePrimaryKeySupportKey     = 'CreatePrimaryKeySupportKey'
CreateTablesKey                = 'CreateTablesKey'
DropDatabaseKey                = 'DropDatabaseKey'
DropForeignKeyConstraintsKey   = 'DropForeignKeyConstraintsKey'
DropPrimaryKeyConstraintsKey   = 'DropPrimaryKeyConstraintsKey'
DropPrimaryKeySupportKey       = 'DropPrimaryKeySupportKey'
DropTablesKey                  = 'DropTablesKey'
ForeignKeyConstraintsKey       = 'ForeignKeyConstraintsKey'
PrimaryKeyConstraintsKey       = 'PrimaryKeyConstraintsKey'

## The following lock is used by all module's methods
## Remember that you should *NOT* directly access to module's variables
## since they are considered private (MT-safety)
from threading import RLock
SchemaGenerationModule_lock=RLock()
lock=SchemaGenerationModule_lock.acquire
unlock=SchemaGenerationModule_lock.release

__defaultOrderings= ( DropPrimaryKeySupportKey,
                      DropForeignKeyConstraintsKey,
                      DropPrimaryKeyConstraintsKey,
                      DropTablesKey,
                      DropDatabaseKey,
                      CreateDatabaseKey,
                      CreateTablesKey,
                      PrimaryKeyConstraintsKey,
                      ForeignKeyConstraintsKey,
                      CreatePrimaryKeySupportKey
                      )

def defaultOrderingsForSchemaCreation():
  """
  Used in method SchemaGeneration.schemaCreationScriptForEntities() to order
  the schema creation statements produced

  This method is also available as a static method in class SchemaGeneration.
  """
  lock()
  try:
    return __defaultOrderings
  finally: unlock()
        
def setDefaultOrderingsForSchemaCreation(keyOrderings):
  """
  Sets the default ordering. See defaultOrderingsForSchemaCreation() for
  details.

  This method is also available as a static method in class SchemaGeneration.
  """
  lock()
  try:
    __defaultOrderings=keyOrderings
  finally: unlock()
        
class SchemaGeneration:
  """
  ... __TBD
  
  Additionnally the class defines the following static methods, none of which
  should be overriden by subclasses:

    - defaultOrderingsForSchemaCreation

    - setDefaultOrderingsForSchemaCreation
    
  """

  __implements__ = (ISchemaGeneration,)

  def __init__(self, anAdaptor):
    "See interfaces.SchemaGeneration for details"
    self._adaptor=anAdaptor
    
  # Static methods
  defaultOrderingsForSchemaCreation=staticmethod(defaultOrderingsForSchemaCreation)
  setDefaultOrderingsForSchemaCreation=staticmethod(setDefaultOrderingsForSchemaCreation)
  
  # Instance methods
  def appendExpressionToScript(self, expression, script):
    """
    Appends the expression to the StringIO script, prepended with a semi-colon
    (';') and a newline if the StringIO is not empty.

    Subclasses overrides this method if the underlying database uses a
    different syntax.
    """
    script.write(expression.statement()+';\n')


  def createDatabaseStatementsForConnectionDictionary(self, connectionDictionary, administrativeConnectionDictionary):
    """
    Default implementation returns an empty list. Subclasses override this
    method ... __TBD
    """
    return ()
  

  def createTableStatementsForEntityGroup(self, entityGroup):
    """
    Returns a list of SQLExpressions needed to create the tables defined by
    the supplied 'entityGroup', or an empty list if 'entityGroup' is empty.

    Default implementation returns a list consisting of one sole SQLExpression
    whose statement is of the following form::

      CREATE TABLE <TABLE_NAME> ( <COLUMN_CREATE_CLAUSES> )

    where <TABLE_NAME> is the externalName of the first entity in
    'entityGroup', and <COLUMN_CREATE_CLAUSES> is the list of all create
    clauses needed to build the underlying columns
    --see SQLExpression.addCreateClauseForAttribute(). That list can be
    accessed in the returned SQLExpression object through sending it the
    message 'listString()'

    Parameter 'entityGroup' is a sequence of entities which share the same
    external name.

    See also: dropTableStatementsForEntityGroup, Entity.externalName(),
              SQLExpression.addCreateClauseForAttribute(),
              SQLExpression.listString()
    """
    if not entityGroup: return ()
    firstEntity=entityGroup[0]
    sqlExpression=self._adaptor.expressionClass()(firstEntity)
    # Builds the list of attributes
    attributes={} # EXTERNAL_NAME -> Attribute
    for entity in entityGroup:
      for attribute in entity.attributes():
        attributes[attribute.columnName()]=attribute
    for attribute in attributes.values():
      sqlExpression.addCreateClauseForAttribute(attribute)
    statement='CREATE TABLE %s (%s)'%(firstEntity.externalName(),
                                      sqlExpression.listString())
    sqlExpression.setStatement(statement)
    return (sqlExpression,)
  

  def createTableStatementsForEntityGroups(self, entityGroups):
    """
    Invokes createTableStatementsForEntityGroup() for each entityGroup in
    'entityGroups', and returns the list of SQLExpressions needed to create
    the necessary table. It is guaranteed that all SQLExpressions in the
    returned sequence have a different statement (SQLExpression.statement()).
    
    Subclasses should not need to override this method. If they do, they
    should comply with the guaranteed nature of the returned sequence, as
    stated above.

    Parameter:

      entityGroups -- a sequence of entityGroups ; an 'entityGroup' is a list
        of entities whose externalNames are identical.

    """
    statements=[]
    for entityGroup in entityGroups:
      statements.extend(self.createTableStatementsForEntityGroup(entityGroup))
    return statements
      

  def dropDatabaseStatementsForConnectionDictionary(self, connectionDictionary, administrativeConnectionDictionary):
    """
    Default implementation returns an empty list.
    """
    return ()
  

  def dropForeignKeyConstraintStatementsForRelationship(self, relationship):
    """
    Default implementation returns an empty list. Subclasses override this
    method to return a sequence of SQLExpressions designed to remove the
    foreign key constraints built by
    foreignKeyConstraintStatementsForRelationship(). Returned value should be
    an empty list if 'entityGroup' is empty.

    Parameter 'entityGroup' is a list of entities whose externalNames are
    identical.
    
    See also:
      foreignKeyConstraintStatementsForRelationship()
    """
    return ()
    

  def dropPrimaryKeyConstraintStatementsForEntityGroup(self, entityGroup):
    """
    Default implementation returns an empty list. Subclasses override this
    method to return a sequence of SQLExpressions designed to remove the
    constraint Primary Key constraints built by
    primaryKeyConstraintStatementsForEntityGroup(). Returned value should be
    an empty list if 'entityGroup' is empty.

    Parameter 'entityGroup' is a list of entities whose externalNames are
    identical.
    
    See also:
      primaryKeyConstraintStatementsForEntityGroup()
    """
    return ()

    
  def dropPrimaryKeyConstraintStatementsForEntityGroups(self, entityGroups):
    """
    Iterates on each entityGroup in 'entityGroups', sends the message
    'dropPrimaryKeyConstraintStatementsForEntityGroup' to each of them,
    collects the results and returns them in a sequence. It is guaranteed that
    all SQLExpressions in the returned sequence have a different statement
    (SQLExpression.statement()).
    
    Subclasses should not need to override this method. If they do, they
    should comply with the guaranteed nature of the returned sequence, as
    stated above.

    Parameter:

      entityGroups -- a sequence of entityGroups ; an 'entityGroup' is a list
        of entities whose externalNames are identical.

    See also: primaryKeyConstraintStatementsForEntityGroups()
    """
    statements=[]
    for entityGroup in entityGroups:
      statements.extend(self.dropPrimaryKeyConstraintStatementsForEntityGroup(entityGroup))
    statements=eliminate_multiple_occurences(statements, \
                                             lambda a: a.statement())
    return statements


  def dropPrimaryKeySupportStatementsForEntityGroup(self, entityGroup):
    """
    Default implementation returns an empty list. Subclasses override this
    method to return a sequence of SQLExpressions designed to remove the
    elements supporting the mechanism used to generate primary keys, as they
    were created by primaryKeySupportStatementsForEntityGroup(). Returned
    value should be an empty list if 'entityGroup' is empty.

    Parameter 'entityGroup' is a list of entities whose externalNames are
    identical.
    
    See also:
      primaryKeySupportStatementsForEntityGroup()
      AdaptorContext.primaryKeysForNewRowsWithEntity()
    """
    return ()
  

  def dropPrimaryKeySupportStatementsForEntityGroups(self, entityGroups):
    """
    Iterates on each entityGroup in 'entityGroups', sends the message
    'dropPrimaryKeySupportStatementsForEntityGroup' to each of them, collects
    the results and returns them in a sequence. It is guaranteed that all
    SQLExpressions in the returned sequence have a different statement
    (SQLExpression.statement()).
    
    Subclasses should not need to override this method. If they do, they
    should comply with the guaranteed nature of the returned sequence, as
    stated above.

    Parameter:

      entityGroups -- a sequence of entityGroups ; an 'entityGroup' is a list
        of entities whose externalNames are identical.

    """
    statements=[]
    for entityGroup in entityGroups:
      statements.extend(self.dropPrimaryKeySupportStatementsForEntityGroup(entityGroup))
    statements=eliminate_multiple_occurences(statements, \
                                             lambda a: a.statement())
    return statements


  def dropTableStatementsForEntityGroup(self, entityGroup):
    """

    Parameter:

      entityGroup -- a list of entities whose externalNames are identical.
    
    """
    if not entityGroup:
      return ()
    #DROP   TABLE <TABLE_NAME>
    firstEntity=entityGroup[0]
    sqlExpr=self._adaptor.expressionClass()(firstEntity)
    sqlExpr.setStatement('DROP TABLE %s'%firstEntity.externalName())
    return (sqlExpr,)
    

  def dropTableStatementsForEntityGroups(self, entityGroups):
    """
    Iterates on each entityGroup in 'entityGroups', sends the message
    'dropPrimaryKeySupportStatementsForEntityGroup()' to each of them,
    collects the results and returns them in a sequence.

    Subclasses should not need to override this method. If they do, they
    should comply with the guaranteed nature of the returned sequence, as
    stated above.

    """
    statements=[]
    for entityGroup in entityGroups:
      statements.extend(self.dropTableStatementsForEntityGroup(entityGroup))
    statements=eliminate_multiple_occurences(statements, \
                                             lambda a: a.statement())
    return statements


  def foreignKeyConstraintStatementsForRelationship(self, relationship):
    """
    Returns a list of SQLExpressions needed to add a constraint statement
    for the given relationship. Under specific conditions on 'relationship',
    expressed below, that lists consists of a single SQLExpression
    statement of the following form::
    
       ALTER TABLE <TABLE> ADD CONSTRAINT <REL_NAME> FOREIGN KEY
       (<FKS>) REFERENCES <DESTINATION_TABLE>(<DESTINATION_PK>)
       INITIALLY DEFERRED

    where TABLE is the relationship's entity externalName(),
          REL_NAME is the relationship's source attribute columnName(),
          FKS is a comma-separated list of the relationship's source
            attributes.columnName(),
          DESTINATION_PKS is a comma-separated list of the rel.'s
            destinationAttributes().columnName(),
          DESTINATION_TABLE is the relationship.destinationEntity() 's
            externalName()

    The conditions under which the statement above is returned are:

      - the relationship is a toOne rel. and it is not flattened,

      - its inverseRelationship, if any, is a toMany relationship,

      - its source and destinationEntity share the same model

      - the destinationEntity has no sub-entity.

    If any of these conditions is not fulfilled the method returns an empty
    sequence.

    Why 'INITIALLY DEFERRED'? Suppose we need to cascade delete 3 objects in
    relations ; we do not guarantee that they will be deleted in the correct
    order (moreover, determining the order in which objects should be deleted,
    or updated, etc. is a difficult problem and solving it is beyond my
    knowledge). So, we need to defer the constraint until the transaction
    commits. Last, I put it there, not in any specific adaptor, since I do not
    want to hide that fact from the eyes of anyone willing to create a custom
    AdaptorLayer. If this is what you want to do, and your database does not
    allow constraints to be deferred, you'll probably won't add any
    referential constraints at all --or you'll solve the general scheduling
    problem!
    """
    if relationship.isToMany() or relationship.isFlattened():
      return ()

    # if there is an inverseRelationship, it should be a toMany relationship
    inverseRel = relationship.inverseRelationship()
    if not (inverseRel is None or inverseRel.isToMany() ):
      return ()
    
    # Both source and destination entities should belong to the same model
    srcEntity=relationship.sourceEntity()
    dstEntity=relationship.destinationEntity()
    if srcEntity.model() != dstEntity.model():
      return ()

    if dstEntity.subEntities():
      return ()

    sqlExpression=self._adaptor.expressionClass()(srcEntity)
    st='ALTER TABLE %(table)s ADD CONSTRAINT %(relName)s FOREIGN KEY '\
        '(%(FKs)s) REFERENCES %(dst_table)s(%(PKs)s) INITIALLY DEFERRED'
    from string import join
    vars={'table': srcEntity.externalName(),
          'relName': relationship.name(),
          'FKs': join([attr.columnName() for attr in relationship.sourceAttributes()], ','),
          'dst_table': dstEntity.externalName(),
          'PKs': join([attr.columnName() for attr in relationship.destinationAttributes()], ',')
          }
    sqlExpression.setStatement(st%vars)
    return (sqlExpression,)


  def primaryKeyConstraintStatementsForEntityGroup(self, entityGroup):
    """
    Default implementation returns an empty list. __TBD
    
    Parameter 'entityGroup' is a list of entities whose externalNames are
    identical.
    
    See also: dropPrimaryKeyConstraintStatementsForEntityGroup
              primaryKeyConstraintStatementsForEntityGroups
              dropPrimaryKeySupportStatementsForEntityGroup
    """
    #ALTER TABLE A ADD PRIMARY KEY (<PKs>)
    if not entityGroup: return ()
    firstEntity=entityGroup[0]
    sqlExpression=self._adaptor.expressionClass()(firstEntity)
    #ALTER TABLE A ADD PRIMARY KEY (<PKs>)
    pks=tuple(firstEntity.primaryKeyAttributes())
    if not pks:
      return ()
    pks=map(lambda o: o.columnName(), pks)
    st='ALTER TABLE %s ADD PRIMARY KEY ('%firstEntity.externalName()
    for pk in pks:
      st+=pk+', '
    st=st[:-2]+')'
    sqlExpression.setStatement(st)
    return (sqlExpression,)
    
  def primaryKeyConstraintStatementsForEntityGroups(self, entityGroups):
    """
    Iterates on each entityGroup in 'entityGroups', sends the message
    'primaryKeyConstraintStatementsForEntityGroup' to each of them, collects
    the results and returns them in a sequence. It is guaranteed that all
    SQLExpressions in the returned sequence have a different statement
    (SQLExpression.statement()).
    
    Subclasses should not need to override this method. If they do, they
    should comply with the guaranteed nature of the returned sequence, as
    stated above.

    Parameter:

      entityGroups -- a sequence of entityGroups ; an 'entityGroup' is a list
        of entities whose externalNames are identical.

    See also: dropPrimaryKeySupportStatementsForEntityGroups()
    """
    statements=[]
    for entityGroup in entityGroups:
      statements.extend(self.primaryKeyConstraintStatementsForEntityGroup(entityGroup))
    statements=eliminate_multiple_occurences(statements, \
                                             lambda a: a.statement())
    return statements

  def primaryKeySupportStatementsForEntityGroup(self, entityGroup):
    """
    Default implementation returns an empty list. Subclasses override this
    method to return a sequence of SQLExpressions in adequation to the
    mechanism used to generate primary keys, or an empty list if
    'entityGroup' is empty.

    Subclasses overriding this method have no need to call the super
    implementation.

    Parameter:

      entityGroup -- a list of entities whose externalNames are identical.
    
    See also:
      dropPrimaryKeySupportStatementsForEntityGroup()
      AdaptorContext.primaryKeysForNewRowsWithEntity()
      Entity.primaryKeyRootName()
    """
    return ()
  
  def primaryKeySupportStatementsForEntityGroups(self, entityGroups):
    """
    Iterates on each entityGroup in 'entityGroups', sends the message
    'primaryKeySupportStatementsForEntityGroup' to each of them, collects the
    results and returns them in a sequence. It is guaranteed that all
    SQLExpressions in the returned sequence have a different statement
    (SQLExpression.statement()).
    
    Subclasses should not need to override this method. If they do, they
    should comply with the guaranteed nature of the returned sequence, as
    stated above.

    Parameter:

      entityGroups -- a sequence of entityGroups ; an 'entityGroup' is a list
        of entities whose externalNames are identical.

    """
    statements=[]
    for entityGroup in entityGroups:
      statements.extend(self.primaryKeySupportStatementsForEntityGroup(entityGroup))
    statements=eliminate_multiple_occurences(statements, \
                                             lambda a: a.statement())
    return statements

  def schemaCreationScriptForEntities(self, allEntities, options,
                                      keyOrderings=()):
    """
    __TBD

    If parameter 'keyOrdering' is omitted, None or empty it defaults to
    'defaultOrderingsForSchemaCreation()'.

    See also: schemaCreationStatementsForEntities(), appendExpressionToScript()
              defaultOrderingsForSchemaCreation()
    """
    stList=self.schemaCreationStatementsForEntities(allEntities, options,
                                                    keyOrderings)
    script=StringIO()
    for st in stList:
      self.appendExpressionToScript(st, script)
    return script.getvalue()

  def schemaCreationStatementsForEntities(self, allEntities, options,
                                          keyOrderings=()):
    """
    __TBD doc.
    """
    if not keyOrderings:
      keyOrderings=self.defaultOrderingsForSchemaCreation()
    statementsDict=self.schemaCreationStatementsForEntitiesByOptions(allEntities, options)
    stList=[]
    for key in keyOrderings:
      stList.extend(statementsDict.get(key, ()))
    return stList

    
  def schemaCreationStatementsForEntitiesByOptions(self, allEntities, options):
    """
    __TBD doc.
    """
    entityGroups=self.tableEntityGroupsForEntities(allEntities)
    st={} # statements dictionary: key -> sequence of statements

    #if options.get(DropDatabaseKey):
    #  st[DropDatabaseKey]=self.dropDatabaseStatementsForConnectionDictionary()
    #
    #if options.get(CreateDatabaseKey):
    #  st[CreateDatabaseKey]=self.createDatabaseStatementsForConnectionDictionary()

    if options.get(DropTablesKey):
      st[DropTablesKey]=self.dropTableStatementsForEntityGroups(entityGroups)

    if options.get(CreateTablesKey):
      st[CreateTablesKey]=self.createTableStatementsForEntityGroups(entityGroups)

    if options.get(DropPrimaryKeySupportKey):
      st[DropPrimaryKeySupportKey]=self.dropPrimaryKeySupportStatementsForEntityGroups(entityGroups)

    if options.get(DropPrimaryKeyConstraintsKey):
      st[DropPrimaryKeyConstraintsKey]=self.dropPrimaryKeyConstraintStatementsForEntityGroups(entityGroups)

    if options.get(CreatePrimaryKeySupportKey):
      st[CreatePrimaryKeySupportKey]=self.primaryKeySupportStatementsForEntityGroups(entityGroups)

    if options.get(PrimaryKeyConstraintsKey):
      st[PrimaryKeyConstraintsKey]=self.primaryKeyConstraintStatementsForEntityGroups(entityGroups)

    if options.get(DropForeignKeyConstraintsKey):
      st[DropForeignKeyConstraintsKey]=l=[]
      for entity in allEntities:
        for relationship in entity.relationships():
          l.extend(self.dropForeignKeyConstraintStatementsForRelationship(relationship))
      st[DropForeignKeyConstraintsKey]=l

    if options.get(ForeignKeyConstraintsKey):
      st[ForeignKeyConstraintsKey]=l=[]
      for entity in allEntities:
        for relationship in entity.relationships():
          l.extend(self.foreignKeyConstraintStatementsForRelationship(relationship))
      st[ForeignKeyConstraintsKey]=l
    return st

  def tableEntityGroupsForEntities(self, entities):
    """
    Returns a list of entityGroups ; each entityGroup consists of the
    entities in parameter 'entities' which share the same externalName(), and
    wich answered positively to the definesTableColumns() message. Hence, the
    set of entities returned in the entityGroups is a (possibly smaller)
    subset of the entities supplied in parameter 'entities'

    See also: Entity.definesTableColumns()
    """
    d={}
    for entity in entities:
      if not entity.definesTableColumns(): continue
      extName=entity.externalName()
      list=d.get(extName, [])
      list.append(entity)
      d[extName]=list
    return d.values()
  
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.