/** * diqube: Distributed Query Base. * * Copyright (C) 2015 Bastian Gloeckle * * This file is part of diqube. * * diqube is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.diqube.ui.analysis; import org.diqube.diql.DiqlParseUtil; import org.diqube.diql.ParseException; import org.diqube.diql.antlr.DiqlParser.ComparisonContext; import org.diqube.diql.antlr.DiqlParser.DiqlStmtContext; import org.diqube.diql.antlr.DiqlParser.ResultValueContext; import org.diqube.diql.antlr.DiqlParser.SelectStmtContext; import org.diqube.diql.antlr.DiqlParser.TableNameContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Builds a full diql query out of information from a {@link UiAnalysis}, {@link UiQuery} and {@link UiSlice}. * * <p> * The UI splits the lgoic into multiple building blocks: The {@link UiAnalysis} is based on a single table (from-clause * for all queries), then the {@link UiSlice} defines the slice of data that is inspected (where-clause for all queries) * and the {@link UiQuery} defines the rest of the query. This builder takes these information and builds a final diql * string out of the pieces. * * @author Bastian Gloeckle */ public class QueryBuilder { private static final Logger logger = LoggerFactory.getLogger(QueryBuilder.class); private UiAnalysis analysis; private UiQuery query; private UiSlice slice; public QueryBuilder withAnalysis(UiAnalysis analysis) { this.analysis = analysis; return this; } public QueryBuilder withQuery(UiQuery query) { this.query = query; return this; } public QueryBuilder withSlice(UiSlice slice) { this.slice = slice; return this; } /** * @return A valid diql string that can be sent to a diqube-server for evaluation. * @throws QueryBuilderException * if anything went wrong. */ public String build() throws QueryBuilderException { String inputQuery = query.getDiql(); StringBuilder sb = new StringBuilder(); DiqlStmtContext diqlStmt; try { diqlStmt = DiqlParseUtil.parseWithAntlr(inputQuery); } catch (ParseException e) { throw new QueryBuilderException(e.getMessage(), e); } SelectStmtContext selectStmt = diqlStmt.getChild(SelectStmtContext.class, 0); if (selectStmt.getChild(TableNameContext.class, 0) != null) throw new QueryBuilderException("Query contains a FROM clause."); int lastResultValueIdx = 0; while (selectStmt.getChild(ResultValueContext.class, lastResultValueIdx) != null) lastResultValueIdx++; lastResultValueIdx--; ResultValueContext lastResultValue = selectStmt.getChild(ResultValueContext.class, lastResultValueIdx); // copy everything up to the point where we have to insert the "from" (= "select a, b, c"). sb.append(inputQuery.substring(0, lastResultValue.getStop().getStopIndex() + 1)); sb.append(" from "); sb.append(analysis.getTable()); sb.append(" "); int firstIndexOfRemainingInputString = lastResultValue.getStop().getStopIndex() + 1; boolean whereKeyWordAppended = false; ComparisonContext whereCtx = selectStmt.getChild(ComparisonContext.class, 0); if (whereCtx != null) { // the query already contains a "where"! sb.append(" where ("); sb.append(inputQuery.substring(whereCtx.getStart().getStartIndex(), whereCtx.getStop().getStopIndex() + 1)); sb.append(") "); whereKeyWordAppended = true; firstIndexOfRemainingInputString = whereCtx.getStop().getStopIndex() + 1; } if (!slice.getSliceDisjunctions().isEmpty() || (slice.getManualConjunction() != null && !"".equals(slice.getManualConjunction()))) { if (whereKeyWordAppended) sb.append(" and ("); else sb.append(" where ("); boolean andNeeded = false; for (UiSliceDisjunction disjunction : slice.getSliceDisjunctions()) { if (!disjunction.getDisjunctionValues().isEmpty()) { if (andNeeded) sb.append(" and "); andNeeded = true; sb.append("("); boolean firstValue = true; for (String disjunctionValue : disjunction.getDisjunctionValues()) { if (!firstValue) sb.append(" or "); firstValue = false; sb.append(disjunction.getFieldName()); sb.append(" = "); sb.append(disjunctionValue); } sb.append(")"); } } if (slice.getManualConjunction() != null && !"".equals(slice.getManualConjunction())) { if (andNeeded) sb.append(" and "); sb.append("("); sb.append(slice.getManualConjunction()); sb.append(")"); } sb.append(")"); } sb.append(inputQuery.substring(firstIndexOfRemainingInputString)); String res = sb.toString(); logger.info("Created final query for UiQuery {}: {}", query.getId(), res); return res; } public static class QueryBuilderException extends Exception { private static final long serialVersionUID = 1L; public QueryBuilderException(String msg) { super(msg); } public QueryBuilderException(String msg, Throwable cause) { super(msg, cause); } } }