diff --git a/src/main/java/com/alibaba/druid/mapping/DruidMappingException.java b/src/main/java/com/alibaba/druid/mapping/DruidMappingException.java new file mode 100644 index 0000000000..4b33c53dec --- /dev/null +++ b/src/main/java/com/alibaba/druid/mapping/DruidMappingException.java @@ -0,0 +1,23 @@ +package com.alibaba.druid.mapping; + +public class DruidMappingException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public DruidMappingException(){ + super(); + } + + public DruidMappingException(String message, Throwable cause){ + super(message, cause); + } + + public DruidMappingException(String message){ + super(message); + } + + public DruidMappingException(Throwable cause){ + super(cause); + } + +} diff --git a/src/main/java/com/alibaba/druid/mapping/Entity.java b/src/main/java/com/alibaba/druid/mapping/Entity.java index 6156dab3ab..88c639d1e8 100644 --- a/src/main/java/com/alibaba/druid/mapping/Entity.java +++ b/src/main/java/com/alibaba/druid/mapping/Entity.java @@ -1,15 +1,24 @@ package com.alibaba.druid.mapping; -import java.util.ArrayList; -import java.util.List; +import java.util.LinkedHashMap; +import java.util.Map; public class Entity { - private String name; - private String description; - private String tableName; + private String name; + private String description; + private String tableName; - private List properties = new ArrayList(); + private Map properties = new LinkedHashMap(); + + public Entity(){ + } + + public Entity(String name, String description, String tableName){ + this.name = name; + this.description = description; + this.tableName = tableName; + } public String getName() { return name; @@ -35,11 +44,30 @@ public class Entity { this.tableName = tableName; } - public List getProperties() { + public void addProperty(Property property) { + this.properties.put(property.getName(), property); + } + + public Property getProperty(String name) { + Property prop = this.properties.get(name); + + if (prop == null) { + for (Map.Entry entry : properties.entrySet()) { + if (entry.getKey().equalsIgnoreCase(name)) { + prop = entry.getValue(); + break; + } + } + } + + return prop; + } + + public Map getProperties() { return properties; } - public void setProperties(List properties) { + public void setProperties(Map properties) { this.properties = properties; } diff --git a/src/main/java/com/alibaba/druid/mapping/EntityMapingMySqlVisitor.java b/src/main/java/com/alibaba/druid/mapping/EntityMapingMySqlVisitor.java index 432058adf6..dcf6ac7aef 100644 --- a/src/main/java/com/alibaba/druid/mapping/EntityMapingMySqlVisitor.java +++ b/src/main/java/com/alibaba/druid/mapping/EntityMapingMySqlVisitor.java @@ -1,18 +1,150 @@ package com.alibaba.druid.mapping; +import java.util.LinkedHashMap; +import java.util.Map; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLAllColumnExpr; +import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; +import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; +import com.alibaba.druid.sql.ast.statement.SQLSelectItem; +import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock; +import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlSelectQueryBlock; import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlASTVisitorAdapter; public class EntityMapingMySqlVisitor extends MySqlASTVisitorAdapter { - private final Entity entity; + private final LinkedHashMap entities; - public EntityMapingMySqlVisitor(Entity entity){ - super(); - this.entity = entity; + public EntityMapingMySqlVisitor(LinkedHashMap entities){ + this.entities = entities; } - public Entity getEntity() { + public Map getEntities() { + return entities; + } + + public Entity getFirstEntity() { + for (Map.Entry entry : entities.entrySet()) { + return entry.getValue(); + } + + return null; + } + + public Entity getEntity(String name) { + Entity entity = this.entities.get(name); + + if (entity == null) { + for (Map.Entry entry : entities.entrySet()) { + if (entry.getKey().equalsIgnoreCase(name)) { + entity = entry.getValue(); + break; + } + } + } + return entity; } + public boolean visit(SQLSelectItem x) { + x.getExpr().setParent(x); + return true; + } + + public boolean visit(MySqlSelectQueryBlock x) { + if (x.getSelectList().size() == 0) { + fillSelectList(x); + } + + if (x.getSelectList().size() == 1) { + if (x.getSelectList().get(0).getExpr() instanceof SQLAllColumnExpr) { + x.getSelectList().clear(); + fillSelectList(x); + } + } + + if (x.getFrom() == null) { + Entity firstEntity = this.getFirstEntity(); + SQLExprTableSource from = new SQLExprTableSource(new SQLIdentifierExpr(firstEntity.getName())); + x.setFrom(from); + } + + for (SQLSelectItem item : x.getSelectList()) { + item.setParent(x); + } + + return super.visit(x); + } + + public boolean visit(SQLSelectQueryBlock x) { + if (x instanceof MySqlSelectQueryBlock) { + return visit((MySqlSelectQueryBlock) x); + } + + if (x.getSelectList().size() == 0) { + fillSelectList(x); + } + + for (SQLSelectItem item : x.getSelectList()) { + item.setParent(x); + } + + return super.visit(x); + } + + protected void fillSelectList(SQLSelectQueryBlock x) { + Entity entity = getFirstEntity(); + + for (Property item : entity.getProperties().values()) { + x.getSelectList().add(new SQLSelectItem(new SQLIdentifierExpr(item.getName()), '"' + item.getName() + '"')); + } + } + + public boolean visit(SQLIdentifierExpr x) { + String propertyName = x.getName(); + + Property property = null; + for (Entity entity : this.entities.values()) { + property = entity.getProperty(propertyName); + if (property != null) { + break; + } + } + + if (property == null) { + throw new DruidMappingException("property not found : " + propertyName); + } + + String dbColumName = property.getDbColumnName(); + x.setName(dbColumName); + + if (x.getParent() instanceof SQLSelectItem) { + SQLSelectItem selectItem = (SQLSelectItem) x.getParent(); + if (selectItem.getAlias() == null) { + selectItem.setAlias('"' + property.getName() + '"'); + } + } + + return false; + } + + public boolean visit(SQLExprTableSource x) { + SQLExpr expr = x.getExpr(); + + if (expr instanceof SQLIdentifierExpr) { + SQLIdentifierExpr tableExpr = (SQLIdentifierExpr) expr; + String entityName = tableExpr.getName(); + + Entity entity = this.getEntity(entityName); + + if (entity == null) { + throw new DruidMappingException("entity not foudn : " + entityName); + } + + tableExpr.setName(entity.getTableName()); + } + + return false; + } } diff --git a/src/main/java/com/alibaba/druid/mapping/MappingEngine.java b/src/main/java/com/alibaba/druid/mapping/MappingEngine.java new file mode 100644 index 0000000000..b136315ae2 --- /dev/null +++ b/src/main/java/com/alibaba/druid/mapping/MappingEngine.java @@ -0,0 +1,63 @@ +package com.alibaba.druid.mapping; + +import java.util.LinkedHashMap; + +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.expr.SQLNumberExpr; +import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlSelectQueryBlock; +import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlSelectQueryBlock.Limit; +import com.alibaba.druid.sql.dialect.mysql.parser.MySqlSelectParser; + +public class MappingEngine { + + private LinkedHashMap entities = new LinkedHashMap(); + private Integer maxLimit; + + public Integer getMaxLimit() { + return maxLimit; + } + + public void setMaxLimit(Integer maxLimit) { + this.maxLimit = maxLimit; + } + + public LinkedHashMap getEntities() { + return entities; + } + + public void addEntity(Entity entity) { + this.entities.put(entity.getName(), entity); + } + + public EntityMapingMySqlVisitor createMappingVisitor() { + return new EntityMapingMySqlVisitor(entities); + } + + public String explain(String sql) { + MySqlSelectQueryBlock query = explainToSQLObject(sql); + + if (this.maxLimit != null) { + if (query.getLimit() == null) { + Limit limit = new Limit(); + limit.setRowCount(new SQLNumberExpr(maxLimit)); + query.setLimit(limit); + } else { + SQLNumberExpr rowCountExpr = (SQLNumberExpr) query.getLimit().getRowCount(); + int rowCount = rowCountExpr.getNumber().intValue(); + if (rowCount > maxLimit.intValue()) { + rowCountExpr.setNumber(maxLimit); + } + } + } + + query.accept(this.createMappingVisitor()); + + return SQLUtils.toMySqlString(query); + } + + public MySqlSelectQueryBlock explainToSQLObject(String sql) { + MySqlSelectParser selectParser = new MySqlSelectParser(sql); + MySqlSelectQueryBlock query = (MySqlSelectQueryBlock) selectParser.query(); + return query; + } +} diff --git a/src/main/java/com/alibaba/druid/mapping/Property.java b/src/main/java/com/alibaba/druid/mapping/Property.java index 9446b5b844..d2c28288f0 100644 --- a/src/main/java/com/alibaba/druid/mapping/Property.java +++ b/src/main/java/com/alibaba/druid/mapping/Property.java @@ -3,8 +3,18 @@ package com.alibaba.druid.mapping; public class Property { private String name; + private String desciption; private String dbColumnName; + public Property(){ + } + + public Property(String name, String desciption, String dbColumnName){ + this.name = name; + this.desciption = desciption; + this.dbColumnName = dbColumnName; + } + public String getName() { return name; } @@ -21,4 +31,12 @@ public class Property { this.dbColumnName = dbColumnName; } + public String getDesciption() { + return desciption; + } + + public void setDesciption(String desciption) { + this.desciption = desciption; + } + } diff --git a/src/main/java/com/alibaba/druid/sql/SQLUtils.java b/src/main/java/com/alibaba/druid/sql/SQLUtils.java new file mode 100644 index 0000000000..a8525bd99d --- /dev/null +++ b/src/main/java/com/alibaba/druid/sql/SQLUtils.java @@ -0,0 +1,34 @@ +package com.alibaba.druid.sql; + +import com.alibaba.druid.sql.ast.SQLObject; +import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlOutputVisitor; +import com.alibaba.druid.sql.dialect.oracle.visitor.OracleOutputVisitor; +import com.alibaba.druid.sql.dialect.postgresql.visitor.PGOutputVisitor; + +public class SQLUtils { + + public static String toMySqlString(SQLObject sqlObject) { + StringBuilder out = new StringBuilder(); + sqlObject.accept(new MySqlOutputVisitor(out)); + + String sql = out.toString(); + return sql; + } + + public static String toOracleString(SQLObject sqlObject) { + StringBuilder out = new StringBuilder(); + sqlObject.accept(new OracleOutputVisitor(out)); + + String sql = out.toString(); + return sql; + } + + public static String toPGString(SQLObject sqlObject) { + StringBuilder out = new StringBuilder(); + sqlObject.accept(new PGOutputVisitor(out)); + + String sql = out.toString(); + return sql; + } + +} diff --git a/src/main/java/com/alibaba/druid/sql/ast/statement/SQLExprTableSource.java b/src/main/java/com/alibaba/druid/sql/ast/statement/SQLExprTableSource.java index 6f7ad00423..ac21dc28b0 100644 --- a/src/main/java/com/alibaba/druid/sql/ast/statement/SQLExprTableSource.java +++ b/src/main/java/com/alibaba/druid/sql/ast/statement/SQLExprTableSource.java @@ -27,8 +27,10 @@ public class SQLExprTableSource extends SQLTableSourceImpl { public SQLExprTableSource(){ } - - + + public SQLExprTableSource(SQLExpr expr){ + this.expr = expr; + } public SQLExpr getExpr() { return this.expr; diff --git a/src/main/java/com/alibaba/druid/sql/dialect/mysql/visitor/MySqlMappingVisitor.java b/src/main/java/com/alibaba/druid/sql/dialect/mysql/visitor/MySqlMappingVisitor.java deleted file mode 100644 index d4859c4628..0000000000 --- a/src/main/java/com/alibaba/druid/sql/dialect/mysql/visitor/MySqlMappingVisitor.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.alibaba.druid.sql.dialect.mysql.visitor; - -import java.util.HashMap; -import java.util.Map; - -import com.alibaba.druid.sql.ast.SQLExpr; - -public class MySqlMappingVisitor extends MySqlASTVisitorAdapter { - - private final Map mapping = new HashMap(); - - public void addMapping(String column, String targetExpr) { - - } - -} diff --git a/src/main/java/com/alibaba/druid/sql/visitor/SQLASTOutputVisitor.java b/src/main/java/com/alibaba/druid/sql/visitor/SQLASTOutputVisitor.java index 22c41c8f37..001f2e3775 100644 --- a/src/main/java/com/alibaba/druid/sql/visitor/SQLASTOutputVisitor.java +++ b/src/main/java/com/alibaba/druid/sql/visitor/SQLASTOutputVisitor.java @@ -85,14 +85,22 @@ import com.alibaba.druid.sql.ast.statement.SQLUpdateStatement; public class SQLASTOutputVisitor extends SQLASTVisitorAdapter { - protected Appendable appender; - private String indent = "\t"; - private int indentCount = 0; + protected final Appendable appender; + private String indent = "\t"; + private int indentCount = 0; public SQLASTOutputVisitor(Appendable appender){ this.appender = appender; } + public int getIndentCount() { + return indentCount; + } + + public Appendable getAppender() { + return appender; + } + public void decrementIndent() { this.indentCount -= 1; } @@ -213,8 +221,8 @@ public class SQLASTOutputVisitor extends SQLASTVisitorAdapter { if (x.getLeft() instanceof SQLBinaryOpExpr) { SQLBinaryOpExpr left = (SQLBinaryOpExpr) x.getLeft(); boolean leftRational = left.getOperator() == SQLBinaryOperator.BooleanAnd - || left.getOperator() == SQLBinaryOperator.BooleanOr; - + || left.getOperator() == SQLBinaryOperator.BooleanOr; + if (left.getOperator().priority > x.getOperator().priority) { if (leftRational) { incrementIndent(); @@ -222,7 +230,7 @@ public class SQLASTOutputVisitor extends SQLASTVisitorAdapter { print('('); left.accept(this); print(')'); - + if (leftRational) { decrementIndent(); } @@ -257,11 +265,11 @@ public class SQLASTOutputVisitor extends SQLASTVisitorAdapter { if (rightRational) { incrementIndent(); } - + print('('); right.accept(this); print(')'); - + if (rightRational) { decrementIndent(); } @@ -985,5 +993,5 @@ public class SQLASTOutputVisitor extends SQLASTVisitorAdapter { return false; } - + } diff --git a/src/main/java/com/alibaba/druid/sql/visitor/SQLASTVisitor.java b/src/main/java/com/alibaba/druid/sql/visitor/SQLASTVisitor.java index c862bacfc0..6e41d7fda7 100644 --- a/src/main/java/com/alibaba/druid/sql/visitor/SQLASTVisitor.java +++ b/src/main/java/com/alibaba/druid/sql/visitor/SQLASTVisitor.java @@ -135,7 +135,7 @@ public interface SQLASTVisitor { boolean visit(SQLExistsExpr x); - boolean visit(SQLIdentifierExpr astNode); + boolean visit(SQLIdentifierExpr x); boolean visit(SQLInListExpr x); diff --git a/src/main/java/com/alibaba/druid/sql/visitor/SQLASTVisitorAdapter.java b/src/main/java/com/alibaba/druid/sql/visitor/SQLASTVisitorAdapter.java index 259087554f..2cf5dfa83e 100644 --- a/src/main/java/com/alibaba/druid/sql/visitor/SQLASTVisitorAdapter.java +++ b/src/main/java/com/alibaba/druid/sql/visitor/SQLASTVisitorAdapter.java @@ -172,7 +172,7 @@ public class SQLASTVisitorAdapter implements SQLASTVisitor { return true; } - public boolean visit(SQLIdentifierExpr astNode) { + public boolean visit(SQLIdentifierExpr x) { return true; } diff --git a/src/main/java/com/alibaba/druid/util/JdbcUtils.java b/src/main/java/com/alibaba/druid/util/JdbcUtils.java index 6e2d480f9a..0090be6f70 100644 --- a/src/main/java/com/alibaba/druid/util/JdbcUtils.java +++ b/src/main/java/com/alibaba/druid/util/JdbcUtils.java @@ -22,21 +22,28 @@ import java.io.PrintStream; import java.io.PrintWriter; import java.io.Reader; import java.io.StringWriter; +import java.math.BigDecimal; import java.net.URL; import java.sql.Connection; import java.sql.Date; import java.sql.Driver; +import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Statement; import java.sql.Types; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Enumeration; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; import java.util.Properties; import com.alibaba.druid.logging.Log; import com.alibaba.druid.logging.LogFactory; +import com.alibaba.druid.mapping.DruidMappingException; /** * @author wenshao @@ -520,6 +527,69 @@ public final class JdbcUtils { } } + public static List> executeQuery(Connection conn, String sql, List parameters) { + List> rows = new ArrayList>(); + + PreparedStatement stmt = null; + ResultSet rs = null; + try { + stmt = conn.prepareStatement(sql); + + for (int i = 0, size = parameters.size(); i < size; ++i) { + Object val = parameters.get(i); + int paramIndex = i + 1; + + if (val == null) { + stmt.setNull(paramIndex, Types.VARCHAR); + } else if (val instanceof BigDecimal) { + stmt.setBigDecimal(paramIndex, (BigDecimal) val); + } else if (val instanceof String) { + stmt.setString(paramIndex, (String) val); + } else if (val instanceof Date) { + long time = ((Date) val).getTime(); + stmt.setTimestamp(paramIndex, new java.sql.Timestamp(time)); + } else if (val instanceof Byte) { + stmt.setByte(paramIndex, (Byte) val); + } else if (val instanceof Short) { + stmt.setShort(paramIndex, (Short) val); + } else if (val instanceof Integer) { + stmt.setInt(paramIndex, (Integer) val); + } else if (val instanceof Long) { + stmt.setLong(paramIndex, (Long) val); + } else if (val instanceof Float) { + stmt.setFloat(paramIndex, (Float) val); + } else if (val instanceof Double) { + stmt.setDouble(paramIndex, (Double) val); + } else { + throw new DruidMappingException("not support parameters : " + val); + } + } + + rs = stmt.executeQuery(); + + ResultSetMetaData rsMeta = rs.getMetaData(); + + while (rs.next()) { + Map row = new LinkedHashMap(); + + for (int i = 0, size = rsMeta.getColumnCount(); i < size; ++i) { + String columName = rsMeta.getColumnName(i + 1); + Object value = rs.getObject(i + 1); + row.put(columName, value); + } + + rows.add(row); + } + } catch (SQLException e) { + throw new DruidMappingException("execute error", e); + } finally { + JdbcUtils.close(rs); + JdbcUtils.close(stmt); + } + + return rows; + } + public static String toString(StackTraceElement[] stackTrace) { StringBuilder buf = new StringBuilder(); for (StackTraceElement item : stackTrace) { diff --git a/src/test/java/com/alibaba/druid/bvt/mapping/MappingVisitorTest.java b/src/test/java/com/alibaba/druid/bvt/mapping/MappingVisitorTest.java new file mode 100644 index 0000000000..2fce815d43 --- /dev/null +++ b/src/test/java/com/alibaba/druid/bvt/mapping/MappingVisitorTest.java @@ -0,0 +1,45 @@ +package com.alibaba.druid.bvt.mapping; + +import junit.framework.TestCase; + +import org.junit.Assert; + +import com.alibaba.druid.mapping.Entity; +import com.alibaba.druid.mapping.MappingEngine; +import com.alibaba.druid.mapping.Property; + +public class MappingVisitorTest extends TestCase { + + MappingEngine engine = new MappingEngine(); + + protected void setUp() throws Exception { + Entity entity = new Entity(); + entity.setName("设备"); + entity.setDescription("Device"); + entity.setTableName("device"); + + entity.addProperty(new Property("编号", "", "serviceTag")); + entity.addProperty(new Property("IP地址", "", "ip")); + + engine.addEntity(entity); + } + + public void test_0() throws Exception { + + Assert.assertEquals("SELECT serviceTag AS \"编号\", ip AS \"IP地址\"\nFROM device", engine.explain("select *")); + Assert.assertEquals("SELECT serviceTag AS \"编号\", ip AS \"IP地址\"\nFROM device\nWHERE ip = '127.0.0.1'", + engine.explain("select 编号, IP地址 WHERE IP地址 = '127.0.0.1'")); + + } + + public void test_1() throws Exception { + engine.setMaxLimit(10); + + Assert.assertEquals("SELECT serviceTag AS \"编号\", ip AS \"IP地址\"\nFROM device\nLIMIT 10", engine.explain("select *")); + Assert.assertEquals("SELECT serviceTag AS \"编号\", ip AS \"IP地址\"\nFROM device\nWHERE ip = '127.0.0.1'\nLIMIT 10", + engine.explain("select 编号, IP地址 WHERE IP地址 = '127.0.0.1'")); + + engine.setMaxLimit(null); + + } +}