All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.avaje.ebeaninternal.server.deploy.DRawSqlSelectBuilder Maven / Gradle / Ivy

package com.avaje.ebeaninternal.server.deploy;

import java.util.List;

import com.avaje.ebean.config.NamingConvention;
import com.avaje.ebeaninternal.server.querydefn.SimpleTextParser;

/**
 * Parses sql-select queries to try and determine the location where WHERE and HAVING
 * clauses can be added dynamically to the sql.
 */
public class DRawSqlSelectBuilder {

	public static final String $_AND_HAVING = "${andHaving}";
	
	public static final String $_HAVING = "${having}";

	public static final String $_AND_WHERE = "${andWhere}";

	public static final String $_WHERE = "${where}";

	private static final String ORDER_BY = "order by";

	private final BeanDescriptor desc;

	private final NamingConvention namingConvention;

	private final DRawSqlMeta meta;

	private final boolean debug;

	private String sql;

	private final SimpleTextParser textParser;

	private int placeHolderWhere;
	private int placeHolderAndWhere;
	private int placeHolderHaving;
	private int placeHolderAndHaving;
	private final boolean hasPlaceHolders;

	private int selectPos = -1;
	private int fromPos = -1;
	private int wherePos = -1;
	private int groupByPos = -1;
	private int havingPos = -1;
	private int orderByPos = -1;

	private boolean whereExprAnd;
	private int whereExprPos = -1;
	private boolean havingExprAnd;
	private int havingExprPos = -1;

	private final String tableAlias;
	
	public DRawSqlSelectBuilder(NamingConvention namingConvention, BeanDescriptor desc, DRawSqlMeta sqlSelectMeta) {

		this.namingConvention = namingConvention;
		this.desc = desc;
		this.tableAlias = sqlSelectMeta.getTableAlias();
		this.meta = sqlSelectMeta;
		this.debug = sqlSelectMeta.isDebug();
		this.sql = sqlSelectMeta.getQuery().trim();
		this.hasPlaceHolders = findAndRemovePlaceHolders();
		this.textParser = new SimpleTextParser(this.sql);
	}

	protected NamingConvention getNamingConvention() {
		return namingConvention;
	}

	protected BeanDescriptor getBeanDescriptor() {
		return desc;
	}
	
	protected boolean isDebug() {
		return debug;
	}

	protected void debug(String msg) {
		if (debug) {
			System.out.println("debug> " + msg);
		}
	}
	
	public DeployNamedQuery parse() {

		if (debug) {
			debug("");
			debug("Parsing sql-select in " + getErrName());
		}

    if (!hasPlaceHolders()) {
      // parse the sql for the keywords...
      // select, from, where, having, group by, order by
      parseSqlFindKeywords(true);
    }

    List selectColumns = findSelectColumns(meta.getColumnMapping());
		whereExprPos = findWhereExprPosition();
		havingExprPos = findHavingExprPosition();

		String preWhereExprSql = removeWhitespace(findPreWhereExprSql());
		String preHavingExprSql = removeWhitespace(findPreHavingExprSql());

		preWhereExprSql = trimSelectKeyword(preWhereExprSql);
		
		String orderBySql = findOrderBySql();	
		
		DRawSqlSelect rawSqlSelect = new DRawSqlSelect(desc, selectColumns, tableAlias, preWhereExprSql, 
				whereExprAnd, preHavingExprSql, havingExprAnd, orderBySql, meta);
		
		return new DeployNamedQuery(rawSqlSelect);
	}

	/**
	 * Find and remove the known place holders such as ${where}.
	 */
	private boolean findAndRemovePlaceHolders() {
		placeHolderWhere = removePlaceHolder($_WHERE);
		placeHolderAndWhere = removePlaceHolder($_AND_WHERE);
		placeHolderHaving = removePlaceHolder($_HAVING);
		placeHolderAndHaving = removePlaceHolder($_AND_HAVING);
		return hasPlaceHolders();
	}

	private int removePlaceHolder(String placeHolder) {
		int pos = sql.indexOf(placeHolder);
		if (pos > -1) {
			int after = pos + placeHolder.length() + 1;
			if (after > sql.length()) {
				sql = sql.substring(0, pos);
			} else {
				sql = sql.substring(0, pos) + sql.substring(after);
			}
		}
		return pos;
	}

	private boolean hasPlaceHolders() {
    return placeHolderWhere > -1 || placeHolderAndWhere > -1 || placeHolderHaving > -1 || placeHolderAndHaving > -1;
  }

	/**
	 * Trim off the select keyword (to support row_number() limit function).
	 */
	private String trimSelectKeyword(String preWhereExprSql) {

		if (preWhereExprSql.length() < 7){
			throw new RuntimeException("Expecting at least 7 chars in ["+preWhereExprSql+"]");
		}
		
		String select = preWhereExprSql.substring(0, 7);
		if (!select.equalsIgnoreCase("select ")){
			throw new RuntimeException("Expecting ["+preWhereExprSql+"] to start with \"select\"");
		}
		return preWhereExprSql.substring(7);
	}
	

	private String findOrderBySql() {
		if (orderByPos > -1) {
			int pos = orderByPos + ORDER_BY.length();
			return sql.substring(pos);
		}
		return null;
	}

	private String findPreHavingExprSql() {
		if (havingExprPos > whereExprPos) {
			// an order by clause follows...
			return sql.substring(whereExprPos, havingExprPos - 1);
		}
		if (whereExprPos > -1) {
			// the rest of the sql...
			return sql.substring(whereExprPos);
		}
		return null;
	}

	private String findPreWhereExprSql() {
		if (whereExprPos > -1) {
			return sql.substring(0, whereExprPos - 1);
		} else {
			return sql;
		}
	}

	protected String getErrName() {
		return "entity[" + desc.getFullName() + "] query[" + meta.getName() + "]";
	}

	/**
	 * Find the columns in the select clause including the table alias' and
	 * column alias.
	 */
	private List findSelectColumns(String selectClause) {

		if (selectClause == null || selectClause.trim().length() == 0) {
			if (hasPlaceHolders) {
				if (debug) {
					debug("... No explicit ColumnMapping, so parse the sql looking for SELECT and FROM keywords.");
				}
				parseSqlFindKeywords(false);
			}
			if (selectPos == -1 || fromPos == -1) {
				String msg = "Error in [" + getErrName() + "] parsing sql looking ";
				msg += "for SELECT and FROM keywords.";
				msg += " select:" + selectPos + " from:" + fromPos;
				msg += ".  You could use an explicit columnMapping to bypass this error.";
				throw new RuntimeException(msg);
			}
			selectPos += "select".length();
			selectClause = sql.substring(selectPos, fromPos);
		}

		selectClause = selectClause.trim();
		if (debug) {
			debug("ColumnMapping ... [" + selectClause + "]");
		}

		return new DRawSqlSelectColumnsParser(this,selectClause).parse();
	}

	private void parseSqlFindKeywords(boolean allKeywords) {

		debug("Parsing query looking for SELECT...");
		selectPos = textParser.findWordLower("select");
		if (selectPos == -1) {
			String msg = "Error in "+getErrName()+" parsing sql, can not find SELECT keyword in:";
			throw new RuntimeException(msg + sql);
		}
		debug("Parsing query looking for FROM... SELECT found at " + selectPos);
		fromPos = textParser.findWordLower("from");
		if (fromPos == -1) {
			String msg = "Error in "+getErrName()+" parsing sql, can not find FROM keyword in:";
			throw new RuntimeException(msg + sql);
		}

		if (!allKeywords) {
			return;
		}

		debug("Parsing query looking for WHERE... FROM found at " + fromPos);
		wherePos = textParser.findWordLower("where");
		if (wherePos == -1) {
			debug("Parsing query looking for GROUP... no WHERE found");
			groupByPos = textParser.findWordLower("group", fromPos + 5);
		} else {
			debug("Parsing query looking for GROUP... WHERE found at " + wherePos);
			groupByPos = textParser.findWordLower("group");
		}
		if (groupByPos > -1) {
			debug("Parsing query looking for HAVING... GROUP found at " + groupByPos);
			havingPos = textParser.findWordLower("having");
		}

		int startOrderBy = havingPos;
		if (startOrderBy == -1) {
			startOrderBy = groupByPos;
		}
		if (startOrderBy == -1) {
			startOrderBy = wherePos;
		}
		if (startOrderBy == -1) {
			startOrderBy = fromPos;
		}

		debug("Parsing query looking for ORDER... starting at " + startOrderBy);
		orderByPos = textParser.findWordLower("order", startOrderBy);
	}

	private int findWhereExprPosition() {
		if (hasPlaceHolders) {
			if (placeHolderWhere > -1) {
				return placeHolderWhere;
			} else {
				whereExprAnd = true;
				return placeHolderAndWhere;
			}
		}
		whereExprAnd = wherePos > 0;
		if (groupByPos > 0) {
			return groupByPos;
		}
		if (havingPos > 0) {
			return havingPos;
		}
		if (orderByPos > 0) {
			return orderByPos;
		}
		return -1;
	}

	private int findHavingExprPosition() {
		if (hasPlaceHolders) {
			if (placeHolderHaving > -1) {
				return placeHolderHaving;
			} else {
				havingExprAnd = true;
				return placeHolderAndHaving;
			}
		}
		havingExprAnd = havingPos > 0;
		if (orderByPos > 0) {
			return orderByPos;
		}
		return -1;
	}

	private String removeWhitespace(String sql) {
		if (sql == null) {
			return "";
		}

		boolean removeWhitespace = false;

		int length = sql.length();
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < length; i++) {
			char c = sql.charAt(i);
			if (removeWhitespace) {
				if (!Character.isWhitespace(c)) {
					sb.append(c);
					removeWhitespace = false;
				}
			} else {
				if (c == '\r' || c == '\n') {
					sb.append('\n');
					removeWhitespace = true;
				} else {
					sb.append(c);
				}
			}
		}

		return sb.toString();
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy