| /* |
| * 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.oodt.cas.filemgr.util; |
| |
| //OODT imports |
| import org.apache.oodt.cas.filemgr.structs.BooleanQueryCriteria; |
| import org.apache.oodt.cas.filemgr.structs.QueryCriteria; |
| import org.apache.oodt.cas.filemgr.structs.RangeQueryCriteria; |
| import org.apache.oodt.cas.filemgr.structs.TermQueryCriteria; |
| import org.apache.oodt.cas.filemgr.structs.exceptions.QueryFormulationException; |
| import org.apache.oodt.cas.filemgr.structs.query.ComplexQuery; |
| import org.apache.oodt.cas.filemgr.structs.query.QueryFilter; |
| import org.apache.oodt.cas.filemgr.structs.query.filter.FilterAlgor; |
| |
| |
| //JDK imports |
| import java.util.Arrays; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Stack; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| /** |
| * |
| * @author bfoster |
| * @version $Revision$ |
| * |
| * <p> |
| * A fairly robust SQL parser, based on the Shunting yard |
| * algorithm |
| * </p> |
| * |
| * <p> |
| * Evaluates SQL like string statements contained in a string. The SQL |
| * statement should be enclosed within: SQL ({@literal <sql_arguments>} |
| * ) { {@literal <sql_statement>} . the {@literal <sql_arguments>} can |
| * be either FORMAT, SORT_BY, or FILTER. Syntax: SQL (FORMAT='<metadata |
| * formated output>',SORT_BY='<metadata name>', FILTER='<start_time |
| * metadata element>, <end_time metadata element>, <priority metadata |
| * element>, <filter type>') { SELECT |
| * <list-of-comma-segregated-metadata-elements-to-query-on> FROM |
| * <productTypes-comma-segregated> WHERE <metadata-boolean-expressions> |
| * } Here is an example SQL statement: SQL (FORMAT='FileLocation/Filename', |
| * SORT_BY='FileSize',FILTER=<StartDateTime,EndDateTime,DataVersion, |
| * TakeHighestPriority) { SELECT FileLocation,Filename,FileSize FROM IASI_L1C |
| * WHERE ProductionDateTime >= '2007-12-01T00:00:00.000000Z' } This example |
| * would query the cas-filemgr for metadata values: |
| * FileLocation,Filename,FileSize for any data file where the |
| * ProductType == IASI_L1C and the ProductionDateTime >= |
| * 2007-12-01T00:00:00.000000Z. It would then combine the return data |
| * files metadata via the specified FORMAT. Each data file's metadata |
| * will be formated to a string representation of (with the actual |
| * values replaced in the location of the metadata keys): |
| * FileLocation/Filename. They will be concatenated together, in |
| * FileSize order. |
| * </p> |
| */ |
| public class SqlParser { |
| |
| private SqlParser() { |
| } |
| |
| public static ComplexQuery parseSqlQueryMethod(String sqlStringQueryMethod) |
| throws QueryFormulationException { |
| if (!Pattern.matches("((?:SQL)|(?:sql))\\s*(.*)\\s*\\{\\s*SELECT.*FROM.*(?:WHERE.*){0,1}\\}", sqlStringQueryMethod)) |
| throw new QueryFormulationException("Malformed SQL method"); |
| |
| try { |
| ComplexQuery complexQuery = parseSqlQuery(stripOutSqlDefinition(sqlStringQueryMethod)); |
| |
| for (Expression expr : getSqlStatementArgs(sqlStringQueryMethod)) { |
| if (expr.getKey().toUpperCase().equals("FORMAT")) |
| complexQuery.setToStringResultFormat(expr.getValue()); |
| else if (expr.getKey().toUpperCase().equals("SORT_BY")) |
| complexQuery.setSortByMetKey(expr.getValue()); |
| else if (expr.getKey().toUpperCase().equals("FILTER")) |
| complexQuery.setQueryFilter(createFilter(expr)); |
| } |
| |
| return complexQuery; |
| }catch (Exception e) { |
| e.printStackTrace(); |
| throw new QueryFormulationException("Failed to parse SQL method : " + e.getMessage()); |
| } |
| } |
| |
| public static ComplexQuery parseSqlQuery(String sqlStringQuery) |
| throws QueryFormulationException { |
| String[] splitSqlStatement = sqlStringQuery |
| .split("((?:SELECT)|(?:FROM)|(?:WHERE))"); |
| String[] selectValues = (splitSqlStatement[1].trim() + ",").split(","); |
| String[] fromValues = (splitSqlStatement[2].trim() + ",").split(","); |
| ComplexQuery sq = new ComplexQuery(); |
| List<String> selectValuesList = Arrays.asList(selectValues); |
| if (!selectValuesList.contains("*")) |
| sq.setReducedMetadata(Arrays.asList(selectValues)); |
| List<String> fromValuesList = Arrays.asList(fromValues); |
| if (!fromValuesList.contains("*")) |
| sq.setReducedProductTypeNames(fromValuesList); |
| |
| if (splitSqlStatement.length > 3) |
| sq.addCriterion(parseStatement(toPostFix(splitSqlStatement[3] |
| .trim()))); |
| return sq; |
| } |
| |
| public static QueryCriteria parseSqlWhereClause(String sqlWhereClause) |
| throws QueryFormulationException { |
| return parseStatement(toPostFix(sqlWhereClause.trim())); |
| } |
| |
| public static String unparseSqlQuery(ComplexQuery complexQuery) throws QueryFormulationException { |
| LinkedList<String> outputArgs = new LinkedList<String>(); |
| if (complexQuery.getToStringResultFormat() != null) |
| outputArgs.add("FORMAT = '" + complexQuery.getToStringResultFormat() + "'"); |
| if (complexQuery.getSortByMetKey() != null) |
| outputArgs.add("SORT_BY = '" + complexQuery.getSortByMetKey() + "'"); |
| if (complexQuery.getQueryFilter() != null) { |
| String filterString = "FILTER = '" |
| + complexQuery.getQueryFilter().getStartDateTimeMetKey() + "," |
| + complexQuery.getQueryFilter().getEndDateTimeMetKey() + "," |
| + complexQuery.getQueryFilter().getPriorityMetKey() + "," |
| + complexQuery.getQueryFilter().getFilterAlgor().getClass().getCanonicalName() + "," |
| + complexQuery.getQueryFilter().getFilterAlgor().getEpsilon(); |
| outputArgs.add(filterString + "'"); |
| } |
| String sqlQueryString = getInfixCriteriaString(complexQuery.getCriteria()); |
| if (sqlQueryString != null && sqlQueryString.startsWith("(") && sqlQueryString.endsWith(")")) |
| sqlQueryString = sqlQueryString.substring(1, sqlQueryString.length() - 1); |
| return "SQL (" |
| + listToString(outputArgs) |
| + ") { SELECT " + listToString(complexQuery.getReducedMetadata()) |
| + " FROM " + (complexQuery.getReducedProductTypeNames() != null ? listToString(complexQuery.getReducedProductTypeNames()) : "*") |
| + (sqlQueryString != null ? " WHERE " + sqlQueryString : "") + " }"; |
| } |
| |
| public static String getInfixCriteriaString(List<QueryCriteria> criteriaList) throws QueryFormulationException { |
| if (criteriaList.size() > 1) |
| return getInfixCriteriaString(new BooleanQueryCriteria(criteriaList, BooleanQueryCriteria.AND)); |
| else if (criteriaList.size() == 1) |
| return getInfixCriteriaString(criteriaList.get(0)); |
| else |
| return null; |
| } |
| |
| public static String getInfixCriteriaString(QueryCriteria criteria) { |
| String returnString = ""; |
| if (criteria instanceof BooleanQueryCriteria) { |
| BooleanQueryCriteria bqc = (BooleanQueryCriteria) criteria; |
| List<QueryCriteria> terms = bqc.getTerms(); |
| switch(bqc.getOperator()){ |
| case 0: |
| returnString = "(" + getInfixCriteriaString((QueryCriteria) terms.get(0)); |
| for (int i = 1; i < terms.size(); i++) |
| returnString += " AND " + getInfixCriteriaString((QueryCriteria) terms.get(i)); |
| returnString += ")"; |
| break; |
| case 1: |
| returnString = "(" + getInfixCriteriaString((QueryCriteria) terms.get(0)); |
| for (int i = 1; i < terms.size(); i++) |
| returnString += " OR " + getInfixCriteriaString((QueryCriteria) terms.get(i)); |
| returnString += ")"; |
| break; |
| case 2: |
| QueryCriteria qc = bqc.getTerms().get(0); |
| if (qc instanceof TermQueryCriteria) { |
| TermQueryCriteria tqc = (TermQueryCriteria) qc; |
| returnString = tqc.getElementName() + " != '" + tqc.getValue() + "'"; |
| }else { |
| returnString = "NOT(" + getInfixCriteriaString(qc) + ")"; |
| } |
| break; |
| } |
| }else if (criteria instanceof RangeQueryCriteria) { |
| RangeQueryCriteria rqc = (RangeQueryCriteria) criteria; |
| String opString = rqc.getInclusive() ? "=" : ""; |
| if (rqc.getStartValue() != null) { |
| opString = ">" + opString + " '" + rqc.getStartValue() + "'"; |
| }else |
| opString = "<" + opString + " '" + rqc.getEndValue() + "'"; |
| returnString = rqc.getElementName() + " " + opString; |
| }else if (criteria instanceof TermQueryCriteria) { |
| TermQueryCriteria tqc = (TermQueryCriteria) criteria; |
| returnString = tqc.getElementName() + " == '" + tqc.getValue() + "'"; |
| } |
| return returnString; |
| } |
| |
| private static String stripOutSqlDefinition(String sqlStringQueryMethod) { |
| return sqlStringQueryMethod.trim().replaceAll("((?:SQL)|(?:sql))\\s*(.*)\\s*\\{", "").replaceAll("}$", "").trim(); |
| } |
| |
| private static List<Expression> getSqlStatementArgs(String sqlStringQueryMethod) throws QueryFormulationException { |
| boolean inExpr = false; |
| int startArgs = 0; |
| for (int i = 0; i < sqlStringQueryMethod.length(); i++) { |
| char curChar = sqlStringQueryMethod.charAt(i); |
| switch (curChar) { |
| case '(': |
| startArgs = i + 1; |
| break; |
| case ')': |
| if (!inExpr) { |
| String[] args = sqlStringQueryMethod.substring(startArgs, i).trim().split("'\\s*,"); |
| LinkedList<Expression> argsList = new LinkedList<Expression>(); |
| for (String arg : args) |
| argsList.add(new Expression((arg = arg.trim()).endsWith("'") ? arg : (arg + "'"))); |
| return argsList; |
| } else { |
| break; |
| } |
| case '\'': |
| inExpr = !inExpr; |
| break; |
| } |
| } |
| throw new QueryFormulationException("Failed to read in args"); |
| } |
| |
| private static QueryFilter createFilter(Expression expr) throws InstantiationException, IllegalAccessException, ClassNotFoundException { |
| String[] filterArgs = expr.getValue().split(","); |
| FilterAlgor filterAlgor = (FilterAlgor) Class.forName(filterArgs[3]).newInstance(); |
| QueryFilter qf = new QueryFilter(filterArgs[0], filterArgs[1], filterArgs[2], filterAlgor); |
| filterAlgor.setEpsilon(Integer.parseInt(filterArgs[4])); |
| return qf; |
| } |
| |
| /** |
| * Uses "Shunting yard algorithm" (see: |
| * http://en.wikipedia.org/wiki/Shunting_yard_algorithm) |
| */ |
| private static LinkedList<String> toPostFix(String statement) { |
| LinkedList<String> postFix = new LinkedList<String>(); |
| Stack<String> stack = new Stack<String>(); |
| |
| for (int i = 0; i < statement.length(); i++) { |
| char curChar = statement.charAt(i); |
| switch (curChar) { |
| case '(': |
| stack.push(new String("(")); |
| break; |
| case ')': |
| String value = null; |
| while (!(value = stack.pop()).equals("(")) |
| postFix.add(value); |
| if (stack.peek().equals("NOT")) |
| postFix.add(stack.pop()); |
| break; |
| case ' ': |
| break; |
| default: |
| if (statement.substring(i, i + 3).equals("AND")) { |
| while (!stack.isEmpty() |
| && (stack.peek().equals("AND"))) |
| postFix.add(stack.pop()); |
| stack.push("AND"); |
| i += 2; |
| } else if (statement.substring(i, i + 2).equals("OR")) { |
| while (!stack.isEmpty() |
| && (stack.peek().equals("AND") || stack.peek() |
| .equals("OR"))) |
| postFix.add(stack.pop()); |
| stack.push("OR"); |
| i += 1; |
| } else if (statement.substring(i, i + 3).equals("NOT")) { |
| stack.push("NOT"); |
| i += 2; |
| } else { |
| int endIndex = statement.indexOf('\'', statement.indexOf( |
| '\'', i) + 1) + 1; |
| postFix.add(statement.substring(i, endIndex)); |
| i = endIndex - 1; |
| } |
| } |
| } |
| |
| while (!stack.isEmpty()) |
| postFix.add(stack.pop()); |
| |
| return postFix; |
| } |
| |
| private static QueryCriteria parseStatement(LinkedList<String> postFixStatement) |
| throws QueryFormulationException { |
| Stack<QueryCriteria> stack = new Stack<QueryCriteria>(); |
| for (String item : postFixStatement) { |
| if (item.equals("AND")) { |
| BooleanQueryCriteria bQC = new BooleanQueryCriteria(); |
| bQC.addTerm(stack.pop()); |
| bQC.addTerm(stack.pop()); |
| stack.push(bQC); |
| } else if (item.equals("OR")) { |
| BooleanQueryCriteria bQC = new BooleanQueryCriteria(); |
| bQC.setOperator(BooleanQueryCriteria.OR); |
| bQC.addTerm(stack.pop()); |
| bQC.addTerm(stack.pop()); |
| stack.push(bQC); |
| } else if (item.equals("NOT")) { |
| BooleanQueryCriteria bQC = new BooleanQueryCriteria(); |
| bQC.setOperator(BooleanQueryCriteria.NOT); |
| bQC.addTerm(stack.pop()); |
| stack.push(bQC); |
| } else { |
| stack.push(new Expression(item).convertToQueryCriteria()); |
| } |
| } |
| return stack.pop(); |
| } |
| |
| private static String listToString(List<String> list) { |
| String arrayString = ""; |
| if (list.size() > 0) { |
| arrayString = list.get(0); |
| for (int i = 1; i < list.size(); i++) |
| arrayString += "," + list.get(i); |
| } |
| return arrayString; |
| } |
| |
| |
| private static class Expression { |
| |
| public static final short GREATER_THAN = 12; |
| |
| public static final short LESS_THAN = 3; |
| |
| public static final short EQUAL_TO = 9; |
| |
| public static final short NOT_EQUAL_TO = 15; |
| |
| public static final short GREATER_THAN_OR_EQUAL_TO = 13; |
| |
| public static final short LESS_THAN_OR_EQUAL_TO = 11; |
| |
| public static final short NOT = 6; |
| |
| private String[] stringValues = new String[] { "`", "`", "`", "<", "`", |
| "`", "!", "`", "`", "=", "`", "<=", ">", ">=", "`", "!=" }; |
| |
| private String expression; |
| |
| private String key; |
| |
| private String val; |
| |
| private int op; |
| |
| public Expression(String expression) { |
| this.parseExpression(this.expression = expression); |
| } |
| |
| public Expression(String key, int op, String val) { |
| this.key = key.trim(); |
| this.op = op; |
| this.val = this.removeTickBounds(val.trim()); |
| } |
| |
| private void parseExpression(String expression) { |
| Matcher matcher = Pattern.compile("((?:>=)|(?:<=)|(?:==)|(?:!=)|(?:=)|(?:>)|(?:<))").matcher(expression); |
| matcher.find(); |
| this.key = expression.substring(0, matcher.start()).trim(); |
| this.val = this.removeTickBounds(expression.substring(matcher.end()).trim()); |
| String opString = matcher.group(); |
| for (char c : opString.toCharArray()) |
| this.op = this.op | this.getShortValueForOp(c); |
| } |
| |
| private String removeTickBounds(String value) { |
| if (value.startsWith("'") && value.endsWith("'")) |
| value = value.substring(1, value.length() - 1); |
| return value; |
| } |
| |
| private int getShortValueForOp(char op) { |
| switch (op) { |
| case '>': |
| return GREATER_THAN; |
| case '<': |
| return LESS_THAN; |
| case '=': |
| return EQUAL_TO; |
| case '!': |
| return NOT; |
| default: |
| return 0; |
| } |
| } |
| |
| public QueryCriteria convertToQueryCriteria() |
| throws QueryFormulationException { |
| switch (this.op) { |
| case GREATER_THAN: |
| return new RangeQueryCriteria(this.key, this.val, null, false); |
| case LESS_THAN: |
| return new RangeQueryCriteria(this.key, null, this.val, false); |
| case EQUAL_TO: |
| return new TermQueryCriteria(this.key, this.val); |
| case NOT_EQUAL_TO: |
| BooleanQueryCriteria notEqBQC = new BooleanQueryCriteria(); |
| notEqBQC.setOperator(BooleanQueryCriteria.NOT); |
| notEqBQC.addTerm(new TermQueryCriteria(this.key, this.val)); |
| return notEqBQC; |
| case GREATER_THAN_OR_EQUAL_TO: |
| return new RangeQueryCriteria(this.key, this.val, null, true); |
| case LESS_THAN_OR_EQUAL_TO: |
| return new RangeQueryCriteria(this.key, null, this.val, true); |
| } |
| throw new QueryFormulationException( |
| "Was not able to form query . . . probably an invalid operator -- " |
| + this.toString()); |
| } |
| |
| public String getKey() { |
| return this.key; |
| } |
| |
| public String getValue() { |
| return this.val; |
| } |
| |
| public int getOp() { |
| return this.op; |
| } |
| |
| public String getExpression() { |
| return this.expression; |
| } |
| |
| public String toString() { |
| return this.key + " " + this.stringValues[this.op] + " " + this.val; |
| } |
| |
| } |
| |
| public static void main(String[] args) throws QueryFormulationException { |
| String query = "SELECT * FROM IASI_L1C WHERE one == '1' AND two == '2' OR NOT(five == '5') OR three == '3' AND four == '4'"; |
| System.out.println("query: " + query); |
| System.out.println("query after : " + unparseSqlQuery(parseSqlQuery(query))); |
| query = "SELECT * FROM IASI_L1C"; |
| System.out.println("query: " + query); |
| System.out.println("query after : " + unparseSqlQuery(parseSqlQuery(query))); |
| query = "SELECT * FROM *"; |
| System.out.println("query: " + query); |
| System.out.println("query after : " + unparseSqlQuery(parseSqlQuery(query))); |
| } |
| } |