TestUtils.java
/*-
* #%L
* JSQLParser library
* %%
* Copyright (C) 2004 - 2019 JSQLParser
* %%
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
* #L%
*/
package net.sf.jsqlparser.test;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.logging.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.MySQLIndexHint;
import net.sf.jsqlparser.expression.OracleHint;
import net.sf.jsqlparser.parser.CCJSqlParser;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.parser.Node;
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.*;
import net.sf.jsqlparser.statement.update.Update;
import net.sf.jsqlparser.util.deparser.ExpressionDeParser;
import net.sf.jsqlparser.util.deparser.SelectDeParser;
import net.sf.jsqlparser.util.deparser.StatementDeParser;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.builder.MultilineRecursiveToStringStyle;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
/**
*
* @author toben
*/
public class TestUtils {
private static final Pattern SQL_COMMENT_PATTERN =
Pattern.compile("(--.*$)|(/\\*.*?\\*/)", Pattern.MULTILINE);
private static final Pattern SQL_SANITATION_PATTERN =
Pattern.compile("(\\s+)", Pattern.MULTILINE);
// Assure SPACE around Syntax Characters
private static final Pattern SQL_SANITATION_PATTERN2 =
Pattern.compile("\\s*([!/,()=+\\-*|\\]<>:\\[\\]\\{\\}])\\s*", Pattern.MULTILINE);
/**
* @param statement
* @return the parsed {@link Statement}
* @throws JSQLParserException
*/
public static Statement assertSqlCanBeParsedAndDeparsed(String statement)
throws JSQLParserException {
return assertSqlCanBeParsedAndDeparsed(statement, true);
}
/**
* Tries to parse and deparse the given statement.
*
* @param statement
* @param laxDeparsingCheck removes all linefeeds from the original and removes all double
* spaces. The check is caseinsensitive.
* @return the parsed {@link Statement}
* @throws JSQLParserException
*/
public static Statement assertSqlCanBeParsedAndDeparsed(String statement,
boolean laxDeparsingCheck) throws JSQLParserException {
return assertSqlCanBeParsedAndDeparsed(statement, laxDeparsingCheck, null);
}
/**
* @param statement
* @param laxDeparsingCheck removes all linefeeds from the original and removes all double
* spaces. The check is caseinsensitive.
* @param consumer - a parser-consumer for parser-configurations from outside
* @return the parsed {@link Statement}
* @throws JSQLParserException
*/
public static Statement assertSqlCanBeParsedAndDeparsed(String statement,
boolean laxDeparsingCheck, Consumer<CCJSqlParser> consumer) throws JSQLParserException {
Statement parsed = CCJSqlParserUtil.parse(statement, consumer);
assertStatementCanBeDeparsedAs(parsed, statement, laxDeparsingCheck);
return parsed;
}
public static void assertStatementCanBeDeparsedAs(Statement parsed, String statement) {
assertStatementCanBeDeparsedAs(parsed, statement, false);
}
public static void assertStatementCanBeDeparsedAs(Statement parsed, String statement,
boolean laxDeparsingCheck) {
String sanitizedInputSqlStr = buildSqlString(parsed.toString(), laxDeparsingCheck);
String sanitizedStatementStr = buildSqlString(statement, laxDeparsingCheck);
assertEquals(sanitizedStatementStr, sanitizedInputSqlStr,
"Output from toString() does not match.");
// Export all the Test SQLs to /tmp/net/sf/jsqlparser
boolean exportToFile = Boolean.parseBoolean(System.getenv("EXPORT_TEST_TO_FILE"));
if (exportToFile) {
writeTestToFile(sanitizedInputSqlStr);
}
StringBuilder builder = new StringBuilder();
parsed.accept(new StatementDeParser(builder));
String sanitizedDeparsedStr = buildSqlString(builder.toString(), laxDeparsingCheck);
assertEquals(sanitizedStatementStr, sanitizedDeparsedStr,
"Output from Deparser does not match.");
}
private static void writeTestToFile(String sanitizedInputSqlStr) {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
String testMethodName;
String testClassName;
int i = 1;
do {
testMethodName = stackTrace[i].getMethodName();
testClassName = stackTrace[i].getClassName();
i++;
} while (testMethodName.equals("writeTestToFile") || testMethodName.startsWith("assert"));
if (!testMethodName.equals("testRelObjectNameExt")) {
int classNameSeparator = testClassName.lastIndexOf(".");
String simpleClassName = testClassName.substring(classNameSeparator + 1);
String packageName = testClassName.substring(0, classNameSeparator).replace(".",
System.getProperty("file.separator"));
File file = new File(System.getProperty("java.io.tmpdir")
+ System.getProperty("file.separator") + packageName);
file.mkdirs();
file = new File(file, simpleClassName + ".sql");
try (FileWriter fileWriter = new FileWriter(file, true)) {
IOUtils.write("-- " + testMethodName + "\n", fileWriter);
IOUtils.write(sanitizedInputSqlStr, fileWriter);
if (!sanitizedInputSqlStr.trim().endsWith(";")) {
IOUtils.write("\n;", fileWriter);
}
IOUtils.write("\n\n", fileWriter);
} catch (IOException ex) {
Logger.getLogger(TestUtils.class.getName()).log(Level.SEVERE,
"Writing SQL to file failed.", ex);
}
}
}
/**
* Asserts that the {@link Statement} can be deparsed and deparsing results in given #statement
*
* @param stmt
* @param statement
*/
public static void assertDeparse(Statement stmt, String statement) {
assertDeparse(stmt, statement, false);
}
/**
* Compares the object-tree of a given parsed model and a created one.
*
* @param parsed
* @param created
*/
public static void assertEqualsObjectTree(Statement parsed, Statement created) {
assertEquals(toReflectionString(parsed), toReflectionString(created));
}
/**
* @param stmt
* @return a {@link String} build by {@link ToStringBuilder} and
* {@link ObjectTreeToStringStyle#INSTANCE}
*/
public static String toReflectionString(Statement stmt) {
return toReflectionString(stmt, false);
}
/**
* @param stmt
* @return a {@link String} build by {@link ToStringBuilder} and
* {@link ObjectTreeToStringStyle#INSTANCE}
*/
public static String toReflectionString(Statement stmt, boolean includingASTNode) {
ReflectionToStringBuilder strb = new ReflectionToStringBuilder(stmt,
includingASTNode ? ObjectTreeToStringStyle.INSTANCE_INCLUDING_AST
: ObjectTreeToStringStyle.INSTANCE);
return strb.build();
}
/**
* Replacement of {@link Arrays#asList(Object...)} which returns java.util.Arrays$ArrayList not
* java.util.ArrayList, the internal model uses java.util.ArrayList by default, which supports
* modification
*
* @param <T>
* @param obj
* @return a {@link ArrayList} of given items
*/
@SafeVarargs
public static <T> List<T> asList(T... obj) {
return Stream.of(obj).collect(Collectors.toCollection(ArrayList::new));
}
/**
* <p>
* {@code ToStringStyle} that outputs on multiple lines without identity hashcode.
* </p>
*/
private static final class ObjectTreeToStringStyle extends MultilineRecursiveToStringStyle {
private static final long serialVersionUID = 1L;
public static final ObjectTreeToStringStyle INSTANCE = new ObjectTreeToStringStyle(false);
public static final ObjectTreeToStringStyle INSTANCE_INCLUDING_AST =
new ObjectTreeToStringStyle(true);
private boolean includingASTNode;
/**
* <p>
* Constructor.
* </p>
*
* <p>
* Use the static constant rather than instantiating.
* </p>
*/
private ObjectTreeToStringStyle(boolean includingASTNode) {
super();
this.includingASTNode = includingASTNode;
this.setUseClassName(true);
this.setUseIdentityHashCode(false);
ToStringBuilder.setDefaultStyle(this);
}
@Override
public void append(final StringBuffer buffer, final String fieldName, final Object value,
final Boolean fullDetail) {
if (includingASTNode || !"node".equals(fieldName)) {
super.append(buffer, fieldName, value, fullDetail);
}
}
/**
* empty {@link Collection}'s should be printed as <code>null</code>, otherwise the outcome
* cannot be compared
*/
@Override
protected void appendDetail(final StringBuffer buffer, final String fieldName,
final Collection<?> coll) {
if (coll.isEmpty()) {
appendNullText(buffer, fieldName);
} else {
super.appendDetail(buffer, fieldName, coll);
}
}
/**
* empty {@link Map}'s should be printed as <code>null</code>, otherwise the outcome cannot
* be compared
*/
@Override
protected void appendDetail(final StringBuffer buffer, final String fieldName,
final Map<?, ?> coll) {
if (coll.isEmpty()) {
appendNullText(buffer, fieldName);
} else {
super.appendDetail(buffer, fieldName, coll);
}
}
@Override
protected boolean accept(Class<?> clazz) {
if (includingASTNode) {
return super.accept(clazz);
} else {
return isNotANode(clazz) && super.accept(clazz);
}
}
public boolean isNotANode(Class<?> clazz) {
return !Node.class.isAssignableFrom(clazz);
}
}
/**
* Asserts that the {@link Statement} can be deparsed and deparsing results in given #statement
*
* @param stmt
* @param statement
* @param laxDeparsingCheck removes all line feeds from the original and removes all double
* spaces. The check is case-insensitive.
*/
public static void assertDeparse(Statement stmt, String statement, boolean laxDeparsingCheck) {
StatementDeParser deParser = new StatementDeParser(new StringBuilder());
stmt.accept(deParser);
assertEquals(buildSqlString(statement, laxDeparsingCheck),
buildSqlString(deParser.getBuilder().toString(), laxDeparsingCheck));
}
public static String buildSqlString(final String originalSql, boolean laxDeparsingCheck) {
if (laxDeparsingCheck) {
// remove comments
String sanitizedSqlStr = SQL_COMMENT_PATTERN.matcher(originalSql).replaceAll("");
// redundant white space
sanitizedSqlStr = SQL_SANITATION_PATTERN.matcher(sanitizedSqlStr).replaceAll(" ");
// assure spacing around Syntax Characters
sanitizedSqlStr = SQL_SANITATION_PATTERN2.matcher(sanitizedSqlStr).replaceAll("$1");
sanitizedSqlStr = sanitizedSqlStr.trim().toLowerCase();
if (laxDeparsingCheck && sanitizedSqlStr.endsWith(";")) {
sanitizedSqlStr = sanitizedSqlStr.substring(0, sanitizedSqlStr.length() - 1).trim();
}
// Rewrite statement separators "/" and "GO"
if (sanitizedSqlStr.endsWith("/")) {
sanitizedSqlStr = sanitizedSqlStr.substring(0, sanitizedSqlStr.length() - 1);
} else if (sanitizedSqlStr.endsWith("go")) {
sanitizedSqlStr = sanitizedSqlStr.substring(0, sanitizedSqlStr.length() - 2);
}
return sanitizedSqlStr;
} else {
// remove comments only
return SQL_COMMENT_PATTERN.matcher(originalSql).replaceAll("");
}
}
@Test
public void testBuildSqlString() {
assertEquals("select col from test",
buildSqlString(" SELECT col FROM \r\n \t TEST \n", true));
assertEquals("select col from test", buildSqlString("select col from test", false));
}
public static void assertExpressionCanBeDeparsedAs(final Expression parsed, String expression) {
ExpressionDeParser expressionDeParser = new ExpressionDeParser();
StringBuilder stringBuilder = new StringBuilder();
expressionDeParser.setBuilder(stringBuilder);
SelectDeParser selectDeParser = new SelectDeParser(expressionDeParser, stringBuilder);
expressionDeParser.setSelectVisitor(selectDeParser);
parsed.accept(expressionDeParser, null);
assertEquals(expression, stringBuilder.toString());
}
public static void assertExpressionCanBeParsedAndDeparsed(String expressionStr,
boolean laxDeparsingCheck) throws JSQLParserException {
Expression expression = CCJSqlParserUtil.parseExpression(expressionStr);
assertEquals(buildSqlString(expressionStr, laxDeparsingCheck),
buildSqlString(expression.toString(), laxDeparsingCheck));
}
public static void assertOracleHintExists(String sql, boolean assertDeparser, String... hints)
throws JSQLParserException {
if (assertDeparser) {
assertSqlCanBeParsedAndDeparsed(sql, true);
}
Statement statement = CCJSqlParserUtil.parse(sql);
if (statement instanceof Select) {
Select stmt = (Select) statement;
if (stmt instanceof PlainSelect) {
OracleHint hint = OracleHint.getHintFromSelectBody(stmt);
assertNotNull(hint);
assertEquals(hints[0], hint.getValue());
} else if (stmt instanceof SetOperationList) {
SetOperationList setOperationList = (SetOperationList) stmt;
for (int i = 0; i < setOperationList.getSelects().size(); i++) {
OracleHint hint =
OracleHint.getHintFromSelectBody(setOperationList.getSelects().get(i));
if (hints[i] == null) {
assertNull(hint);
} else {
assertNotNull(hint);
assertEquals(hints[i], hint.getValue());
}
}
}
} else if (statement instanceof Update) {
Update stmt = (Update) statement;
OracleHint hint = stmt.getOracleHint();
assertNotNull(hint);
assertEquals(hints[0], hint.getValue());
} else if (statement instanceof Insert) {
Insert stmt = (Insert) statement;
OracleHint hint = stmt.getOracleHint();
assertNotNull(hint);
assertEquals(hints[0], hint.getValue());
} else if (statement instanceof Delete) {
Delete stmt = (Delete) statement;
OracleHint hint = stmt.getOracleHint();
assertNotNull(hint);
assertEquals(hints[0], hint.getValue());
}
}
public static void assertUpdateMysqlHintExists(String sql, boolean assertDeparser,
String action, String qualifier, String... indexNames)
throws JSQLParserException {
if (assertDeparser) {
assertSqlCanBeParsedAndDeparsed(sql, true);
}
Statement statement = CCJSqlParserUtil.parse(sql);
assertInstanceOf(Update.class, statement);
Update updateStmt = (Update) statement;
final MySQLIndexHint indexHint = updateStmt.getTable().getIndexHint();
assertNotNull(indexHint);
assertEquals(indexHint.getAction(), action);
assertEquals(indexHint.getIndexQualifier(), qualifier);
assertArrayEquals(indexHint.getIndexNames().toArray(), indexNames);
}
}