/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 * * 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 org.apache.metamodel.query.parser; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.metamodel.DataContext; import org.apache.metamodel.query.FromItem; import org.apache.metamodel.query.JoinType; import org.apache.metamodel.query.Query; import org.apache.metamodel.query.SelectItem; import org.apache.metamodel.schema.Table; import org.slf4j.Logger; import org.slf4j.LoggerFactory; final class FromItemParser implements QueryPartProcessor { /** * This field will hold start and end character for delimiter that can be * used */ private static final Map<Character, Character> delimiterMap = new HashMap<Character, Character>(); static { delimiterMap.put('\"', '\"'); delimiterMap.put('[', ']'); } private static final Logger logger = LoggerFactory.getLogger(FromItemParser.class); private final Query _query; private final DataContext _dataContext; public FromItemParser(DataContext dataContext, Query query) { _dataContext = dataContext; _query = query; } @Override public void parse(String delim, String itemToken) { final FromItem fromItem; final int parenthesisStart = itemToken.indexOf('('); if (parenthesisStart != -1) { if (parenthesisStart != 0) { throw new QueryParserException("Not capable of parsing FROM token: " + itemToken + ". Expected parenthesis to start at first character."); } final int parenthesisEnd = itemToken.indexOf(')', parenthesisStart); if (parenthesisEnd == -1) { throw new QueryParserException("Not capable of parsing FROM token: " + itemToken + ". Expected end parenthesis."); } final String subQueryString = itemToken.substring(parenthesisStart + 1, parenthesisEnd); logger.debug("Parsing sub-query: {}", subQueryString); final Query subQuery = new QueryParser(_dataContext, subQueryString).parse(); fromItem = new FromItem(subQuery); final String alias = itemToken.substring(parenthesisEnd + 1).trim(); if (!alias.isEmpty()) { fromItem.setAlias(alias); } } else if (itemToken.toUpperCase().indexOf(" JOIN ") != -1) { fromItem = parseAllJoinItems(itemToken); } else { fromItem = parseTableItem(itemToken); } _query.from(fromItem); } private FromItem parseTableItem(String itemToken) { // From token can be starting with [ final String tableNameToken; final String aliasToken; char startDelimiter = itemToken.trim().charAt(0); if (delimiterMap.containsKey(startDelimiter)) { char endDelimiter = delimiterMap.get(startDelimiter); int endIndex = itemToken.trim().lastIndexOf(endDelimiter, itemToken.trim().length()); if (endIndex <= 0) { throw new QueryParserException("Not capable of parsing FROM token: " + itemToken + ". Expected end " + endDelimiter); } tableNameToken = itemToken.trim().substring(1, endIndex).trim(); if (itemToken.trim().substring(1 + endIndex).trim().equalsIgnoreCase("")) { /* * As per code in FromClause Method: getItemByReference(FromItem * item, String reference) if (alias == null && table != null && * reference.equals(table.getName())) { Either we have to change * the code to add alias.equals("") there or return null here. */ aliasToken = null; } else { aliasToken = itemToken.trim().substring(1 + endIndex).trim(); } } else { // Default assumption is space being delimiter for tablename and // alias.. If otherwise use DoubleQuotes or [] around tableName final String[] tokens = itemToken.split(" "); tableNameToken = tokens[0]; if (tokens.length == 2) { aliasToken = tokens[1]; } else if (tokens.length == 1) { aliasToken = null; } else { throw new QueryParserException("Not capable of parsing FROM token: " + itemToken); } } final Table table = _dataContext.getTableByQualifiedLabel(tableNameToken); if (table == null) { throw new QueryParserException("Not capable of parsing FROM token: " + itemToken); } final FromItem result = new FromItem(table); result.setAlias(aliasToken); result.setQuery(_query); return result; } private FromItem parseAllJoinItems(final String itemToken) { String[] joinSplit = itemToken.split("(?i) JOIN "); List<String> joinsList = new ArrayList<String>(); for (int i = 0; i < joinSplit.length - 1; i++) { joinSplit[i] = joinSplit[i].trim(); joinSplit[i + 1] = joinSplit[i + 1].trim(); String leftPart = joinSplit[i].substring(0, joinSplit[i].lastIndexOf(" ")); String joinType = joinSplit[i].substring(joinSplit[i].lastIndexOf(" ")); String rightPart = (i + 1 == joinSplit.length - 1) ? joinSplit[i + 1] : joinSplit[i + 1].substring(0, joinSplit[i + 1].lastIndexOf(" ")); joinsList.add((leftPart + " " + joinType + " JOIN " + rightPart).replaceAll(" +", " ")); String rightTable = rightPart.substring(0, rightPart.toUpperCase().lastIndexOf(" ON ")); String nextJoinType = joinSplit[i + 1].substring(joinSplit[i + 1].lastIndexOf(" ")); joinSplit[i + 1] = rightTable + " " + nextJoinType; } Set<FromItem> fromItems = new HashSet<FromItem>(); FromItem leftFromItem = null; for (String token : joinsList) { leftFromItem = parseJoinItem(leftFromItem, token, fromItems); } return leftFromItem; } // this method will be documented based on this example itemToken: FOO f // INNER JOIN BAR b ON f.id = b.id private FromItem parseJoinItem(final FromItem leftFromItem, final String itemToken, Set<FromItem> fromItems) { final int indexOfJoin = itemToken.toUpperCase().indexOf(" JOIN "); // firstPart = "FOO f INNER" final String firstPart = itemToken.substring(0, indexOfJoin).trim(); // secondPart = "BAR b ON f.id = b.id" final String secondPart = itemToken.substring(indexOfJoin + " JOIN ".length()).trim(); final int indexOfJoinType = firstPart.lastIndexOf(" "); // joinTypeString = "INNER" final String joinTypeString = firstPart.substring(indexOfJoinType).trim().toUpperCase(); final JoinType joinType = JoinType.valueOf(joinTypeString); // firstTableToken = "FOO f" final String firstTableToken = firstPart.substring(0, indexOfJoinType).trim(); final int indexOfOn = secondPart.toUpperCase().indexOf(" ON "); // secondTableToken = "BAR b" final String secondTableToken = secondPart.substring(0, indexOfOn).trim(); final FromItem leftSide = parseTableItem(firstTableToken); final FromItem rightSide = parseTableItem(secondTableToken); fromItems.add(leftSide); fromItems.add(rightSide); // onClausess = ["f.id = b.id"] final String[] onClauses = secondPart.substring(indexOfOn + " ON ".length()).split(" AND "); final SelectItem[] leftOn = new SelectItem[onClauses.length]; final SelectItem[] rightOn = new SelectItem[onClauses.length]; for (int i = 0; i < onClauses.length; i++) { final String onClause = onClauses[i]; final int indexOfEquals = onClause.indexOf("="); // leftPart = "f.id" final String leftPart = onClause.substring(0, indexOfEquals).trim(); // rightPart = "b.id" final String rightPart = onClause.substring(indexOfEquals + 1).trim(); leftOn[i] = findSelectItem(leftPart, fromItems.toArray(new FromItem[fromItems.size()])); rightOn[i] = findSelectItem(rightPart, fromItems.toArray(new FromItem[fromItems.size()])); } final FromItem leftItem = (leftFromItem != null) ? leftFromItem : leftSide; final FromItem result = new FromItem(joinType, leftItem, rightSide, leftOn, rightOn); result.setQuery(_query); return result; } private SelectItem findSelectItem(String token, FromItem[] joinTables) { // first look in the original query SelectItemParser selectItemParser = new SelectItemParser(_query, false); SelectItem result = selectItemParser.findSelectItem(token); if (result == null) { // fail over and try with the from items available in the join that // is being built. final Query temporaryQuery = new Query().from(joinTables); selectItemParser = new SelectItemParser(temporaryQuery, false); result = selectItemParser.findSelectItem(token); if (result == null) { throw new QueryParserException("Not capable of parsing ON token: " + token); } // set the query on the involved query parts (since they have been // temporarily moved to the searched query). result.setQuery(_query); } return result; } }