DBStatistics.java

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package nz.co.gregs.dbvolution.datatypes;

import java.sql.ResultSet;
import java.sql.SQLException;
import nz.co.gregs.dbvolution.DBReport;
import nz.co.gregs.dbvolution.DBRow;
import nz.co.gregs.dbvolution.databases.definitions.DBDefinition;
import nz.co.gregs.dbvolution.expressions.AnyExpression;
import nz.co.gregs.dbvolution.expressions.EqualExpression;
import nz.co.gregs.dbvolution.expressions.IntegerExpression;
import nz.co.gregs.dbvolution.expressions.RangeExpression;
import nz.co.gregs.dbvolution.internal.properties.PropertyWrapperDefinition;
import nz.co.gregs.dbvolution.results.EqualResult;
import nz.co.gregs.dbvolution.results.ExpressionHasStandardStringResult;

/**
 *
 * @author gregorygraham
 * @param <B> the base Java type of the values to be processed by this
 * DBStatistics
 * @param <R> the Results type of the DBStatistics, e.g. if B is Integer, R will
 * be IntegerResult
 * @param <D> the QDT to be used by this DBStatistics. E.g. if B is Integer, Q
 * will be DBInteger
 * @param <X> the expression type to be used by this DBStatistics. E.g if B is
 * integer X will be IntegerExpression
 */
public class DBStatistics<B, R extends EqualResult<B>, D extends QueryableDatatype<B>, X extends EqualExpression<B, R, D> & ExpressionHasStandardStringResult> extends DBString {

	private static final long serialVersionUID = 1;

	private final X originalExpression;
	private final DBString numberProxy = new DBString();
	private Number countOfRows;
	private B modeSimple;
	private B modeStrict;
	private B median;
	private B firstQuartileValue;
	private B thirdQuartileValue;
	private IntegerExpression countExpr;
	private EqualExpression.ModeSimpleExpression<B, R, D, X> modeSimpleExpression;
	private EqualExpression.ModeStrictExpression<B, R, D, X> modeStrictExpression;
	private RangeExpression.UniqueRankingExpression<B, R, D, X> uniqueRankingExpression;
	private RangeExpression.MedianExpression<B, R, D, X> medianExpression;
	private X firstQuartileExpression;
	private X thirdQuartileExpression;

	/**
	 * The default constructor for DBStatistics.
	 *
	 * <p>
	 * Creates an unset undefined DBNumber object.</p>
	 *
	 * <p>
	 * Use {@link #DBStatistics(nz.co.gregs.dbvolution.expressions.EqualExpression)
	 * } instead.</p>
	 *
	 */
	public DBStatistics() {
		originalExpression = null;
	}

	/**
	 * Creates a column expression with a statistics result from the expression
	 * provided.
	 *
	 * <p>
	 * Used in {@link DBReport}, and some {@link DBRow}, sub-classes to derive
	 * data from the database prior to retrieval.
	 *
	 * @param expressionToGenerateStatsFrom the expression or column to be used
	 * for statistics
	 */
	public DBStatistics(X expressionToGenerateStatsFrom) {
		super(expressionToGenerateStatsFrom.stringResult());
		this.originalExpression = expressionToGenerateStatsFrom;
		countExpr = originalExpression.count();
		modeSimpleExpression = new EqualExpression.ModeSimpleExpression<B, R, D, X>(originalExpression);
		modeStrictExpression = new EqualExpression.ModeStrictExpression<B, R, D, X>(originalExpression);
//		medianExpression = new EqualExpression.UniqueRankingExpression<B, R, D, X>(originalExpression);

		this.setColumnExpression(new AnyExpression<?, ?, ?>[]{
			countExpr,
			modeSimpleExpression,
			modeStrictExpression
		//,medianExpression
		});

	}

	/**
	 * Count of the rows included in this set of statistics
	 *
	 * @return the size of the statistics collection
	 */
	public Number count() {
		return this.countOfRows;
	}

	/**
	 *
	 *
	 * @return the minimum (smallest) value in this grouping
	 */
	public B modeSimple() {
		return this.modeSimple;
	}

	/**
	 *
	 * @return the maximum (largest) value in this grouping
	 */
	public B modeStrict() {
		return this.modeStrict;
	}

	/**
	 * The median value, that is the value that occurs halfway through the
	 * distribution.
	 *
	 * @return the ranking
	 */
	public B median() {
		return this.median;
	}

	/**
	 * Returns the value that occurs 1/4 of the way through the distribution.
	 *
	 * @return the middle number between the median and the smallest value.
	 */
	public B firstQuartile() {
		return this.firstQuartileValue;
	}

	/**
	 * Returns the value that occurs 3/4 of the way through the distribution.
	 *
	 * @return the middle number between the median and the largest value.
	 */
	public B thirdQuartile() {
		return this.thirdQuartileValue;
	}

	@Override
	public String getSQLDatatype() {
		return numberProxy.getSQLDatatype();
	}

	@Override
	public boolean isAggregator() {
		return numberProxy.isAggregator();
	}

	@Override
	@SuppressWarnings("unchecked")
	public DBStatistics<B, R, D, X> copy() {
		DBStatistics<B, R, D, X> copy = (DBStatistics<B, R, D, X>) super.copy();
		copy.countOfRows = this.countOfRows;
		copy.firstQuartileValue = this.firstQuartileValue;
		copy.modeStrict = this.modeStrict;
		copy.modeSimple = this.modeSimple;
		copy.thirdQuartileValue = this.thirdQuartileValue;
		copy.median = this.median;
		return copy;
	}

	protected Number getFromResultSet(DBDefinition database, ResultSet resultSet, String fullColumnName, int offset) throws SQLException {
		int columnIndex = resultSet.findColumn(fullColumnName) + offset;
		try {
			return resultSet.getBigDecimal(columnIndex);
		} catch (SQLException ex) {
			try {
				return resultSet.getDouble(columnIndex);
			} catch (SQLException ex2) {
				try {
					return resultSet.getLong(columnIndex);
				} catch (SQLException ex3) {
					return null;
				}
			}
		}
	}

	@Override
	public DBStatistics<B, R, D, X> getQueryableDatatypeForExpressionValue() {
		return new DBStatistics<>();
	}

	@Override
	public void setFromResultSet(DBDefinition database, ResultSet resultSet, String resultSetColumnName) throws SQLException {
		removeConstraints();
		if (resultSet == null || resultSetColumnName == null) {
			this.setToNull();
		} else {
			String dbValue;
			try {
				dbValue = getFromResultSet(database, resultSet, resultSetColumnName);
				if (resultSet.wasNull()) {
					dbValue = null;
				}
			} catch (SQLException ex) {
				// Probably means the column wasn't selected.
				dbValue = null;
			}
			if (dbValue == null) {
				this.setToNull(database);
			} else {
				var propertyWrapperDefinition = getPropertyWrapperDefinition();
				if (propertyWrapperDefinition != null && propertyWrapperDefinition.allColumnAspects != null) {
					final String countColumnAlias = propertyWrapperDefinition.allColumnAspects.get(0).getColumnAlias();
					final String modeSimpleAlias = propertyWrapperDefinition.allColumnAspects.get(1).getColumnAlias();
					final String modeStrictAlias = propertyWrapperDefinition.allColumnAspects.get(2).getColumnAlias();
//					final String medianAlias = propertyWrapperDefinition.allColumnAspects.get(3).columnAlias;
					countOfRows = new DBInteger().getFromResultSet(database, resultSet, countColumnAlias);
					modeSimple = modeSimpleExpression.asExpressionColumn().getFromResultSet(database, resultSet, modeSimpleAlias);
					modeStrict = modeStrictExpression.asExpressionColumn().getFromResultSet(database, resultSet, modeStrictAlias);
//					median = medianExpression.asExpressionColumn().getFromResultSet(database, resultSet, medianAlias);
				} else {
					countOfRows = getFromResultSet(database, resultSet, resultSetColumnName, 0);
//					modeSimple = modeSimpleExpression.asExpressionColumn().getFromResultSet(database, resultSet, resultSetColumnName, 1);
//					modeStrict = getFromResultSet(database, resultSet, resultSetColumnName, 1);
				}
				this.setLiteralValue(dbValue);
			}
		}
		setUnchanged();
		setDefined(true);
		propertyWrapperDefn = null;
	}

	@Override
	public String toString() {
		return ("count=" + countOfRows);
	}
	
	@Override
	public boolean getCouldProduceEmptyStringForNull() {
		return false;
	}
}