/** * * Copyright * 2009-2015 Jayway Products AB * 2016-2017 Föreningen Sambruk * * Licensed under AGPL, Version 3.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.gnu.org/licenses/agpl.txt * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package se.streamsource.dci.value.table.gdq; import java.util.ArrayList; import java.util.List; /** Parses a sort-of subset of google data query language. * See the test for examples. * */ public class GdQueryParser { public static GdQuery parse(String s) { GdqTokenizer tokenizer = new GdqTokenizer(s); GdQuery result = new GdQuery(); if (tokenizer.hasToken(GdqTokenType.KEYWORD_SELECT)) { result.select = parseSelectClause(tokenizer); } if (tokenizer.hasToken(GdqTokenType.KEYWORD_WHERE)) { result.where = parseMultiWordClause(tokenizer); } if (tokenizer.hasToken(GdqTokenType.KEYWORD_ORDER)) { result.orderBy = parseOrderByClause(tokenizer); } if (tokenizer.hasToken(GdqTokenType.KEYWORD_LIMIT)) { result.limit = parseIntegerClause(tokenizer); } if (tokenizer.hasToken(GdqTokenType.KEYWORD_OFFSET)) { result.offset = parseIntegerClause(tokenizer); } if (tokenizer.hasToken(GdqTokenType.KEYWORD_OPTIONS)) { result.options = parseMultiWordClause(tokenizer); } if (tokenizer.hasToken(GdqTokenType.KEYWORD_GROUP) || tokenizer.hasToken(GdqTokenType.KEYWORD_PIVOT) || tokenizer.hasToken(GdqTokenType.KEYWORD_FORMAT) || tokenizer.hasToken(GdqTokenType.KEYWORD_LABEL) ) { throw new GdQueryParseException("Unsupported query clause: " + tokenizer.tokenStringValue()); } if (tokenizer.hasToken()) { throw new GdQueryParseException("Unexpected token: " + tokenizer.tokenStringValue()); } return result; } private static List<String> parseSelectClause(GdqTokenizer tokenizer) { consumeExpectedToken(tokenizer, GdqTokenType.KEYWORD_SELECT, "select"); List<String> result = new ArrayList<String>(); result.add(consumeExpectedToken(tokenizer, GdqTokenType.WORD, "column name in select clause")); while (tokenizer.hasToken(GdqTokenType.COMMA)) { tokenizer.consumeToken(); result.add(consumeExpectedToken(tokenizer, GdqTokenType.WORD, "column name in select clause")); } return result; } /** Parses a clause of the form <keyword> <word>*, eg. * "select foo bar baz". */ private static String parseMultiWordClause(GdqTokenizer tokenizer) { tokenizer.consumeToken(); StringBuilder result = new StringBuilder(); while (tokenizer.hasToken(GdqTokenType.WORD)) { if (result.length() > 0) { result.append(" "); } result.append(tokenizer.tokenStringValue()); tokenizer.consumeToken(); } return result.toString(); } /** Parses an order by clause of the form * order by <word> (asc|desc)? ( , <word> (asc|desc)?)* */ private static List<OrderByElement> parseOrderByClause(GdqTokenizer tokenizer) { consumeExpectedToken(tokenizer, GdqTokenType.KEYWORD_ORDER, "order"); consumeExpectedToken(tokenizer, GdqTokenType.KEYWORD_BY, "by"); List<OrderByElement> result = new ArrayList<OrderByElement>(); result.add(parseOrderByElement(tokenizer)); while (tokenizer.hasToken(GdqTokenType.COMMA)) { tokenizer.consumeToken(); result.add(parseOrderByElement(tokenizer)); } return result; } private static OrderByElement parseOrderByElement(GdqTokenizer tokenizer) { String name = consumeExpectedToken(tokenizer, GdqTokenType.WORD, "name in order by clause"); OrderByDirection direction = OrderByDirection.UNDEFINED; if (tokenizer.hasToken() && tokenizer.tokenType() == GdqTokenType.WORD) { String directionString = tokenizer.tokenStringValue(); if ("asc".equals(directionString)) { direction = OrderByDirection.ASCENDING; } else if ("desc".equals(directionString)) { direction = OrderByDirection.DESCENDING; } else { throw new GdQueryParseException("Invalid order by direction: " + directionString); } tokenizer.consumeToken(); } return new OrderByElement(name, direction); } private static Integer parseIntegerClause(GdqTokenizer tokenizer) { String initialToken = tokenizer.tokenStringValue(); tokenizer.consumeToken(); if (tokenizer.hasToken(GdqTokenType.WORD)) { try { String s = tokenizer.tokenStringValue(); tokenizer.consumeToken(); return Integer.parseInt(s); } catch (NumberFormatException e) { } } throw new GdQueryParseException("Expected integer in "+ initialToken +" clause"); } private static String consumeExpectedToken(GdqTokenizer tokenizer, GdqTokenType expectedTokenType, String description) { if (tokenizer.tokenType() != expectedTokenType) { throw new GdQueryParseException("Expected " + description + ", but found "+ tokenizer.tokenStringValue()); } String value = tokenizer.tokenStringValue(); tokenizer.consumeToken(); return value; } }