WindowFunctionRequiresOrderBy.java

/*
 * Copyright 2019 Gregory Graham.
 *
 * Commercial licenses are available, please contact info@gregs.co.nz for details.
 * 
 * This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 
 * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 
 * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
 * 
 * You are free to:
 *     Share - copy and redistribute the material in any medium or format
 *     Adapt - remix, transform, and build upon the material
 * 
 *     The licensor cannot revoke these freedoms as long as you follow the license terms.               
 *     Under the following terms:
 *                 
 *         Attribution - 
 *             You must give appropriate credit, provide a link to the license, and indicate if changes were made. 
 *             You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.
 *         NonCommercial - 
 *             You may not use the material for commercial purposes.
 *         ShareAlike - 
 *             If you remix, transform, or build upon the material, 
 *             you must distribute your contributions under the same license as the original.
 *         No additional restrictions - 
 *             You may not apply legal terms or technological measures that legally restrict others from doing anything the 
 *             license permits.
 * 
 * Check the Creative Commons website for any details, legalese, and updates.
 */
package nz.co.gregs.dbvolution.expressions.windows;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import nz.co.gregs.dbvolution.DBRow;
import nz.co.gregs.dbvolution.columns.ColumnProvider;
import nz.co.gregs.dbvolution.databases.DBDatabase;
import nz.co.gregs.dbvolution.databases.definitions.DBDefinition;
import nz.co.gregs.dbvolution.datatypes.QueryableDatatype;
import nz.co.gregs.dbvolution.expressions.EqualExpression;
import nz.co.gregs.dbvolution.expressions.IntegerExpression;
import nz.co.gregs.dbvolution.expressions.SortProvider;
import nz.co.gregs.dbvolution.results.AnyResult;

/**
 *
 * @author gregorygraham
 * @param <A> the expression type returned by this windowing function, e.g. IntegerExpression
 */
public class WindowFunctionRequiresOrderBy<A extends EqualExpression<?, ?, ?>> implements WindowingFunctionRequiresOrderByInterface<A> {

	private final A innerExpression;

	public WindowFunctionRequiresOrderBy(A expression) {
		super();
		this.innerExpression = expression;
	}

	@Override
	public Partitioned<A> partition(ColumnProvider... cols) {
		return new WindowFunctionRequiresOrderBy.Partitioned<A>(this, cols);
	}

	public A getInnerExpression() {
		return innerExpression;
	}

	public A AllRowsAndOrderBy(SortProvider sort, SortProvider... sorts) {
				return this.partition().orderBy(sort, sorts);
	}

	@Override
	public String toSQLString(DBDefinition defn) {
		return innerExpression.toSQLString(defn) + " OVER (";
	}

	@SuppressWarnings("unchecked")
	@Override
	public Class<A> getRequiredExpressionClass() {
		return (Class<A>) innerExpression.getClass();
	}

	@SuppressWarnings("unchecked")
	@Override
	public WindowFunctionRequiresOrderBy<A> copy() {
		return new WindowFunctionRequiresOrderBy<A>((A) this.innerExpression.copy());
	}

	@Override
	public QueryableDatatype<?> getQueryableDatatypeForExpressionValue() {
		throw new UnsupportedOperationException("WindowFunctionRequiresOrderBy does not support getQueryableDatatypeForExpressionValue() yet.");
	}

	@Override
	public boolean isAggregator() {
		throw new UnsupportedOperationException("WindowFunctionRequiresOrderBy does not support isAggregator() yet.");
	}

	@Override
	public Set<DBRow> getTablesInvolved() {
		throw new UnsupportedOperationException("WindowFunctionRequiresOrderBy does not support getTablesInvolved() yet.");
	}

	@Override
	public boolean isPurelyFunctional() {
		boolean functional = innerExpression.isPurelyFunctional();
		return functional;
	}

	@Override
	public boolean isComplexExpression() {
		throw new UnsupportedOperationException("WindowFunctionRequiresOrderBy does not support isComplexExpression() yet.");
	}

	@Override
	public String createSQLForFromClause(DBDatabase database) {
		throw new UnsupportedOperationException("WindowFunctionRequiresOrderBy does not support createSQLForFromClause(DBDatabase) yet.");
	}

	@Override
	public String createSQLForGroupByClause(DBDatabase database) {
		throw new UnsupportedOperationException("WindowFunctionRequiresOrderBy does not support createSQLForGroupByClause(DBDatabase) yet.");
	}

	@Override
	public boolean isWindowingFunction() {
		throw new UnsupportedOperationException("WindowFunctionRequiresOrderBy does not support isWindowingFunction() yet.");
	}

	public Partitioned<A> unpartitioned() {
		return this.partition();
	}

	public Partitioned<A> allRows() {
		return this.partition();
	}

	public static class Partitioned<A extends EqualExpression<?, ?, ?>> implements WindowingFunctionRequiresOrderByInterface.Partitioned<A> {

		private final WindowFunctionRequiresOrderBy<A> innerExpression;
		private final ColumnProvider[] columns;

		private Partitioned(WindowFunctionRequiresOrderBy<A> expression, ColumnProvider... cols) {
			super();
			this.innerExpression = expression;
			this.columns = cols;
		}

		@Override
		public A orderBy(SortProvider sort, SortProvider... sorts) {
			SortProvider[] newSorts = new SortProvider[sorts.length + 1];
			newSorts[0] = sort;
			System.arraycopy(sorts, 0, newSorts, 1, sorts.length);
			return new WindowFunctionRequiresOrderBy.Sorted<A>(this, newSorts).getRequiredExpression();
		}

		@Override
		public String toSQLString(DBDefinition defn) {
			StringBuilder partitionClause = new StringBuilder();
			if (columns.length > 0) {
				partitionClause.append("PARTITION BY ");
				String separator = "";
				for (ColumnProvider partitionByColumn : columns) {
					partitionClause.append(separator).append(partitionByColumn.toSQLString(defn));
					separator = ", ";
				}
			}
			return innerExpression.toSQLString(defn) + partitionClause;
		}

		@Override
		public Class<A> getRequiredExpressionClass() {
			return innerExpression.getRequiredExpressionClass();
		}

		@SuppressWarnings("unchecked")
		@Override
		public Partitioned<A> copy() {
			return new Partitioned<A>(this.innerExpression.copy(), this.columns);
		}

		@Override
		public QueryableDatatype<?> getQueryableDatatypeForExpressionValue() {
			throw new UnsupportedOperationException("WindowFunctionRequiresOrderBy.Partitioned does not support getQueryableDatatypeForExpressionValue() yet.");
		}

		@Override
		public boolean isAggregator() {
			throw new UnsupportedOperationException("WindowFunctionRequiresOrderBy.Partitioned does not support isAggregator() yet.");
		}

		@Override
		public Set<DBRow> getTablesInvolved() {
			throw new UnsupportedOperationException("WindowFunctionRequiresOrderBy.Partitioned does not support getTablesInvolved() yet.");
		}

		@Override
		public boolean isPurelyFunctional() {
			boolean functional = innerExpression.isPurelyFunctional();
			if (functional == true) {
				for (ColumnProvider column : columns) {
					functional = functional && column.isPurelyFunctional();
				}
			}
			return functional;
		}

		@Override
		public boolean isComplexExpression() {
			throw new UnsupportedOperationException("WindowFunctionRequiresOrderBy.Partitioned does not support isComplexExpression() yet.");
		}

		@Override
		public String createSQLForFromClause(DBDatabase database) {
			throw new UnsupportedOperationException("WindowFunctionRequiresOrderBy.Partitioned does not support createSQLForFromClause() yet.");
		}

		@Override
		public String createSQLForGroupByClause(DBDatabase database) {
			throw new UnsupportedOperationException("WindowFunctionRequiresOrderBy.Partitioned does not support createSQLForGroupByClause() yet.");
		}

		@Override
		public boolean isWindowingFunction() {
			throw new UnsupportedOperationException("WindowFunctionRequiresOrderBy.Partitioned does not support isWindowingFunction() yet.");
		}

		@Override
		public A orderByWithPrimaryKeys(SortProvider... partitionFields) {
			throw new UnsupportedOperationException("WindowFunctionRequiresOrderBy.Partitioned does not support orderByWithPrimaryKeys() yet.");
		}

		@Override
		public A orderByWithPrimaryKeys(ColumnProvider... partitionFields) {
			return orderByWithPrimaryKeys(SortProvider.getSortProviders(partitionFields));
		}

	}

	public static class Sorted<A extends EqualExpression<?, ?, ?>> implements WindowingFunctionInterface.Sorted<A>, AnyResult<A> {

		private final WindowFunctionRequiresOrderBy.Partitioned<A> innerExpression;
		private final SortProvider[] sorts;

		public Sorted(WindowFunctionRequiresOrderBy.Partitioned<A> expression, SortProvider... sorts) {
			super();
			this.innerExpression = expression;
			this.sorts = sorts;
		}

		@Override
		public String toSQLString(DBDefinition defn) {
			StringBuilder orderByClause = new StringBuilder();
			if (getSorts().length > 0) {
				orderByClause.append(" ORDER BY ");
				String separator = "";
				for (SortProvider partitionByColumn : getSorts()) {
					orderByClause.append(separator).append(partitionByColumn.toSQLString(defn));
					separator = ", ";
				}
			}
			return getInnerExpression().toSQLString(defn) + orderByClause + ")";
		}

		@Override
		public A getRequiredExpression() {
			try {
				final Class<A> clazz = getInnerExpression().getRequiredExpressionClass();
				Constructor<?>[] constructors = clazz.getDeclaredConstructors();
				for (Constructor<?> constructor : constructors) {
					if (constructor.getParameterTypes().length == 1 && constructor.getParameterTypes()[0].equals(AnyResult.class)) {
						constructor.setAccessible(true);
						@SuppressWarnings("unchecked")
						A newInstance = (A) constructor.newInstance((AnyResult) this);
						return newInstance;
					}
				}
			} catch (SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
				Logger.getLogger(WindowFunctionRequiresOrderBy.class.getName()).log(Level.SEVERE, null, ex);
			}
			return null;
		}

		@Override
		public Class<A> getRequiredExpressionClass() {
			return getInnerExpression().getRequiredExpressionClass();
		}

		@SuppressWarnings("unchecked")
		@Override
		public Sorted<A> copy() {
			return new Sorted<A>(this.getInnerExpression().copy(), this.getSorts());
		}

		@Override
		public boolean isPurelyFunctional() {
			boolean functional = getInnerExpression().isPurelyFunctional();
			if (functional == true) {
				for (SortProvider sort : getSorts()) {
					functional = functional && sort.isPurelyFunctional();
				}
			}
			return functional;
		}

		@Override
		public QueryableDatatype<?> getQueryableDatatypeForExpressionValue() {
			throw new UnsupportedOperationException("WindowFunctionRequiresOrderBy.Sorted does not support getQueryableDatatypeForExpressionValue() yet.");
		}

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

		@Override
		public Set<DBRow> getTablesInvolved() {
			return getInnerExpression().getTablesInvolved();
		}

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

		@Override
		public String createSQLForFromClause(DBDatabase database) {
			throw new UnsupportedOperationException("WindowFunctionRequiresOrderBy.Sorted does not support createSQLForFromClause(DBDatabase) yet.");
		}

		@Override
		public String createSQLForGroupByClause(DBDatabase database) {
			throw new UnsupportedOperationException("WindowFunctionRequiresOrderBy.Sorted does not support createSQLForGroupByClause(DBDatabase) yet.");
		}

		@Override
		public boolean isWindowingFunction() {
			return true;
		}

		@Override
		public boolean getIncludesNull() {
			return true;
		}

		/**
		 * @return the innerExpression
		 */
		protected WindowFunctionRequiresOrderBy.Partitioned<A> getInnerExpression() {
			return innerExpression;
		}

		/**
		 * @return the sorts
		 */
		protected SortProvider[] getSorts() {
			return sorts;
		}
	}
}