/*******************************************************************************
* Copyright (c) 2013 hangum.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* hangum - initial API and implementation
******************************************************************************/
package com.hangum.tadpole.engine.sql.util;
import java.util.regex.Pattern;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import com.hangum.tadpole.commons.libs.core.define.PublicTadpoleDefine;
import com.hangum.tadpole.commons.libs.core.define.PublicTadpoleDefine.OBJECT_TYPE;
import com.hangum.tadpole.db.metadata.TadpoleMetaData;
import com.hangum.tadpole.engine.define.DBGroupDefine;
import com.hangum.tadpole.engine.manager.TadpoleSQLManager;
import com.hangum.tadpole.engine.query.dao.mysql.TableDAO;
import com.hangum.tadpole.engine.query.dao.system.UserDBDAO;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.update.Update;
/**
* <pre>
* java.sql.ResultSet과 ResultSetMeta를 TableViewer로 바꾸기 위해 가공하는 Util
*
* resource데이터를 저장하기 위해 data를 배열화시킨다.
* </pre>
*
* @author hangum
*
*/
public class SQLUtil {
/**
* Logger for this class
*/
private static final Logger logger = Logger.getLogger(SQLUtil.class);
/** REGEXP pattern flag */
private static final int PATTERN_FLAG = Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL;
/**
* pattern statement
*
* <PRE>
* CHECK는 MYSQL의 CHECK TABLE VIEW_TABLE_NAME; 명령으로 VIEW의 정보를 볼수 있습니다.
* PRAGMA는 sqlite의 시스템 쿼리 얻는 거.
* </PRE>
*/
private static final String MSSQL_PATTERN_STATEMENT = "|^SP_HELP.*|^EXEC.*";
private static final String ORACLE_PATTERN_STATEMENT = "";
private static final String MYSQL_PATTERN_STATEMENT = "|^CALL.*";
private static final String PGSQL_PATTERN_STATEMENT = "";
private static final String SQLITE_PATTERN_STATEMENT = "";
private static final String CUBRID_PATTERN_STATEMENT = "";
private static final String BASE_PATTERN_STATEMENT = "^SELECT.*|^EXPLAIN.*|^SHOW.*|^DESCRIBE.*|^DESC.*|^CHECK.*|^PRAGMA.*|^WITH.*|^OPTIMIZE.*"
+ MSSQL_PATTERN_STATEMENT
+ ORACLE_PATTERN_STATEMENT
+ MYSQL_PATTERN_STATEMENT
+ PGSQL_PATTERN_STATEMENT
+ SQLITE_PATTERN_STATEMENT
+ CUBRID_PATTERN_STATEMENT;
private static final Pattern PATTERN_DML_BASIC = Pattern.compile(BASE_PATTERN_STATEMENT, PATTERN_FLAG);
/** 허용되지 않는 sql 정의 */
private static final String[] NOT_ALLOWED_SQL = {
/* MSSQL- USE DATABASE명 */
// "USE"
};
/**
* tadpole 에서 사용하는 특수 컬럼여부
* 0번째 컬럼 #과 {@code PublicTadpoleDefine#SPECIAL_USER_DEFINE_HIDE_COLUMN}
*
* @param strColumnName
* @return
*/
public static boolean isTDBSpecialColumn(String strColumnName) {
if(StringUtils.equals(strColumnName, "#") || StringUtils.startsWithIgnoreCase(strColumnName, PublicTadpoleDefine.SPECIAL_USER_DEFINE_HIDE_COLUMN)) {
return true;
}
return false;
}
/**
* remove comment
*
* @param strSQL
* @return
*/
public static String removeComment(String strSQL) {
// try {
// Pattern regex = Pattern.compile("(?:/\\*[^;]*?\\*/)|(?:--[^;]*?$)", Pattern.DOTALL | Pattern.MULTILINE);
// Matcher regexMatcher = regex.matcher(subjectString);
// while (regexMatcher.find()) {
// // matched text: regexMatcher.group()
// // match start: regexMatcher.start()
// // match end: regexMatcher.end()
// }
// } catch (PatternSyntaxException ex) {
// // Syntax error in the regular expression
// }
if(null == strSQL) return "";
String strCheckSQL = strSQL.replaceAll("(?:/\\*(?:[^*]|(?:\\*+[^*/]))*\\*+/)|(?:--.*)", "");
strCheckSQL = StringUtils.trimToEmpty(strCheckSQL);
return strCheckSQL;
}
/**
* SQL의 DML, DDL 을 테스트 하기위 사용하는
* @param strSQL
* @return
*/
public static String makeSQLTestString(String strSQL) {
String strCheckSQL = removeComment(strSQL);
strCheckSQL = StringUtils.removeStart(strCheckSQL, "(");
strCheckSQL = StringUtils.trimToEmpty(strCheckSQL);
return strCheckSQL;
}
/**
* 쿼리중에 허용하지 않는 쿼리 목록.
* 쿼리문 위에 주석을 빼야... -- / ** * / / * * /
*
* @param strSQL
* @return
*/
public static boolean isNotAllowed(String strSQL) {
boolean isRet = false;
String cmpSql = removeComment(strSQL);
for (String strNAllSQL : NOT_ALLOWED_SQL) {
if(StringUtils.startsWithIgnoreCase(cmpSql, strNAllSQL)) {
return true;
}
}
return isRet;
}
/**
* 쿼리의 패턴이 <code>PATTERN_STATEMENT</code>인지?
*
* @param strSQL
* @return
*/
public static boolean isStatement(String strSQL) {
strSQL = makeSQLTestString(strSQL);
if((PATTERN_DML_BASIC.matcher(strSQL)).matches()) {
return true;
// } else {
// try {
// // 영문일때만 검사하도록 합니다. 영문이 아닐 경우 무조건 false 입니다.
// // 검사를 하는 이유는 한글이 파서에 들어가면 무한루프돌면서 에디터 전체가 데드락으로 빠집니다.
// // //if(!isEnglish(strSQL)) return false;
//
// CCJSqlParserManager parserManager = new CCJSqlParserManager();
// Statement statement = parserManager.parse(new StringReader(strSQL));
// if(statement instanceof Select) return true;
// } catch(Exception e) {
// logger.error("SQL Parser Exception.\n sql is [" + strSQL + "]");
// }
// return false;
}
return false;
}
/**
* sql 관련 없는 모든 코드를 삭제한다.
*
* @param userDB
* @param exeSQL
* @return
*/
public static String removeCommentAndOthers(UserDBDAO userDB, String exeSQL) {
exeSQL = StringUtils.trimToEmpty(exeSQL);
exeSQL = removeComment(exeSQL);
exeSQL = StringUtils.trimToEmpty(exeSQL);
exeSQL = StringUtils.removeEnd(exeSQL, "/");
exeSQL = StringUtils.trimToEmpty(exeSQL);
//TO DO 오라클 프로시저등의 오브젝트는 마지막 딜리미터(;)가 없으면 오류입니다. 하여서 이 코드는 문제입니다.
exeSQL = StringUtils.removeEnd(exeSQL, PublicTadpoleDefine.SQL_DELIMITER);
return exeSQL;
}
/**
* 쿼리를 jdbc에서 실행 가능한 쿼리로 보정합니다.
*
* @param userDB
* @param exeSQL
* @return
*/
public static String makeExecutableSQL(UserDBDAO userDB, String exeSQL) {
// tmpStrSelText = UnicodeUtils.getUnicode(tmpStrSelText);
// https://github.com/hangum/TadpoleForDBTools/issues/140 오류로 불럭지정하였습니다.
// TO DO 특정 쿼리에서는 주석이 있으면 오류인데..DB에서 쿼리를 실행받는 다양한 조건을 고려할 필요가 있습니다.
// 문장 의 // 뒤에를 주석으로 인식 쿼리열에서 제외합니다.
/*
* mysql의 경우 주석문자 즉, -- 바로 다음 문자가 --와 붙어 있으면 주석으로 인식하지 않아 오류가 발생합니다. --comment 이면 주석으로 인식하지 않습니다.(다른 디비(mssql, oralce, pgsql)은 주석으로 인식합니다)
* 고칠가 고민하지만, 실제 쿼리에서도 동일하게 오류로 처리할 것이기에 주석을 지우지 않고 놔둡니다. - 2013.11.11- (hangum)
*/
exeSQL = StringUtils.trimToEmpty(exeSQL);
// 주석제거.
// oracle, tibero, altibase은 힌트가 주석 문법을 쓰므로 주석을 삭제하지 않는다.
if(DBGroupDefine.ORACLE_GROUP == userDB.getDBGroup()
|| DBGroupDefine.MYSQL_GROUP == userDB.getDBGroup()
|| DBGroupDefine.ALTIBASE_GROUP == userDB.getDBGroup()
) {
// ignore code
} else {
exeSQL = removeComment(exeSQL);
}
// 주석으로 종료되는 행이면 지우지 않도록 수정.
exeSQL = StringUtils.trimToEmpty(exeSQL);
if(!StringUtils.endsWith(exeSQL, "*/")) {
exeSQL = StringUtils.removeEnd(exeSQL, "/");
}
exeSQL = StringUtils.trimToEmpty(exeSQL);
//TO DO 오라클 프로시저등의 오브젝트는 마지막 딜리미터(;)가 없으면 오류입니다. 하여서 이 코드는 문제입니다.
exeSQL = StringUtils.removeEnd(exeSQL, PublicTadpoleDefine.SQL_DELIMITER);
return exeSQL;
}
/**
* 쿼리에 사용 할 Table, column name을 만듭니다.
*
* @param userDB
* @param name
* @return
*/
public static String makeIdentifierName(UserDBDAO userDB, String name) {
boolean isChanged = false;
name = name == null ? "" : name;
String retStr = name;
//
// 오라클 평선의 파라미터 중에 리턴값의 아규먼트 명칭은 널이다.
//
TadpoleMetaData tmd = TadpoleSQLManager.getDbMetadata(userDB);
if(tmd == null) return name;
//
// mssql일 경우 시스템 테이블 스키서부터 "가 붙여 있는 경우 "가 있으면 []을 양쪽에 붙여 줍니다. --;;
//
if(DBGroupDefine.MSSQL_GROUP == userDB.getDBGroup()) {
if(StringUtils.contains(name, "\"")) {
return name = String.format("[%s]", name);
}
}
// 정의 된 형태로 오브젝트 명을 변경한다.
switch(tmd.getSTORE_TYPE()) {
case NONE:
// 오브젝트명이 전부 대문자로 변경한것과도 틀리고 전부 소문자로 변경한것과도 틀린경우 대, 소문자가 혼합된 명칭으로 간주하고 구분자를 추가해 준다.
if(!StringUtils.equals(name, StringUtils.lowerCase(name)) && !StringUtils.equals(name, StringUtils.upperCase(name)) ) {
isChanged = true;
retStr = makeFullyTableName(name, tmd.getIdentifierQuoteString());
}
break;
case BLANK:
if(name.matches(".*\\s.*")) {
isChanged = true;
retStr = makeFullyTableName(name, tmd.getIdentifierQuoteString());
}
break;
case LOWCASE_BLANK:
if(name.matches(".*[a-z\\s].*")) {
isChanged = true;
retStr = makeFullyTableName(name, tmd.getIdentifierQuoteString());
}else if(name.matches(".*[.].*")) {
isChanged = true;
retStr = makeFullyTableName(name, tmd.getIdentifierQuoteString());
}
break;
case UPPERCASE_BLANK:
if(name.matches(".*[A-Z\\s].*")) {
isChanged = true;
retStr = makeFullyTableName(name, tmd.getIdentifierQuoteString());
}else if(name.matches(".*[.].*")) {
isChanged = true;
retStr = makeFullyTableName(name, tmd.getIdentifierQuoteString());
}
break;
}
// 키워드 인지 검사하여 오브젝트 명을 변경한다.
if(!isChanged) {
if(StringUtils.containsIgnoreCase(","+tmd.getKeywords()+",", ","+retStr+",")) {
retStr = tmd.getIdentifierQuoteString() + name + tmd.getIdentifierQuoteString();
}
}
return retStr;
}
/**
* remove identifier quote string
*
* @param userDB
* @param name
* @return
*/
public static String removeIdentifierQuoteString(UserDBDAO userDB, String name) {
TadpoleMetaData tmd = TadpoleSQLManager.getDbMetadata(userDB);
if(tmd == null) return name;
return StringUtils.replace(name, tmd.getIdentifierQuoteString(), "");
}
/**
* make fully table name
* @param tableName
* @param strIdentifier
* @return
*/
private static String makeFullyTableName(String tableName, String strIdentifier) {
return strIdentifier + tableName + strIdentifier;
}
/**
* 에디터에서 쿼리 실행 단위 조절.
*
* https://github.com/hangum/TadpoleForDBTools/issues/466
*
* 오라클 디비링크 관련 스크립트는 SQL에디터를 사용하도록 OBJECT_TYPE.LINK 추가.
*
* @param dbAction
* @return
*/
public static boolean isSELECTEditor(OBJECT_TYPE dbAction) {
if(dbAction == OBJECT_TYPE.TABLES ||
dbAction == OBJECT_TYPE.VIEWS ||
dbAction == OBJECT_TYPE.SYNONYM ||
dbAction == OBJECT_TYPE.INDEXES ||
dbAction == OBJECT_TYPE.SEQUENCE ||
dbAction == OBJECT_TYPE.LINK ||
dbAction == OBJECT_TYPE.JOBS ||
dbAction == OBJECT_TYPE.VERTEX ||
dbAction == OBJECT_TYPE.GRAPHPATH ||
dbAction == OBJECT_TYPE.EDGE
) {
return true;
}
return false;
}
/**
* sql of query type
*
* @param sql
* @return query type
*/
public static PublicTadpoleDefine.QUERY_DML_TYPE sqlQueryType(String sql) {
PublicTadpoleDefine.QUERY_DML_TYPE queryType = PublicTadpoleDefine.QUERY_DML_TYPE.UNKNOWN;
try {
Statement statement = CCJSqlParserUtil.parse(sql);
if(statement instanceof Select) {
queryType = PublicTadpoleDefine.QUERY_DML_TYPE.SELECT;
} else if(statement instanceof Insert) {
queryType = PublicTadpoleDefine.QUERY_DML_TYPE.INSERT;
} else if(statement instanceof Update) {
queryType = PublicTadpoleDefine.QUERY_DML_TYPE.UPDATE;
} else if(statement instanceof Delete) {
queryType = PublicTadpoleDefine.QUERY_DML_TYPE.DELETE;
// } else {
// queryType = PublicTadpoleDefine.QUERY_DML_TYPE.DDL;
}
} catch (Throwable e) {
logger.error(String.format("sql parse exception. [ %s ]", sql));
queryType = PublicTadpoleDefine.QUERY_DML_TYPE.UNKNOWN;
}
return queryType;
}
/**
* make quote mark
*
* @param value
* @return
*/
public static String makeQuote(Object value) {
if (null == value){
return null;
}else{
return String.format("'%s'", StringEscapeUtils.escapeSql(value.toString()));
}
}
/**
* Table name
* @param userDB
* @param tableDAO
* @return
*/
public static String getTableName(UserDBDAO userDB, TableDAO tableDAO) {
return tableDAO.getFullName();
}
}