package jef.database;
import java.lang.reflect.Array;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import jef.common.Entry;
import jef.database.annotation.EasyEntity;
import jef.database.dialect.DatabaseDialect;
import jef.database.jsqlparser.JPQLConvert;
import jef.database.jsqlparser.JPQLSelectConvert;
import jef.database.jsqlparser.RemovedDelayProcess;
import jef.database.jsqlparser.SelectToCountWrapper;
import jef.database.jsqlparser.SqlFunctionlocalization;
import jef.database.jsqlparser.expression.JpqlDataType;
import jef.database.jsqlparser.expression.JpqlParameter;
import jef.database.jsqlparser.expression.Table;
import jef.database.jsqlparser.expression.operators.relational.Between;
import jef.database.jsqlparser.expression.operators.relational.EqualsTo;
import jef.database.jsqlparser.expression.operators.relational.ExpressionList;
import jef.database.jsqlparser.expression.operators.relational.GreaterThan;
import jef.database.jsqlparser.expression.operators.relational.GreaterThanEquals;
import jef.database.jsqlparser.expression.operators.relational.InExpression;
import jef.database.jsqlparser.expression.operators.relational.LikeExpression;
import jef.database.jsqlparser.expression.operators.relational.MinorThan;
import jef.database.jsqlparser.expression.operators.relational.MinorThanEquals;
import jef.database.jsqlparser.expression.operators.relational.NotEqualsTo;
import jef.database.jsqlparser.parser.ParseException;
import jef.database.jsqlparser.statement.delete.Delete;
import jef.database.jsqlparser.statement.insert.Insert;
import jef.database.jsqlparser.statement.select.Limit;
import jef.database.jsqlparser.statement.select.PlainSelect;
import jef.database.jsqlparser.statement.select.Select;
import jef.database.jsqlparser.statement.select.Union;
import jef.database.jsqlparser.statement.update.Update;
import jef.database.jsqlparser.visitor.Expression;
import jef.database.jsqlparser.visitor.SelectBody;
import jef.database.jsqlparser.visitor.Statement;
import jef.database.jsqlparser.visitor.VisitorAdapter;
import jef.database.meta.Feature;
import jef.database.meta.MetaHolder;
import jef.database.query.ParameterProvider;
import jef.database.query.ParameterProvider.MapProvider;
import jef.database.query.SqlExpression;
import jef.database.routing.sql.SqlAndParameter;
import jef.tools.ArrayUtils;
import jef.tools.StringUtils;
/**
* 描述一个命名查询的配置.
*
* <h3>什么是命名查询</h3>
* 命名查询即Named-Query,在Hibernate和JPA中都有相关的功能定义。简单来说,命名查询就是将查询语句(SQL,HQL,JPQL等)
* 事先编写好, 然后为其指定一个名称。<br>
* 在使用ORM框架时,取出事先解析好的查询,向其中填入绑定变量的参数,形成完整的查询。
*
* <h3>EF-ORM的命名查询和上述两种框架定义有什么不同</h3> EF-ORM也支持命名查询,机制和上述框架相似,具体有以下的不同。
* <ul>
* <li>命名查询默认定义在配置文件 named-queries.xml中。不支持使用Annotation等方法定义</li>
* <li>命名查询也可以定义在数据库表中,数据库表的名称可由用户配置</li>
* <li>命名查询可以支持 {@linkplain jef.database.NativeQuery E-SQL}
* 和JPQL两种语法(后者特性未全部实现,不推荐)</li>
* <li>由于支持E-SQL,命名查询可以实现动态SQL语句的功能,配置XML的配置功能,比较近似与IBatis的操作方式</li>
* </ul>
*
* <h3>使用示例</h3> 在named-queries.xml中配置
*
* <pre>
* <tt><query name = "testIn" type="sql" fetch-size="100" >
* <![CDATA[
* select * from person_table where id in (:names<int>)
* ]]>
* </query></tt>
* </pre>
*
* 上例中,:names就是一个绑定变量占位符。实际使用方式如下:
*
* <pre>
* <tt> ...
* Session session=getSession();
* NativeQuery<Person> query=session.createNamedQuery("testIn",Person.class);
* query.setParam("names",new String[]{"张三","李四","王五"});
* List<Person> persons=query.getResultList(); //相当于执行了 select * from person_table where id in ('张三','李四','王五')
* ...
* </tt>
* </pre>
*
* @author jiyi
* @see jef.database.NativeQuery
*
*/
@EasyEntity(checkEnhanced = false)
@Entity
@javax.persistence.Table(name = "NAMED_QUERIES")
public class NamedQueryConfig extends jef.database.DataObject {
private static final long serialVersionUID = 1L;
public static final int TYPE_SQL = 0;
public static final int TYPE_JPQL = 1;
@Id
@Column(name = "NAME")
private String name;
@Column(name = "SQL_TEXT", length = 4000)
private String rawsql;
/**
* 设置该命名查询的类型,是SQL,还是JPQL(TYPE_JPQL/TYPE_SQL)
*/
@Column(name = "TYPE", precision = 1)
private int type;
/**
* 标记
*/
@Column(name = "TAG")
private String tag;
/**
* 备注信息
*/
@Column(name = "REMARK")
private String remark;
/**
* fetchSize of Result.
*/
@Column(name = "FETCH_SIZE", precision = 6)
private int fetchSize;
private boolean fromDb = false;
private Map<DatabaseDialect, DialectCase> datas = new IdentityHashMap<DatabaseDialect, DialectCase>();;
public boolean isFromDb() {
return fromDb;
}
public void setFromDb(boolean fromDb) {
this.fromDb = fromDb;
}
private static final class DialectCase {
Statement statement;
jef.database.jsqlparser.statement.select.Select count;
Map<Object, ParameterMetadata> params;
RemovedDelayProcess delays;
public Limit countLimit;
}
static final class ParameterMetadata {
JpqlParameter param;
Object parent;
boolean escape;
ParameterMetadata(DatabaseDialect dialect, JpqlParameter p, Object parent) {
this.param = p;
this.parent = parent;
if (parent instanceof LikeExpression) {
if (dialect.has(Feature.NOT_SUPPORT_LIKE_ESCAPE)) {
escape = false;
((LikeExpression) parent).setEscape(null);
} else {
if (p.getDataType() != null && p.getDataType().ordinal() > 9) {
((LikeExpression) parent).setEscape("/");
escape = true;
}
}
}
}
final JpqlDataType getDataType() {
return param.getDataType();
}
}
/*
* 解析SQL语句,改写
*/
private static DialectCase analy(String sql, int type, OperateTarget db) throws SQLException {
final DatabaseDialect dialect = db.getProfile();
try {
Statement st = DbUtils.parseStatement(sql);
final Map<Object, ParameterMetadata> params = new HashMap<Object, ParameterMetadata>();
// Schema重定向处理:将SQL语句中的schema替换为映射后的schema
st.accept(new VisitorAdapter() {
@Override
public void visit(JpqlParameter param) {
params.put(param.getKey(), new ParameterMetadata(dialect, param, visitPath.getFirst()));
}
@Override
public void visit(Table table) {
String schema = table.getSchemaName();
if (schema != null) {
String newSchema = MetaHolder.getMappingSchema(schema);
if (newSchema != schema) {
table.setSchemaName(newSchema);
}
}
if (dialect.containKeyword(table.getName())) {
table.setName(DbUtils.escapeColumn(dialect, table.getName()));
}
}
@Override
public void visit(jef.database.jsqlparser.expression.Column c) {
String schema = c.getSchema();
if (schema != null) {
String newSchema = MetaHolder.getMappingSchema(schema);
if (newSchema != schema) {
c.setSchema(newSchema);
}
}
if (dialect.containKeyword(c.getColumnName())) {
c.setColumnName(DbUtils.escapeColumn(dialect, c.getColumnName()));
}
}
});
// 进行本地语言转化
SqlFunctionlocalization localization = new SqlFunctionlocalization(dialect, db);
st.accept(localization);
if (type == TYPE_JPQL){
if(st instanceof Select){
st.accept(new JPQLSelectConvert(dialect));
}else if(st instanceof Insert){
st.accept(new JPQLConvert(dialect));
}else if(st instanceof Update){
st.accept(new JPQLConvert(dialect));
}else if(st instanceof Delete){
st.accept(new JPQLConvert(dialect));
}
}
DialectCase result = new DialectCase();
result.statement = st;
result.params = params;
if (localization.delayLimit != null || localization.delayStartWith != null) {
result.delays = new RemovedDelayProcess(localization.delayLimit, localization.delayStartWith);
}
return result;
} catch (ParseException e) {
String message = e.getMessage();
int n = message.indexOf("Was expecting");
if (n > -1) {
message = message.substring(0, n);
}
throw new SQLException(StringUtils.concat("Parse error:", sql, "\n", message));
}
}
public NamedQueryConfig() {
};
public NamedQueryConfig(String name, String sql, boolean isJpql, int fetchSize) {
stopUpdate();
this.rawsql = sql;
this.name = name;
this.fetchSize = fetchSize;
this.type = isJpql ? TYPE_JPQL : TYPE_SQL;
}
/**
* 获得SQL语句中所有的参数和定义
*
* @param db
* @return
* @throws SQLException
*/
public Map<Object, ParameterMetadata> getParams(OperateTarget db) {
DialectCase dc;
try {
dc = getDialectCase(db);
} catch (SQLException e) {
throw DbUtils.toRuntimeException(e);
}
return dc.params;
}
/**
* 得到SQL和绑定参数
*
* @param db
* @param prov
* @return 要执行的语句和绑定变量列表
* @throws SQLException
*/
public SqlAndParameter getSqlAndParams(OperateTarget db, ParameterProvider prov) throws SQLException {
DialectCase dc = getDialectCase(db);
SqlAndParameter result = applyParam(dc.statement, prov);
result.setInMemoryClause(dc.delays);
return result;
}
private DialectCase getDialectCase(OperateTarget db) throws SQLException {
DatabaseDialect profile = db.getProfile();
if (datas == null) {
// 当使用testNamedQueryConfigedInDb案例时,由于使用Unsafe方式构造对象,故构造器方法未运行造成datas为null;
datas = new IdentityHashMap<DatabaseDialect, DialectCase>();
}
DialectCase dc = datas.get(profile);
if (dc == null) {
synchronized (datas) {
if ((dc = datas.get(profile)) == null) {
dc = analy(this.rawsql, this.type, db);
datas.put(profile, dc);
}
}
}
return dc;
}
/**
* 得到修改后的count语句和绑定参数 注意只有select语句能修改成count语句
*
* @param db
* @param prov
* @return
* @throws SQLException
*/
public SqlAndParameter getCountSqlAndParams(OperateTarget db, ParameterProvider prov) throws SQLException {
DialectCase dc = getDialectCase(db);
if (dc.count == null) {
if (dc.statement instanceof jef.database.jsqlparser.statement.select.Select) {
SelectBody oldBody = ((jef.database.jsqlparser.statement.select.Select) dc.statement).getSelectBody();
SelectToCountWrapper body = null;
if (oldBody instanceof PlainSelect) {
body = new SelectToCountWrapper((PlainSelect) oldBody, db.getProfile());
} else if (oldBody instanceof Union) {
body = new SelectToCountWrapper((Union) oldBody);
}
if (body == null) {
throw new SQLException("Can not generate count SQL statement for " + dc.statement.getClass().getName());
}
jef.database.jsqlparser.statement.select.Select ctst = new jef.database.jsqlparser.statement.select.Select();
ctst.setSelectBody(body);
dc.count = ctst;
if (dc.delays != null && dc.delays.limit != null) {
dc.countLimit = dc.delays.limit;
} else {
dc.countLimit = body.getRemovedLimit();
}
} else {
throw new IllegalArgumentException();
}
}
SqlAndParameter result = applyParam(dc.count, prov);
result.setInMemoryClause(dc.delays);
result.setLimit(dc.countLimit);
return result;
}
private final static class ParamApplier extends VisitorAdapter {
private ParameterProvider prov;
private List<Object> params;
public ParamApplier(ParameterProvider prov, List<Object> params) {
this.prov = prov;
this.params = params;
}
// 进行绑定变量匹配
@Override
public void visit(JpqlParameter param) {
Object value = null;
boolean contains;
if (param.isIndexParam()) {
value = prov.getIndexedParam(param.getIndex());
contains = prov.containsParam(param.getIndex());
} else {
value = prov.getNamedParam(param.getName());
contains = prov.containsParam(param.getName());
}
if (value instanceof SqlExpression) {
param.setResolved(((SqlExpression) value).getText());
} else if (value != null) {
if (value.getClass().isArray()) {
int size = Array.getLength(value);
if (value.getClass().getComponentType().isPrimitive()) {
value = ArrayUtils.toObject(value);
}
for (Object v : (Object[]) value) {
params.add(v);
}
param.setResolved(size);
} else if (value instanceof Collection) {
int size = ((Collection<?>) value).size();
for (Object v : (Collection<?>) value) {
params.add(v);
}
param.setResolved(size);
} else {
params.add(value);
param.setResolved(0);
}
} else if (contains) {
params.add(value);
param.setResolved(0);
} else {
param.setNotUsed();
}
}
@Override
public void visit(NotEqualsTo notEqualsTo) {
super.visit(notEqualsTo);
notEqualsTo.checkEmpty();
}
@Override
public void visit(InExpression inExpression) {
super.visit(inExpression);
inExpression.setEmpty(Boolean.FALSE);
if (inExpression.getItemsList() instanceof ExpressionList) {
ExpressionList list0 = (ExpressionList) inExpression.getItemsList();
List<Expression> list = list0.getExpressions();
if (list.size() == 1 && (list.get(0) instanceof JpqlParameter)) {
JpqlParameter p = (JpqlParameter) list.get(0);
if (p.resolvedCount() == -1) {
inExpression.setEmpty(Boolean.TRUE);
}
}
}
}
@Override
public void visit(Between between) {
super.visit(between);
if (between.getBetweenExpressionStart() instanceof JpqlParameter) {
JpqlParameter p = (JpqlParameter) between.getBetweenExpressionStart();
if (p.resolvedCount() == -1) {
between.setEmpty(Boolean.TRUE);
return;
}
}
if (between.getBetweenExpressionEnd() instanceof JpqlParameter) {
JpqlParameter p = (JpqlParameter) between.getBetweenExpressionEnd();
if (p.resolvedCount() == -1) {
between.setEmpty(Boolean.TRUE);
return;
}
}
between.setEmpty(Boolean.FALSE);
}
@Override
public void visit(EqualsTo equalsTo) {
super.visit(equalsTo);
equalsTo.checkEmpty();
}
@Override
public void visit(MinorThan minorThan) {
super.visit(minorThan);
minorThan.checkEmpty();
}
@Override
public void visit(MinorThanEquals minorThanEquals) {
super.visit(minorThanEquals);
minorThanEquals.checkEmpty();
}
@Override
public void visit(GreaterThan greaterThan) {
super.visit(greaterThan);
greaterThan.checkEmpty();
}
@Override
public void visit(GreaterThanEquals greaterThanEquals) {
super.visit(greaterThanEquals);
greaterThanEquals.checkEmpty();
}
@Override
public void visit(LikeExpression likeExpression) {
super.visit(likeExpression);
likeExpression.checkEmpty();
}
}
/**
* 在指定的SQL表达式中应用参数
*
* @param ex
* @param prov
* @return
*/
public static Entry<String, List<Object>> applyParam(Expression ex, MapProvider prov) {
final List<Object> params = new ArrayList<Object>();
ex.accept(new ParamApplier(prov, params));
return new Entry<String, List<Object>>(ex.toString(), params);
}
/*
* 返回应用参数后的查询
*/
public static SqlAndParameter applyParam(Statement st, final ParameterProvider prov) {
final List<Object> params = new ArrayList<Object>();
st.accept(new ParamApplier(prov, params));
return new SqlAndParameter(st, params, prov);
}
public String getName() {
return name;
}
public int getType() {
return type;
}
public String getTag() {
return tag;
}
public void setTag(String tag) {
this.tag = tag;
}
public String toString() {
if (this.datas.isEmpty()) {
return rawsql;
} else {
StringBuilder sb = new StringBuilder();
for (Map.Entry<DatabaseDialect, DialectCase> e : datas.entrySet()) {
DialectCase dc = e.getValue();
sb.append(e.getKey().getName()).append(":");
sb.append(dc.statement).append("\n");
}
return sb.toString();
}
}
public void setType(int type) {
this.type = type;
}
public String getRawsql() {
return rawsql;
}
public void setRawsql(String rawsql) {
this.rawsql = rawsql;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
public void setName(String name) {
this.name = name;
}
public int getFetchSize() {
return fetchSize;
}
public void setFetchSize(int fetchSize) {
this.fetchSize = fetchSize;
}
public enum Field implements jef.database.Field {
rawsql, name, type, tag, remark, fetchSize
}
}