/*
 * MiningMart Version 1.0
 * 
 * Copyright (C) 2006 Martin Scholz, Timm Euler, 
 *                    Daniel Hakenjos, Katharina Morik
 *
 * Contact: miningmart@ls8.cs.uni-dortmund.de
 *
 * A list of contributing developers (other than the copyright 
 * holders) can be found at
 * http://mmart.cs.uni-dortmund.de/downloads/download.html
 * 
 * This program 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 version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program, see the file MM_HOME/LICENSE; if not, write
 * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
 * Floor, Boston, MA 02110-1301, USA.
 */
package edu.udo.cs.miningmart.m4.core;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import java.util.Iterator;
import java.util.Vector;

import edu.udo.cs.miningmart.db.DB;
import edu.udo.cs.miningmart.exception.DbConnectionClosed;
import edu.udo.cs.miningmart.exception.M4CompilerError;
import edu.udo.cs.miningmart.exception.M4Exception;
import edu.udo.cs.miningmart.exception.MiningMartException;
import edu.udo.cs.miningmart.m4.Columnset;
import edu.udo.cs.miningmart.m4.utils.InterM4ColumnColumnStatist1;
import edu.udo.cs.miningmart.m4.utils.InterM4ColumnColumnStatist2;
import edu.udo.cs.miningmart.m4.utils.InterM4ColumnForeignKeyMember;
import edu.udo.cs.miningmart.m4.utils.InterM4ColumnPrimaryKeyMember;
import edu.udo.cs.miningmart.m4.utils.InterM4Communicator;
import edu.udo.cs.miningmart.m4.utils.M4Info;
import edu.udo.cs.miningmart.m4.utils.M4InfoEntry;
import edu.udo.cs.miningmart.m4.utils.Print;
import edu.udo.cs.miningmart.m4.utils.XmlInfo;

/**
 * This class represents an M4 column.
 * 
 * @author Martin Scholz
 * @version $Id: Column.java,v 1.12 2006/04/11 14:10:13 euler Exp $
 */
public class Column extends M4Data implements XmlInfo, edu.udo.cs.miningmart.m4.Column {

	static final InterM4Communicator colCstat1 = new InterM4ColumnColumnStatist1();
	static final InterM4Communicator colCstat2 = new InterM4ColumnColumnStatist2();
	static final InterM4Communicator colPkMem  = new InterM4ColumnPrimaryKeyMember();
	static final InterM4Communicator colFkMem  = new InterM4ColumnForeignKeyMember();
	
	// ***** Database constants for the Column table *****
	
	/** Name of the M4 table representing <code>Column</code>s */	
	public static final String M4_TABLE_NAME = "column_t";

	/** DB level: name of the column ID attribute */
	public static final String ATTRIB_COLUMN_ID       = "COL_ID"; // NOT NULL NUMBER
	
	/** DB level: name of the column name attribute */
	public static final String ATTRIB_COLUMN_NAME     = "COL_NAME"; // NOT NULL VARCHAR2(100)
	
	/** DB level: name of the column's columnset id reference attribute */
	public static final String ATTRIB_COLUMNSET_ID    = "COL_CSID"; // NUMBER

	/** DB level: name of the column's (relational) datatype ID attribute */
	public static final String ATTRIB_COLUMN_DATATYPE = "COL_COLDTID"; // NOT NULL NUMBER
	
	/** DB level: name of the column's SQL definition attribute */
	public static final String ATTRIB_COLUMN_SQL      = "COL_SQL"; // VARCHAR2(4000)

	
	// ***** Database constants for the Column to BaseAttribute cross-table *****
	
	/** Name of the Column to BaseAttribute cross-table */
	public static final String M4_TABLE_COL_BA = "ba_column_t";

	/** DB level: The primary key attribute of the Column to BaseAttribute cross-table */
	public static final String ATTRIB_COL_BA_TUPLE_ID = "bac_id";
	
	/** DB level: Column ID attribute of the Column to BaseAttribute cross-table */
	public static final String ATTRIB_COL_BA_COLID = "bac_colid";
	
	/** DB level: BaseAttribute ID of the Column to BaseAttribute cross-table */
	public static final String ATTRIB_COL_BA_BAID = "bac_baid";



	/** The only instance of relational datypes, <code>null</code> means not loaded yet! */
	private static RelationalDatatypes dataTypeMapping = null;

	/**
	 * Active getter for the datatype id to name and vice versa mapping.
	 * @param db a <code>DB</code> object for reading from the M4 system table
	 * */
	private RelationalDatatypes getRelationalDatatypeMapping(DB db)
		throws M4Exception, DbConnectionClosed
	{
    	if (dataTypeMapping == null) {
    		dataTypeMapping = new RelationalDatatypes(db);
    	}
    	return dataTypeMapping;
	}
	


	/** Cache for getM4Info() */
	public static M4Info m4Info = null;

	/** @see M4Table.getM4TableName() */
	public String getM4TableName() {
		return M4_TABLE_NAME;	
	}

	/** @see M4Table.getIdAttributeName() */
	public String getIdAttributeName() {
		return ATTRIB_COLUMN_ID;
	}

	/** @see M4Table.getM4Info() */
	public M4Info getM4Info() {
		if (m4Info == null) {
			M4InfoEntry[] m4i = {
				new M4InfoEntry(ATTRIB_COLUMN_ID,       "getId",            "setId",            	 long.class,   NOT_NULL),
				new M4InfoEntry(ATTRIB_COLUMN_NAME,     "getName",          "setName",               String.class, NOT_NULL),
				new M4InfoEntry(ATTRIB_COLUMNSET_ID,    "getColumnset",     "primitiveSetColumnset", edu.udo.cs.miningmart.m4.Columnset.class, NOT_NULL),
				new M4InfoEntry(ATTRIB_COLUMN_DATATYPE, "getColumnDataType","setColumnDataType",     long.class,   NOT_NULL),
				new M4InfoEntry(ATTRIB_COLUMN_SQL,      "getSQLDefinition", "setSQLDefinition",      String.class)
			};
			m4Info = new M4Info(m4i);
		}
		return m4Info;
	}

	// ***** Method from the XmlInfo interface *****

	/** Cache for getXmlInfo() */
	private static M4Info xmlInfo = null;

	/** @see XmlInfo.getXmlInfo() */
	public M4Info getXmlInfo() {
		if (xmlInfo == null) {
			M4InfoEntry[] m4i = {
				new M4InfoEntry("Name",          "getName",               "setName",               String.class),
				new M4InfoEntry("Columnset",     "getColumnset",          "setColumnset",          edu.udo.cs.miningmart.m4.Columnset.class),
				new M4InfoEntry("BaseAttribute", "getTheBaseAttribute",   "setBaseAttribute",      edu.udo.cs.miningmart.m4.BaseAttribute.class),
				new M4InfoEntry("ColDatatype",   "getColumnDataTypeName", "setColumnDataTypeName", String.class),
				new M4InfoEntry("SqlDefinition", "getSQLDefinition",      "setSQLDefinition",      String.class),
				new M4InfoEntry("Docu",          "getDocumentation",      "setDocumentation",      String.class)
			};
			xmlInfo = new M4Info(m4i);
		}
		return xmlInfo;
	}
	
    // ***** Instance variables from M4-DB *****
    
    private BaseAttribute myBaseAttribute;
    private Columnset     myColumnSet;
    private long          dataType;
    private String        sql;

    // ***** Column statistic Variables from M4-DB (colstatist1_t) *****
    private boolean allBasicStatisticsLoaded = false;
	private final Vector myBasicStatistics = new Vector();

    // ***** Value statistics from M4-DB (colstatist2_t) *****
    private boolean allDistribStatisticsLoaded = false;
	private final Vector myDistribStatistics = new Vector();
    // private ValueCount[] valueDistribution;

	// ***** KeyMember references to this column: *****
	private final Vector myPrimaryKeyMembers = new Vector();
	private boolean allPrimaryKeyMembersLoaded = false;
	private final Vector myForeignKeyMembers = new Vector();
	private boolean allForeignKeyMembersLoaded = false;

	// ***** Constructor *****
	
	/**
	 * @see edu.udo.cs.miningmart.m4.core.M4Data#Constructor
	 */
	public Column(DB m4Db) {
		super(m4Db);
	}

	// *****


	/**
	 * Print the information about this column.
	 */
    public void print(){
    	edu.udo.cs.miningmart.m4.Columnset cs = this.getColumnset();
    	String cdt = "[ID: " + this.getColumnDataType() + "]";
    	try {
    		cdt = this.getColumnDataTypeName();
    	}
    	catch (MiningMartException e) {}
    	
    	String colSetName = (cs == null) ? "<Columnset not set>" : cs.getSchema() + "." + cs.getName();
    	try {
			this.doPrint(Print.M4_OBJECT, "Column " + this.getName() + "(Id = " + this.getId() + ";" +
			      " Type = " + cdt + ") of Columnset " + colSetName +
			      "  defined in SQL as " + this.getSQLDefinition() +
		    	  "  (Unique: "  + this.getNumberOfUniqueValues() +
			      " Missing: "   + this.getNumberOfMissingValues() +
			      " Min: "       + this.getMinValue() +
			      " Max: "       + this.getMaxValue() +
		    	  " Median: "    + this.getMedianValue() +
			      " Modal: "     + this.getModalValue() +
			      " Average: "   + this.getAverageValue() +
			      " Std. Dev.: " + this.getStandardDeviation() + ")"
		    	  );
    	}
    	catch (M4Exception e) {}
    };

	/**
	 * Do not use spaces in Column names, because these names
	 * are also used at the DB level.
	 * @param name the new name to be set
	 * 
	 * @see M4Object#setName(String)
	 */
	public void setName(String name) {
		name = this.replaceSpacesInName(name);
		super.setName(name);
	}
	
	/**
	 * @see edu.udo.cs.miningmart.m4.core.M4Data#getObjectsInNamespace(Class)
	 */
	protected Collection getObjectsInNamespace(Class typeOfObjects) throws M4Exception {
		return null;
	}

    /**
     * Makes a copy of this <code>Column</code> that belongs to the given <code>Columnset</code>.
     * 
     * @param cs A Columnset
     * @return A copy of this Column that points to the given Columnset
     */
    public edu.udo.cs.miningmart.m4.Column copyColToCS(edu.udo.cs.miningmart.m4.Columnset cs) throws M4Exception
    {
	    Column ret = (Column) this.getM4Db().createNewInstance(edu.udo.cs.miningmart.m4.core.Column.class);
	    ret.myId = 0; // The DB-Interface assign a ret id from the DB-object-sequence			
		String nameOfCopy = cs.getValidName(this.getName(), Column.class);
		ret.setName(nameOfCopy);
	    ret.setColumnset(cs);
        ret.setBaseAttribute(this.getTheBaseAttribute());
	    ret.setColumnDataType(this.getColumnDataType());
	    ret.setSQLDefinition(this.getSQLDefinition());
	    
	    /*// Statistics should not be copied, because the contents may change
	      // for the new Column. 
        ret.setAverageValue(this.getAverageValue());
        ret.setVariance(this.getVariance());        
        ret.setMaxValue(this.getMaxValue());
        ret.setMedianValue(this.getMedianValue());
        ret.setMinValue(this.getMinValue());
        ret.setModalValue(this.getModalValue());
        ret.setNumberOfMissingValues(this.getNumberOfMissingValues());
        ret.setNumberOfUniqueValues(this.getNumberOfUniqueValues());
        ret.setStandardDeviation(this.getStandardDeviation());
        ret.setDistributionStatistics(this.getDistributionStatistics());
		*/
	    
        cs.addColumn(ret);

	    return ret;
    }

	/**
	 * Set this column's BaseAttribute.
	 * 
	 * @param ba The new BaseAttribute.
	 */
    public void setBaseAttribute(edu.udo.cs.miningmart.m4.BaseAttribute ba) throws M4Exception {
		BaseAttribute.ba2col.updateReferenceTo(this, ba);
    }

	/**
	 * Get this column's BaseAtttribute.
	 * 
	 * @return this column's BaseAttribute.
	 */
    public edu.udo.cs.miningmart.m4.BaseAttribute getTheBaseAttribute() throws M4Exception {
    	return this.myBaseAttribute;
    }

	/**
	 * Set the sql definition of this column.
	 * 
	 * @param sqlDefinition the new definition
	 */
    public void setSQLDefinition(String sqlDefinition) {
    	this.setDirty();
    	if (sqlDefinition == null || sqlDefinition.length() == 0) {
			this.sql = this.getName();
		}
		else {
	    	this.sql = sqlDefinition;
		}
    }

	/**
	 * @return this column's sql definition as a String
	 */
    public String getSQLDefinition()
    {
        if (sql == null)
        {   return this.getName();  }
        return sql;
    }
    
    /**
     * @return this column's sql definition together with
     * 		   schema name and table name (<code>schema.table.sql</code>)
     */
    public String getSQLPlusLocation()
    {
    	String location = this.getColumnset().getSchemaPlusName();
    	if ((location != null) && ( ! location.equals("")))
    	{   location += ".";   	}
    	else {  location = "";  }
    	
    	return (location + this.getSQLDefinition());
    }

	/**
	 * Set the column data type for this column.
	 * 
	 * @param type the new column data type
	 */
    public void setColumnDataType(long type)
    {   
		this.setDirty();
    	this.dataType = type;
    }

	/**
	 * @return  this column's column data type
	 */
    public long getColumnDataType()
    {   return dataType;   }

	/**
	 * Set the name of this column's column data type.
	 * 
	 * @param dtname the new name
	 */
    public void setColumnDataTypeName(String dtname)
    	throws M4Exception
    {
    	long type;
    	try {
        	type = getRelationalDatatypeMapping(this.getM4Db()).getIdForName(dtname);    		
    	}
    	catch (DbConnectionClosed d) {
			throw new M4Exception("Connection to DB lost: " + d.getMessage());		
		}
    	this.setColumnDataType(type);
	}

	/**
	 * @return the name of this column's column data type.
	 */
    public String getColumnDataTypeName()
    	throws DbConnectionClosed, M4Exception
    {
    	long type = this.getColumnDataType();
    	return getRelationalDatatypeMapping(this.getM4Db()).getNameForId(type);
    }

	/**
	 * Active getter for the <code>KeyMember</code>s referencing this object
	 * as their primary key.
	 * 
	 * @return a <code>Collection</code> of <code>KeyMember</code> objects
	 */
	public Collection getPrimaryKeyMembers() throws M4Exception {
		if ( ! this.allPrimaryKeyMembersLoaded && ! this.isNew() ) {
			this.allPrimaryKeyMembersLoaded = true;
			Collection pkms = this.getObjectsReferencingMe(KeyMember.class, KeyMember.ATTRIB_MEM_PK_COLUMN);
			Column.colPkMem.setCollectionTo(this, pkms);
		}
		return this.myPrimaryKeyMembers;
	}

	/**
	 * Active getter for the <code>KeyMember</code>s referencing this object
	 * as their foreign key.
	 * 
	 * @return a <code>Collection</code> of <code>KeyMember</code> objects
	 */
	public Collection getForeignKeyMembers() throws M4Exception {
		if ( ! this.allForeignKeyMembersLoaded && ! this.isNew() ) {
			this.allForeignKeyMembersLoaded = true;
			Collection fkms = this.getObjectsReferencingMe(KeyMember.class, KeyMember.ATTRIB_MEM_FK_COLUMN);
			Column.colFkMem.setCollectionTo(this, fkms);
		}
		return this.myForeignKeyMembers;
	}

	/**
	 * Active getter for all basic statistics tuples pointing to this Columm.
	 * @return <code>Collection</code> of <code>ColumnStatistics1</code> objects
	 */
	public Collection getBasicColStats() throws M4Exception {
		if ( ! this.allBasicStatisticsLoaded && ! this.isNew() ) {
			this.allBasicStatisticsLoaded = true;
			Collection references = this.getObjectsReferencingMe(ColumnStatistics1.class);
			Column.colCstat1.setCollectionTo(this, references);
		}
		return this.myBasicStatistics;
	}

	/** 
	 * @return the first (and hopefully only) ColumnStatist1 object of this
	 * Column. If no such objects exists, a new one is created.
	 */
	private ColumnStatistics1 getBasicStat() throws M4Exception {
		Collection col = this.getBasicColStats();
		ColumnStatistics1 ret = null;
		if (col != null && col.size() >= 1) {
			if (col instanceof Vector)
				ret = (ColumnStatistics1) ((Vector) col).get(0);
			else ret = (ColumnStatistics1) col.iterator().next();
		}
		else {
			ret = (ColumnStatistics1) this.getM4Db().createNewInstance(edu.udo.cs.miningmart.m4.core.ColumnStatistics1.class);
			Column.colCstat1.add(this, ret);
		}
		return ret;
	}

	/**
	 * Active getter for all distribution statistics tuples pointing to this Columm.
	 * @return <code>Collection</code> of <code>ColumnStatistics2</code> objects
	 */
	public Collection getDistributionStatistics() throws M4Exception {
		if ( ! this.allDistribStatisticsLoaded && ! this.isNew() ) {
			this.allDistribStatisticsLoaded  = true;
			Collection references = this.getObjectsReferencingMe(ColumnStatistics2.class);
			Column.colCstat2.setCollectionTo(this, references);
		}
		return this.myDistribStatistics;
	}
	
	/** 
	 * Sets the distribution statistics collection at once to the specified collection.
	 * @param dStats a <code>Collection</code> of <code>ColumnStatistics2</code> objects.
	 */
	public void setDistributionStatistics(Collection dStats) throws M4Exception {
		if (dStats == null) {
			dStats = new Vector();	
		}
		Column.colCstat2.setCollectionTo(this, dStats);
	}
	
	/**
	 * @return If known, the number of unique values of this column.
	 */
    public int getNumberOfUniqueValues() throws M4Exception
    {
    	Integer i = this.getBasicStat().getNrOfUniqueValuesI();
    	return (i == null ? 0 : i.intValue());   	
	}

    private boolean isMissingNumberOfUniqueValues() throws M4Exception {
    	return this.getBasicStat().getNrOfUniqueValuesI() == null;
    }
    
	/**
	 * @return If known, the number of missing values of this column.
	 */
    public int getNumberOfMissingValues() throws M4Exception
    {   
        Integer i = this.getBasicStat().getNrOfMissingValuesI();
    	return (i == null ? 0 : i.intValue());   	
    }

    private boolean isMissingNumberOfMissingValues() throws M4Exception {
    	return this.getBasicStat().getNrOfMissingValuesI() == null;
    }

	/**
	 * @return If known, the smallest value in this column.
	 */
    public String getMinValue() throws M4Exception
    {   return this.getBasicStat().getMinimum();   }

	/**
	 * @return If known, the biggest value in this column.
	 */
    public String getMaxValue() throws M4Exception
    {   return this.getBasicStat().getMaximum();  }

	/**
	 * @return If known, the median value in this column.
	 */
    public String getMedianValue() throws M4Exception
    {   return this.getBasicStat().getMedian();  }

	/**
	 * @return If known, the modal value in this column.
	 */
    public String getModalValue() throws M4Exception
    {   return this.getBasicStat().getModal();   }

	/**
	 * @return If known, the average of the values in this column.
	 */
    public String getAverageValue() throws M4Exception
    {   
       	Double doub = this.getBasicStat().getAverageD();
    	if (doub == null)
    		return null;
	   	return doub.toString();
    }

	/**
	 * @return If known, the standard deviation of the values in this column.
	 */
    public String getStandardDeviation() throws M4Exception
    {
       	Double doub = this.getBasicStat().getStandardDeviationD();
    	if (doub == null)
    		return null;
	   	return doub.toString();
    }

	/**
	 * @return If known, the variance of the values in this column.
	 */
    public String getVariance() throws M4Exception
    {   
    	Double doub = this.getBasicStat().getVarianceD();
    	if (doub == null)
    		return null;
	   	return doub.toString();
    }

	/**
	 * Sets the number of unique values.
	 * 
	 * @param nuv the new number
	 */
    protected void setNumberOfUniqueValues(int nuv) throws M4Exception
    {   
    	this.getBasicStat().setNrOfUniqueValues(new Integer(nuv));
    }

	/**
	 * Sets the number of missing values.
	 * 
	 * @param nmv the new number
	 */
    protected void setNumberOfMissingValues(int nmv) throws M4Exception
    {   
    	this.getBasicStat().setNrOfMissingValues(new Integer(nmv));
    }

	/**
	 * Sets the minimal value.
	 * 
	 * @param min the new minimum
	 */
    protected void setMinValue(String min) throws M4Exception
    {   
    	this.getBasicStat().setMinimum(min);
    }

	/**
	 * Sets the maximal value.
	 * 
	 * @param max the new maximum
	 */
    protected void setMaxValue(String max) throws M4Exception
    {   
    	this.getBasicStat().setMaximum(max);
    }

	/**
	 * Sets the median value.
	 * 
	 * @param med the new median
	 */
    protected void setMedianValue(String med) throws M4Exception
    {
    	this.getBasicStat().setMedian(med);
    }

	/**
	 * Sets the modal value.
	 * 
	 * @param mod the new modal
	 */
    protected void setModalValue(String mod) throws M4Exception
    {   
    	this.getBasicStat().setModal(mod);
    }

	/**
	 * Sets the average of values of this column.
	 * 
	 * @param avg the new average
	 */
    protected void setAverageValue(String avg) throws M4Exception
    {  
		Double v = null;
		if (avg != null && avg.trim().length() > 0)
			v = new Double(avg);
    	this.getBasicStat().setAverage(v);
    }

	/**
	 * Sets the standard deviation of values of this column.
	 * 
	 * @param stddev the new standard deviation
	 */
    protected void setStandardDeviation(String stddev) throws M4Exception
    {
    	Double v = null;
    	if (stddev != null && stddev.trim().length() > 0)
			v = new Double(stddev);
    	this.getBasicStat().setStandardDeviation(v);
    }

	/**
	 * Sets the variance of values of this column.
	 * 
	 * @param var the new variance
	 */
    protected void setVariance(String var) throws M4Exception
    {   
    	Double v = null;
    	if (var != null && var.trim().length() > 0)
			v = new Double(var);
		this.getBasicStat().setVariance(v);
    }
   
	/**
	 * @return this column's ColumnSet.
	 */
	public edu.udo.cs.miningmart.m4.Columnset getColumnset()
	{	return myColumnSet;   }

	/**
	 * Sets the ColumnSet for this column.
	 * @param cs The new ColumnSet
	 */
	public void setColumnset(Columnset cs) throws M4Exception {
		edu.udo.cs.miningmart.m4.core.Columnset.cs2col.checkNameExists(this, cs);
		edu.udo.cs.miningmart.m4.core.Columnset.cs2col.updateReferenceTo(this, cs);
	}

    /**
     * Reads, or computes the minimum value of this column and returns it as
     * a string.
     * 
     * @return the minimum value in the column as a String
     */
    public String readOrComputeMinimum() throws M4Exception
    {
        String min = this.getMinValue();
        
        if (min == null)
        {
        	edu.udo.cs.miningmart.m4.Columnset cs = this.getColumnset();
            String query =
            	"SELECT MIN(" + this.getSQLDefinition()
            	+ ") FROM "	+ cs.getSchemaPlusName();  

			min = this.executeBusinessSingleValueSqlRead(query);
			if (min == null) {  
				String addInfo = (cs.getStatisticsAll() > 0) ? "!": " because this columnset has no rows!";
				throw new M4Exception(
					"Could not compute minimum for column '"
					+ this.getName() + "', M4 id " + this.getId()
					+ " in columnset '" + cs.getName() + "'" + addInfo);
			}
            this.setMinValue(min);
        }
    	 
        return min;
    }

    /**
     * Reads, or computes the average value of this column and returns it as
     * a string.
     * 
     * @return the average value in the column as a String
     */
    public String readOrComputeAverage() throws M4Exception
    {
        String avg = this.getAverageValue();
        
        if (avg == null)
        {
        	Columnset cs = this.getColumnset();
            String query =
            	"SELECT AVG(" + this.getSQLDefinition()
            	+ ") FROM "	+ cs.getSchemaPlusName();  

			avg = this.executeBusinessSingleValueSqlRead(query);
			if (avg == null)
			{  throw new M4Exception(
					"Could not compute average value for column '"
					+ this.getName() + "', M4 id " + this.getId()
					+ " in columnset '" + cs.getName() + "'!");
			}
            this.setAverageValue(avg);
        }
    	 
        return avg;
    }

	/**
	 * Reads, or computes the standard deviation of this column and returns
	 * the result as a string.
	 * 
	 * @return the average value in the column as a String
	 */
	public String readOrComputeStdDev() throws M4Exception {
        String stddev = this.getStandardDeviation();
        
        if (stddev == null)
        {
        	Columnset cs = this.getColumnset();
            String query =
            	"SELECT STDDEV(" + this.getSQLDefinition()
            	+ ") FROM "	+ cs.getSchemaPlusName();  

			stddev = this.executeBusinessSingleValueSqlRead(query);
			if (stddev == null)
			{  throw new M4Exception(
					"Could not compute standard deviation for column '"
					+ this.getName() + "', M4 id " + this.getId()
					+ " in columnset '" + cs.getName() + "'!");
			}
            this.setAverageValue(stddev);
        }
    	 
        return stddev;		
	}

    /**
     * Reads, or computes the maximum value of this column and returns it as
     * a string.
     * 
     * @return the maximum value in the column as a String
     */
    public String readOrComputeMaximum() throws M4Exception
    {
        String max = this.getMaxValue();
        
        if (max == null)
        {
        	edu.udo.cs.miningmart.m4.Columnset cs = this.getColumnset();
            String query =
            	"SELECT MAX(" + this.getSQLDefinition()
            	+ ") FROM "	+ cs.getSchemaPlusName();  

			max = this.executeBusinessSingleValueSqlRead(query);
			if (max == null)
			{  throw new M4Exception(
					"Could not compute minimum for column '"
					+ this.getName() + "', M4 id " + this.getId()
					+ " in columnset '" + cs.getName() + "'!");
			}
            this.setMaxValue(max);
        }
    	 
        return max;
    }    
    
	/**
	 * Reads, or computes the number of missing values for this column and
	 * returns it as an int.
	 * 
	 * @return the maximum value in the column as an int
	 */
    public int readOrComputeNumMissingValues() throws M4Exception
    {
        if (this.isMissingNumberOfMissingValues()) {
        	Columnset cs = this.getColumnset();
            String query =
            	"SELECT COUNT(*) FROM " + cs.getSchemaPlusName()
				+ " WHERE " + this.getSQLDefinition() + " IS NULL";  

			Long missingL = this.executeBusinessSingleValueSqlReadL(query);
			if (missingL == null)
			{  throw new M4Exception(
					"Could not compute number of missing values for column '"
					+ this.getName() + "', M4 id " + this.getId()
					+ " in columnset '" + cs.getName() + "'!");
			}
            this.setNumberOfMissingValues(missingL.intValue());
        }
    	 
        return this.getNumberOfMissingValues();
    }

	/**
	 * Reads, or computes the number of unique values for this column and
	 * returns it as an int.
	 * 
	 * @return the maximum value in the column as a String
	 */
    public int readOrComputeUniqueValues() throws M4Exception {
    	if (this.isMissingNumberOfUniqueValues()) {
        	Columnset cs = this.getColumnset();
            String query =
            	"SELECT COUNT(*) FROM (SELECT DISTINCT " + this.getSQLDefinition()
				+ " FROM " + cs.getSchemaPlusName() + " WHERE "
				+ this.getSQLDefinition() + " IS NOT NULL) foo";
            	
			Long uniqueL = this.executeBusinessSingleValueSqlReadL(query);
			if (uniqueL == null)
			{  throw new M4Exception(
					"Could not compute number of missing values for column '"
					+ this.getName() + "', M4 id " + this.getId()
					+ " in columnset '" + cs.getName() + "'!");
			}
            this.setNumberOfUniqueValues(uniqueL.intValue());
        }
    	 
        return this.getNumberOfUniqueValues();
    }
    
    /**
     * If data changes in the database, e.g. a DB Concept is edited,
     * then this method allows to delete the deprecated statistics objects
     * without running a garbage collection.
     * 
     * @throws M4Exception
     */
    public void clearStatistics() throws M4Exception {
    	Column.colCstat1.setCollectionTo(this, new Vector(0));
    	Column.colCstat2.setCollectionTo(this, new Vector(0));
    }
    
	/**
	 * Calculates statistics for the specified column and stores it in the M4 tables
	 * COLSTATIST1/2. Values that are already present are not overwritten, because
	 * it is expected that the garbage collection removes any deprecated values.  
	 *
	 * @param conceptualDataType The conceptual datatype of the column.
	 * */
	public void updateStatistics()
		throws M4Exception
	{		
		try {
			final String csName = this.getColumnset().getSchemaPlusName();
			final String colSql = this.getSQLDefinition().trim();
			final long colDt = this.getColumnDataType();
	
			// Count unique value of column:
			if (this.isMissingNumberOfUniqueValues())
			{
				final Long numDistinct = this.executeBusinessSingleValueSqlReadL(
					"SELECT COUNT(DISTINCT " + colSql + ")"
					+ " FROM " + csName
				);

				if (numDistinct != null) {
					this.setNumberOfUniqueValues(numDistinct.intValue());
				}
			}
	
			// Count missing values of column
			final int numMissing;
			if (this.isMissingNumberOfMissingValues()) {
				Long numMissingL = this.executeBusinessSingleValueSqlReadL(
						"SELECT COUNT(*) FROM " + csName + " WHERE (" + colSql + ") IS NULL"
				);
	
				if (numMissingL != null) {
					numMissing = numMissingL.intValue();
					this.setNumberOfMissingValues(numMissing);
				}
				else numMissing = 0; // error
			}
			else {
				numMissing = this.getNumberOfMissingValues();
			}

			final int rowCount = this.getColumnset().getStatisticsAll() - numMissing;
			
			if (rowCount == 0)
				return;
			
			switch ((short) colDt) {
			  	// Statistics for numeric columns:
				case (12) : updateOrdinalValueStatistics(rowCount);
					break;
						
				// Statistics for (var)char columns:
				case (13) : updateNominalValueStatistics();
					break;
						
				// Statistics for date columns:
				case (14) : updateTimeValueStatistics(rowCount);
					break;
						
				// Statistics for key columns:
				case (15) : updateKeyValueStatistics();
					break;
						
				default: throw new M4CompilerError(
					"Invalid Column Datatype '" + colDt + "' in CompilerDatabaseService.updateStatistics!"
					);
			}

		}
		catch (M4CompilerError m4e)
		{   throw new M4Exception("M4 error when updating statistics: " + m4e.getMessage());  }		
	}


	/**
	 * This procedure calculates distribution-information for columns with datatype ordinal (coldtid = 12).
	 * For every different value the existence within the columnSet is counted. Null-values are not counted.
	 * For every distribution value, the min and max-value of the distribution range is also saved.
	 * Furthermore for every column the average, standard deviation and variance is calculated.
	*/
	private void updateOrdinalValueStatistics(long rowCount)
		throws M4Exception
	{
		try {
			final String csName = this.getColumnset().getSchemaPlusName();
			final String colSql = this.getSQLDefinition().trim();
//			final long colId = this.getId();

			/* Calculate min and max value: */
			final String minVal;
			final String maxVal;
			try {
				minVal = this.readOrComputeMinimum();
				maxVal = this.readOrComputeMaximum();
			}
			catch (M4Exception e) {
				// Work-around.
				// In case a nominal attribute is by mistake considered ordinal ...
				this.updateNominalValueStatistics();
				return;
			}

			// compute average without later use:
			this.readOrComputeAverage();
				
			ResultSet rs = null;
			try	{	
				/*
				 * Calculate stddev, variance for this column using min and max.
				 * Added the modulo to avoid overflows of the 38 precision in the storage field.
				 */
				if ((this.getStandardDeviation() == null) || (this.getVariance() == null))
				{
					String que = "SELECT ROUND(STDDEV(" + colSql + "),5), "
			   				         + " ROUND(MOD(VARIANCE(" + colSql
			   				   + "),100000000000000000000000000000000000000),5) "
			   				   + " FROM " + csName;
					rs = this.executeBusinessSqlRead(que);
					if (rs.next()) {
						double stddevVal = rs.getDouble(1);
						if (!rs.wasNull()) {
							this.setStandardDeviation(Double.toString(stddevVal));
						}
						
						double varVal = rs.getDouble(2);
						if (!rs.wasNull()) {
							this.setVariance(Double.toString(varVal));
						}				
					}
					rs.close();
					rs = null;
				}
	
				/* Calculate distribution for column if necessary. */
				Collection oldDistStat = this.getDistributionStatistics();
				if ((oldDistStat ==  null) || oldDistStat.isEmpty()
					|| (this.getMedianValue() == null) || (this.getModalValue() == null))
				{
					// delete old distributional statistics
					Column.colCstat2.setCollectionTo(this, new Vector(0));
					
					if (minVal != null && maxVal != null && !minVal.equals(maxVal)) {
						String que = "SELECT AVG(" + colSql + "),"
					           + " COUNT(*), "
							   + " MIN(" + colSql + "),"
							   + " MAX(" + colSql + ") "
							   + " FROM " + csName
							   + " WHERE NOT (" + colSql + ") IS NULL "
							   + " GROUP BY ROUND(("
							   + colSql	+ " - " + minVal + ")"
							   + " / (" + maxVal + " - " + minVal + ") * 10)"
							   + " ORDER BY AVG(" + colSql + ")";

						/*
						 * Insert one distribution range of a column into
						 * colstatist2_t. for every column the average-value for
						 * this distribution range, number of elements belonging to
						 * this distribution range, min-value and max value of this
						 * distribution range are saved.
						 */
						double modalCount = 0;
						double medianSum = 0;
						String modalVal = "NULL";
						String medianVal = "NULL";

						rs = this.executeBusinessSqlRead(que);
						while (rs.next()) {
							ColumnStatistics2 csstat2 =
								(ColumnStatistics2) this.getM4Db().createNewInstance(
										edu.udo.cs.miningmart.m4.core.ColumnStatistics2.class);
							csstat2.setTheColumn(this);

							csstat2.setDistributionValue(rs.getString(1));
							csstat2.setDistributionCount((int) rs.getLong(2));

							double d = rs.getDouble(3);
							if (!rs.wasNull()) {
								csstat2.setDistributionMinimum(d);
							}
							d = rs.getDouble(4);
							if (!rs.wasNull()) {
								csstat2.setDistributionMaximum(d);
							}

							if (csstat2.getDistributionCount() > modalCount) {
								modalCount = csstat2.getDistributionCount();
								modalVal = csstat2.getDistributionValue();
							}

							if (medianSum <= rowCount / 2
								&& rowCount / 2 <= medianSum
										+ csstat2.getDistributionCount()) {
								medianVal = csstat2.getDistributionValue();
							}
							medianSum += csstat2.getDistributionCount();
						}

						this.setModalValue(modalVal);
						this.setMedianValue(medianVal);
					}
				}
			} catch (SQLException e) {
				throw new M4CompilerError(
						"Column.updateOrdinalValueStatistics(Column ID: "
								+ this.getId() + ") :" + e.getMessage());
			} finally {
				DB.closeResultSet(rs);
			}
		}
		catch (M4CompilerError m4e)
		{   throw new M4Exception("M4 error when updating statistics: " + m4e.getMessage());  }
	}


	/**
	 * This procedure calculates distribution-information for columns with datatype nominal (coldtid = 13).
	 * For every different value the existence within the columnSet is counted. Null-values are not counted.
	 * */
	private void updateNominalValueStatistics()
		throws M4Exception
	{
		final String csName = this.getColumnset().getSchemaPlusName();
		final String colSql = this.getSQLDefinition().trim();
		final long colId = this.getId();
	
		ResultSet rs = null;
		try	{
			/* Calculate distribution for column if necessary. */
			Collection oldDistStat = this.getDistributionStatistics();
			if ((oldDistStat ==  null) || oldDistStat.isEmpty() || (this.getModalValue() == null))
			{
				// delete old distributional statistics
				Column.colCstat2.setCollectionTo(this, new Vector(0));
			
				String que =
					"SELECT (" + colSql + "), "
					+ " COUNT(*) FROM " + csName
					+ " WHERE (" + colSql + ") IS NOT NULL"
					+ " GROUP BY (" + colSql + ")";

				double modalCount = 0;
				String modalVal   = "NULL";

				rs = this.executeBusinessSqlRead(que);
				while (rs.next()) {
					String attrName = rs.getString(1);
					if (rs.wasNull()) {
						attrName = "NULL";
					}				
					long attrCount = rs.getLong(2);
				
					ColumnStatistics2 cstat2 =
						(ColumnStatistics2) this.getM4Db().createNewInstance(edu.udo.cs.miningmart.m4.core.ColumnStatistics2.class);
					cstat2.setTheColumn(this);
					cstat2.setDistributionValue(attrName);
					cstat2.setDistributionCount((int) attrCount);
	
					if (attrCount > modalCount) {
						modalCount = attrCount;
						modalVal   = attrName;
					}
				}
				this.setModalValue(modalVal);
			}
		}
		catch (SQLException e) {
			throw new M4Exception(
				"CompilerDatabaseService.updateOrdinalValueStatistics(Column ID: "
				+ colId + ") :" + e.getMessage()
			);
		}
		finally {
			DB.closeResultSet(rs);
		}
	}

	/**
	 * This procedure calculates distribution-information for columns with datatype date (coldtid = 14).
	 * For every different value the existence within the columnSet is counted. Null-values are not counted.
	 * For every distribution value, the min and max-value of the distribution range is also saved.
	 * Dependent on the range between the minimal and maximal value of the column, distribution information
	 * is calculated on a yearly, quarterly, monthly or daily basis.
	 */
	private void updateTimeValueStatistics(long rowCount)
		throws M4Exception
	{
		final String csName = this.getColumnset().getSchemaPlusName();
		final String colSql = this.getSQLDefinition().trim();
		final long colId = this.getId();

		/* Calculate min and max value: */
		try {
			this.readOrComputeMinimum();
			this.readOrComputeMaximum();
		}
		catch (M4Exception e) {
			// Probably not a numeric attribute. Nothing to do.
		}
		
		/* Calculate distribution for column only if necessary. */
		Collection oldDistStat = this.getDistributionStatistics();
		if ((oldDistStat !=  null) && !oldDistStat.isEmpty()
			&& (this.getModalValue() != null) && this.getMedianValue() != null) {
			return;
		}
		
		ResultSet rs = null;
		try	{

			// delete old distributional statistics
			Column.colCstat2.setCollectionTo(this, new Vector(0));

			/* Calculate min, max, avg value: */
//			final String minVal = this.readOrComputeMinimum();
//			final String maxVal = this.readOrComputeMaximum();
			// this.readOrComputeAverage();
	
			/* Read number of months between min and max value of column. */
			final long monthBetween;
			{
				String que =
					"SELECT MONTHS_BETWEEN(MAX(" + colSql + "), "
			    	+ " MIN(" + colSql + ")) FROM "	+ csName;
			    Long monthL = this.executeBusinessSingleValueSqlReadL(que);
				monthBetween = (monthL == null ? 0 : monthL.longValue());
			}
	
			/* Calculate distribution for column, depending on months between. */
			final String distBase;
			{
		    	if (monthBetween > 600) {
		    		distBase = "YYYY";	// distribution base is years.
		    	} else if (monthBetween > 60) {
	    			distBase = "YYYYQ";	 // distribution base is quarter years
		    	} else if (monthBetween > 3) {
	    			distBase = "YYYYMM"; // distribution base is months
		    	} else if (monthBetween > 0) {
	    			distBase = "YYYYMMDD"; // distribution base is days
		    	} else {
		    		distBase = "YYYYMMDD"; // distribtion base is days
		    	}
			}

			/*
			 * Calculate distribution for column.
			 * Insert one distribution range of a column into colstatist2_t.
			 * For every column the value for this distribution range, number
			 * of elements belonging to this distribution range, min-value and
			 * max value of this distribution range are saved.
			 */
			String query = "SELECT"
						 + " TO_CHAR(" + colSql + ", '" + distBase + "'),"
	    		 		 + " COUNT(*),"
	    		 		 + " TO_CHAR(MIN(" + colSql + "), '" + distBase + "'),"
	    		 		 + " TO_CHAR(MAX(" + colSql + "), '" + distBase + "')"
	    		 		 + " FROM " + csName
						 + " WHERE (" + colSql + ") IS NOT NULL"
						 + " GROUP BY TO_CHAR(" + colSql + ", '" + distBase + "')"
						 + " ORDER BY TO_CHAR(" + colSql + ", '" + distBase + "')";

			double modalCount = 0;
			double medianSum  = 0;
			String modalVal   = "NULL";
			String medianVal  = "NULL";
	
			rs = this.executeBusinessSqlRead(query);
			while (rs.next()) {
				String attrVal = rs.getString(1);
				if (rs.wasNull()) {
					attrVal = "NULL";
				}				
				long attrCount = rs.getLong(2);
				String attrMin = rs.getString(3);
				if (rs.wasNull()) {
					attrMin = "NULL";
				}				
				String attrMax = rs.getString(4);
				if (rs.wasNull()) {
					attrMax = "NULL";
				}				
				ColumnStatistics2 cstat2 =
					(ColumnStatistics2) this.getM4Db().createNewInstance(edu.udo.cs.miningmart.m4.core.ColumnStatistics2.class);
				cstat2.setTheColumn(this);
				cstat2.setDistributionValue(attrVal); // "TO_CHAR(TO_DATE('" + attrVal + "', '" + distBase + "')), "
				cstat2.setDistributionCount((int) attrCount);
				cstat2.setDistributionMinimum(Double.parseDouble(attrMin));
				cstat2.setDistributionMaximum(Double.parseDouble(attrMax));

			  	if (attrCount > modalCount) {
			  	   modalCount = attrCount;
				   modalVal   = attrVal;
			  	}

			    if (medianSum <= rowCount/2 && rowCount/2 <= medianSum + attrCount) {
			       medianVal = attrVal;
				}
			  	medianSum += attrCount;
			}
				
			this.setModalValue(modalVal); //  " colst1_modal  = TO_CHAR(TO_DATE('" + modalVal  + "', '" + distBase + "')),"
			this.setMedianValue(medianVal); // " colst1_median = TO_CHAR(TO_DATE('" + medianVal + "', '" + distBase + "'))"
		}
		catch (SQLException e) {
			throw new M4Exception(
				"CompilerDatabaseService.updateOrdinalValueStatistics(Column ID: "
				+ colId + ") :" + e.getMessage()
			);
		}
		finally {
			DB.closeResultSet(rs);
		}
	}

	/**
	 * This procedure calculates statistics for key columns
	 * Since keys are usually unique it just counts the missing values
	 * and reports minimum and maximum if applicable. 
	 * */
	private void updateKeyValueStatistics()
		throws M4Exception
	{
		/* Calculate min and max value: */
		try {
			this.readOrComputeMinimum();
			this.readOrComputeMaximum();
		}
		catch (M4Exception e) {
			// Probably not a numeric attribute. Nothing to do.
		}
	}

   
	/**
	 * Sets the BaseAttribute that is found in the database as belonging to 
	 * this Column into the Column object. This method is executed when
	 * loading this object.
	 */
    private void readBaseAttributeForColumnFromDB() throws M4Exception
    {
		String query = "SELECT " + ATTRIB_COL_BA_BAID +
					   " FROM "  + M4_TABLE_COL_BA +
					   " WHERE " + ATTRIB_COL_BA_COLID +
					   " = " + this.getId();

		Long baId = this.executeM4SingleValueSqlReadL(query);
		BaseAttribute ba = null;
		if (baId != null) {
			ba = (BaseAttribute) this.getM4Db().getM4Object(baId.longValue(), BaseAttribute.class);
		}
		this.primitiveSetBaseAttribute(ba);
    }

	/**
	 * Primitive setter, do not use.
	 * @param ba the <code>BaseAttribute</code> to be set
	 * */
	public void primitiveSetBaseAttribute(edu.udo.cs.miningmart.m4.BaseAttribute ba) {
		this.setDirty();
		this.myBaseAttribute = (BaseAttribute) ba;	
	}

	/**
	 * Primitive setter, do not use.
	 * @param cs the <code>Columnset</code> to be set
	 * */
	public void primitiveSetColumnset(edu.udo.cs.miningmart.m4.Columnset cs) {
		this.setDirty();
		this.myColumnSet = (Columnset) cs;
	}


	/**
	 * Loads <code>this</code> object's <code>BaseAttribute</code>
	 * from a cross-table at loading time.
	 */
	protected void readFromDbLocal() throws M4Exception {
		this.readBaseAttributeForColumnFromDB();
	}

    /**
	 * This method stores the pseudo foreign key reference to this objects's
	 * <code>BaseAttribute</code>, which is still realized by a cross-table
	 *(<i>BA_COLUMN_T</i>).
	 */
	public void storeLocal() throws M4Exception {
		super.storeLocal();
		
		{ // Delete all references to BAs first:
			String sql = "DELETE FROM " + M4_TABLE_COL_BA
			           + " WHERE " + ATTRIB_COL_BA_COLID
				           + " = " + this.getId();
			
			this.executeM4SqlWrite(sql);
		}

		// If there is no BaseAttribute then there is nothing else to do.		
		if (this.getTheBaseAttribute() != null)			
		{ // Otherwise write the reference to the BaseAttribute:
			String baId = Long.toString(this.getTheBaseAttribute().getId());

			String sql = "INSERT INTO " + M4_TABLE_COL_BA
					   + " ( " + ATTRIB_COL_BA_TUPLE_ID
					   + ", " + ATTRIB_COL_BA_COLID
					   + ", " + ATTRIB_COL_BA_BAID
					   + " ) VALUES ( "
					   + this.getNextM4SequenceValue()
					   + ", " + this.getId()
					   + ", " + baId + " )";
					
			this.executeM4SqlWrite(sql);
		}
	}

	protected void deleteLocal() throws M4Exception {
		super.deleteLocal();
		
		// Delete all references to BAs first:
		String sql = "DELETE FROM " + M4_TABLE_COL_BA
		           + " WHERE " + ATTRIB_COL_BA_COLID
		           + " = " + this.getId();
		
		this.executeM4SqlWrite(sql);	
	}

	/**
	 * Overwrites the superclass method because the keys referring to 
	 * this column must be deleted, too.
	 * 
	 * @throws M4Exception
	 */
	public void deleteSoon() throws M4Exception {
		Collection pks = this.getPrimaryKeyMembers();
		if (pks != null && ( ! pks.isEmpty())) {
			Iterator it = (new Vector(pks)).iterator();
			while (it.hasNext()) {
				KeyMember km = (KeyMember) it.next();
				km.deleteSoon();
			}
		}
		Collection fks = this.getForeignKeyMembers();
		if (fks != null && ( ! fks.isEmpty())) {
			Iterator it = (new Vector(fks)).iterator();
			while (it.hasNext()) {
				KeyMember km = (KeyMember) it.next();
				km.deleteSoon();
			}
		}
		Collection stats = this.getDistributionStatistics();
		if (stats != null && ( ! stats.isEmpty())) {
			Iterator it = (new Vector(stats)).iterator();
			while (it.hasNext()) {
				ColumnStatistics2 mystats2 = (ColumnStatistics2) it.next();
				mystats2.deleteSoon();
			}
		}
		stats = this.getBasicColStats();
		if (stats != null && ( ! stats.isEmpty())) {
			Iterator it = (new Vector(stats)).iterator();
			while (it.hasNext()) {
				ColumnStatistics1 mystats1 = (ColumnStatistics1) it.next();
				mystats1.deleteSoon();
			}
		}
		super.deleteSoon();
	}
	
	/** @see M4Data#removeAllM4References() */
	protected void removeAllM4References() throws M4Exception {
		Vector empty = new Vector(0);
		Column.colCstat1.setCollectionTo(this, empty);
		Column.colPkMem.setCollectionTo(this, empty);
		Column.colFkMem.setCollectionTo(this, empty);
		this.setDistributionStatistics(null);
		this.setBaseAttribute(null);
		this.setColumnset(null);
		this.removeDocObject();
	}

	/** @see M4Data#getDependentObjects */
	public Collection getDependentObjects() throws M4Exception {
		Collection ret = super.getDependentObjects();
		ret.addAll(this.getBasicColStats());
		ret.addAll(this.getDistributionStatistics());
		ret.addAll(this.getPrimaryKeyMembers());
		ret.addAll(this.getForeignKeyMembers());
		return ret;
	}

	public void removeBaseAttribute() throws M4Exception {
		this.setBaseAttribute(null);
	}
}
/*
 * Historie
 * --------
 * 
 * $Log: Column.java,v $
 * Revision 1.12  2006/04/11 14:10:13  euler
 * Updated license text.
 *
 * Revision 1.11  2006/04/06 16:31:13  euler
 * Prepended license remark.
 *
 * Revision 1.10  2006/04/03 09:32:53  euler
 * Improved Export/Import of Projections
 * and Subconcept links.
 *
 * Revision 1.9  2006/03/21 09:04:31  euler
 * Bugfix in readOrComputeUniqueValues
 *
 * Revision 1.8  2006/03/20 13:22:17  scholz
 * added readOrComputeStdDev()
 *
 * Revision 1.7  2006/03/20 12:38:55  scholz
 * bugfix
 *
 * Revision 1.6  2006/03/19 21:16:51  scholz
 * conditions are now checked before executing an operator
 *
 * Revision 1.5  2006/01/24 12:31:45  euler
 * Added recognition of key type for new columns.
 * Removed EstimatedStatistics from context menu
 * because they are in too basic status for the release.
 *
 * Revision 1.4  2006/01/13 01:19:46  scholz
 * improved statistics computation
 *
 * Revision 1.3  2006/01/12 20:35:18  scholz
 * bugfix statistics
 *
 * Revision 1.2  2006/01/06 16:25:04  euler
 * Updates and bugfixes in the delete-Mechanism for M4Data objects.
 *
 * Revision 1.1  2006/01/03 09:54:16  hakenjos
 * Initial version!
 *
 */
