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

net.avcompris.commons.query.impl.SqlWhereClause Maven / Gradle / Ivy

There is a newer version: 0.6.3
Show newest version
package net.avcompris.commons.query.impl;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Maps.newHashMap;
import static com.google.common.collect.Sets.newHashSet;
import static org.apache.commons.lang3.StringUtils.isBlank;

import java.lang.reflect.Field;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.Nullable;

import org.apache.commons.lang3.NotImplementedException;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;

import net.avcompris.commons.query.DateTimePrecision;
import net.avcompris.commons.query.Filtering;
import net.avcompris.commons.query.FilteringHandler;

public final class SqlWhereClause {

	private final String sql;

	private static SqlWhereClause EMPTY = new SqlWhereClause("");

	private SqlWhereClause(final String sql) {

		this.sql = checkNotNull(sql, "sql");
	}

	public static  SqlWhereClause build(@Nullable final Filtering filtering,
			final Class fieldClass) {

		return build(filtering, fieldClass, null);
	}

	public static  SqlWhereClause build(@Nullable final Filtering filtering,
			final Class fieldClass, @Nullable final String prefix) {

		if (filtering == null) {

			return EMPTY;
		}

		final SqlBuilder sqlBuilder = new SqlBuilder(fieldClass, //
				isBlank(prefix) ? "" : prefix);

		filtering.applyTo(sqlBuilder);

		return new SqlWhereClause(sqlBuilder.build());
	}

	/**
	 * @param prefix e.g. " WHERE", or " AND"
	 */
	public String getSQL(final String prefix) {

		checkNotNull(prefix, "prefix");

		return isBlank(sql) //
				? "" //
				: isBlank(prefix) //
						? sql //
						: prefix + " " + sql;
	}

	public void setParameters(final PreparedStatement pstmt, final int parmIndex) throws SQLException {

		checkNotNull(pstmt, "pstmt");
	}

	private static final class FieldSpec {

		public final String sqlName;

		public FieldSpec(final String sqlName) {

			this.sqlName = checkNotNull(sqlName, "sqlName");
		}
	}

	private static class SqlBuilder implements FilteringHandler {

		// private String sql = "";

		private static final Set> CLASSES_WITH_FIELD_DESCS = newHashSet();
		private static final Map FIELD_SPECS = newHashMap();

		private final String prefix;

		private final Class fieldClass;

		private String sqlClause = null;
		// private Type type = null;
		private SqlBuilder notMember = null;
		private final List> orMembers = new ArrayList<>();
		private final List> andMembers = new ArrayList<>();

		private SqlBuilder(final Class fieldClass, final String prefix) {

			this.fieldClass = checkNotNull(fieldClass, "fieldClass");
			this.prefix = checkNotNull(prefix, "prefix");

			checkArgument(fieldClass.isEnum(), //
					"fieldClass should be enum: %s", fieldClass.getName());

			if (!CLASSES_WITH_FIELD_DESCS.contains(fieldClass)) {

				for (final U enumValue : fieldClass.getEnumConstants()) {

					final Field field = FieldUtils.getEnumField(enumValue);

					final String sqlName = FieldUtils.extractSqlName(field);

					final FieldSpec fieldSpec = new FieldSpec(sqlName);

					FIELD_SPECS.put(enumValue, fieldSpec);
				}
			}
		}

		public String build() {

			// 1. VALIDATE STATE

			checkState(sqlClause != null || notMember != null || !orMembers.isEmpty() || !andMembers.isEmpty(), //
					"sqlClause: %s, orMembers: %s, andMembers: %s", sqlClause, orMembers.size(), andMembers.size());

			checkState(sqlClause == null || notMember == null, //
					"sqlClause: %s, orMembers: %s, andMembers: %s", sqlClause, orMembers.size(), andMembers.size());

			checkState(sqlClause == null && notMember == null || orMembers.isEmpty() && andMembers.isEmpty(), //
					"sqlClause: %s, orMembers: %s, andMembers: %s", sqlClause, orMembers.size(), andMembers.size());

			checkState(orMembers.isEmpty() || andMembers.isEmpty(), //
					"sqlClause: %s, orMembers: %s, andMembers: %s", sqlClause, orMembers.size(), andMembers.size());

			// 2. COMPOSE

			if (sqlClause != null) {

				return sqlClause;

			} else if (notMember != null) {

				final StringBuilder sb = new StringBuilder();

				sb.append("NOT(");

//				final boolean hasMembers = !notMember.andMembers.isEmpty() || !notMember.andMembers.isEmpty();

//				if (hasMembers) {
//					sb.append("(");
//				}

				sb.append(notMember.build());

//				if (hasMembers) {
				sb.append(")");
//				}

				return sb.toString();

			} else if (!orMembers.isEmpty()) {

				final StringBuilder sb = new StringBuilder();

				for (final SqlBuilder orMember : orMembers) {

					if (sb.length() != 0) {

						sb.append(" OR ");
					}

					final boolean hasMembers = true; // !orMember.andMembers.isEmpty() ||
														// !orMember.andMembers.isEmpty();

					if (hasMembers) {
						sb.append("(");
					}

					sb.append(orMember.build());

					if (hasMembers) {
						sb.append(")");
					}
				}

				return sb.toString();

			} else if (!andMembers.isEmpty()) {

				final StringBuilder sb = new StringBuilder();

				for (final SqlBuilder andMember : andMembers) {

					if (sb.length() != 0) {

						sb.append(" AND ");
					}

					final boolean hasMembers = true; // !andMember.andMembers.isEmpty() ||
														// !andMember.andMembers.isEmpty();

					if (hasMembers) {
						sb.append("(");
					}

					sb.append(andMember.build());

					if (hasMembers) {
						sb.append(")");
					}
				}

				return sb.toString();

			} else {

				throw new NotImplementedException("");
			}
		}

		@Override
		public void setTrue() {

			sqlClause = "1";
		}

		@Override
		public void setFalse() {

			sqlClause = "0";
		}

		// ======== STRING COMPARISONS

		@Override
		public void eq(final U field, @Nullable final String refValue) {

			checkNotNull(field, "field");

			if (refValue == null) {

				sqlClause = prefix + FIELD_SPECS.get(field).sqlName + " IS NULL";

			} else {

				sqlClause = prefix + FIELD_SPECS.get(field).sqlName + " = '" + refValue.replace("'", "''") + "'";
			}
		}

		@Override
		public void neq(final U field, @Nullable final String refValue) {

			checkNotNull(field, "field");

			if (refValue == null) {

				sqlClause = prefix + FIELD_SPECS.get(field).sqlName + " IS NOT NULL";

			} else {

				sqlClause = prefix + FIELD_SPECS.get(field).sqlName + " != '" + refValue.replace("'", "''") + "'";
			}
		}

		@Override
		public void contains(final U field, final String refValue) {

			checkNotNull(field, "field");
			checkNotNull(refValue, "refValue");

			sqlClause = prefix + FIELD_SPECS.get(field).sqlName + " LIKE '%" //
					+ refValue //
							.replace("'", "''") //
							.replace("\\", "\\\\") //
							.replace("%", "\\%") //
					+ "%'";
		}

		@Override
		public void doesntContain(final U field, final String refValue) {

			checkNotNull(field, "field");
			checkNotNull(refValue, "refValue");

			sqlClause = "NOT(" + FIELD_SPECS.get(field).sqlName + " LIKE '%" //
					+ refValue //
							.replace("'", "''") //
							.replace("\\", "\\\\") //
							.replace("%", "\\%") //
					+ "%')";
		}

		// ======== BOOLEAN COMPARISONS

		@Override
		public void eq(final U field, final boolean refValue) {

			checkNotNull(field, "field");

			sqlClause = prefix + FIELD_SPECS.get(field).sqlName + " = " + (refValue ? "TRUE" : "FALSE");
		}

		@Override
		public void neq(final U field, final boolean refValue) {

			checkNotNull(field, "field");

			sqlClause = prefix + FIELD_SPECS.get(field).sqlName + " != " + (refValue ? "TRUE" : "FALSE");
		}

		// ======== INT COMPARISONS

		@Override
		public void eq(final U field, final int refValue) {

			checkNotNull(field, "field");

			sqlClause = prefix + FIELD_SPECS.get(field).sqlName + " = " + refValue;
		}

		@Override
		public void neq(final U field, final int refValue) {

			checkNotNull(field, "field");

			sqlClause = prefix + FIELD_SPECS.get(field).sqlName + " != " + refValue;
		}

		@Override
		public void gte(final U field, final int refValue) {

			checkNotNull(field, "field");

			sqlClause = prefix + FIELD_SPECS.get(field).sqlName + " >= " + refValue;
		}

		@Override
		public void gt(final U field, final int refValue) {

			checkNotNull(field, "field");

			sqlClause = prefix + FIELD_SPECS.get(field).sqlName + " > " + refValue;
		}

		@Override
		public void lte(final U field, final int refValue) {

			checkNotNull(field, "field");

			sqlClause = prefix + FIELD_SPECS.get(field).sqlName + " <= " + refValue;
		}

		@Override
		public void lt(final U field, final int refValue) {

			checkNotNull(field, "field");

			sqlClause = prefix + FIELD_SPECS.get(field).sqlName + " < " + refValue;
		}

		// ======== DATETIME COMPARISONS

		private static String formatDateTimeField(final String fieldName, final DateTimePrecision precision) {

			checkNotNull(fieldName, "fieldName");
			checkNotNull(precision, "precision");

			switch (precision) {

			case DAY_OF_MONTH:
				return "DATE_TRUNC('day', " + fieldName + ")";

			case MINUTE:
				return "DATE_TRUNC('minute', " + fieldName + ")";

			case SECOND:
				return "DATE_TRUNC('second', " + fieldName + ")";

			case MILLISECOND:
				return "DATE_TRUNC('milliseconds', " + fieldName + ")";

			default:
				throw new NotImplementedException("precision: " + precision);
			}

		}

		private static String format(final DateTime refValue, final DateTimePrecision precision) {

			checkNotNull(refValue, "refValue");
			checkNotNull(precision, "precision");

			switch (precision) {

			case DAY_OF_MONTH:
				return "TO_TIMESTAMP('" //
						+ refValue.toString(DateTimeFormat.forPattern("yyyy-MM-dd")) //
						+ "', 'YYYY-MM-DD')";

			case MINUTE:
				return "TO_TIMESTAMP('" //
						+ refValue.toString(DateTimeFormat.forPattern("yyyy-MM-dd HH:mm")) //
						+ "', 'YYYY-MM-DD HH24:MI')";

			case SECOND:
				return "TO_TIMESTAMP('" //
						+ refValue.toString(DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss")) //
						+ "', 'YYYY-MM-DD HH24:MI:SS')";

			case MILLISECOND:
				return "TO_TIMESTAMP('" //
						+ refValue.toString(DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS")) //
						+ "', 'YYYY-MM-DD HH24:MI:SS.MS')";

			default:
				throw new NotImplementedException("precision: " + precision);
			}
		}

		@Override
		public void eq(final U field, @Nullable final DateTime refValue, final DateTimePrecision precision) {

			checkNotNull(field, "field");

			if (refValue == null) {

				sqlClause = prefix + FIELD_SPECS.get(field).sqlName + " IS NULL";

			} else {

				sqlClause = formatDateTimeField(FIELD_SPECS.get(field).sqlName, precision)

						+ " = " + format(refValue, precision);
			}
		}

		@Override
		public void neq(final U field, @Nullable final DateTime refValue, final DateTimePrecision precision) {

			checkNotNull(field, "field");

			if (refValue == null) {

				sqlClause = prefix + FIELD_SPECS.get(field).sqlName + " IS NOT NULL";

			} else {

				sqlClause = formatDateTimeField(FIELD_SPECS.get(field).sqlName, precision)

						+ " != " + format(refValue, precision);
			}
		}

		@Override
		public void gte(final U field, final DateTime refValue, final DateTimePrecision precision) {

			checkNotNull(field, "field");

			sqlClause = formatDateTimeField(FIELD_SPECS.get(field).sqlName, precision)

					+ " >= " + format(refValue, precision);
		}

		@Override
		public void gt(final U field, final DateTime refValue, final DateTimePrecision precision) {

			checkNotNull(field, "field");

			sqlClause = formatDateTimeField(FIELD_SPECS.get(field).sqlName, precision)

					+ " > " + format(refValue, precision);
		}

		@Override
		public void lte(final U field, final DateTime refValue, final DateTimePrecision precision) {

			checkNotNull(field, "field");

			sqlClause = formatDateTimeField(FIELD_SPECS.get(field).sqlName, precision)

					+ " <= " + format(refValue, precision);
		}

		@Override
		public void lt(final U field, final DateTime refValue, final DateTimePrecision precision) {

			checkNotNull(field, "field");

			sqlClause = formatDateTimeField(FIELD_SPECS.get(field).sqlName, precision)

					+ " < " + format(refValue, precision);
		}

		// ======== ENUM COMPARISONS

		@Override
		public void eq(final U field, final Enum refValue) {

			checkNotNull(field, "field");

			sqlClause = prefix + FIELD_SPECS.get(field).sqlName + " = '" + refValue.name() + "'";
		}

		@Override
		public void neq(final U field, final Enum refValue) {

			checkNotNull(field, "field");

			sqlClause = prefix + FIELD_SPECS.get(field).sqlName + " != '" + refValue.name() + "'";
		}

		// ======== STRUCTURAL

		@Override
		public SqlBuilder newNotMember() {

			final SqlBuilder notMember = new SqlBuilder(fieldClass, prefix);

			this.notMember = notMember;

			return notMember;
		}

		@Override
		public SqlBuilder newAndMember() {

			final SqlBuilder andMember = new SqlBuilder(fieldClass, prefix);

			andMembers.add(andMember);

			return andMember;
		}

		@Override
		public SqlBuilder newOrMember() {

			final SqlBuilder orMember = new SqlBuilder(fieldClass, prefix);

			orMembers.add(orMember);

			return orMember;
		}
	}
}