db-derby-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From rhille...@apache.org
Subject svn commit: r890115 - in /db/derby/code/trunk/java: engine/org/apache/derby/iapi/sql/dictionary/ engine/org/apache/derby/impl/sql/execute/ engine/org/apache/derby/loc/ shared/org/apache/derby/shared/common/reference/ testing/org/apache/derbyTesting/fun...
Date Sun, 13 Dec 2009 20:30:50 GMT
Author: rhillegas
Date: Sun Dec 13 20:30:49 2009
New Revision: 890115

URL: http://svn.apache.org/viewvc?rev=890115&view=rev
Log:
DERBY-651: Add dependencies of routines on UDTs.

Modified:
    db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/AliasDescriptor.java
    db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/TableDescriptor.java
    db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/TupleDescriptor.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/CreateAliasConstantAction.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/DDLConstantAction.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/DropAliasConstantAction.java
    db/derby/code/trunk/java/engine/org/apache/derby/loc/messages.xml
    db/derby/code/trunk/java/shared/org/apache/derby/shared/common/reference/SQLState.java
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/UDTTest.java

Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/AliasDescriptor.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/AliasDescriptor.java?rev=890115&r1=890114&r2=890115&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/AliasDescriptor.java
(original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/AliasDescriptor.java
Sun Dec 13 20:30:49 2009
@@ -23,9 +23,11 @@
 
 import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
 import org.apache.derby.iapi.sql.depend.DependencyManager;
+import org.apache.derby.iapi.sql.depend.Dependent;
 import org.apache.derby.iapi.sql.depend.Provider;
 import org.apache.derby.iapi.store.access.TransactionController;
 import org.apache.derby.iapi.error.StandardException;
+import org.apache.derby.iapi.reference.SQLState;
 import org.apache.derby.iapi.services.sanity.SanityManager;
 
 import	org.apache.derby.catalog.AliasInfo;
@@ -56,7 +58,7 @@
 
 public final class AliasDescriptor 
 	extends TupleDescriptor
-	implements UniqueTupleDescriptor, Provider
+	implements UniqueTupleDescriptor, Provider, Dependent
 {
 	private final UUID		aliasID;
 	private final String		aliasName;
@@ -125,6 +127,28 @@
 	}
 
 	/**
+	 * Gets the name of the schema that the alias lives in.
+	 *
+	 * @return	A String containing the name of the schema that the alias
+	 *		lives in.
+	 */
+	public String	getSchemaName() throws StandardException
+	{
+		return getDataDictionary().getSchemaDescriptor( schemaID, null ).getSchemaName();
+	}
+
+	/**
+	 * Gets the full, qualified name of the alias.
+	 *
+	 * @return	A String containing the name of the table.
+	 */
+	public String	getQualifiedName() throws StandardException
+	{
+		return quoteProtectName(getSchemaName()) + "." +
+			quoteProtectName( aliasName );
+	}
+
+	/**
 	 * Gets the java class name of the alias.
 	 *
 	 * @return	The java class name of the alias.
@@ -404,4 +428,78 @@
         /* Drop the alias */
         dd.dropAliasDescriptor(this, tc);
     }
+
+	//////////////////////////////////////////////////////
+	//
+	// DEPENDENT INTERFACE
+	//
+	//////////////////////////////////////////////////////
+	/**
+	 * Check that all of the dependent's dependencies are valid.
+	 *
+	 * @return true if the dependent is currently valid
+	 */
+	public synchronized boolean isValid()
+	{
+		return true;
+	}
+
+	/**
+	 * Prepare to mark the dependent as invalid (due to at least one of
+	 * its dependencies being invalid).
+	 *
+	 * @param action	The action causing the invalidation
+	 * @param p		the provider
+	 *
+	 * @exception StandardException thrown if unable to make it invalid
+	 */
+	public void prepareToInvalidate(Provider p, int action,
+					LanguageConnectionContext lcc) 
+		throws StandardException
+	{
+		DependencyManager dm = getDataDictionary().getDependencyManager();
+
+		switch (action)
+		{
+			/*
+			** Currently, the only thing we are dependent
+			** on is an alias descriptor for an ANSI UDT.
+			*/
+		    default:
+
+				throw StandardException.newException(SQLState.LANG_PROVIDER_HAS_DEPENDENT_ALIAS, 
+									dm.getActionString(action), 
+									p.getObjectName(),
+									getQualifiedName());
+		}
+	}
+
+	/**
+	 * Mark the dependent as invalid (due to at least one of
+	 * its dependencies being invalid).  Always an error
+	 * for an alias -- should never have gotten here.
+	 *
+	 * @param	action	The action causing the invalidation
+	 *
+	 * @exception StandardException thrown if called in sanity mode
+	 */
+	public void makeInvalid(int action, LanguageConnectionContext lcc) 
+		throws StandardException
+	{
+		/* 
+		** We should never get here, we should have barfed on 
+		** prepareToInvalidate().
+		*/
+		if (SanityManager.DEBUG)
+		{
+			DependencyManager dm;
+	
+			dm = getDataDictionary().getDependencyManager();
+
+			SanityManager.THROWASSERT("makeInvalid("+
+				dm.getActionString(action)+
+				") not expected to get called");
+		}
+	}
+    
 }

Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/TableDescriptor.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/TableDescriptor.java?rev=890115&r1=890114&r2=890115&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/TableDescriptor.java
(original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/TableDescriptor.java
Sun Dec 13 20:30:49 2009
@@ -274,40 +274,6 @@
 	}
 
 	/**
-	 * If the name has double quotes in it, put two double quotes for every single
-	 * double quote.
-	 * Finally put double quotes around string to protect against
-	 * names with blanks, reserved words being used as identifiers etc.
-	 * For eg, if table name is m"n, return it as "m""n". For now, this is used
-	 * by DMLModStatementNode.parseCheckConstraint().
-	 *
-	 * Possible improvement: We could possibly analyze string to
-	 * avoid double quotes in normal cases.
-	 *
-	 * @param name	The String with or without double quotes
-	 *
-	 * @return	The quoted String
-	 */
-
-	private String quoteProtectName(String name)
-	{
-		String quotedString = name;
-		int quotePos = name.indexOf("\"");
-
-		if (quotePos == -1)
-			return "\"" + name + "\"";
-
-		//string does have quotes in it.
-		while(quotePos != -1) {
-			quotedString = quotedString.substring(0,quotePos) + "\"" +
-				quotedString.substring(quotePos);
-			quotePos = quotedString.indexOf("\"",quotePos+2);
-		}
-		return "\"" + quotedString + "\"";
-
-	}
-
-	/**
 	 * Gets the UUID of the table.
 	 *
 	 * @return	The UUID of the table.

Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/TupleDescriptor.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/TupleDescriptor.java?rev=890115&r1=890114&r2=890115&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/TupleDescriptor.java
(original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/TupleDescriptor.java
Sun Dec 13 20:30:49 2009
@@ -91,6 +91,40 @@
 	}
 
 
+	/**
+	 * If the name has double quotes in it, put two double quotes for every single
+	 * double quote.
+	 * Finally put double quotes around string to protect against
+	 * names with blanks, reserved words being used as identifiers etc.
+	 * For eg, if table name is m"n, return it as "m""n". For now, this is used
+	 * by DMLModStatementNode.parseCheckConstraint().
+	 *
+	 * Possible improvement: We could possibly analyze string to
+	 * avoid double quotes in normal cases.
+	 *
+	 * @param name	The String with or without double quotes
+	 *
+	 * @return	The quoted String
+	 */
+
+	public String quoteProtectName(String name)
+	{
+		String quotedString = name;
+		int quotePos = name.indexOf("\"");
+
+		if (quotePos == -1)
+			return "\"" + name + "\"";
+
+		//string does have quotes in it.
+		while(quotePos != -1) {
+			quotedString = quotedString.substring(0,quotePos) + "\"" +
+				quotedString.substring(quotePos);
+			quotePos = quotedString.indexOf("\"",quotePos+2);
+		}
+		return "\"" + quotedString + "\"";
+
+	}
+
 	//////////////////////////////////////////////////////////////////
 	//
 	//	BEHAVIOR. These are only used by Replication!!

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/CreateAliasConstantAction.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/CreateAliasConstantAction.java?rev=890115&r1=890114&r2=890115&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/CreateAliasConstantAction.java
(original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/CreateAliasConstantAction.java
Sun Dec 13 20:30:49 2009
@@ -313,5 +313,7 @@
 
 		dd.addDescriptor(ads, null, DataDictionary.SYSALIASES_CATALOG_NUM,
 						 false, tc);
+
+        adjustUDTDependencies( lcc, dd, ads, true );
 	}
 }

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/DDLConstantAction.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/DDLConstantAction.java?rev=890115&r1=890114&r2=890115&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/DDLConstantAction.java
(original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/DDLConstantAction.java
Sun Dec 13 20:30:49 2009
@@ -29,6 +29,8 @@
 import org.apache.derby.catalog.AliasInfo;
 import org.apache.derby.catalog.DependableFinder;
 import org.apache.derby.catalog.UUID;
+import org.apache.derby.catalog.TypeDescriptor;
+import org.apache.derby.catalog.types.RoutineAliasInfo;
 import org.apache.derby.iapi.error.StandardException;
 import org.apache.derby.iapi.reference.SQLState;
 import org.apache.derby.iapi.services.sanity.SanityManager;
@@ -905,11 +907,33 @@
             }
         }
 
+        adjustUDTDependencies( lcc, dd, td, addUdtMap, dropUdtMap );
+    }
+    /**
+     * Add and drop dependencies of an object on UDTs.
+     *
+     * @param lcc Interpreter's state variable for this session.
+     * @param dd Metadata
+     * @param dependent Object which depends on UDT
+     * @param addUdtMap Map of UDTs for which dependencies should be added
+     * @param dropUdtMap Map of UDT for which dependencies should be dropped
+     */
+    private   void    adjustUDTDependencies
+        (
+         LanguageConnectionContext  lcc,
+         DataDictionary             dd,
+         Dependent                  dependent,
+         HashMap                    addUdtMap,
+         HashMap                    dropUdtMap
+         )
+        throws StandardException
+    {
         // again, nothing to do if there are no columns of udt type
         if ( (addUdtMap.size() == 0) && (dropUdtMap.size() == 0) ) { return; }
 
-        DependencyManager   dm = dd.getDependencyManager();
-        ContextManager      cm = lcc.getContextManager();
+		TransactionController tc = lcc.getTransactionExecute();
+        DependencyManager     dm = dd.getDependencyManager();
+        ContextManager        cm = lcc.getContextManager();
 
         // add new dependencies
         Iterator            addIterator = addUdtMap.values().iterator();
@@ -917,7 +941,7 @@
         {
             AliasDescriptor ad = (AliasDescriptor) addIterator.next();
 
-            dm.addDependency( td, ad, cm );
+            dm.addDependency( dependent, ad, cm );
         }
 
         // drop dependencies that are orphaned
@@ -926,12 +950,69 @@
         {
             AliasDescriptor ad = (AliasDescriptor) dropIterator.next();
 
-            DependencyDescriptor dependency = new DependencyDescriptor( td, ad );
+            DependencyDescriptor dependency = new DependencyDescriptor( dependent, ad );
 
             dd.dropStoredDependency( dependency, tc );
         }
     }
 
+    /**
+     * Add and drop dependencies of a routine on UDTs.
+     *
+     * @param lcc Interpreter's state variable for this session.
+     * @param dd Metadata
+     * @param ad Alias descriptor for the routine
+     * @param adding True if we are adding dependencies, false if we're dropping them
+     */
+    protected   void    adjustUDTDependencies
+        (
+         LanguageConnectionContext  lcc,
+         DataDictionary             dd,
+         AliasDescriptor            ad,
+         boolean                    adding
+         )
+        throws StandardException
+    {
+        // nothing to do if this is not a routine
+        switch ( ad.getAliasType() )
+        {
+		case AliasInfo.ALIAS_TYPE_PROCEDURE_AS_CHAR:
+		case AliasInfo.ALIAS_TYPE_FUNCTION_AS_CHAR:
+            break;
+
+        default: return;
+        }
+        
+		TransactionController tc = lcc.getTransactionExecute();
+        RoutineAliasInfo      aliasInfo = (RoutineAliasInfo) ad.getAliasInfo();
+        HashMap               addUdtMap = new HashMap();
+        HashMap               dropUdtMap = new HashMap();
+        HashMap               udtMap = adding ? addUdtMap : dropUdtMap;
+        TypeDescriptor        rawReturnType = aliasInfo.getReturnType();
+
+        if ( rawReturnType != null )
+        {
+            AliasDescriptor       returnTypeAD = dd.getAliasDescriptorForUDT
+                ( tc, DataTypeDescriptor.getType( rawReturnType ) );
+
+            if ( returnTypeAD != null ) { udtMap.put( returnTypeAD.getObjectID().toString(),
returnTypeAD ); }
+        }
+
+        TypeDescriptor[]      paramTypes = aliasInfo.getParameterTypes();
+        if ( paramTypes != null )
+        {
+            int paramCount = paramTypes.length;
+            for ( int i = 0; i < paramCount; i++ )
+            {
+                AliasDescriptor       paramType = dd.getAliasDescriptorForUDT
+                    ( tc, DataTypeDescriptor.getType( paramTypes[ i ] ) );
+
+                if ( paramType != null ) { udtMap.put( paramType.getObjectID().toString(),
paramType ); }
+            }
+        }
+
+        adjustUDTDependencies( lcc, dd, ad, addUdtMap, dropUdtMap );
+    }
     
 	/**
 	 * Mutable Boolean wrapper, initially false

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/DropAliasConstantAction.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/DropAliasConstantAction.java?rev=890115&r1=890114&r2=890115&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/DropAliasConstantAction.java
(original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/DropAliasConstantAction.java
Sun Dec 13 20:30:49 2009
@@ -108,6 +108,8 @@
 		{
 			throw StandardException.newException(SQLState.LANG_OBJECT_NOT_FOUND, ad.getAliasType(nameSpace),
 aliasName);
 		}
+
+        adjustUDTDependencies( lcc, dd, ad, false );
         
         ad.drop(lcc);
 

Modified: db/derby/code/trunk/java/engine/org/apache/derby/loc/messages.xml
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/loc/messages.xml?rev=890115&r1=890114&r2=890115&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/loc/messages.xml (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/loc/messages.xml Sun Dec 13 20:30:49
2009
@@ -3140,7 +3140,15 @@
                 <text>Operation '{0}' cannot be performed on object '{1}' because TABLE
'{2}' is dependent on that object.</text>
                 <arg>operationName</arg>
                 <arg>objectName</arg>
-                <arg>viewName</arg>
+                <arg>tableName</arg>
+            </msg>
+
+            <msg>
+                <name>X0Y30.S</name>
+                <text>Operation '{0}' cannot be performed on object '{1}' because ROUTINE
'{2}' is dependent on that object.</text>
+                <arg>operationName</arg>
+                <arg>objectName</arg>
+                <arg>routineName</arg>
             </msg>
 
             <msg>

Modified: db/derby/code/trunk/java/shared/org/apache/derby/shared/common/reference/SQLState.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/shared/org/apache/derby/shared/common/reference/SQLState.java?rev=890115&r1=890114&r2=890115&view=diff
==============================================================================
--- db/derby/code/trunk/java/shared/org/apache/derby/shared/common/reference/SQLState.java
(original)
+++ db/derby/code/trunk/java/shared/org/apache/derby/shared/common/reference/SQLState.java
Sun Dec 13 20:30:49 2009
@@ -1322,6 +1322,7 @@
 	String LANG_INDEX_AND_TABLE_IN_DIFFERENT_SCHEMAS                   = "X0Y26.S";
 	String LANG_CREATE_SYSTEM_INDEX_ATTEMPTED                          = "X0Y28.S";
 	String LANG_PROVIDER_HAS_DEPENDENT_TABLE                            = "X0Y29.S";
+	String LANG_PROVIDER_HAS_DEPENDENT_ALIAS                            = "X0Y30.S";
 	String LANG_OBJECT_ALREADY_EXISTS_IN_OBJECT						   = "X0Y32.S";
 	String LANG_CREATE_INDEX_NO_TABLE                                  = "X0Y38.S";
 	String LANG_INVALID_FK_NO_PK                                       = "X0Y41.S";

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/UDTTest.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/UDTTest.java?rev=890115&r1=890114&r2=890115&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/UDTTest.java
(original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/UDTTest.java
Sun Dec 13 20:30:49 2009
@@ -53,6 +53,7 @@
     public static final String SYNTAX_ERROR = "42X01";
     public static final String TABLE_DEPENDS_ON_TYPE = "X0Y29";
     public static final String VIEW_DEPENDS_ON_TYPE = "X0Y23";
+    public static final String ROUTINE_DEPENDS_ON_TYPE = "X0Y30";
 
     ///////////////////////////////////////////////////////////////////////////////////
     //
@@ -497,6 +498,108 @@
         assertTrue( Price.makePrice().equals( result ) );
     }
 
+    /**
+     * <p>
+     * Dependencies of routines on UDTs.
+     * </p>
+     */
+    public void test_07_routineDependencies() throws Exception
+    {
+        Connection conn = getConnection();
+
+        // function that returns a udt
+        goodStatement
+            ( conn,
+              "create type price_07_a external name 'org.apache.derbyTesting.functionTests.tests.lang.Price'
language java\n" );
+        goodStatement
+            ( conn,
+              "create function makePrice_07_a( )\n" +
+              "returns price_07_a\n" +
+              "language java\n" +
+              "parameter style java\n" +
+              "no sql\n" +
+              "external name 'org.apache.derbyTesting.functionTests.tests.lang.Price.makePrice'\n"
+              );
+        expectExecutionError( conn, ROUTINE_DEPENDS_ON_TYPE, "drop type price_07_a restrict\n"
);
+        goodStatement
+            ( conn,
+              "drop function makePrice_07_a\n" );
+        goodStatement
+            ( conn,
+              "drop type price_07_a restrict\n" );
+
+        // function with a udt arg
+        goodStatement
+            ( conn,
+              "create type price_07_b external name 'org.apache.derbyTesting.functionTests.tests.lang.Price'
language java\n" );
+        goodStatement
+            ( conn,
+              "create function getCurrencyCode_07_b(  priceArg1 price_07_b  )\n" +
+              "returns char( 3 )\n" +
+              "language java\n" +
+              "parameter style java\n" +
+              "no sql\n" +
+              "external name 'org.apache.derbyTesting.functionTests.tests.lang.Price.getCurrencyCode'\n"
+              );
+        expectExecutionError( conn, ROUTINE_DEPENDS_ON_TYPE, "drop type price_07_b restrict\n"
);
+        goodStatement
+            ( conn,
+              "drop function getCurrencyCode_07_b\n" );
+        goodStatement
+            ( conn,
+              "drop type price_07_b restrict\n" );
+
+        // procedure with a udt arg
+        goodStatement
+            ( conn,
+              "create type price_07_c external name 'org.apache.derbyTesting.functionTests.tests.lang.Price'
language java\n" );
+        goodStatement
+            ( conn,
+              "create procedure oneArgPriceProc_07( price1 price_07_c )\n" +
+              "language java\n" +
+              "parameter style java\n" +
+              "no sql\n" +
+              "external name 'org.apache.derbyTesting.functionTests.tests.lang.UDTTest.oneArgPriceProc_07'\n"
+              );
+        expectExecutionError( conn, ROUTINE_DEPENDS_ON_TYPE, "drop type price_07_c restrict\n"
);
+        goodStatement
+            ( conn,
+              "drop procedure oneArgPriceProc_07\n" );
+        goodStatement
+            ( conn,
+              "drop type price_07_c restrict\n" );
+
+        // procedure with two udt args
+        goodStatement
+            ( conn,
+              "create type price_07_d external name 'org.apache.derbyTesting.functionTests.tests.lang.Price'
language java\n" );
+        goodStatement
+            ( conn,
+              "create procedure twoArgPriceProc_07( price1 price_07_d, price2 price_07_d
)\n" +
+              "language java\n" +
+              "parameter style java\n" +
+              "no sql\n" +
+              "external name 'org.apache.derbyTesting.functionTests.tests.lang.UDTTest.twoArgPriceProc_07'\n"
+              );
+        expectExecutionError( conn, ROUTINE_DEPENDS_ON_TYPE, "drop type price_07_d restrict\n"
);
+        goodStatement
+            ( conn,
+              "drop procedure twoArgPriceProc_07\n" );
+        goodStatement
+            ( conn,
+              "drop type price_07_d restrict\n" );
+
+    }
+
+    ///////////////////////////////////////////////////////////////////////////////////
+    //
+    // PROCEDURES
+    //
+    ///////////////////////////////////////////////////////////////////////////////////
+
+    public static void oneArgPriceProc( Price price1 ) {}
+    public static void twoArgPriceProc( Price price1, Price price2 ) {}
+
     ///////////////////////////////////////////////////////////////////////////////////
     //
     // MINIONS



Mime
View raw message