/** * 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.diql.visitors; import org.antlr.v4.runtime.tree.TerminalNode; import org.diqube.diql.ParseException; import org.diqube.diql.antlr.DiqlBaseVisitor; import org.diqube.diql.antlr.DiqlParser.AnyValueContext; import org.diqube.diql.antlr.DiqlParser.LimitClauseContext; import org.diqube.diql.antlr.DiqlParser.OrderClauseContext; import org.diqube.diql.antlr.DiqlParser.OrderTermContext; import org.diqube.diql.antlr.DiqlParser.PositiveDecimalLiteralValueContext; import org.diqube.diql.request.OrderRequest; import org.diqube.name.FunctionBasedColumnNameBuilderFactory; import org.diqube.name.RepeatedColumnNameGenerator; import org.diqube.util.ColumnOrValue; import org.diqube.util.ColumnOrValue.Type; import org.diqube.util.Pair; /** * Visits 'ORDER BY' clause and returns a corresponding {@link OrderRequest}. * * <p> * If aggregations/projections are encountered, they are automatically added to * {@link ExecutionRequestVisitorEnvironment#getExecutionRequest()}. * * @author Bastian Gloeckle */ public class OrderVisitor extends DiqlBaseVisitor<OrderRequest> { public static final int IDX_ASC_OR_DESC = 0; public static final int IDX_LIMIT = 1; public static final int IDX_LIMIT_START = 2; public static final long SORT_ASC = 0; public static final long SORT_DESC = 1; private ExecutionRequestVisitorEnvironment env; private RepeatedColumnNameGenerator repeatedColNames; private FunctionBasedColumnNameBuilderFactory functionBasedColumnNameBuilderFactory; public OrderVisitor(ExecutionRequestVisitorEnvironment env, RepeatedColumnNameGenerator repeatedColNames, FunctionBasedColumnNameBuilderFactory functionBasedColumnNameBuilderFactory) { this.env = env; this.repeatedColNames = repeatedColNames; this.functionBasedColumnNameBuilderFactory = functionBasedColumnNameBuilderFactory; } @Override public OrderRequest visitOrderClause(OrderClauseContext orderClauseCtx) { OrderRequest res = new OrderRequest(); // check if there is a limit clause LimitClauseContext limitClauseCtx = orderClauseCtx.getChild(LimitClauseContext.class, 0); if (limitClauseCtx != null) { long limit; try { limit = Long.parseLong(limitClauseCtx.getChild(PositiveDecimalLiteralValueContext.class, 0).getText()); } catch (NumberFormatException e) { throw new ParseException("Could not parse limit value."); } res.setLimit(limit); PositiveDecimalLiteralValueContext startCtx = limitClauseCtx.getChild(PositiveDecimalLiteralValueContext.class, 1); if (startCtx != null) { long limitStart; try { limitStart = Long.parseLong(startCtx.getText()); } catch (NumberFormatException e) { throw new ParseException("Could not parse limit start value."); } res.setLimitStart(limitStart); } } // check all columns that we should order by int termPos = 0; OrderTermContext termCtx = null; while ((termCtx = orderClauseCtx.getChild(OrderTermContext.class, termPos++)) != null) { AnyValueContext anyValueContext = termCtx.getChild(AnyValueContext.class, 0); ColumnOrValue anyValueResult = anyValueContext .accept(new AnyValueVisitor(env, repeatedColNames, functionBasedColumnNameBuilderFactory)).getLeft(); if (anyValueResult.getType().equals(Type.LITERAL)) throw new ParseException("Ordering by literal values is not supported. " + "Please use a column directly or a function based on a column to order by."); String colName = anyValueResult.getColumnName(); boolean isAscending = true; // check last terminal node if it is 'desc'. for (int childPos = termCtx.getChildCount() - 1; childPos >= 0; childPos--) if (termCtx.getChild(childPos) instanceof TerminalNode) { String terminalText = ((TerminalNode) termCtx.getChild(childPos)).getText(); if (terminalText != null && terminalText.toLowerCase().equals("desc")) isAscending = false; break; } res.getColumns().add(new Pair<String, Boolean>(colName, isAscending)); } return res; } @Override protected OrderRequest aggregateResult(OrderRequest aggregate, OrderRequest nextResult) { if (aggregate == null) return nextResult; return aggregate; } }