package cn.com.ty.lift.common.sql; import com.baomidou.mybatisplus.core.parser.SqlParserHelper; import lombok.Data; import lombok.Getter; import lombok.Setter; import lombok.ToString; import lombok.experimental.Accessors; import lombok.extern.slf4j.Slf4j; import net.sf.jsqlparser.expression.Alias; import net.sf.jsqlparser.expression.BinaryExpression; import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.Function; import net.sf.jsqlparser.expression.operators.conditional.OrExpression; import net.sf.jsqlparser.expression.operators.relational.InExpression; import net.sf.jsqlparser.expression.operators.relational.ItemsList; import net.sf.jsqlparser.expression.operators.relational.NotEqualsTo; import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.schema.Table; import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.delete.Delete; import net.sf.jsqlparser.statement.select.*; import net.sf.jsqlparser.statement.update.Update; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.SqlCommandType; import org.apache.ibatis.plugin.*; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.reflection.SystemMetaObject; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.SQLException; import java.util.*; import java.util.concurrent.CopyOnWriteArraySet; /** * 由于开发人员水平参差不齐,即使订了开发规范很多人也不遵守 *

SQL是影响系统性能最重要的因素,所以拦截掉垃圾SQL语句

*
*

拦截SQL类型的场景

*

1.必须使用到索引,包含left join连接字段,符合索引最左原则

*

必须使用索引好处,

*

1.1 如果因为动态SQL,bug导致update的where条件没有带上,全表更新上万条数据

*

1.2 如果检查到使用了索引,SQL性能基本不会太差

*
*

2.SQL尽量单表执行,有查询left join的语句,必须在注释里面允许该SQL运行,否则会被拦截,有left join的语句,如果不能拆成单表执行的SQL,请leader商量在做

*

https://gaoxianglong.github.io/shark

*

SQL尽量单表执行的好处

*

2.1 查询条件简单、易于开理解和维护;

*

2.2 扩展性极强;(可为分库分表做准备)

*

2.3 缓存利用率高;

*

2.在字段上使用函数

*
*

3.where条件为空

*

4.where条件使用了 !=

*

5.where条件使用了 not 关键字

*

6.where条件使用了 or 关键字

*

7.where条件使用了 使用子查询

* * @author willenfoo * @since 2018-03-22 */ @Slf4j @Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})}) public class SqlIllegalInterceptor implements Interceptor { /** * cache the legal sql result. */ private final Set legalSqlCacheSet = new HashSet<>(); /** * cache the index info exist on the column. */ private static final Set indexInfoCacheSet = new CopyOnWriteArraySet<>(); /** * cache the table had load index info from the connection. */ private static final Set tableCacheSet = new HashSet<>(); @Getter @Setter private boolean devMode = true; @Getter @Setter private boolean sqlWrite = false; @Override public Object intercept(Invocation invocation) throws Throwable { StatementHandler statementHandler = SqlUtils.realTarget(invocation.getTarget()); MetaObject metaObject = SystemMetaObject.forObject(statementHandler); // 如果是insert操作, 或者 @SqlParser(filter = true) 跳过该方法解析 , 不进行验证 MappedStatement mappedStatement = SqlUtils.getMappedStatement(metaObject); if (SqlCommandType.INSERT.equals(mappedStatement.getSqlCommandType()) || SqlParserHelper.getSqlParserInfo(metaObject)) { return invocation.proceed(); } String originalSql = statementHandler.getBoundSql().getSql(); log.info("Verifying SQL ID: {}", mappedStatement.getId()); if (sqlWrite) { log.info("Verifying SQL:"); log.info(originalSql); } String sqlEncode = SqlUtils.sqlEncode(originalSql); if (legalSqlCacheSet.contains(sqlEncode)) { log.info("Original Sql is legal, No need verify."); return invocation.proceed(); } //get databaseMetaData & catalog & schema from connection, and put to local thread. Object firstArg = invocation.getArgs()[0]; if(!(firstArg instanceof Connection)){ return invocation.proceed(); } Connection con = (Connection) firstArg; //the illegal rule. IllegalRule illegalRule = new IllegalRule(con.getMetaData(), con.getCatalog(), con.getSchema(), originalSql); // if any to verify, just return. if (!illegalRule.verifiable()) { illegalRule.error(); return invocation.proceed(); } // if the sql is legal, cache the sql encode. if (illegalRule.verify()) { legalSqlCacheSet.add(sqlEncode); } else { if (devMode) { illegalRule.warn(); } else { illegalRule.error(); } } return invocation.proceed(); } @Override public Object plugin(Object target) { if (target instanceof StatementHandler) { return Plugin.wrap(target, this); } return target; } @Override public void setProperties(Properties prop) { String devMode = prop.getProperty("devMode"); String sqlWrite = prop.getProperty("sqlWrite"); if (SqlUtils.isBoolean(devMode)) { this.devMode = Boolean.valueOf(devMode); } if (SqlUtils.isBoolean(devMode)) { this.sqlWrite = Boolean.valueOf(sqlWrite); } } /** * the rules of illegal sql. */ private static class IllegalRule { // the list of where exp parse from the sql to verify. private List wheres = new ArrayList<>(); // the list of index info parse from the sql to verify. private List indexInfos = new ArrayList<>(); // the verify info parse from sql, include WHERE & TABLE & JOINS. private List verifyInfos = new ArrayList<>(); // the verify message. private List verifyMessage = new ArrayList<>(); private DatabaseMetaData databaseMetaData; private String catalog; private String schema; /** * the constructor with a original sql. * * @param originalSql the original sql string. */ private IllegalRule(DatabaseMetaData databaseMetaData, String catalog, String schema, String originalSql) { this.databaseMetaData = databaseMetaData; this.catalog = catalog; this.schema = schema; // parse original sql parseSql(originalSql); } private void addMessage(String message, Object... values){ if(message.contains("%s") && null != values && values.length > 0){ verifyMessage.add(String.format(message, values)); }else { verifyMessage.add(message); } } /** * verify no use WHERE. * * @param where a where {@link Expression} */ private int whereNoUse(Expression where) { if (SqlUtils.isNull(where)) { addMessage("illegal sql. not have WHERE condition."); return 0; } return 1; } /** * verify use OR expression in WHERE. * * @param where a where {@link Expression} */ private int whereHasOr(Expression where) { if (where instanceof OrExpression) { OrExpression or = (OrExpression) where; addMessage("illegal use [%s] in WHERE, will cause full table scan, try UNION ALL. sql: %s", or.getStringExpression(), or.toString()); return 0; } return 1; } /** * verify use NotEqualsTo expression in WHERE. * * @param where a where {@link Expression} */ private int whereHasNotEqualsTo(Expression where) { if (where instanceof NotEqualsTo) { NotEqualsTo notEqualsTo = (NotEqualsTo) where; addMessage("illegal use [%s] in WHERE, will cause full table scan. sql: %s", notEqualsTo.getStringExpression(), notEqualsTo.toString()); return 0; } return 1; } /** * verify use binary expression in WHERE. * 1. NOT binary expression * 2. the left of binary is FUNCTION. * 3. the right of binary is SubSelect. * * @param where a where {@link Expression} */ private int whereHasNotBinary(Expression where) { if (where instanceof BinaryExpression) { BinaryExpression binary = (BinaryExpression) where; if (binary.isNot()) { addMessage("illegal use [%s] in WHERE, will cause full table scan. Sql: %s", binary.getStringExpression(), binary.toString()); return 0; } Expression leftExpression = binary.getLeftExpression(); if (leftExpression instanceof Function) { Function function = (Function) leftExpression; addMessage("illegal use db function in WHERE, will cause full table scan. sql: %s", function.toString()); return 0; } Expression rightExpression = binary.getRightExpression(); if (rightExpression instanceof SubSelect) { SubSelect subSelect = (SubSelect) rightExpression; addMessage("illegal use subSelect in WHERE, will cause full table scan. sql: %s", subSelect.toString()); return 0; } } return 1; } /** * verify use IN expression and the rightItem of IN is SubSelect in WHERE. * * @param where a where {@link Expression} */ private int whereHasInSubSelect(Expression where) { if (where instanceof InExpression) { InExpression in = (InExpression) where; ItemsList rightItemsList = in.getRightItemsList(); if (rightItemsList instanceof SubSelect) { SubSelect subSelect = (SubSelect) rightItemsList; addMessage("illegal use IN (subSelect) in WHERE, Replacing IN with EXISTS(subSelect) is better. sql: %s", subSelect.toString()); return 0; } } return 1; } /** * verify check NULL in WHERE. * * @param where a where {@link Expression} */ private int whereCheckNull(Expression where) { String lower = where.toString().toLowerCase(); boolean checkNull = lower.contains("is null") || lower.contains("is not null"); if (checkNull) { addMessage("illegal check null in WHERE, will cause full table scan, set a default value on column and equals it. sql: %s", where); return 0; } return 1; } /** * verify whether use index on the column. * * @param indexInfo include catalog & schema & tableName & columnName to Specify a unique index. */ private int columnNoUseIndex(IndexInfo indexInfo) { String fullyQualifiedColumn = indexInfo.fullyQualifiedColumn(); //if the column has set. and the cache contains the column. if (indexInfo.fullyColumn() && indexInfoCacheSet.contains(fullyQualifiedColumn)) { return 1; } //if the table has set. and the cache do not contains the table. String fullyQualifiedTable = indexInfo.fullyQualifiedTable(); // if new create index on the column. must to reload index, if (indexInfo.fullyTable() && tableCacheSet.contains(fullyQualifiedTable)) { addMessage("Index not use on %s. Example sql: CREATE [UNIQUE|CLUSTERED] INDEX idx_%s ON %s(%s)", fullyQualifiedColumn, indexInfo.columnName, indexInfo.tableName, indexInfo.columnName); return 0; } //get index info from the connection. try (ResultSet resultSet = databaseMetaData.getIndexInfo(indexInfo.catalog, indexInfo.schema, indexInfo.tableName, false, true)) { int indexCount = 0; while (resultSet.next()) { //索引中的列序列号等于1,才有效, index: 8, label: ORDINAL_POSITION if (resultSet.getShort("ORDINAL_POSITION") == 1) { //在索引中的列名列 index:9, label: COLUMN_NAME IndexInfo index = IndexInfo.newInstance(); //TABLE_CAT index.setCatalog(indexInfo.catalog); //TABLE_SCHEM index.setSchema(indexInfo.schema); //TABLE_NAME index.setTableName(indexInfo.tableName); //COLUMN_NAME index.setColumnName(resultSet.getString("COLUMN_NAME")); indexInfoCacheSet.add(index.fullyQualifiedColumn()); indexCount++; //cache table, no reload index every time. tableCacheSet.add(index.fullyQualifiedTable()); } } if (indexCount > 6) { addMessage("Too many indexes (%s > 6) use on table (%s).", indexCount, fullyQualifiedTable); } log.info("Cache index: {}, table: {}", indexInfoCacheSet.size(), tableCacheSet.size()); } catch (SQLException e) { log.error("get index info from DatabaseMetaData fail."); } if (indexInfo.fullyColumn() && indexInfoCacheSet.contains(fullyQualifiedColumn)) { return 1; } addMessage("Index not use on %s. Example sql: CREATE [UNIQUE|CLUSTERED] INDEX idx_%s ON %s(%s)", fullyQualifiedColumn, indexInfo.columnName, indexInfo.tableName, indexInfo.columnName); return 0; } /** * parse original sql to WHERE & TABLE & JOIN TABLES. */ private void parseSql(String originalSql) { try { log.info("Parsing original sql."); if(null == originalSql || originalSql.trim().isEmpty()){ addMessage("The original sql is null or empty."); return; } Statement statement = CCJSqlParserUtil.parse(originalSql); //select from where if (isSelect(statement)) { //SELECT t.* FROM (SELECT * FROM t1 LEFT JOIN t2 ON t1.c1 = t2.c1 WHERE ...) t WHERE t.... log.info("original sql is SELECT."); SelectBody selectBody = ((Select) statement).getSelectBody(); if(isPlainSelect(selectBody)){ parseSelect((PlainSelect) selectBody); } } else if (isUpdate(statement)) { //UPDATE table SET column1 = v1 WHERE column2 = v2 log.info("original sql is UPDATE."); verifyInfos.add(VerifyInfo.newInstance().parse((Update) statement)); } else if (isDelete(statement)) { //DELETE FROM table WHERE column = v log.info("original sql is DELETE."); verifyInfos.add(VerifyInfo.newInstance().parse((Delete) statement)); } } catch (Exception e) { verifyInfos.clear(); addMessage("Parse original sql fail. %s", e.getMessage()); } } /** * parse plain select to WHERE & TABLE & JOIN TABLES. * * @param plainSelect a plain select. */ private void parseSelect(PlainSelect plainSelect) { verifyInfos.add(VerifyInfo.newInstance().parse(plainSelect)); FromItem fromItem = plainSelect.getFromItem(); //if is SubSelect, recursion parse select. if (isSubSelect(fromItem)) { SelectBody selectBody = ((SubSelect) fromItem).getSelectBody(); if (isPlainSelect(selectBody)) { parseSelect((PlainSelect) selectBody); } } } /** * parse a join to WHERE & INDEX */ private void parseJoin(VerifyInfo verifyInfo) { Expression on = verifyInfo.getWhere(); Table table = verifyInfo.getTable(); List joinTables = verifyInfo.getJoinTables(); wheres.add(on); if (!isBinary(on)) { return; } BinaryExpression binary = (BinaryExpression) on; //获得左边表达式 Expression leftExp = binary.getLeftExpression(); //获得右边表达式 Expression rightExp = binary.getRightExpression(); wheres.add(rightExp); if (isBinary(leftExp)) { verifyInfo.setWhere(leftExp); parseJoin(verifyInfo); } else if (isColumn(leftExp) && isColumn(rightExp)) { // the alias of primary table Alias alias = table.getAlias(); Column leftCol = (Column) leftExp; String leftColumn = leftCol.getColumnName(); Table leftTable = leftCol.getTable(); Column rightCol = (Column) rightExp; String rightColumn = rightCol.getColumnName(); Table rightTable = rightCol.getTable(); // if left or right belong to the main table, that verify the index info. // if the alias or the table name equals. if (SqlUtils.notNull(leftTable, rightTable)) { String leftTableName = leftTable.getName(); String rightTableName = rightTable.getName(); if (SqlUtils.notNull(alias) && Objects.equals(leftTableName, alias.getName())) { parseTable(table, leftColumn); } else if (Objects.equals(leftTableName, table.getName())) { parseTable(table, leftColumn); } else if (SqlUtils.notNull(alias) && Objects.equals(rightTableName, alias.getName())) { parseTable(table, rightColumn); } else if (Objects.equals(rightTableName, table.getName())) { parseTable(table, rightColumn); } else if (verifyInfo.hasJoinTables()) { // if left and right both don't belong to main table, find in the join tables. addMessage("Join column does not contain primary table (%s). sql: %s", table, binary); // the column is belong to the join tables. for (Table joinTable : joinTables) { Alias joinTableAlias = joinTable.getAlias(); if (SqlUtils.notNull(joinTableAlias) && Objects.equals(joinTableAlias.getName(), leftTableName)) { parseTable(joinTable, leftColumn); } else if (SqlUtils.notNull(joinTableAlias) && Objects.equals(joinTableAlias.getName(), rightTableName)) { parseTable(joinTable, rightColumn); } else if (Objects.equals(joinTable.getName(), leftTableName)) { parseTable(joinTable, leftColumn); } else if (Objects.equals(joinTable.getName(), rightTableName)) { parseTable(joinTable, rightColumn); } } } } } } /** * parse where exp to WHERE & INDEX. */ private void parseWhere(VerifyInfo verifyInfo) { Expression where = verifyInfo.getWhere(); Table table = verifyInfo.getTable(); List
joinTables = verifyInfo.getJoinTables(); wheres.add(where); if (!isBinary(where)) { return; } BinaryExpression binary = (BinaryExpression) where; //获得右边表达式 Expression rightExp = binary.getRightExpression(); wheres.add(rightExp); //获得左边表达式 Expression leftExp = binary.getLeftExpression(); if (isBinary(leftExp)) { verifyInfo.setWhere(leftExp); parseWhere(verifyInfo); } else if (isColumn(leftExp)) { Column leftCol = (Column) leftExp; Table leftTable = leftCol.getTable(); Alias alias = table.getAlias(); String leftColumn = leftCol.getColumnName(); // with table alias if (SqlUtils.notNull(leftTable, alias) && Objects.equals(leftTable.getName(), alias.getName())) { parseTable(table, leftColumn); // single table select without alias } else if (SqlUtils.isNull(leftTable, alias)) { parseTable(table, leftColumn); } else if (verifyInfo.hasJoinTables()) { // the column is belong to join tables. addMessage("Column in the WHERE is not of the primary table (%s). sql: %s", table, binary); for (Table joinTable : joinTables) { Alias joinTableAlias = joinTable.getAlias(); if (SqlUtils.notNull(joinTableAlias) && Objects.equals(joinTableAlias.getName(), leftTable.getName())) { parseTable(joinTable, leftColumn); } else if (Objects.equals(joinTable.getName(), leftTable.getName())) { parseTable(joinTable, leftColumn); } } } } } /** * parse joins exp to to WHERE & INDEX */ private void parseJoins(VerifyInfo verifyInfo) { //允许执行join,验证join是否使用索引等等 if (verifyInfo.hasJoins()) { for (Join join : verifyInfo.getJoins()) { verifyInfo.setWhere(join.getOnExpression()); parseJoin(verifyInfo); } } } private void parseOrderBy(VerifyInfo verifyInfo) { List orderByElements = verifyInfo.getOrderByElements(); Table table = verifyInfo.getTable(); List
joinTables = verifyInfo.getJoinTables(); if (SqlUtils.notEmpty(orderByElements)) { for (OrderByElement orderByElement : orderByElements) { Expression orderBy = orderByElement.getExpression(); if (!isColumn(orderBy)) { continue; } Column orderByCol = (Column) orderBy; String orderByColumnName = orderByCol.getColumnName(); Table orderByTable = orderByCol.getTable(); Alias alias = table.getAlias(); if (SqlUtils.notNull(orderByTable, alias) && Objects.equals(orderByTable.getName(), alias.getName())) { parseTable(table, orderByColumnName); } else if (SqlUtils.isNull(orderByTable, alias)) { parseTable(table, orderByColumnName); } else if (verifyInfo.hasJoinTables()) { // the column is belong to join tables. addMessage("Column in ORDER BY is not of the primary table (%s). sql: %s", table, orderByElement); for (Table joinTable : joinTables) { Alias joinTableAlias = joinTable.getAlias(); if (SqlUtils.notNull(joinTableAlias) && Objects.equals(joinTableAlias.getName(), orderByTable.getName())) { parseTable(joinTable, orderByColumnName); } else if (Objects.equals(joinTable.getName(), orderByTable.getName())) { parseTable(joinTable, orderByColumnName); } } } } } } /** * parse table and it's column to get index. * * @param table a table. * @param columnName the column belong to the table. */ private void parseTable(Table table, String columnName) { String tableInfo = table.getName(); //table : db.tableName IndexInfo indexInfo = new IndexInfo(); String[] tableArray = tableInfo.split("\\."); if (tableArray.length == 1) { indexInfo.setTableName(tableArray[0]); } else { indexInfo.setTableName(tableArray[1]); } indexInfo.setCatalog(catalog); indexInfo.setSchema(schema); indexInfo.setColumnName(columnName); indexInfos.add(indexInfo); } /** * check whether the sql is verifiable. */ private boolean verifiable() { return !verifyInfos.isEmpty(); } /** * verify the where exp & index info parse from the sql. return finally. */ private boolean verify() { if (verifyInfos.isEmpty()) { return false; } log.info("Parsing all expressions of sql."); for (VerifyInfo verifyInfo : verifyInfos) { if (verifyInfo.hasTable()) { log.info("==============================="); parseWhere(verifyInfo); parseJoins(verifyInfo); parseOrderBy(verifyInfo); } else { wheres.add(verifyInfo.getWhere()); } } int whereTotal = wheres.size(); int indexTotal = indexInfos.size(); log.info("parse expression complete, WHERE: {}, INDEX: {}.", whereTotal, indexTotal); int verifyCount = 0; if(!wheres.isEmpty()){ log.info("Verifying all WHERE expressions."); for (int i = 0; i < whereTotal; i++) { Expression where = wheres.get(i); log.info("{}. where: {}", (i + 1), where); int whereNoUse = whereNoUse(where); verifyCount += whereNoUse; if (whereNoUse > 0) { verifyCount += whereHasOr(where); verifyCount += whereHasNotEqualsTo(where); verifyCount += whereHasNotBinary(where); verifyCount += whereHasInSubSelect(where); verifyCount += whereCheckNull(where); } } } if(!indexInfos.isEmpty()){ log.info("Verifying all INDEX on column."); for (int i = 0; i < indexTotal; i++) { IndexInfo indexInfo = indexInfos.get(i); log.info("{}. indexInfo: {}", (i + 1), indexInfo.fullyQualifiedColumn()); verifyCount += columnNoUseIndex(indexInfo); } } return verifyCount == whereTotal * 6 + indexTotal; } public void warn() { if (verifyMessage.isEmpty()) { log.warn("no any message."); } else { verifyMessage.forEach(m -> { log.warn("Warn: {}", m); }); } } public void error() { if (verifyMessage.isEmpty()) { log.error("no any message."); } else { verifyMessage.forEach(m -> { log.error("Error: {}", m); }); } } private boolean isSelect(Statement statement) { return (statement instanceof Select); } private boolean isUpdate(Statement statement) { return (statement instanceof Update); } private boolean isDelete(Statement statement) { return (statement instanceof Delete); } private boolean isPlainSelect(Object object) { return (object instanceof PlainSelect); } private boolean isSubSelect(Object object) { return (object instanceof SubSelect); } private boolean isBinary(Expression expression) { return (expression instanceof BinaryExpression); } private boolean isColumn(Expression expression) { return (expression instanceof Column); } } @Data @Accessors(chain = true) @ToString private static class VerifyInfo { private Expression where; private Table table; private List joins; private List orderByElements; // the list tables in the JOIN condition. private List
joinTables = new ArrayList<>(); private static VerifyInfo newInstance(){ return new VerifyInfo(); } private void parseJoinTables() { if (hasJoins()) { for (Join join : joins) { FromItem rightItem = join.getRightItem(); if(rightItem instanceof Table){ joinTables.add((Table) rightItem); } } } } private VerifyInfo parse(PlainSelect plainSelect) { this.setWhere(plainSelect.getWhere()); this.setJoins(plainSelect.getJoins()); this.setOrderByElements(plainSelect.getOrderByElements()); FromItem fromItem = plainSelect.getFromItem(); // if is Table if (fromItem instanceof Table) { this.setTable((Table) fromItem); } parseJoinTables(); return this; } private VerifyInfo parse(Update update) { this.setWhere(update.getWhere()); this.setJoins(update.getJoins()); List
tables = update.getTables(); if (!tables.isEmpty()) { this.setTable(tables.get(0)); } parseJoinTables(); return this; } private VerifyInfo parse(Delete delete) { this.setWhere(delete.getWhere()); this.setJoins(delete.getJoins()); this.setTable(delete.getTable()); parseJoinTables(); return this; } private boolean hasWhere() { return null != where; } private boolean hasTable() { return null != table; } private boolean hasJoins() { return null != joins && !joins.isEmpty(); } private boolean hasJoinTables() { return !joinTables.isEmpty(); } } /** * the information of the index. */ @Data @Accessors(chain = true) @ToString private static class IndexInfo { private String catalog; private String schema; private String tableName; private String columnName; private static IndexInfo newInstance() { return new IndexInfo(); } private boolean fullyColumn() { return SqlUtils.notNull(getTableName(), getColumnName()); } private boolean fullyTable() { return SqlUtils.notNull(getTableName()); } private String fullyQualifiedColumn() { return fullyColumn() ? String.format("%s.%s.%s.%s", fixCatalog(), fixSchema(), getTableName(), getColumnName()) : null; } private String fullyQualifiedTable() { return fullyTable() ? String.format("%s.%s.%s", fixCatalog(), fixSchema(), getTableName()) : null; } private String fixCatalog() { return null == this.catalog ? "" : this.catalog; } private String fixSchema() { return null == this.schema ? "" : this.schema; } } }