AbstractColumn.java

/*
 * Copyright 2013 Gregory Graham.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package nz.co.gregs.dbvolution.columns;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
import nz.co.gregs.dbvolution.databases.DBDatabase;
import nz.co.gregs.dbvolution.DBRow;
import nz.co.gregs.dbvolution.databases.definitions.DBDefinition;
import nz.co.gregs.dbvolution.datatypes.QueryableDatatype;
import nz.co.gregs.dbvolution.exceptions.IncorrectRowProviderInstanceSuppliedException;
import nz.co.gregs.dbvolution.expressions.DBExpression;
import nz.co.gregs.dbvolution.expressions.SortProvider;
import nz.co.gregs.dbvolution.internal.properties.PropertyWrapper;
import nz.co.gregs.dbvolution.query.RowDefinition;

/**
 * Represents the connection between a table and a column in a portable way.
 *
 * <p>
 * Used by
 * {@link RowDefinition#column(java.lang.Boolean) RowDefinition.getColumn(*)} to
 * produce an expression object that references a database table and column.
 *
 * <p>
 * Also allows PropertyWrapper to be passed around without confusing the public
 * interface.
 *
 * @author greg
 */
public class AbstractColumn implements DBExpression, Serializable {

	private static final long serialVersionUID = 1l;

	private final transient PropertyWrapper<?, ?, ?> propertyWrapper;
	private final RowDefinition dbrow;
	private final Object field;
	private boolean useTableAlias = true;

	/**
	 * Creates an AbstractColumn representing a table and column.
	 *
	 * <p>
	 * Stores the RowDefinition (generally a DBRow subclass) and a field of the
	 * RowDefinition so that the original association can be rebuilt where the
	 * expression is converted into SQL.
	 *
	 * @param row the row which contains the field
	 * @param field the field of the row that represents the database column
	 * @throws IncorrectRowProviderInstanceSuppliedException Please note the the
	 * field must be a field of the row
	 */
	public AbstractColumn(RowDefinition row, Object field) throws IncorrectRowProviderInstanceSuppliedException {
		this.dbrow = row;
		this.field = field;
		if (row != null) {
			this.propertyWrapper = row.getPropertyWrapperOf(field);
			if (propertyWrapper == null) {
				throw IncorrectRowProviderInstanceSuppliedException.newMultiRowInstance(field);
			}
		} else {
			propertyWrapper = null;
		}
	}

	@Override
	public String toSQLString(DBDefinition db) {
		String result = "";
		RowDefinition rowDefn = this.getRowDefinition();
		if ((field instanceof QueryableDatatype) && ((QueryableDatatype) field).hasColumnExpression()) {
			final QueryableDatatype<?> qdtField = (QueryableDatatype) field;
			DBExpression[] columnExpressions = qdtField.getColumnExpression();
			StringBuilder toSQLString = new StringBuilder();
			for (DBExpression columnExpression : columnExpressions) {
				toSQLString.append(columnExpression.toSQLString(db));
			}
			result = toSQLString.toString();
		} else {
			String formattedColumnName = "";
			if (useTableAlias) {
				formattedColumnName = db.formatTableAliasAndColumnName(rowDefn, propertyWrapper.columnName());
			} else if (rowDefn instanceof DBRow) {
				DBRow dbRow = (DBRow) rowDefn;
				formattedColumnName = db.formatTableAndColumnName(dbRow, propertyWrapper.columnName());
			}
			result = propertyWrapper.getPropertyWrapperDefinition().getQueryableDatatype(this.dbrow).formatColumnForSQLStatement(db, formattedColumnName);
		}
		if (needsToConvertNullToEmptyString(db)) {
			result = db.convertNullToEmptyString(result);
		}
		return result;
	}

	public boolean hasExpression() {
		if (field instanceof QueryableDatatype) {
			return ((QueryableDatatype) field).hasColumnExpression();
		}
		return false;
	}

	public DBExpression getExpression() {
		if (field instanceof QueryableDatatype) {
			final QueryableDatatype<?> qdt = (QueryableDatatype) field;
			if (qdt.hasColumnExpression()) {
				return qdt.getColumnExpression()[0];
			}
		}
		return null;
	}

	@Override
	public AbstractColumn copy() {
		final DBRow row = getInstanceOfRow();
		AbstractColumn newInstance = new AbstractColumn(row, getAppropriateQDTFromRow(row));
		return newInstance;
	}

	/**
	 * <p style="color: #F90;">Support DBvolution at
	 * <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
	 *
	 * @return the propertyWrapperOfQDT
	 */
	public PropertyWrapper<?, ?, ?> getPropertyWrapper() {
		return propertyWrapper;
	}

	/**
	 * Wrap this column in the equivalent DBValue subclass.
	 *
	 * <p>
	 * Probably this should be implemented as:<br>
	 * public MyValue asExpression(){return new MyValue(this);}
	 *
	 * <p style="color: #F90;">Support DBvolution at
	 * <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
	 *
	 * @return this instance as a StringValue, NumberValue, DateValue, or
	 * LargeObjectValue as appropriate
	 */
	public DBExpression asExpression() {
		return this;
	}

	@Override
	public QueryableDatatype<?> getQueryableDatatypeForExpressionValue() {
		return QueryableDatatype.getQueryableDatatypeForObject(getField());
	}

	@Override
	public boolean isAggregator() {
		boolean aggregator = false;
		if ((field instanceof QueryableDatatype) && ((QueryableDatatype) field).hasColumnExpression()) {
			final QueryableDatatype<?> qdtField = (QueryableDatatype) field;
			DBExpression[] columnExpressions = qdtField.getColumnExpression();
			for (DBExpression columnExpression : columnExpressions) {
				aggregator = aggregator || columnExpression.isAggregator();
			}
		}
		return aggregator;
	}

	@Override
	public boolean isWindowingFunction() {
		boolean windower = false;
		if ((field instanceof QueryableDatatype) && ((QueryableDatatype) field).hasColumnExpression()) {
			final QueryableDatatype<?> qdtField = (QueryableDatatype) field;
			DBExpression[] columnExpressions = qdtField.getColumnExpression();
			for (DBExpression columnExpression : columnExpressions) {
				windower = windower || columnExpression.isWindowingFunction();
			}
		}
		return windower;
	}

	@Override
	public boolean isPurelyFunctional() {
		return getTablesInvolved().isEmpty();
	}

	@Override
	public Set<DBRow> getTablesInvolved() {
		HashSet<DBRow> hashSet = new HashSet<>();
		if (DBRow.class.isAssignableFrom(getRowDefinition().getClass())) {
			hashSet.add((DBRow) getRowDefinition());
		}
		return hashSet;
	}

	/**
	 * <p style="color: #F90;">Support DBvolution at
	 * <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
	 *
	 * @return the dbrow
	 */
	protected RowDefinition getRowDefinition() {
		return dbrow;
	}

	/**
	 * <p style="color: #F90;">Support DBvolution at
	 * <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
	 *
	 * @return the field
	 */
	public Object getField() {
		return field;
	}

	/**
	 * Gets the DBvolution-centric value of the column for the instance of
	 * RowDefinition (DBRow/DBreport) supplied.
	 *
	 * <p>
	 * The value returned may have undergone type conversion from the target
	 * object's actual property type, if a type adaptor is present.
	 *
	 * @param row resolve the column for this row and provide the
	 * QueryableDatatype that is appropriate
	 * <p style="color: #F90;">Support DBvolution at
	 * <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
	 * @return the QDT version of the field on the DBRow
	 */
	public QueryableDatatype<?> getAppropriateQDTFromRow(RowDefinition row) {
		return this.getPropertyWrapper().getPropertyWrapperDefinition().getQueryableDatatype(row);
	}

	/**
	 * Gets the value of the declared column in the RowDefinition/DBRow/DBReport
	 * supplied, prior to type conversion to the DBvolution-centric type.
	 *
	 * <p>
	 * you should probably be using {@link #getAppropriateQDTFromRow(nz.co.gregs.dbvolution.query.RowDefinition)
	 * }
	 *
	 * @param row resolve the column for this row and provide the appropriate Java
	 * field (may be a QueryableDatatype)
	 * <p style="color: #F90;">Support DBvolution at
	 * <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
	 * @return the actual field on the DBRow object referenced by this column.
	 */
	public Object getAppropriateFieldFromRow(RowDefinition row) {
		return this.getPropertyWrapper().getPropertyWrapperDefinition().rawJavaValue(row);
	}

	/**
	 * <p style="color: #F90;">Support DBvolution at
	 * <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
	 *
	 * @return the useTableAlias
	 */
	protected boolean isUseTableAlias() {
		return useTableAlias;
	}

	/**
	 * @param useTableAlias the useTableAlias to set
	 */
	protected void setUseTableAlias(boolean useTableAlias) {
		this.useTableAlias = useTableAlias;
	}

	/**
	 * Returns a new version of the DBRow from which this column has been made.
	 *
	 * <p style="color: #F90;">Support DBvolution at
	 * <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
	 *
	 * @return an appropriate DBRow
	 */
	@SuppressWarnings("unchecked")
	public DBRow getInstanceOfRow() {
		final Class<? extends DBRow> originatingClass;
		originatingClass = (Class<? extends DBRow>) this.getPropertyWrapper().getRowDefinitionInstanceWrapper().adapteeRowDefinitionClass();
		final DBRow originatingRow = DBRow.getDBRow(originatingClass);
		return originatingRow;
	}

	/**
	 * Returns the class of the DBRow from which this column has been made.
	 *
	 *
	 * <p style="color: #F90;">Support DBvolution at
	 * <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
	 *
	 * @return an appropriate DBRow class
	 */
	public Class<? extends DBRow> getClassReferencedByForeignKey() {
		return this.getPropertyWrapper().referencedClass();
	}

	@Override
	public String createSQLForFromClause(DBDatabase database) {
		DBDefinition defn = database.getDefinition();
		String result = toSQLString(database.getDefinition());
		if (needsToConvertNullToEmptyString(defn)) {
			result = defn.convertNullToEmptyString(result);
		}
		return result;
	}

	private boolean needsToConvertNullToEmptyString(DBDefinition defn) {
		if (field instanceof QueryableDatatype) {
			var qdt = (QueryableDatatype) field;
			return (qdt.getCouldProduceEmptyStringForNull()) 
					&& (defn.requiredToProduceEmptyStringsForNull() 
					&& defn.supportsDifferenceBetweenNullAndEmptyStringNatively());
		} else {
			return false;
		}
	}

	@Override
	public boolean isComplexExpression() {
		return false;
	}

	@Override
	public String createSQLForGroupByClause(DBDatabase database) {
		return "";
	}

	/**
	 * Returns the sort order configured on the column.
	 *
	 * @return {@link QueryableDatatype#SORT_ASCENDING} or
	 * {@link QueryableDatatype#SORT_DESCENDING}
	 */
	public boolean getSortDirection() {
		if (this.field instanceof QueryableDatatype) {
			QueryableDatatype<?> qdt = (QueryableDatatype) field;
			return qdt.getSortOrder();
		} else {
			return QueryableDatatype.SORT_ASCENDING;
		}
	}

	/**
	 * Returns the sort order configured on the column.
	 *
	 * @return {@link QueryableDatatype#SORT_ASCENDING} or
	 * {@link QueryableDatatype#SORT_DESCENDING}
	 */
	public SortProvider.Column getSortProvider() {
		return new SortProvider.Column(this);
	}

	/**
	 * Returns the sort order configured on the column.
	 *
	 * @return {@link QueryableDatatype#SORT_ASCENDING} or
	 * {@link QueryableDatatype#SORT_DESCENDING}
	 */
	public SortProvider ascending() {
		return getSortProvider().ascending();
	}

	/**
	 * Returns the sort order configured on the column.
	 *
	 * @return {@link QueryableDatatype#SORT_ASCENDING} or
	 * {@link QueryableDatatype#SORT_DESCENDING}
	 */
	public SortProvider descending() {
		return getSortProvider().descending();
	}
}