package com.ctrip.platform.dal.dao.sqlbuilder;
import java.sql.SQLException;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import com.ctrip.platform.dal.common.enums.DatabaseCategory;
import com.ctrip.platform.dal.dao.DalHintEnum;
import com.ctrip.platform.dal.dao.DalHints;
import com.ctrip.platform.dal.dao.DalResultSetExtractor;
import com.ctrip.platform.dal.dao.DalRowMapper;
import com.ctrip.platform.dal.dao.ResultMerger;
import com.ctrip.platform.dal.dao.StatementParameters;
import com.ctrip.platform.dal.dao.helper.DalFirstResultMerger;
import com.ctrip.platform.dal.dao.helper.DalListMerger;
import com.ctrip.platform.dal.dao.helper.DalObjectRowMapper;
import com.ctrip.platform.dal.dao.helper.DalRangedResultMerger;
import com.ctrip.platform.dal.dao.helper.DalRowMapperExtractor;
import com.ctrip.platform.dal.dao.helper.DalSingleResultExtractor;
import com.ctrip.platform.dal.dao.helper.DalSingleResultMerger;
import com.ctrip.platform.dal.dao.helper.SupportPartialResultMapping;
/**
* This builder is only for internal use of DalTableDao
* @author jhhe
*
*/
public class BaseTableSelectBuilder implements TableSelectBuilder {
private static final String ALL_COLUMNS = "*";
private static final String COUNT = "COUNT(1)";
private static final String SPACE = " ";
private static final String ORDER_BY = "ORDER BY ";
private static final String ASC = " ASC";
private static final String DESC = " DESC";
private static final String ORDER_BY_SEPARATOR = ", ";
private static final String QUERY_ALL_CRITERIA = "1=1";
/**
* 对于select first,会在语句中追加limit 0,1(MySQL)或者top 1(SQL Server):
* @return
*/
private String tableName;
private DatabaseCategory dbCategory;
private String[] selectedColumns;
private String customized;
private String whereClause;
private Map<String, Boolean> orderBys = new LinkedHashMap<>();
private StatementParameters parameters;
private DalRowMapper mapper;
private ResultMerger merger;
private DalResultSetExtractor extractor;
private boolean requireFirst = false;
private boolean requireSingle = false;
private boolean nullable = false;
private int count;
private int start;
public BaseTableSelectBuilder() {
selectAll();
}
public BaseTableSelectBuilder(String tableName, DatabaseCategory dbCategory) throws SQLException {
this();
from(tableName).setDatabaseCategory(dbCategory);
}
public BaseTableSelectBuilder from(String tableName) throws SQLException {
if(tableName ==null || tableName.isEmpty())
throw new SQLException("table name is illegal.");
this.tableName = tableName;
return this;
}
public BaseTableSelectBuilder setDatabaseCategory(DatabaseCategory dbCategory) throws SQLException {
Objects.requireNonNull(dbCategory, "DatabaseCategory can't be null.");
this.dbCategory = dbCategory;
return this;
}
public BaseTableSelectBuilder select(String... selectedColumns) {
this.selectedColumns = selectedColumns;
customized = null;
return this;
}
public BaseTableSelectBuilder selectAll() {
this.customized = ALL_COLUMNS;
selectedColumns = null;
return this;
}
public BaseTableSelectBuilder selectCount() {
this.customized = COUNT;
selectedColumns = null;
mergerWith(new LongNumberSummary());
requireSingle();
return this.simpleType();
}
public BaseTableSelectBuilder where(String whereClause) {
whereClause = whereClause.trim();
this.whereClause = whereClause.length() == 0 ? QUERY_ALL_CRITERIA : whereClause;
return this;
}
public BaseTableSelectBuilder orderBy(String orderBy, boolean ascending) {
orderBys.put(orderBy, ascending);
return this;
}
public BaseTableSelectBuilder with(StatementParameters parameters) {
this.parameters = parameters;
return this;
}
public <T> BaseTableSelectBuilder mapWith(DalRowMapper<T> mapper) {
this.mapper = mapper;
return this;
}
public <T> BaseTableSelectBuilder mapWith(Class<T> type) {
return mapWith(new DalObjectRowMapper(type));
}
public BaseTableSelectBuilder simpleType() {
return mapWith(new DalObjectRowMapper());
}
public String build() {
return internalBuild(tableName);
}
@Override
public String build(String shardStr) {
return internalBuild(tableName + shardStr);
}
private String internalBuild(String effectiveTableName) {
effectiveTableName = wrapField(effectiveTableName);
if(requireFirst)
return buildFirst(effectiveTableName);
if(start == 0 && count > 0)
return buildTop(effectiveTableName);
if(start > 0 && count > 0)
return buildPage(effectiveTableName);
return buildList(effectiveTableName);
}
private String getCompleteWhereExp() {
return orderBys.size() == 0 ? whereClause : whereClause + SPACE + buildOrderbyExp();
}
private String buildOrderbyExp(){
StringBuilder orderbyExp = new StringBuilder();
orderbyExp.append(ORDER_BY);
boolean first = true;
for(String orderBy: orderBys.keySet()) {
if(first)
first = false;
else
orderbyExp.append(ORDER_BY_SEPARATOR);
orderbyExp.append(wrapField(orderBy));
orderbyExp.append(orderBys.get(orderBy) ? ASC: DESC);
}
return orderbyExp.toString();
}
/**
* 对字段进行包裹,数据库是MySQL则用 `进行包裹,数据库是SqlServer则用[]进行包裹
* @param fieldName
* @return
*/
public String wrapField(String fieldName){
return AbstractSqlBuilder.wrapField(dbCategory, fieldName);
}
@Override
public <T> BaseTableSelectBuilder mergerWith(ResultMerger<T> merger) {
this.merger = merger;
return this;
}
@Override
public <T> BaseTableSelectBuilder extractorWith(DalResultSetExtractor<T> extractor) {
this.extractor = extractor;
return this;
}
public <T> ResultMerger<T> getResultMerger(DalHints hints){
if(hints.is(DalHintEnum.resultMerger))
return (ResultMerger<T>)hints.get(DalHintEnum.resultMerger);
if(merger != null)
return merger;
if(isRequireSingle() || isRequireFirst())
return isRequireSingle() ? new DalSingleResultMerger() : new DalFirstResultMerger((Comparator)hints.getSorter());
return count > 0 ? new DalRangedResultMerger((Comparator)hints.getSorter(), count): new DalListMerger((Comparator)hints.getSorter());
}
public <T> DalResultSetExtractor<T> getResultExtractor(DalHints hints) throws SQLException {
if(extractor != null)
return extractor;
DalRowMapper<T> mapper = checkAllowPartial(hints);
if(isRequireSingle() || isRequireFirst())
return new DalSingleResultExtractor<>(mapper, isRequireSingle());
return count > 0 ? new DalRowMapperExtractor(mapper, count) : new DalRowMapperExtractor(mapper);
}
private <T> DalRowMapper<T> checkAllowPartial(DalHints hints) throws SQLException {
if(!(mapper instanceof SupportPartialResultMapping))
return mapper;
// If it is COUNT case, we do nothing here
if(customized == COUNT)
return mapper;
if(customized == ALL_COLUMNS && !hints.is(DalHintEnum.partialQuery))
return mapper;
String[] queryColumns = hints.is(DalHintEnum.partialQuery) ? hints.getPartialQueryColumns() : selectedColumns;
select(queryColumns);
//It is partial query, we need to create another mapper for this.
return ((SupportPartialResultMapping)mapper).mapWith(queryColumns, hints.is(DalHintEnum.ignoreMissingFields));
}
private String buildFirst(String effectiveTableName){
count = 1;
return buildTop(effectiveTableName);
}
private String buildTop(String effectiveTableName){
return dbCategory.buildTop(effectiveTableName, buildColumns(), getCompleteWhereExp(), count);
}
private String buildPage(String effectiveTableName){
return dbCategory.buildPage(effectiveTableName, buildColumns(), getCompleteWhereExp(), start, count);
}
private String buildList(String effectiveTableName){
return dbCategory.buildList(effectiveTableName, buildColumns(), getCompleteWhereExp());
}
private String buildColumns() {
if(customized != null)
return customized;
if(selectedColumns != null) {
StringBuilder fieldBuf = new StringBuilder();
for(int i=0, count= selectedColumns.length; i < count; i++){
fieldBuf.append(this.wrapField(selectedColumns[i]));
if(i<count-1){
fieldBuf.append(", ");
}
}
return fieldBuf.toString();
}
// This will be an exceptional case
return SPACE;
}
@Override
public StatementParameters buildParameters() {
return parameters;
}
@Override
public String getTableName() {
return tableName;
}
public BaseTableSelectBuilder requireFirst() {
requireFirst = true;
return this;
}
public BaseTableSelectBuilder requireSingle() {
requireSingle = true;
return this;
}
public BaseTableSelectBuilder nullable() {
nullable = true;
return this;
}
public boolean isRequireFirst () {
return requireFirst;
}
public boolean isRequireSingle() {
return requireSingle;
}
public boolean isNullable() {
return nullable;
}
public BaseTableSelectBuilder top(int count) {
this.count = count;
return this;
}
public BaseTableSelectBuilder range(int start, int count) {
this.start = start;
this.count = count;
return this;
}
public DatabaseCategory getDbCategory() {
return dbCategory;
}
public BaseTableSelectBuilder atPage(int pageNo, int pageSize) throws SQLException {
if(pageNo < 1 || pageSize < 1)
throw new SQLException("Illigal pagesize or pageNo, please check");
range((pageNo - 1) * pageSize, pageSize);
return this;
}
private static class LongNumberSummary implements ResultMerger<Number>{
private long total;
@Override
public void addPartial(String shard, Number partial) {
total += partial.longValue();
}
@Override
public Number merge() {
return total;
}
}
}