Select.java

package org.microspace.table.query.sql;

import java.io.Serializable;
import java.util.LinkedList;
import java.util.List;

import org.microspace.space.SimpleSpace;
import org.microspace.table.Entry;
import org.microspace.table.column.Accessor;
import org.microspace.table.column.ColumnReferences;
import org.microspace.table.column.GetSetPair;
import org.microspace.table.column.IndexedMap;
import org.microspace.table.query.SqlFormat;
import org.microspace.util.PojoUtil;

/**
 *
 * @author Gaspar Sinai - {@literal gaspar.sinai@microspace.org}
 * @version 2017-09-29
 * @param <T>
 *            The type of the Table.
 */
public class Select<T> implements Serializable {

	private static final long serialVersionUID = -8265165469283289385L;

	final Class<T> tableClass;
	final List<String> columnNames;
	final List<String> columnNamesLiteral;
	final List<String> columnNamesGiga;
	final boolean distinct;
	String nativeSqlQuery;
	String id;
	boolean topLevel;

	Expression<T> expression = null;
	RowNumSortExpression<T> rowNumSortExpression;
	
	List<Select<?>> innerSelects;
	
	static ThreadLocal<InnerSelectContext<?>> innerSelectContext = new ThreadLocal<> ();
	

	/**
	 * Select all fields,  SELECT * FROM ... SELECT DISTINCT
	 * 
	 * columnName1, columnName2
	 * 
	 * @param tableClass
	 *            The table class.
	 * @param distinct True if record merges occur.
	 */
	public Select(Class<T> tableClass, boolean distinct) {
		this.tableClass = tableClass;
		this.columnNames = new LinkedList<>();
		this.columnNamesGiga = new LinkedList<>();
		this.columnNamesLiteral = new LinkedList<>();
		this.distinct = distinct;
		rowNumSortExpression = new RowNumSortExpression<T>(tableClass);
	}
	
	public boolean isTopLevel () {
		return this.topLevel;
	}
	public void setInnerSelectContext (SimpleSpace space) {
		if (!topLevel) {
			return; 
		}
		InnerSelectContext<T> context = new InnerSelectContext<T>(space, tableClass);
		innerSelectContext.set(context);
		for (Select<?> inner : innerSelects) {
			context.putInnerSelect(inner);
		}
	}
	
	public void clearInnerSelectContext () {
		if (!topLevel) {
			return; 
		}
		innerSelectContext.set(null);
	}

	/**
	 * Finalize building the Select.
	 * @param nativeSqlQuery The text of this Query.
	 * @param innerSelects The list of inner selects in dependency order.
	 */
	public void finalize (String nativeSqlQuery, List<Select<?>> innerSelects) {
		//System.err.println("finalize: " + innerSelects);
		this.innerSelects = innerSelects;
		this.topLevel = (innerSelects != null);
		this.nativeSqlQuery = nativeSqlQuery;
		this.id = formatSqlQuery(SqlFormat.MICROSPACE);
	}
	
	void addColumn(String columnName, String columnEscape) {
		GetSetPair<T> pair = PojoUtil.getColumnGetSetPair(tableClass, columnName);
		if (pair == null) {
			throw new IllegalArgumentException("Unkown Column: " + columnName);
		}
		columnNames.add(columnName);
		columnNamesLiteral.add(columnEscape + columnName + columnEscape);
		columnNamesGiga.add("`" + columnName + "`");
	}

	List<String> getColumnNames() {
		return columnNames;
	}

	public Class<T> getTableClass() {
		return tableClass;
	}
	
	public IndexedMap<Object, Entry<T>> apply(Accessor<T> accessor, IndexedMap<Object, Entry<T>> entries,
			ColumnReferences<T>[] indexedColumns) {
		if (expression != null) {
			InnerSelectContext<?> context = innerSelectContext.get();
			entries = expression.apply(accessor, entries, indexedColumns, context);
		}
		return entries;
	}
	
	public List<Entry<T>> sortAndLimit (Accessor<T> accessor, IndexedMap<Object, Entry<T>> entries) {
		return rowNumSortExpression.sortApply(accessor, entries, columnNames, distinct);
	}

	public boolean match(T object, Accessor<T> accessor) {
		if (expression == null)
			return true;
		return expression.match(object, accessor);
	}

	public void setExpression(Expression<T> expression) {
		this.expression = expression;
	}

	public void setRowNumSortExpression(RowNumSortExpression<T> rowNumSortExpression) {
		this.rowNumSortExpression = rowNumSortExpression;
	}
	
	public Expression<T> getExpression() {
		return expression;
	}

	public RowNumSortExpression<T> getRowNumSortExpression() {
		return rowNumSortExpression;
	}

	public String formatSqlQuery(SqlFormat format) {
		if (format == SqlFormat.NATIVE) {
			return nativeSqlQuery;
		}
		if (format == SqlFormat.GIGASPACES || format == SqlFormat.GIGASPACES_LITERAL) {
			StringBuilder ret = new StringBuilder();
			// inner select.
			StringBuilder cols = new StringBuilder();
			if (!topLevel) {
				ret.append("SELECT");

				if (columnNames.size() == 0) {
					ret.prefix(" ", "*");
					cols.append("*");
				} else {
					if (format == SqlFormat.GIGASPACES_LITERAL) {
						cols.join(", ", columnNamesLiteral);
					} else {
						cols.join(", ", columnNamesGiga);
					}
					ret.prefix(" ", cols.toString());
				}
				ret.join(" ", " FROM", tableClass.getName());

			}

			if (distinct) {
				ret.prefix(" ", cols.toString());
			}
			
			StringBuilder where = new StringBuilder();
			String sqlQuery = expression == null ? "" : expression.formatSqlQuery(format);
			where.join(" AND ", sqlQuery, rowNumSortExpression.formatRowNumString(format));
			if (topLevel) {
				ret.prefix(" ", where.toString());
			} else {
				ret.prefix(" WHERE ", where.toString());
			}
			if (distinct) {
				ret.prefix(" ", "GROUP BY");
				if (columnNames.size() == 0) {
					// Should not happen
					ret.prefix(" ", "*");
				} else {
					StringBuilder cn = new StringBuilder();
					if (format == SqlFormat.GIGASPACES_LITERAL) {
						cn.join(", ", columnNamesLiteral);
					} else {
						cn.join(", ", columnNamesGiga);
					}
					ret.prefix(" ", cn.toString());
				}
			}
			
			ret.prefix(" ", rowNumSortExpression.formatCmpString(format));
			return ret.toString();
		} else if (format == SqlFormat.MICROSPACE) {
			StringBuilder ret = new StringBuilder();

			ret.append("SELECT");
			if (distinct) {
				ret.prefix(" ", "DISTINCT");
			}
			if (columnNames.size() == 0) {
				ret.prefix(" ", "*");
			} else {
				StringBuilder cn = new StringBuilder();
				cn.join(", ", columnNamesLiteral);
				ret.prefix(" ", cn.toString());
			}
			ret.join(" ", " FROM", tableClass.getName());
			StringBuilder where = new StringBuilder();
			String sqlQuery = expression == null ? "" : expression.formatSqlQuery(format);
			where.join(" AND ", 
					sqlQuery, rowNumSortExpression.formatRowNumString(format));
			ret.prefix(" WHERE ", where.toString());

			ret.prefix(" ", rowNumSortExpression.formatCmpString(format));
			return ret.toString();

		} else {
			throw new IllegalArgumentException("Unkown SqlFormat " + format);
		}

	}
	
	public String getId () {
		return id;
	}
	
	public int hasCode () {
		return getId().hashCode();
	}
	
	public boolean equals (Object o) {
		if (! (o instanceof Select)) {
			return false;
		}
		@SuppressWarnings("unchecked")
		Select<T> sel = (Select<T>) o;
		return getId().equals(sel.getId());
	}
}