package adipe.translate;
import static com.google.common.base.Preconditions.checkState;
import java.util.List;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.Parser;
import org.antlr.v4.runtime.misc.Interval;
import org.antlr.v4.runtime.misc.ParseCancellationException;
import org.antlr.v4.runtime.tree.ErrorNode;
import ra.Term;
import ra.Utils;
import adipe.translate.impl.TranslationVisitor;
import adipe.translate.ra.Schema;
import adipe.translate.sql.parser.SqlParser;
import adipe.translate.sql.parser.SqlParser.SelectStatementContext;
import adipe.translate.sql.parser.SqlParser.SelectStatementEofContext;
import adipe.translate.sql.parser.SqlParser.SelectStatementsEofContext;
import adipe.translate.sql.parser.SqlParserUtils;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
/**
* Utils
*
*/
public final class Queries {
private Queries() { }
/**
* Get relational algebra of sql query
* @param schema the database schema under which to translate
* @param sqlQuery the sql query in a text form
* @return the query translated into a relational algebra term
* @side calls {@code Utils.reset()}
* @side calls {@code schema.reset()}
* @throws TranslationException
* @throws ParseCancellationException
* @throws TranslationException when a derived table is parsed, but has a derived column list of the wrong size
* @throws RuntimeException
*/
public static Term getRaOf(Schema schema, String sqlQuery)
throws RuntimeException, TranslationException, ParseCancellationException
{
// TODO do we actually throw RuntimeException?
Utils.reset();
schema.reset();
SelectStatementEofContext tree = Queries.getQueryTree(sqlQuery);
//TODO this catch code is duplicated in {@link getMultipleRas}
try {
return TranslationVisitor.create(schema).visit(tree);
} catch (WrappedException e) {
//TODO change exceptions thrown
throw new TranslationException(e.getCause().getMessage(), e.getCause());
} catch (NullPointerException e) {
throw new TranslationException(String.format("%s", e), e);
} catch (RuntimeException e) {
// TODO the translation should not explicitly throw RuntimeException instances
// this should only happen on programming errors, like array[-1], etc.
String message = e.getMessage();
if (message == null) {
message = e.toString();
}
throw new TranslationException(message, e);
}
}
/**
* Get relational algebras of multiple semicolon separated sql queries
* @param schema the database schema under which to translate
* @param sqlSource the semicolon separated sql queries in a text form
* @param queries must be non-null
* @param formulas must be non-null
* @side calls {@code Utils.reset()}
* @side calls {@code schema.reset()}
* @side clears and populates the lists {@code queries} and {@code formulas} respectively with
* the sql queries and their translated counterparts, in order of presence in {@code sqlSource}
* @throws TranslationException
* @throws TranslationException when a derived table is parsed, but has a derived column list of the wrong size
* @throws ParseCancellationException
* @throws RuntimeException
*/
public static void getMultipleRasOf(Schema schema, String sqlSource,
List<String> queries, List<Term> formulas) throws RuntimeException, TranslationException, ParseCancellationException {
// TODO do we actually throw RuntimeException?
Utils.reset();
schema.reset();
queries.clear();
formulas.clear();
List<SelectStatementContext> trees = Lists.newArrayList();
Queries.getMultipleQueryTrees(sqlSource, queries, trees);
try {
for (int i = 0; i < trees.size(); ++i) {
formulas.add(TranslationVisitor.create(schema).visit(trees.get(i)));
}
} catch (WrappedException e) {
throw new TranslationException(e.getCause().getMessage(), e.getCause());
} catch (NullPointerException e) {
throw new TranslationException(String.format("%s", e), e);
} catch (RuntimeException e) {
// TODO the translation should not explicitly throw RuntimeException instances
// this should only happen on programming errors, like array[-1], etc.
String message = e.getMessage();
if (message == null) {
message = e.toString();
}
throw new TranslationException(message, e);
// TODO extract common code from {@link #getMultipleRasOf} and {@link #getRaOf}
}
}
/**
* Parse an SQL query to a tree
* Do not use, public because @VisibleForTesting
*
* @param source
* @return The parse tree of the query
* @throws ParseCancellationException
*/
@VisibleForTesting
public static SelectStatementEofContext getQueryTree(String source)
throws ParseCancellationException
{
SelectStatementEofContext tree;
tree = getParser(source, null).selectStatementEof();
checkState(tree != null);
checkState(!(tree instanceof ErrorNode));
return tree;
}
/**
* Parse multiple semicolon separated SQL queries to a trees
*
* @param source semicolon separated SQL queries
* @side populate {@code queries} and {@code trees} with the sql queries and their trees, in order of presence in {@code source}
* @throws ParseCancellationException
*/
private static void getMultipleQueryTrees(String source,
List<String> queries, List<SelectStatementContext> trees)
throws ParseCancellationException
{
SelectStatementsEofContext tree;
queries.clear();
trees.clear();
List<CharStream> charStreamL = Lists.newArrayList();
SqlParser parser = getParser(source, charStreamL);
tree = parser.selectStatementsEof();
checkState(tree != null);
checkState(!(tree instanceof ErrorNode));
for (SelectStatementContext ssctx : tree.selectStatementsEtc().selectStatement())
{
trees.add(ssctx);
queries.add(charStreamL.get(0).getText(new Interval(ssctx.start.getStartIndex(), ssctx.stop.getStopIndex())));
}
}
/**
* Get a {@link Parser} instance of parsing {@code source}
* @side if {@code charStreamL} is not null, add the {@code CharStream} instance representing
* the query source into {@code charStreamL}
* @throws none
*/
@VisibleForTesting
public static SqlParser getParser(String source, List<CharStream> charStreamL) {
// System.err.println(source);
return new SqlParserUtils().getParser(source, charStreamL);
}
}